Skip to content

Commit c9afe48

Browse files
author
Gusted
committed
Merge pull request '[GITEA] Detect file rename and show in history' (#1475) from Gusted/forgejo:forgejo-rename-history into forgejo-dependency
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1475
2 parents bf48f02 + 72c2975 commit c9afe48

22 files changed

+140
-2
lines changed

modules/git/commit.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,62 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
509509
return fileStatus, nil
510510
}
511511

512+
func parseCommitRenames(renames *[][2]string, stdout io.Reader) {
513+
rd := bufio.NewReader(stdout)
514+
for {
515+
// Skip (R || three digits || NULL byte)
516+
_, err := rd.Discard(5)
517+
if err != nil {
518+
if err != io.EOF {
519+
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
520+
}
521+
return
522+
}
523+
oldFileName, err := rd.ReadString('\x00')
524+
if err != nil {
525+
if err != io.EOF {
526+
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
527+
}
528+
return
529+
}
530+
newFileName, err := rd.ReadString('\x00')
531+
if err != nil {
532+
if err != io.EOF {
533+
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
534+
}
535+
return
536+
}
537+
oldFileName = strings.TrimSuffix(oldFileName, "\x00")
538+
newFileName = strings.TrimSuffix(newFileName, "\x00")
539+
*renames = append(*renames, [2]string{oldFileName, newFileName})
540+
}
541+
}
542+
543+
// GetCommitFileRenames returns the renames that the commit contains.
544+
func GetCommitFileRenames(ctx context.Context, repoPath, commitID string) ([][2]string, error) {
545+
renames := [][2]string{}
546+
stdout, w := io.Pipe()
547+
done := make(chan struct{})
548+
go func() {
549+
parseCommitRenames(&renames, stdout)
550+
close(done)
551+
}()
552+
553+
stderr := new(bytes.Buffer)
554+
err := NewCommand(ctx, "show", "--name-status", "--pretty=format:", "-z", "--diff-filter=R").AddDynamicArguments(commitID).Run(&RunOpts{
555+
Dir: repoPath,
556+
Stdout: w,
557+
Stderr: stderr,
558+
})
559+
w.Close() // Close writer to exit parsing goroutine
560+
if err != nil {
561+
return nil, ConcatenateError(err, stderr.String())
562+
}
563+
564+
<-done
565+
return renames, nil
566+
}
567+
512568
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
513569
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
514570
commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})

modules/git/commit_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,30 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
278278
assert.Equal(t, commitFileStatus.Removed, expected.Removed)
279279
assert.Equal(t, commitFileStatus.Modified, expected.Modified)
280280
}
281+
282+
func TestParseCommitRenames(t *testing.T) {
283+
testcases := []struct {
284+
output string
285+
renames [][2]string
286+
}{
287+
{
288+
output: "R090\x00renamed.txt\x00history.txt\x00",
289+
renames: [][2]string{{"renamed.txt", "history.txt"}},
290+
},
291+
{
292+
output: "R090\x00renamed.txt\x00history.txt\x00R000\x00corruptedstdouthere",
293+
renames: [][2]string{{"renamed.txt", "history.txt"}},
294+
},
295+
{
296+
output: "R100\x00renamed.txt\x00history.txt\x00R001\x00readme.md\x00README.md\x00",
297+
renames: [][2]string{{"renamed.txt", "history.txt"}, {"readme.md", "README.md"}},
298+
},
299+
}
300+
301+
for _, testcase := range testcases {
302+
renames := [][2]string{}
303+
parseCommitRenames(&renames, strings.NewReader(testcase.output))
304+
305+
assert.Equal(t, testcase.renames, renames)
306+
}
307+
}

options/locale/locale_en-US.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,8 @@ commits.signed_by_untrusted_user = Signed by untrusted user
12831283
commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does not match committer
12841284
commits.gpg_key_id = GPG Key ID
12851285
commits.ssh_key_fingerprint = SSH Key Fingerprint
1286+
commits.browse_further = Browse further
1287+
commits.renamed_from = Renamed from %s
12861288
12871289
commit.operations = Operations
12881290
commit.revert = Revert

routers/web/repo/commit.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,22 @@ func FileHistory(ctx *context.Context) {
239239
ctx.ServerError("CommitsByFileAndRange", err)
240240
return
241241
}
242+
oldestCommit := commits[len(commits)-1]
243+
244+
renamedFiles, err := git.GetCommitFileRenames(ctx, ctx.Repo.GitRepo.Path, oldestCommit.ID.String())
245+
if err != nil {
246+
ctx.ServerError("GetCommitFileRenames", err)
247+
return
248+
}
249+
250+
for _, renames := range renamedFiles {
251+
if renames[1] == fileName {
252+
ctx.Data["OldFilename"] = renames[0]
253+
ctx.Data["OldFilenameHistory"] = fmt.Sprintf("%s/commits/commit/%s/%s", ctx.Repo.RepoLink, oldestCommit.ID.String(), renames[0])
254+
break
255+
}
256+
}
257+
242258
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
243259

244260
ctx.Data["Username"] = ctx.Repo.Owner.Name

templates/repo/commits.tmpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
</div>
1616
</div>
1717
{{template "repo/commits_table" .}}
18+
{{if .OldFilename}}
19+
<div class="ui bottom attached header">
20+
<span>{{.locale.Tr "repo.commits.renamed_from" .OldFilename}} (<a href="{{.OldFilenameHistory}}">{{.locale.Tr "repo.commits.browse_further"}}</a>)</span>
21+
</div>
22+
{{end}}
1823
</div>
1924
</div>
2025
{{template "base/footer" .}}
Binary file not shown.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
P pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack
2+
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# pack-refs with: peeled fully-peeled sorted
2-
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/heads/master
2+
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/heads/cake-recipe
3+
80b83c5c8220c3aa3906e081f202a2a7563ec879 refs/heads/master
34
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/tags/v1.0

tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe

Lines changed: 0 additions & 1 deletion
This file was deleted.

tests/integration/repo_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,3 +548,33 @@ func TestRepoHTMLTitle(t *testing.T) {
548548
})
549549
})
550550
}
551+
552+
func TestRenamedFileHistory(t *testing.T) {
553+
defer tests.PrepareTestEnv(t)()
554+
555+
t.Run("Renamed file", func(t *testing.T) {
556+
defer tests.PrintCurrentTest(t)()
557+
558+
req := NewRequest(t, "GET", "/user2/repo59/commits/branch/master/license")
559+
resp := MakeRequest(t, req, http.StatusOK)
560+
561+
htmlDoc := NewHTMLParser(t, resp.Body)
562+
563+
renameNotice := htmlDoc.doc.Find(".ui.bottom.attached.header")
564+
assert.Equal(t, 1, renameNotice.Length())
565+
assert.Contains(t, renameNotice.Text(), "Renamed from licnse (Browse further)")
566+
567+
oldFileHistoryLink, ok := renameNotice.Find("a").Attr("href")
568+
assert.True(t, ok)
569+
assert.Equal(t, "/user2/repo59/commits/commit/80b83c5c8220c3aa3906e081f202a2a7563ec879/licnse", oldFileHistoryLink)
570+
})
571+
572+
t.Run("Non renamed file", func(t *testing.T) {
573+
req := NewRequest(t, "GET", "/user2/repo59/commits/branch/master/README.md")
574+
resp := MakeRequest(t, req, http.StatusOK)
575+
576+
htmlDoc := NewHTMLParser(t, resp.Body)
577+
578+
htmlDoc.AssertElement(t, ".ui.bottom.attached.header", false)
579+
})
580+
}

0 commit comments

Comments
 (0)