diff --git a/modules/git/commit.go b/modules/git/commit.go
index 5d960e92f3..012ba975e8 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -515,6 +515,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})
diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go
index e512eecc56..2de6feeb31 100644
--- a/modules/git/commit_test.go
+++ b/modules/git/commit_test.go
@@ -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)
+ }
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 84d031811a..fb8d46ef63 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1285,6 +1285,8 @@ commits.find = Search
commits.search_all = All Branches
commits.author = Author
commits.message = Message
+commits.browse_further = Browse further
+commits.renamed_from = Renamed from %s
commits.date = Date
commits.older = Older
commits.newer = Newer
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index abb39caa57..fe11b7fa69 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -243,6 +243,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
diff --git a/templates/repo/commits.tmpl b/templates/repo/commits.tmpl
index 42004c2610..7b3b27af1d 100644
--- a/templates/repo/commits.tmpl
+++ b/templates/repo/commits.tmpl
@@ -13,6 +13,11 @@
{{template "repo/commits_table" .}}
+ {{if .OldFilename}}
+
+ {{end}}
{{template "base/footer" .}}
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a b/tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a
deleted file mode 100644
index 567284ef1c..0000000000
Binary files a/tests/gitea-repositories-meta/user2/repo59.git/objects/40/8bbd3bd1f96950f8cf2f98c479557f6b18817a and /dev/null differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b b/tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b
deleted file mode 100644
index f23960f4cc..0000000000
Binary files a/tests/gitea-repositories-meta/user2/repo59.git/objects/5d/5c87a90af64cc67f22d60a942d5efaef8bc96b and /dev/null differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52 b/tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52
deleted file mode 100644
index 46cc9e3e5e..0000000000
Binary files a/tests/gitea-repositories-meta/user2/repo59.git/objects/88/3e2970ed6937cbb63311e941adb97df0ae3a52 and /dev/null differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/8c/ac7a8f434451410cc91ab9c04d07baff974ad8 b/tests/gitea-repositories-meta/user2/repo59.git/objects/8c/ac7a8f434451410cc91ab9c04d07baff974ad8
deleted file mode 100644
index 5a1e79326f..0000000000
Binary files a/tests/gitea-repositories-meta/user2/repo59.git/objects/8c/ac7a8f434451410cc91ab9c04d07baff974ad8 and /dev/null differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e b/tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e
deleted file mode 100644
index 3b71228a7e..0000000000
Binary files a/tests/gitea-repositories-meta/user2/repo59.git/objects/a0/ccafed39086ef520be6886d9395eb2100d317e and /dev/null differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99 b/tests/gitea-repositories-meta/user2/repo59.git/objects/ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99
deleted file mode 100644
index dcbd3b3eb9..0000000000
Binary files a/tests/gitea-repositories-meta/user2/repo59.git/objects/ab/e2a9ddfd7f542ff89bc13960a929dc8ca86c99 and /dev/null differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56 b/tests/gitea-repositories-meta/user2/repo59.git/objects/cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56
deleted file mode 100644
index cd9cea6797..0000000000
Binary files a/tests/gitea-repositories-meta/user2/repo59.git/objects/cd/879fb6cf5b7bbe0fbc3a0ef44c8695fde89a56 and /dev/null differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/d8/f53dfb33f6ccf4169c34970b5e747511c18beb b/tests/gitea-repositories-meta/user2/repo59.git/objects/d8/f53dfb33f6ccf4169c34970b5e747511c18beb
deleted file mode 100644
index bea28c9f69..0000000000
Binary files a/tests/gitea-repositories-meta/user2/repo59.git/objects/d8/f53dfb33f6ccf4169c34970b5e747511c18beb and /dev/null differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41 b/tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41
deleted file mode 100644
index b6803eb5a2..0000000000
Binary files a/tests/gitea-repositories-meta/user2/repo59.git/objects/f3/c1ec36c0e7605be54e71f24035caa675b7ba41 and /dev/null differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/info/commit-graph b/tests/gitea-repositories-meta/user2/repo59.git/objects/info/commit-graph
new file mode 100644
index 0000000000..d151dc87e6
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/info/commit-graph differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/info/packs b/tests/gitea-repositories-meta/user2/repo59.git/objects/info/packs
new file mode 100644
index 0000000000..0374746b5e
--- /dev/null
+++ b/tests/gitea-repositories-meta/user2/repo59.git/objects/info/packs
@@ -0,0 +1,2 @@
+P pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack
+
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.idx b/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.idx
new file mode 100644
index 0000000000..aaa9981cf5
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.idx differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack b/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack
new file mode 100644
index 0000000000..ddb8c16caf
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.pack differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.rev b/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.rev
new file mode 100644
index 0000000000..81554dba74
Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo59.git/objects/pack/pack-6dd3a6fe138f1d77e14c2e6b8e6c41e5ae242adf.rev differ
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/packed-refs b/tests/gitea-repositories-meta/user2/repo59.git/packed-refs
index 114c84d2aa..77fedbf67d 100644
--- a/tests/gitea-repositories-meta/user2/repo59.git/packed-refs
+++ b/tests/gitea-repositories-meta/user2/repo59.git/packed-refs
@@ -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
diff --git a/tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe b/tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe
deleted file mode 100644
index 63bbea6692..0000000000
--- a/tests/gitea-repositories-meta/user2/repo59.git/refs/heads/cake-recipe
+++ /dev/null
@@ -1 +0,0 @@
-d8f53dfb33f6ccf4169c34970b5e747511c18beb
diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go
index 157d2ba99d..8176fea5e8 100644
--- a/tests/integration/repo_test.go
+++ b/tests/integration/repo_test.go
@@ -473,6 +473,36 @@ func TestViewRepoDirectoryReadme(t *testing.T) {
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) {
defer tests.PrepareTestEnv(t)()