Skip to content

Commit

Permalink
Merge pull request '[GITEA] Detect file rename and show in history' (#…
Browse files Browse the repository at this point in the history
…1475) from Gusted/forgejo:forgejo-rename-history into forgejo-dependency

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1475
  • Loading branch information
Gusted committed Sep 18, 2023
2 parents bf48f02 + 72c2975 commit c9afe48
Show file tree
Hide file tree
Showing 22 changed files with 140 additions and 2 deletions.
56 changes: 56 additions & 0 deletions modules/git/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,62 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
return fileStatus, nil
}

func parseCommitRenames(renames *[][2]string, stdout io.Reader) {
rd := bufio.NewReader(stdout)
for {
// Skip (R || three digits || NULL byte)
_, err := rd.Discard(5)
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
oldFileName, err := rd.ReadString('\x00')
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
newFileName, err := rd.ReadString('\x00')
if err != nil {
if err != io.EOF {
log.Error("Unexpected error whilst reading from git log --name-status. Error: %v", err)
}
return
}
oldFileName = strings.TrimSuffix(oldFileName, "\x00")
newFileName = strings.TrimSuffix(newFileName, "\x00")
*renames = append(*renames, [2]string{oldFileName, newFileName})
}
}

// GetCommitFileRenames returns the renames that the commit contains.
func GetCommitFileRenames(ctx context.Context, repoPath, commitID string) ([][2]string, error) {
renames := [][2]string{}
stdout, w := io.Pipe()
done := make(chan struct{})
go func() {
parseCommitRenames(&renames, stdout)
close(done)
}()

stderr := new(bytes.Buffer)
err := NewCommand(ctx, "show", "--name-status", "--pretty=format:", "-z", "--diff-filter=R").AddDynamicArguments(commitID).Run(&RunOpts{
Dir: repoPath,
Stdout: w,
Stderr: stderr,
})
w.Close() // Close writer to exit parsing goroutine
if err != nil {
return nil, ConcatenateError(err, stderr.String())
}

<-done
return renames, nil
}

// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})
Expand Down
27 changes: 27 additions & 0 deletions modules/git/commit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,30 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
assert.Equal(t, commitFileStatus.Removed, expected.Removed)
assert.Equal(t, commitFileStatus.Modified, expected.Modified)
}

func TestParseCommitRenames(t *testing.T) {
testcases := []struct {
output string
renames [][2]string
}{
{
output: "R090\x00renamed.txt\x00history.txt\x00",
renames: [][2]string{{"renamed.txt", "history.txt"}},
},
{
output: "R090\x00renamed.txt\x00history.txt\x00R000\x00corruptedstdouthere",
renames: [][2]string{{"renamed.txt", "history.txt"}},
},
{
output: "R100\x00renamed.txt\x00history.txt\x00R001\x00readme.md\x00README.md\x00",
renames: [][2]string{{"renamed.txt", "history.txt"}, {"readme.md", "README.md"}},
},
}

for _, testcase := range testcases {
renames := [][2]string{}
parseCommitRenames(&renames, strings.NewReader(testcase.output))

assert.Equal(t, testcase.renames, renames)
}
}
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,8 @@ commits.signed_by_untrusted_user = Signed by untrusted user
commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does not match committer
commits.gpg_key_id = GPG Key ID
commits.ssh_key_fingerprint = SSH Key Fingerprint
commits.browse_further = Browse further
commits.renamed_from = Renamed from %s
commit.operations = Operations
commit.revert = Revert
Expand Down
16 changes: 16 additions & 0 deletions routers/web/repo/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,22 @@ func FileHistory(ctx *context.Context) {
ctx.ServerError("CommitsByFileAndRange", err)
return
}
oldestCommit := commits[len(commits)-1]

renamedFiles, err := git.GetCommitFileRenames(ctx, ctx.Repo.GitRepo.Path, oldestCommit.ID.String())
if err != nil {
ctx.ServerError("GetCommitFileRenames", err)
return
}

for _, renames := range renamedFiles {
if renames[1] == fileName {
ctx.Data["OldFilename"] = renames[0]
ctx.Data["OldFilenameHistory"] = fmt.Sprintf("%s/commits/commit/%s/%s", ctx.Repo.RepoLink, oldestCommit.ID.String(), renames[0])
break
}
}

ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)

ctx.Data["Username"] = ctx.Repo.Owner.Name
Expand Down
5 changes: 5 additions & 0 deletions templates/repo/commits.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
</div>
</div>
{{template "repo/commits_table" .}}
{{if .OldFilename}}
<div class="ui bottom attached header">
<span>{{.locale.Tr "repo.commits.renamed_from" .OldFilename}} (<a href="{{.OldFilenameHistory}}">{{.locale.Tr "repo.commits.browse_further"}}</a>)</span>
</div>
{{end}}
</div>
</div>
{{template "base/footer" .}}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
P pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack

Binary file not shown.
Binary file not shown.
Binary file not shown.
3 changes: 2 additions & 1 deletion tests/gitea-repositories-meta/user2/repo59.git/packed-refs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pack-refs with: peeled fully-peeled sorted
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/heads/master
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/heads/cake-recipe
80b83c5c8220c3aa3906e081f202a2a7563ec879 refs/heads/master
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/tags/v1.0

This file was deleted.

30 changes: 30 additions & 0 deletions tests/integration/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,3 +548,33 @@ func TestRepoHTMLTitle(t *testing.T) {
})
})
}

func TestRenamedFileHistory(t *testing.T) {
defer tests.PrepareTestEnv(t)()

t.Run("Renamed file", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

req := NewRequest(t, "GET", "/user2/repo59/commits/branch/master/license")
resp := MakeRequest(t, req, http.StatusOK)

htmlDoc := NewHTMLParser(t, resp.Body)

renameNotice := htmlDoc.doc.Find(".ui.bottom.attached.header")
assert.Equal(t, 1, renameNotice.Length())
assert.Contains(t, renameNotice.Text(), "Renamed from licnse (Browse further)")

oldFileHistoryLink, ok := renameNotice.Find("a").Attr("href")
assert.True(t, ok)
assert.Equal(t, "/user2/repo59/commits/commit/80b83c5c8220c3aa3906e081f202a2a7563ec879/licnse", oldFileHistoryLink)
})

t.Run("Non renamed file", func(t *testing.T) {
req := NewRequest(t, "GET", "/user2/repo59/commits/branch/master/README.md")
resp := MakeRequest(t, req, http.StatusOK)

htmlDoc := NewHTMLParser(t, resp.Body)

htmlDoc.AssertElement(t, ".ui.bottom.attached.header", false)
})
}

0 comments on commit c9afe48

Please sign in to comment.