forked from NYANDEV/forgejo
[GITEA] Detect file rename and show in history
- Add a indication to the file history if the file has been renamed, this indication contains a link to browse the history of the file further. - Added unit testing. - Added integration testing. - Resolves https://codeberg.org/forgejo/forgejo/issues/1279 (cherry picked from commit 72c297521b1830360aab4b50e37efcc7e67e0d5d) (cherry picked from commit 283f9648947f8dd2f315ecca19566ccca2b49c18) Conflicts: options/locale/locale_en-US.ini https://codeberg.org/forgejo/forgejo/pulls/1550 (cherry picked from commit 7c30af7fdee08efd02041c01abca47394a69bb8b) (cherry picked from commit f3be6eb269526a9f4ea7861189f07977f2d4a32f) (cherry picked from commit 78e1755b94c18c043e0c8f8c2849803cc8069feb) (cherry picked from commit 73799479e0fb68534dac10f809ee246dbc809b62) (cherry picked from commit 938359b94120b7ea7bcdfbfda265ada691620da1) (cherry picked from commitb168a9c081
) [GITEA] Detect file rename and show in history (squash) ctx.Locale (cherry picked from commit40447752ff
) (cherry picked from commitea23594cdb
) (cherry picked from commit cdc473850c85abcbe38c799c2d2446966978f2b2) (cherry picked from commit 86e6641c29df213d7db1b85867dafebcafeee1dd) (cherry picked from commit 2757de586b80834513e61033692ac72d25381431) (cherry picked from commit def4ae32ddb4b0b83f6bb9c197e00fdcd784928e) (cherry picked from commit 6dada09329e28840f7ad890bed333ae122838fb2) (cherry picked from commit 5d6d5272513629b126917c30f7bfde421fdcbe27) Conflicts: tests/integration/repo_test.go https://codeberg.org/forgejo/forgejo/pulls/2119 (cherry picked from commit d3c1bce7db31b243a7142b71bf4af36506752e6e)
This commit is contained in:
parent
d6850bc308
commit
04bcb22d5c
22 changed files with 140 additions and 2 deletions
|
@ -515,6 +515,62 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
|
||||||
return fileStatus, nil
|
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.
|
// 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) {
|
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
|
||||||
commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})
|
commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})
|
||||||
|
|
|
@ -278,3 +278,30 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
|
||||||
assert.Equal(t, commitFileStatus.Removed, expected.Removed)
|
assert.Equal(t, commitFileStatus.Removed, expected.Removed)
|
||||||
assert.Equal(t, commitFileStatus.Modified, expected.Modified)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1285,6 +1285,8 @@ commits.find = Search
|
||||||
commits.search_all = All Branches
|
commits.search_all = All Branches
|
||||||
commits.author = Author
|
commits.author = Author
|
||||||
commits.message = Message
|
commits.message = Message
|
||||||
|
commits.browse_further = Browse further
|
||||||
|
commits.renamed_from = Renamed from %s
|
||||||
commits.date = Date
|
commits.date = Date
|
||||||
commits.older = Older
|
commits.older = Older
|
||||||
commits.newer = Newer
|
commits.newer = Newer
|
||||||
|
|
|
@ -243,6 +243,22 @@ func FileHistory(ctx *context.Context) {
|
||||||
ctx.ServerError("CommitsByFileAndRange", err)
|
ctx.ServerError("CommitsByFileAndRange", err)
|
||||||
return
|
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["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
|
||||||
|
|
||||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "repo/commits_table" .}}
|
{{template "repo/commits_table" .}}
|
||||||
|
{{if .OldFilename}}
|
||||||
|
<div class="ui bottom attached header">
|
||||||
|
<span>{{ctx.Locale.Tr "repo.commits.renamed_from" .OldFilename}} (<a href="{{.OldFilenameHistory}}">{{ctx.Locale.Tr "repo.commits.browse_further"}}</a>)</span>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "base/footer" .}}
|
{{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.
|
@ -0,0 +1,2 @@
|
||||||
|
P pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,3 +1,4 @@
|
||||||
# pack-refs with: peeled fully-peeled sorted
|
# 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
|
d8f53dfb33f6ccf4169c34970b5e747511c18beb refs/tags/v1.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
d8f53dfb33f6ccf4169c34970b5e747511c18beb
|
|
|
@ -473,6 +473,36 @@ func TestViewRepoDirectoryReadme(t *testing.T) {
|
||||||
missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/")
|
missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestMarkDownReadmeImage(t *testing.T) {
|
func TestMarkDownReadmeImage(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue