mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	Add RSS Feeds for branches and files (#22719)
Fix #22228 adding RSS feeds for branches and files. RSS feeds are accessed through: * [gitea]/src/branch/{branch}.rss * [gitea]/src/branch/{branch}/{file_name}.rss No changes have been made to the UI to expose the feed urls for branches and files.
This commit is contained in:
		
							
								
								
									
										50
									
								
								routers/web/feed/branch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								routers/web/feed/branch.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | // Copyright 2022 The Gitea Authors. All rights reserved. | ||||||
|  | // SPDX-License-Identifier: MIT | ||||||
|  |  | ||||||
|  | package feed | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models/repo" | ||||||
|  | 	"code.gitea.io/gitea/modules/context" | ||||||
|  |  | ||||||
|  | 	"github.com/gorilla/feeds" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed | ||||||
|  | func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) { | ||||||
|  | 	commits, err := ctx.Repo.Commit.CommitsByRange(0, 10) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("ShowBranchFeed %s", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	title := fmt.Sprintf("Latest commits for branch %s", ctx.Repo.BranchName) | ||||||
|  | 	link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.BranchNameSubURL()} | ||||||
|  |  | ||||||
|  | 	feed := &feeds.Feed{ | ||||||
|  | 		Title:       title, | ||||||
|  | 		Link:        link, | ||||||
|  | 		Description: repo.Description, | ||||||
|  | 		Created:     time.Now(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, commit := range commits { | ||||||
|  | 		feed.Items = append(feed.Items, &feeds.Item{ | ||||||
|  | 			Id:    commit.ID.String(), | ||||||
|  | 			Title: strings.TrimSpace(strings.Split(commit.Message(), "\n")[0]), | ||||||
|  | 			Link:  &feeds.Link{Href: repo.HTMLURL() + "/commit/" + commit.ID.String()}, | ||||||
|  | 			Author: &feeds.Author{ | ||||||
|  | 				Name:  commit.Author.Name, | ||||||
|  | 				Email: commit.Author.Email, | ||||||
|  | 			}, | ||||||
|  | 			Description: commit.Message(), | ||||||
|  | 			Content:     commit.Message(), | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	writeFeed(ctx, feed, formatType) | ||||||
|  | } | ||||||
							
								
								
									
										56
									
								
								routers/web/feed/file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								routers/web/feed/file.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | // Copyright 2022 The Gitea Authors. All rights reserved. | ||||||
|  | // SPDX-License-Identifier: MIT | ||||||
|  |  | ||||||
|  | package feed | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/models/repo" | ||||||
|  | 	"code.gitea.io/gitea/modules/context" | ||||||
|  | 	"code.gitea.io/gitea/modules/util" | ||||||
|  |  | ||||||
|  | 	"github.com/gorilla/feeds" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ShowFileFeed shows tags and/or releases on the repo as RSS / Atom feed | ||||||
|  | func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string) { | ||||||
|  | 	fileName := ctx.Repo.TreePath | ||||||
|  | 	if len(fileName) == 0 { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(ctx.Repo.RefName, fileName, 1) | ||||||
|  | 	if err != nil { | ||||||
|  | 		ctx.ServerError("ShowBranchFeed %s", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	title := fmt.Sprintf("Latest commits for file %s", ctx.Repo.TreePath) | ||||||
|  |  | ||||||
|  | 	link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)} | ||||||
|  |  | ||||||
|  | 	feed := &feeds.Feed{ | ||||||
|  | 		Title:       title, | ||||||
|  | 		Link:        link, | ||||||
|  | 		Description: repo.Description, | ||||||
|  | 		Created:     time.Now(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, commit := range commits { | ||||||
|  | 		feed.Items = append(feed.Items, &feeds.Item{ | ||||||
|  | 			Id:    commit.ID.String(), | ||||||
|  | 			Title: strings.TrimSpace(strings.Split(commit.Message(), "\n")[0]), | ||||||
|  | 			Link:  &feeds.Link{Href: repo.HTMLURL() + "/commit/" + commit.ID.String()}, | ||||||
|  | 			Author: &feeds.Author{ | ||||||
|  | 				Name:  commit.Author.Name, | ||||||
|  | 				Email: commit.Author.Email, | ||||||
|  | 			}, | ||||||
|  | 			Description: commit.Message(), | ||||||
|  | 			Content:     commit.Message(), | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	writeFeed(ctx, feed, formatType) | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								routers/web/feed/render.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								routers/web/feed/render.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | // Copyright 2022 The Gitea Authors. All rights reserved. | ||||||
|  | // SPDX-License-Identifier: MIT | ||||||
|  |  | ||||||
|  | package feed | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	model "code.gitea.io/gitea/models/repo" | ||||||
|  | 	"code.gitea.io/gitea/modules/context" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // RenderBranchFeed render format for branch or file | ||||||
|  | func RenderBranchFeed(ctx *context.Context) { | ||||||
|  | 	_, _, showFeedType := GetFeedType(ctx.Params(":reponame"), ctx.Req) | ||||||
|  | 	var renderer func(ctx *context.Context, repo *model.Repository, formatType string) | ||||||
|  | 	switch { | ||||||
|  | 	case ctx.Repo.TreePath == "": | ||||||
|  | 		renderer = ShowBranchFeed | ||||||
|  | 	case ctx.Repo.TreePath != "": | ||||||
|  | 		renderer = ShowFileFeed | ||||||
|  | 	} | ||||||
|  | 	renderer(ctx, ctx.Repo.Repository, showFeedType) | ||||||
|  | } | ||||||
| @@ -25,6 +25,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	"code.gitea.io/gitea/modules/web" | 	"code.gitea.io/gitea/modules/web" | ||||||
| 	"code.gitea.io/gitea/routers/utils" | 	"code.gitea.io/gitea/routers/utils" | ||||||
|  | 	"code.gitea.io/gitea/routers/web/feed" | ||||||
| 	"code.gitea.io/gitea/services/forms" | 	"code.gitea.io/gitea/services/forms" | ||||||
| 	release_service "code.gitea.io/gitea/services/release" | 	release_service "code.gitea.io/gitea/services/release" | ||||||
| 	repo_service "code.gitea.io/gitea/services/repository" | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
| @@ -340,6 +341,11 @@ func getDeletedBranches(ctx *context.Context) ([]*Branch, error) { | |||||||
| 	return branches, nil | 	return branches, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // BranchFeedRSS get feeds for tags in RSS format | ||||||
|  | func BranchFeedRSS(ctx *context.Context) { | ||||||
|  | 	feed.ShowBranchFeed(ctx, ctx.Repo.Repository, "rss") | ||||||
|  | } | ||||||
|  |  | ||||||
| // CreateBranch creates new branch in repository | // CreateBranch creates new branch in repository | ||||||
| func CreateBranch(ctx *context.Context) { | func CreateBranch(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewBranchForm) | 	form := web.GetForm(ctx).(*forms.NewBranchForm) | ||||||
|   | |||||||
| @@ -710,7 +710,14 @@ func Home(ctx *context.Context) { | |||||||
| 	if setting.Other.EnableFeed { | 	if setting.Other.EnableFeed { | ||||||
| 		isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req) | 		isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req) | ||||||
| 		if isFeed { | 		if isFeed { | ||||||
| 			feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType) | 			switch { | ||||||
|  | 			case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType): | ||||||
|  | 				feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType) | ||||||
|  | 			case ctx.Repo.TreePath == "": | ||||||
|  | 				feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType) | ||||||
|  | 			case ctx.Repo.TreePath != "": | ||||||
|  | 				feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType) | ||||||
|  | 			} | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1450,6 +1450,9 @@ func RegisterRoutes(m *web.Route) { | |||||||
| 			m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick) | 			m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick) | ||||||
| 		}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader) | 		}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader) | ||||||
|  |  | ||||||
|  | 		m.Get("/rss/branch/*", context.RepoRefByType(context.RepoRefBranch), feed.RenderBranchFeed) | ||||||
|  | 		m.Get("/atom/branch/*", context.RepoRefByType(context.RepoRefBranch), feed.RenderBranchFeed) | ||||||
|  |  | ||||||
| 		m.Group("/src", func() { | 		m.Group("/src", func() { | ||||||
| 			m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home) | 			m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home) | ||||||
| 			m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home) | 			m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home) | ||||||
|   | |||||||
| @@ -26,6 +26,11 @@ | |||||||
| 										{{svg "octicon-git-branch"}} | 										{{svg "octicon-git-branch"}} | ||||||
| 									</button> | 									</button> | ||||||
| 								{{end}} | 								{{end}} | ||||||
|  | 								{{if .EnableFeed}} | ||||||
|  | 									<a role="button" class="ui basic button icon" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .DefaultBranch}}"> | ||||||
|  | 										{{svg "octicon-rss"}} | ||||||
|  | 									</a> | ||||||
|  | 								{{end}} | ||||||
| 								{{if not $.DisableDownloadSourceArchives}} | 								{{if not $.DisableDownloadSourceArchives}} | ||||||
| 									<button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranch)}}"> | 									<button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranch)}}"> | ||||||
| 										{{svg "octicon-download"}} | 										{{svg "octicon-download"}} | ||||||
| @@ -113,6 +118,11 @@ | |||||||
| 												{{svg "octicon-git-branch"}} | 												{{svg "octicon-git-branch"}} | ||||||
| 											</button> | 											</button> | ||||||
| 										{{end}} | 										{{end}} | ||||||
|  | 										{{if $.EnableFeed}} | ||||||
|  | 											<a role="button" class="ui basic button icon" href="{{$.FeedURL}}/rss/branch/{{PathEscapeSegments .Name}}"> | ||||||
|  | 												{{svg "octicon-rss"}} | ||||||
|  | 											</a> | ||||||
|  | 										{{end}} | ||||||
| 										{{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}} | 										{{if and (not .IsDeleted) (not $.DisableDownloadSourceArchives)}} | ||||||
| 											<button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.Name)}}"> | 											<button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.Name)}}"> | ||||||
| 												{{svg "octicon-download"}} | 												{{svg "octicon-download"}} | ||||||
|   | |||||||
| @@ -63,12 +63,12 @@ | |||||||
| 				{{$n := len .TreeNames}} | 				{{$n := len .TreeNames}} | ||||||
| 				{{$l := Eval $n "-" 1}} | 				{{$l := Eval $n "-" 1}} | ||||||
| 				<!-- If home page, show new pr. If not, show breadcrumb --> | 				<!-- If home page, show new pr. If not, show breadcrumb --> | ||||||
|  | 				{{if and (eq $n 0) .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}} | ||||||
|  | 					<a href="{{CompareLink .BaseRepo .Repository .BranchName}}"> | ||||||
|  | 						<button id="new-pull-request" class="ui compact basic button" data-tooltip-content="{{if .PullRequestCtx.Allowed}}{{.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{.locale.Tr "action.compare_branch"}}{{end}}"><span class="text">{{svg "octicon-git-pull-request"}}</span></button> | ||||||
|  | 					</a> | ||||||
|  | 				{{end}} | ||||||
| 				{{if eq $n 0}} | 				{{if eq $n 0}} | ||||||
| 					{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}} |  | ||||||
| 						<a href="{{CompareLink .BaseRepo .Repository .BranchName}}"> |  | ||||||
| 							<button id="new-pull-request" class="ui compact basic button" data-tooltip-content="{{if .PullRequestCtx.Allowed}}{{.locale.Tr "repo.pulls.compare_changes"}}{{else}}{{.locale.Tr "action.compare_branch"}}{{end}}"><span class="text">{{svg "octicon-git-pull-request"}}</span></button> |  | ||||||
| 						</a> |  | ||||||
| 					{{end}} |  | ||||||
| 					<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{.locale.Tr "repo.find_file.go_to_file"}}</a> | 					<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{.locale.Tr "repo.find_file.go_to_file"}}</a> | ||||||
| 				{{end}} | 				{{end}} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -42,6 +42,11 @@ | |||||||
| 				</div> | 				</div> | ||||||
| 				<a download href="{{$.RawFileLink}}"><span class="btn-octicon" data-tooltip-content="{{.locale.Tr "repo.download_file"}}">{{svg "octicon-download"}}</span></a> | 				<a download href="{{$.RawFileLink}}"><span class="btn-octicon" data-tooltip-content="{{.locale.Tr "repo.download_file"}}">{{svg "octicon-download"}}</span></a> | ||||||
| 				<a id="copy-content" class="btn-octicon {{if not .CanCopyContent}} disabled{{end}}"{{if or .IsImageFile (and .HasSourceRenderedToggle (not .IsDisplayingSource))}} data-link="{{$.RawFileLink}}"{{end}} data-tooltip-content="{{if .CanCopyContent}}{{.locale.Tr "copy_content"}}{{else}}{{.locale.Tr "copy_type_unsupported"}}{{end}}">{{svg "octicon-copy" 14}}</a> | 				<a id="copy-content" class="btn-octicon {{if not .CanCopyContent}} disabled{{end}}"{{if or .IsImageFile (and .HasSourceRenderedToggle (not .IsDisplayingSource))}} data-link="{{$.RawFileLink}}"{{end}} data-tooltip-content="{{if .CanCopyContent}}{{.locale.Tr "copy_content"}}{{else}}{{.locale.Tr "copy_type_unsupported"}}{{end}}">{{svg "octicon-copy" 14}}</a> | ||||||
|  | 				{{if .EnableFeed}} | ||||||
|  | 				<a class="btn-octicon" href="{{$.FeedURL}}/rss/{{$.BranchNameSubURL}}{{range $i, $v := .TreeNames}}/{{$v}}{{end}}"> | ||||||
|  | 					{{svg "octicon-rss" 14}} | ||||||
|  | 				</a> | ||||||
|  | 				{{end}} | ||||||
| 				{{if .Repository.CanEnableEditor}} | 				{{if .Repository.CanEnableEditor}} | ||||||
| 					{{if .CanEditFile}} | 					{{if .CanEditFile}} | ||||||
| 						<a href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}"><span class="btn-octicon" data-tooltip-content="{{.EditFileTooltip}}">{{svg "octicon-pencil"}}</span></a> | 						<a href="{{.RepoLink}}/_edit/{{PathEscapeSegments .BranchName}}/{{PathEscapeSegments .TreePath}}"><span class="btn-octicon" data-tooltip-content="{{.EditFileTooltip}}">{{svg "octicon-pencil"}}</span></a> | ||||||
|   | |||||||
| @@ -39,6 +39,9 @@ | |||||||
|       <div class="scrolling menu" ref="scrollContainer"> |       <div class="scrolling menu" ref="scrollContainer"> | ||||||
|         <div v-for="(item, index) in filteredItems" :key="item.name" class="item" :class="{selected: item.selected, active: active === index}" @click="selectItem(item)" :ref="'listItem' + index"> |         <div v-for="(item, index) in filteredItems" :key="item.name" class="item" :class="{selected: item.selected, active: active === index}" @click="selectItem(item)" :ref="'listItem' + index"> | ||||||
|           {{ item.name }} |           {{ item.name }} | ||||||
|  |           <a v-if="mode === 'branches'" role="button" class="ui compact muted right" :href="(branchURLPrefix + item.url).replace('src', 'rss')"> | ||||||
|  |             <svg-icon name="octicon-rss" :size="14"/> | ||||||
|  |           </a> | ||||||
|         </div> |         </div> | ||||||
|         <div class="item" v-if="showCreateNewBranch" :class="{active: active === filteredItems.length}" :ref="'listItem' + filteredItems.length"> |         <div class="item" v-if="showCreateNewBranch" :class="{active: active === filteredItems.length}" :ref="'listItem' + filteredItems.length"> | ||||||
|           <a href="#" @click="createNewBranch()"> |           <a href="#" @click="createNewBranch()"> | ||||||
|   | |||||||
| @@ -43,6 +43,7 @@ import octiconChevronLeft from '../../public/img/svg/octicon-chevron-left.svg'; | |||||||
| import octiconOrganization from '../../public/img/svg/octicon-organization.svg'; | import octiconOrganization from '../../public/img/svg/octicon-organization.svg'; | ||||||
| import octiconTag from '../../public/img/svg/octicon-tag.svg'; | import octiconTag from '../../public/img/svg/octicon-tag.svg'; | ||||||
| import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg'; | import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg'; | ||||||
|  | import octiconRss from '../../public/img/svg/octicon-rss.svg'; | ||||||
|  |  | ||||||
| const svgs = { | const svgs = { | ||||||
|   'octicon-blocked': octiconBlocked, |   'octicon-blocked': octiconBlocked, | ||||||
| @@ -89,6 +90,7 @@ const svgs = { | |||||||
|   'octicon-organization': octiconOrganization, |   'octicon-organization': octiconOrganization, | ||||||
|   'octicon-tag': octiconTag, |   'octicon-tag': octiconTag, | ||||||
|   'octicon-git-branch': octiconGitBranch, |   'octicon-git-branch': octiconGitBranch, | ||||||
|  |   'octicon-rss': octiconRss, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // TODO: use a more general approach to access SVG icons. | // TODO: use a more general approach to access SVG icons. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user