Skip to content

File history for renamed files, with '--follow' equivalent to show the complete history #34686

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions modules/git/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,25 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file

// CommitsCountOptions the options when counting commits
type CommitsCountOptions struct {
RepoPath string
Not string
Revision []string
RelPath []string
Since string
Until string
RepoPath string
Not string
Revision []string
RelPath []string
Since string
Until string
FollowRename bool
}

// CommitsCount returns number of total commits of until given revision.
func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) {
cmd := NewCommand("rev-list", "--count")
var cmd *Command
followRename := len(opts.RelPath) > 0 && opts.FollowRename

if followRename {
cmd = NewCommand("--no-pager", "log", "--pretty=format:%H")
} else {
cmd = NewCommand("rev-list", "--count")
}

cmd.AddDynamicArguments(opts.Revision...)

Expand All @@ -181,14 +189,19 @@ func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error)
}

if len(opts.RelPath) > 0 {
if opts.FollowRename {
cmd.AddOptionValues("--follow")
}
cmd.AddDashesAndList(opts.RelPath...)
}

stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: opts.RepoPath})
if err != nil {
return 0, err
}

if followRename {
return int64(len(strings.Split(stdout, "\n"))), nil
}
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}

Expand Down
45 changes: 31 additions & 14 deletions modules/git/repo_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,22 +205,29 @@ func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bo
}

// FileCommitsCount return the number of files at a revision
func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
func (repo *Repository) FileCommitsCount(revision, file string, followRename ...bool) (int64, error) {
_followRename := false
if len(followRename) > 0 {
_followRename = followRename[0]
}

return CommitsCount(repo.Ctx,
CommitsCountOptions{
RepoPath: repo.Path,
Revision: []string{revision},
RelPath: []string{file},
RepoPath: repo.Path,
Revision: []string{revision},
RelPath: []string{file},
FollowRename: _followRename,
})
}

type CommitsByFileAndRangeOptions struct {
Revision string
File string
Not string
Page int
Since string
Until string
Revision string
File string
Not string
Page int
Since string
Until string
FollowRename bool
}

// CommitsByFileAndRange return the commits according revision file and the page
Expand All @@ -232,9 +239,18 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
}()
go func() {
stderr := strings.Builder{}
gitCmd := NewCommand("rev-list").
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
var gitCmd *Command

if !opts.FollowRename {
gitCmd = NewCommand("rev-list")
} else {
gitCmd = NewCommand("--no-pager", "log").
AddOptionFormat("--pretty=format:%%H").
AddOptionFormat("--follow")
}
gitCmd.AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)

gitCmd.AddDynamicArguments(opts.Revision)

if opts.Not != "" {
Expand All @@ -253,7 +269,8 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
Stdout: stdoutWriter,
Stderr: &stderr,
})
if err != nil {

if err != nil && !(opts.FollowRename && err == io.ErrUnexpectedEOF) {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, (&stderr).String()))
} else {
_ = stdoutWriter.Close()
Expand All @@ -270,7 +287,7 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
shaline := make([]byte, length+1)
for {
n, err := io.ReadFull(stdoutReader, shaline)
if err != nil || n < length {
if (err != nil && !(opts.FollowRename && err == io.ErrUnexpectedEOF)) || n < length {
if err == io.EOF {
err = nil
}
Expand Down
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,7 @@ editor.fork_branch_exists = Branch "%s" already exists in your fork, please choo

commits.desc = Browse source code change history.
commits.commits = Commits
commits.history_follow_rename = Include renames
commits.no_commits = No commits in common. "%s" and "%s" have entirely different histories.
commits.nothing_to_compare = These branches are equal.
commits.search.tooltip = You can prefix keywords with "author:", "committer:", "after:", or "before:", e.g. "revert author:Alice before:2019-01-13".
Expand Down
11 changes: 7 additions & 4 deletions routers/web/repo/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,14 @@ func SearchCommits(ctx *context.Context) {

// FileHistory show a file's reversions
func FileHistory(ctx *context.Context) {
followRename := strings.Contains(ctx.Req.RequestURI, "history_follow_rename=true")

if ctx.Repo.TreePath == "" {
Commits(ctx)
return
}

commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath)
commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath, followRename)
if err != nil {
ctx.ServerError("FileCommitsCount", err)
return
Expand All @@ -231,9 +233,10 @@ func FileHistory(ctx *context.Context) {

commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
File: ctx.Repo.TreePath,
Page: page,
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
File: ctx.Repo.TreePath,
Page: page,
FollowRename: followRename,
})
if err != nil {
ctx.ServerError("CommitsByFileAndRange", err)
Expand Down
4 changes: 4 additions & 0 deletions templates/repo/commits_table.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
{{ctx.Locale.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch}}
{{end}}
</div>
<div class="commits-table-left tw-flex tw-items-center">
<input type="checkbox" name="history-enable-follow-renames" class="tw-mr-[5px]"/>
<label for="history-enable-follow-renames">{{ctx.Locale.Tr "repo.commits.history_follow_rename"}}</label>
</div>
{{if .IsDiffCompare}}
<div class="commits-table-right tw-whitespace-nowrap">
<a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID | PathEscape}}" class="ui green sha label tw-mx-0">{{if not .BaseIsCommit}}{{if .BaseIsBranch}}{{svg "octicon-git-branch"}}{{else if .BaseIsTag}}{{svg "octicon-tag"}}{{end}}{{.BaseBranch}}{{else}}{{ShortSha .BaseBranch}}{{end}}</a>
Expand Down
17 changes: 17 additions & 0 deletions web_src/js/features/repo-commit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,20 @@
});
});
}

export function initCommitFileHistoryFollowRename() {
const checkbox : HTMLInputElement | null = document.querySelector('input[name=history-enable-follow-renames]');

if (!checkbox) {
return;
}
const url = new URL(window.location.toString());
checkbox.checked = url.searchParams.has('history_follow_rename', 'true');

checkbox.addEventListener('change', () => {
const url = new URL(window.location);

Check failure on line 38 in web_src/js/features/repo-commit.ts

View workflow job for this annotation

GitHub Actions / frontend

Argument of type 'Location' is not assignable to parameter of type 'string | URL'.

url.searchParams.set('history_follow_rename', `${checkbox.checked}`);
window.location.replace(url);
});
}
3 changes: 2 additions & 1 deletion web_src/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {initMarkupContent} from './markup/content.ts';
import {initPdfViewer} from './render/pdf.ts';
import {initUserAuthOauth2, initUserCheckAppUrl} from './features/user-auth.ts';
import {initRepoPullRequestAllowMaintainerEdit, initRepoPullRequestReview, initRepoIssueSidebarDependency, initRepoIssueFilterItemLabel} from './features/repo-issue.ts';
import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts';
import {initRepoEllipsisButton, initCommitStatuses, initCommitFileHistoryFollowRename} from './features/repo-commit.ts';
import {initRepoTopicBar} from './features/repo-home.ts';
import {initAdminCommon} from './features/admin/common.ts';
import {initRepoCodeView} from './features/repo-code.ts';
Expand Down Expand Up @@ -151,6 +151,7 @@ onDomReady(() => {
initRepoRecentCommits,

initCommitStatuses,
initCommitFileHistoryFollowRename,
initCaptcha,

initUserCheckAppUrl,
Expand Down
Loading