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:
parent
f16b668980
commit
56d4893b2a
11 changed files with 170 additions and 6 deletions
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 {
|
||||||
|
switch {
|
||||||
|
case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
|
||||||
feed.ShowRepoFeed(ctx, ctx.Repo.Repository, 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 eq $n 0}}
|
{{if and (eq $n 0) .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
||||||
{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
|
|
||||||
<a href="{{CompareLink .BaseRepo .Repository .BranchName}}">
|
<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>
|
<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>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if eq $n 0}}
|
||||||
<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.
|
||||||
|
|
Loading…
Reference in a new issue