Skip to content

File history for renamed files, with '--follow' equivalent to show the complete history (v2) #34994

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 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1902830
Use git log instead of git rev-list
Chi-Iroh Jun 5, 2025
22f63d9
Fix last commit discarded
Chi-Iroh Jun 10, 2025
88d70d3
Fixed commit count
Chi-Iroh Jun 10, 2025
8de55d6
Added checkbox to follow renames in history
Chi-Iroh Jun 10, 2025
8c9518c
Fixed command creation
Chi-Iroh Jun 10, 2025
5684f5e
Localized history follow rename checkbox
Chi-Iroh Jun 10, 2025
6c62a51
Add small spacing between checkbox and label
Chi-Iroh Jun 10, 2025
8cef3d3
Fixed linting
Chi-Iroh Jun 10, 2025
bb67785
Removed jquery
Chi-Iroh Jun 11, 2025
2acc230
Updated URL search params manipulation
Chi-Iroh Jun 11, 2025
308119f
Uses git rev-list when no rename follow
Chi-Iroh Jun 11, 2025
a9d4c14
Fixed go linting
Chi-Iroh Jun 11, 2025
0991eb3
Replace location by window.location + removed useless .toString()
Chi-Iroh Jun 11, 2025
0bbfaef
Use tailwind style instead of inline style
Chi-Iroh Jun 11, 2025
dd01694
Fix last commit discarded
Chi-Iroh Jun 10, 2025
76f705d
Fixed commit count
Chi-Iroh Jun 10, 2025
907088a
Added checkbox to follow renames in history
Chi-Iroh Jun 10, 2025
44818fe
Fixed command creation
Chi-Iroh Jun 10, 2025
44574bb
Localized history follow rename checkbox
Chi-Iroh Jun 10, 2025
fe816c0
Add small spacing between checkbox and label
Chi-Iroh Jun 10, 2025
204a140
Fixed linting
Chi-Iroh Jun 10, 2025
c5bb7db
Removed jquery
Chi-Iroh Jun 11, 2025
7601f76
Updated URL search params manipulation
Chi-Iroh Jun 11, 2025
c44eb5e
Uses git rev-list when no rename follow
Chi-Iroh Jun 11, 2025
ed87edc
Fixed go linting
Chi-Iroh Jun 11, 2025
f26f97b
Replace location by window.location + removed useless .toString()
Chi-Iroh Jun 11, 2025
8202621
Use tailwind style instead of inline style
Chi-Iroh Jun 11, 2025
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 initCommitStatuses() {
});
});
}

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);

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