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/web" | ||||
| 	"code.gitea.io/gitea/routers/utils" | ||||
| 	"code.gitea.io/gitea/routers/web/feed" | ||||
| 	"code.gitea.io/gitea/services/forms" | ||||
| 	release_service "code.gitea.io/gitea/services/release" | ||||
| 	repo_service "code.gitea.io/gitea/services/repository" | ||||
| @@ -340,6 +341,11 @@ func getDeletedBranches(ctx *context.Context) ([]*Branch, error) { | ||||
| 	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 | ||||
| func CreateBranch(ctx *context.Context) { | ||||
| 	form := web.GetForm(ctx).(*forms.NewBranchForm) | ||||
|   | ||||
| @@ -710,7 +710,14 @@ func Home(ctx *context.Context) { | ||||
| 	if setting.Other.EnableFeed { | ||||
| 		isFeed, _, showFeedType := feed.GetFeedType(ctx.Params(":reponame"), ctx.Req) | ||||
| 		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 | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -1450,6 +1450,9 @@ func RegisterRoutes(m *web.Route) { | ||||
| 			m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick) | ||||
| 		}, 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.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home) | ||||
| 			m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home) | ||||
|   | ||||
| @@ -26,6 +26,11 @@ | ||||
| 										{{svg "octicon-git-branch"}} | ||||
| 									</button> | ||||
| 								{{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}} | ||||
| 									<button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" ($.DefaultBranch)}}"> | ||||
| 										{{svg "octicon-download"}} | ||||
| @@ -113,6 +118,11 @@ | ||||
| 												{{svg "octicon-git-branch"}} | ||||
| 											</button> | ||||
| 										{{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)}} | ||||
| 											<button class="ui basic jump dropdown icon button" data-tooltip-content="{{$.locale.Tr "repo.branch.download" (.Name)}}"> | ||||
| 												{{svg "octicon-download"}} | ||||
|   | ||||
| @@ -63,12 +63,12 @@ | ||||
| 				{{$n := len .TreeNames}} | ||||
| 				{{$l := Eval $n "-" 1}} | ||||
| 				<!-- 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 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> | ||||
| 				{{end}} | ||||
|  | ||||
|   | ||||
| @@ -42,6 +42,11 @@ | ||||
| 				</div> | ||||
| 				<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> | ||||
| 				{{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 .CanEditFile}} | ||||
| 						<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 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 }} | ||||
|           <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 class="item" v-if="showCreateNewBranch" :class="{active: active === filteredItems.length}" :ref="'listItem' + filteredItems.length"> | ||||
|           <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 octiconTag from '../../public/img/svg/octicon-tag.svg'; | ||||
| import octiconGitBranch from '../../public/img/svg/octicon-git-branch.svg'; | ||||
| import octiconRss from '../../public/img/svg/octicon-rss.svg'; | ||||
|  | ||||
| const svgs = { | ||||
|   'octicon-blocked': octiconBlocked, | ||||
| @@ -89,6 +90,7 @@ const svgs = { | ||||
|   'octicon-organization': octiconOrganization, | ||||
|   'octicon-tag': octiconTag, | ||||
|   'octicon-git-branch': octiconGitBranch, | ||||
|   'octicon-rss': octiconRss, | ||||
| }; | ||||
|  | ||||
| // TODO: use a more general approach to access SVG icons. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user