aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--modules/git/commit_info_gogit.go61
-rw-r--r--modules/git/commit_info_nogogit.go47
-rw-r--r--modules/git/last_commit_cache.go3
-rw-r--r--modules/git/last_commit_cache_gogit.go8
-rw-r--r--modules/git/last_commit_cache_nogogit.go16
-rw-r--r--modules/git/log_name_status.go13
-rw-r--r--modules/git/notes_gogit.go3
-rw-r--r--modules/git/notes_nogogit.go3
-rw-r--r--routers/web/repo/view.go205
-rw-r--r--routers/web/web.go3
-rw-r--r--templates/repo/view_list.tmpl68
-rw-r--r--web_src/js/features/lastcommitloader.js40
-rw-r--r--web_src/js/index.js2
13 files changed, 321 insertions, 151 deletions
diff --git a/modules/git/commit_info_gogit.go b/modules/git/commit_info_gogit.go
index 8b82f3f66c..ccf90fc8c7 100644
--- a/modules/git/commit_info_gogit.go
+++ b/modules/git/commit_info_gogit.go
@@ -44,20 +44,17 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
return nil, nil, err
}
if len(unHitPaths) > 0 {
- revs2, err := GetLastCommitForPaths(ctx, c, treePath, unHitPaths)
+ revs2, err := GetLastCommitForPaths(ctx, cache, c, treePath, unHitPaths)
if err != nil {
return nil, nil, err
}
for k, v := range revs2 {
- if err := cache.Put(commit.ID.String(), path.Join(treePath, k), v.ID().String()); err != nil {
- return nil, nil, err
- }
revs[k] = v
}
}
} else {
- revs, err = GetLastCommitForPaths(ctx, c, treePath, entryPaths)
+ revs, err = GetLastCommitForPaths(ctx, nil, c, treePath, entryPaths)
}
if err != nil {
return nil, nil, err
@@ -70,25 +67,29 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
commitsInfo[i] = CommitInfo{
Entry: entry,
}
+
+ // Check if we have found a commit for this entry in time
if rev, ok := revs[entry.Name()]; ok {
entryCommit := convertCommit(rev)
commitsInfo[i].Commit = entryCommit
- if entry.IsSubModule() {
- subModuleURL := ""
- var fullPath string
- if len(treePath) > 0 {
- fullPath = treePath + "/" + entry.Name()
- } else {
- fullPath = entry.Name()
- }
- if subModule, err := commit.GetSubModule(fullPath); err != nil {
- return nil, nil, err
- } else if subModule != nil {
- subModuleURL = subModule.URL
- }
- subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
- commitsInfo[i].SubModuleFile = subModuleFile
+ }
+
+ // If the entry if a submodule add a submodule file for this
+ if entry.IsSubModule() {
+ subModuleURL := ""
+ var fullPath string
+ if len(treePath) > 0 {
+ fullPath = treePath + "/" + entry.Name()
+ } else {
+ fullPath = entry.Name()
}
+ if subModule, err := commit.GetSubModule(fullPath); err != nil {
+ return nil, nil, err
+ } else if subModule != nil {
+ subModuleURL = subModule.URL
+ }
+ subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
+ commitsInfo[i].SubModuleFile = subModuleFile
}
}
@@ -175,7 +176,9 @@ func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cac
}
// GetLastCommitForPaths returns last commit information
-func GetLastCommitForPaths(ctx context.Context, c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
+func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, c cgobject.CommitNode, treePath string, paths []string) (map[string]*object.Commit, error) {
+ refSha := c.ID().String()
+
// We do a tree traversal with nodes sorted by commit time
heap := binaryheap.NewWith(func(a, b interface{}) int {
if a.(*commitAndPaths).commit.CommitTime().Before(b.(*commitAndPaths).commit.CommitTime()) {
@@ -192,10 +195,13 @@ func GetLastCommitForPaths(ctx context.Context, c cgobject.CommitNode, treePath
// Start search from the root commit and with full set of paths
heap.Push(&commitAndPaths{c, paths, initialHashes})
-
+heaploop:
for {
select {
case <-ctx.Done():
+ if ctx.Err() == context.DeadlineExceeded {
+ break heaploop
+ }
return nil, ctx.Err()
default:
}
@@ -233,14 +239,14 @@ func GetLastCommitForPaths(ctx context.Context, c cgobject.CommitNode, treePath
}
var remainingPaths []string
- for i, path := range current.paths {
+ for i, pth := range current.paths {
// The results could already contain some newer change for the same path,
// so don't override that and bail out on the file early.
- if resultNodes[path] == nil {
+ if resultNodes[pth] == nil {
if pathUnchanged[i] {
// The path existed with the same hash in at least one parent so it could
// not have been changed in this commit directly.
- remainingPaths = append(remainingPaths, path)
+ remainingPaths = append(remainingPaths, pth)
} else {
// There are few possible cases how can we get here:
// - The path didn't exist in any parent, so it must have been created by
@@ -250,7 +256,10 @@ func GetLastCommitForPaths(ctx context.Context, c cgobject.CommitNode, treePath
// - We are looking at a merge commit and the hash of the file doesn't
// match any of the hashes being merged. This is more common for directories,
// but it can also happen if a file is changed through conflict resolution.
- resultNodes[path] = current.commit
+ resultNodes[pth] = current.commit
+ if err := cache.Put(refSha, path.Join(treePath, pth), current.commit.ID().String()); err != nil {
+ return nil, err
+ }
}
}
}
diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go
index f57355d16e..261252dab1 100644
--- a/modules/git/commit_info_nogogit.go
+++ b/modules/git/commit_info_nogogit.go
@@ -37,21 +37,18 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
}
if len(unHitPaths) > 0 {
sort.Strings(unHitPaths)
- commits, err := GetLastCommitForPaths(ctx, commit, treePath, unHitPaths)
+ commits, err := GetLastCommitForPaths(ctx, cache, commit, treePath, unHitPaths)
if err != nil {
return nil, nil, err
}
for pth, found := range commits {
- if err := cache.Put(commit.ID.String(), path.Join(treePath, pth), found.ID.String()); err != nil {
- return nil, nil, err
- }
revs[pth] = found
}
}
} else {
sort.Strings(entryPaths)
- revs, err = GetLastCommitForPaths(ctx, commit, treePath, entryPaths)
+ revs, err = GetLastCommitForPaths(ctx, nil, commit, treePath, entryPaths)
}
if err != nil {
return nil, nil, err
@@ -62,27 +59,31 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
commitsInfo[i] = CommitInfo{
Entry: entry,
}
+
+ // Check if we have found a commit for this entry in time
if entryCommit, ok := revs[entry.Name()]; ok {
commitsInfo[i].Commit = entryCommit
- if entry.IsSubModule() {
- subModuleURL := ""
- var fullPath string
- if len(treePath) > 0 {
- fullPath = treePath + "/" + entry.Name()
- } else {
- fullPath = entry.Name()
- }
- if subModule, err := commit.GetSubModule(fullPath); err != nil {
- return nil, nil, err
- } else if subModule != nil {
- subModuleURL = subModule.URL
- }
- subModuleFile := NewSubModuleFile(entryCommit, subModuleURL, entry.ID.String())
- commitsInfo[i].SubModuleFile = subModuleFile
- }
} else {
log.Debug("missing commit for %s", entry.Name())
}
+
+ // If the entry if a submodule add a submodule file for this
+ if entry.IsSubModule() {
+ subModuleURL := ""
+ var fullPath string
+ if len(treePath) > 0 {
+ fullPath = treePath + "/" + entry.Name()
+ } else {
+ fullPath = entry.Name()
+ }
+ if subModule, err := commit.GetSubModule(fullPath); err != nil {
+ return nil, nil, err
+ } else if subModule != nil {
+ subModuleURL = subModule.URL
+ }
+ subModuleFile := NewSubModuleFile(commitsInfo[i].Commit, subModuleURL, entry.ID.String())
+ commitsInfo[i].SubModuleFile = subModuleFile
+ }
}
// Retrieve the commit for the treePath itself (see above). We basically
@@ -121,9 +122,9 @@ func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string
}
// GetLastCommitForPaths returns last commit information
-func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
+func GetLastCommitForPaths(ctx context.Context, cache *LastCommitCache, commit *Commit, treePath string, paths []string) (map[string]*Commit, error) {
// We read backwards from the commit to obtain all of the commits
- revs, err := WalkGitLog(ctx, commit.repo, commit, treePath, paths...)
+ revs, err := WalkGitLog(ctx, cache, commit.repo, commit, treePath, paths...)
if err != nil {
return nil, err
}
diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go
index e2d296641f..d4ec517b51 100644
--- a/modules/git/last_commit_cache.go
+++ b/modules/git/last_commit_cache.go
@@ -26,6 +26,9 @@ func (c *LastCommitCache) getCacheKey(repoPath, ref, entryPath string) string {
// Put put the last commit id with commit and entry path
func (c *LastCommitCache) Put(ref, entryPath, commitID string) error {
+ if c == nil || c.cache == nil {
+ return nil
+ }
log.Debug("LastCommitCache save: [%s:%s:%s]", ref, entryPath, commitID)
return c.cache.Put(c.getCacheKey(c.repoPath, ref, entryPath), commitID, c.ttl())
}
diff --git a/modules/git/last_commit_cache_gogit.go b/modules/git/last_commit_cache_gogit.go
index fb09af6f2a..b57e9ad11f 100644
--- a/modules/git/last_commit_cache_gogit.go
+++ b/modules/git/last_commit_cache_gogit.go
@@ -9,7 +9,6 @@ package git
import (
"context"
- "path"
"code.gitea.io/gitea/modules/log"
@@ -93,15 +92,12 @@ func (c *LastCommitCache) recursiveCache(ctx context.Context, index cgobject.Com
entryMap[entry.Name()] = entry
}
- commits, err := GetLastCommitForPaths(ctx, index, treePath, entryPaths)
+ commits, err := GetLastCommitForPaths(ctx, c, index, treePath, entryPaths)
if err != nil {
return err
}
- for entry, cm := range commits {
- if err := c.Put(index.ID().String(), path.Join(treePath, entry), cm.ID().String()); err != nil {
- return err
- }
+ for entry := range commits {
if entryMap[entry].IsDir() {
subTree, err := tree.SubTree(entry)
if err != nil {
diff --git a/modules/git/last_commit_cache_nogogit.go b/modules/git/last_commit_cache_nogogit.go
index f71e7350a1..5315c0a152 100644
--- a/modules/git/last_commit_cache_nogogit.go
+++ b/modules/git/last_commit_cache_nogogit.go
@@ -10,7 +10,6 @@ package git
import (
"bufio"
"context"
- "path"
"code.gitea.io/gitea/modules/log"
)
@@ -80,28 +79,23 @@ func (c *LastCommitCache) recursiveCache(ctx context.Context, commit *Commit, tr
}
entryPaths := make([]string, len(entries))
- entryMap := make(map[string]*TreeEntry)
for i, entry := range entries {
entryPaths[i] = entry.Name()
- entryMap[entry.Name()] = entry
}
- commits, err := GetLastCommitForPaths(ctx, commit, treePath, entryPaths)
+ _, err = WalkGitLog(ctx, c, commit.repo, commit, treePath, entryPaths...)
if err != nil {
return err
}
- for entry, entryCommit := range commits {
- if err := c.Put(commit.ID.String(), path.Join(treePath, entry), entryCommit.ID.String()); err != nil {
- return err
- }
+ for _, treeEntry := range entries {
// entryMap won't contain "" therefore skip this.
- if treeEntry := entryMap[entry]; treeEntry != nil && treeEntry.IsDir() {
- subTree, err := tree.SubTree(entry)
+ if treeEntry.IsDir() {
+ subTree, err := tree.SubTree(treeEntry.Name())
if err != nil {
return err
}
- if err := c.recursiveCache(ctx, commit, subTree, entry, level-1); err != nil {
+ if err := c.recursiveCache(ctx, commit, subTree, treeEntry.Name(), level-1); err != nil {
return err
}
}
diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go
index 7d83ac7270..e792b02a5d 100644
--- a/modules/git/log_name_status.go
+++ b/modules/git/log_name_status.go
@@ -275,7 +275,9 @@ func (g *LogNameStatusRepoParser) Close() {
}
// WalkGitLog walks the git log --name-status for the head commit in the provided treepath and files
-func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) {
+func WalkGitLog(ctx context.Context, cache *LastCommitCache, repo *Repository, head *Commit, treepath string, paths ...string) (map[string]string, error) {
+ headRef := head.ID.String()
+
tree, err := head.SubTree(treepath)
if err != nil {
return nil, err
@@ -339,6 +341,9 @@ heaploop:
for {
select {
case <-ctx.Done():
+ if ctx.Err() == context.DeadlineExceeded {
+ break heaploop
+ }
g.Close()
return nil, ctx.Err()
default:
@@ -360,10 +365,16 @@ heaploop:
changed[i] = false
if results[i] == "" {
results[i] = current.CommitID
+ if err := cache.Put(headRef, path.Join(treepath, paths[i]), current.CommitID); err != nil {
+ return nil, err
+ }
delete(path2idx, paths[i])
remaining--
if results[0] == "" {
results[0] = current.CommitID
+ if err := cache.Put(headRef, treepath, current.CommitID); err != nil {
+ return nil, err
+ }
delete(path2idx, "")
remaining--
}
diff --git a/modules/git/notes_gogit.go b/modules/git/notes_gogit.go
index 28dbbc0ee5..6cb719ce92 100644
--- a/modules/git/notes_gogit.go
+++ b/modules/git/notes_gogit.go
@@ -17,6 +17,7 @@ import (
)
// GetNote retrieves the git-notes data for a given commit.
+// FIXME: Add LastCommitCache support
func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error {
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
notes, err := repo.GetCommit(NotesRef)
@@ -75,7 +76,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
return err
}
- lastCommits, err := GetLastCommitForPaths(ctx, commitNode, "", []string{path})
+ lastCommits, err := GetLastCommitForPaths(ctx, nil, commitNode, "", []string{path})
if err != nil {
log.Error("Unable to get the commit for the path %q. Error: %v", path, err)
return err
diff --git a/modules/git/notes_nogogit.go b/modules/git/notes_nogogit.go
index 5afe8d4614..13b4b7b36a 100644
--- a/modules/git/notes_nogogit.go
+++ b/modules/git/notes_nogogit.go
@@ -16,6 +16,7 @@ import (
)
// GetNote retrieves the git-notes data for a given commit.
+// FIXME: Add LastCommitCache support
func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note) error {
log.Trace("Searching for git note corresponding to the commit %q in the repository %q", commitID, repo.Path)
notes, err := repo.GetCommit(NotesRef)
@@ -75,7 +76,7 @@ func GetNote(ctx context.Context, repo *Repository, commitID string, note *Note)
path = path[idx+1:]
}
- lastCommits, err := GetLastCommitForPaths(ctx, notes, treePath, []string{path})
+ lastCommits, err := GetLastCommitForPaths(ctx, nil, notes, treePath, []string{path})
if err != nil {
log.Error("Unable to get the commit for the path %q. Error: %v", treePath, err)
return err
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index c0a35bcb4f..0777a10e7b 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -7,6 +7,7 @@ package repo
import (
"bytes"
+ gocontext "context"
"encoding/base64"
"fmt"
gotemplate "html/template"
@@ -16,6 +17,7 @@ import (
"path"
"strconv"
"strings"
+ "time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
@@ -34,11 +36,12 @@ import (
)
const (
- tplRepoEMPTY base.TplName = "repo/empty"
- tplRepoHome base.TplName = "repo/home"
- tplWatchers base.TplName = "repo/watchers"
- tplForks base.TplName = "repo/forks"
- tplMigrating base.TplName = "repo/migrate/migrating"
+ tplRepoEMPTY base.TplName = "repo/empty"
+ tplRepoHome base.TplName = "repo/home"
+ tplRepoViewList base.TplName = "repo/view_list"
+ tplWatchers base.TplName = "repo/watchers"
+ tplForks base.TplName = "repo/forks"
+ tplMigrating base.TplName = "repo/migrate/migrating"
)
type namedBlob struct {
@@ -128,28 +131,8 @@ func getReadmeFileFromPath(commit *git.Commit, treePath string) (*namedBlob, err
}
func renderDirectory(ctx *context.Context, treeLink string) {
- tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
- if err != nil {
- ctx.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err)
- return
- }
-
- entries, err := tree.ListEntries()
- if err != nil {
- ctx.ServerError("ListEntries", err)
- return
- }
- entries.CustomSort(base.NaturalSortLess)
-
- var c *git.LastCommitCache
- if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount {
- c = git.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, setting.LastCommitCacheTTLSeconds, cache.GetCache())
- }
-
- var latestCommit *git.Commit
- ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(ctx, ctx.Repo.Commit, ctx.Repo.TreePath, c)
- if err != nil {
- ctx.ServerError("GetCommitsInfo", err)
+ entries := renderDirectoryFiles(ctx, 1*time.Second)
+ if ctx.Written() {
return
}
@@ -186,6 +169,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
isSymlink := entry.IsLink()
target := entry
if isSymlink {
+ var err error
target, err = entry.FollowLinks()
if err != nil && !git.IsErrBadLink(err) {
ctx.ServerError("FollowLinks", err)
@@ -207,6 +191,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
name := entry.Name()
isSymlink := entry.IsLink()
if isSymlink {
+ var err error
entry, err = entry.FollowLinks()
if err != nil && !git.IsErrBadLink(err) {
ctx.ServerError("FollowLinks", err)
@@ -237,6 +222,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
if entry == nil {
continue
}
+ var err error
readmeFile, err = getReadmeFileFromPath(ctx.Repo.Commit, entry.GetSubJumpablePathName())
if err != nil {
ctx.ServerError("getReadmeFileFromPath", err)
@@ -365,34 +351,12 @@ func renderDirectory(ctx *context.Context, treeLink string) {
}
}
- // Show latest commit info of repository in table header,
- // or of directory if not in root directory.
- ctx.Data["LatestCommit"] = latestCommit
- verification := models.ParseCommitWithSignature(latestCommit)
-
- if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil {
- ctx.ServerError("CalculateTrustStatus", err)
- return
- }
- ctx.Data["LatestCommitVerification"] = verification
-
- ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
-
- statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), db.ListOptions{})
- if err != nil {
- log.Error("GetLatestCommitStatus: %v", err)
- }
-
- ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses)
- ctx.Data["LatestCommitStatuses"] = statuses
-
// Check permission to add or upload new file.
if ctx.Repo.CanWrite(models.UnitTypeCode) && ctx.Repo.IsViewBranch {
ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived
ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived
}
- ctx.Data["SSHDomain"] = setting.SSH.Domain
}
func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
@@ -614,8 +578,7 @@ func safeURL(address string) string {
return u.String()
}
-// Home render repository home page
-func Home(ctx *context.Context) {
+func checkHomeCodeViewable(ctx *context.Context) {
if len(ctx.Repo.Units) > 0 {
if ctx.Repo.Repository.IsBeingCreated() {
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID)
@@ -648,7 +611,6 @@ func Home(ctx *context.Context) {
var firstUnit *models.Unit
for _, repoUnit := range ctx.Repo.Units {
if repoUnit.Type == models.UnitTypeCode {
- renderCode(ctx)
return
}
@@ -667,6 +629,145 @@ func Home(ctx *context.Context) {
ctx.NotFound("Home", fmt.Errorf(ctx.Tr("units.error.no_unit_allowed_repo")))
}
+// Home render repository home page
+func Home(ctx *context.Context) {
+ checkHomeCodeViewable(ctx)
+ if ctx.Written() {
+ return
+ }
+
+ renderCode(ctx)
+}
+
+// LastCommit returns lastCommit data for the provided branch/tag/commit and directory (in url) and filenames in body
+func LastCommit(ctx *context.Context) {
+ checkHomeCodeViewable(ctx)
+ if ctx.Written() {
+ return
+ }
+
+ renderDirectoryFiles(ctx, 0)
+ if ctx.Written() {
+ return
+ }
+
+ var treeNames []string
+ paths := make([]string, 0, 5)
+ if len(ctx.Repo.TreePath) > 0 {
+ treeNames = strings.Split(ctx.Repo.TreePath, "/")
+ for i := range treeNames {
+ paths = append(paths, strings.Join(treeNames[:i+1], "/"))
+ }
+
+ ctx.Data["HasParentPath"] = true
+ if len(paths)-2 >= 0 {
+ ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
+ }
+ }
+ branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
+ ctx.Data["BranchLink"] = branchLink
+
+ ctx.HTML(http.StatusOK, tplRepoViewList)
+}
+
+func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entries {
+ tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
+ if err != nil {
+ ctx.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err)
+ return nil
+ }
+
+ ctx.Data["LastCommitLoaderURL"] = ctx.Repo.RepoLink + "/lastcommit/" + ctx.Repo.CommitID + "/" + ctx.Repo.TreePath
+
+ // Get current entry user currently looking at.
+ entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
+ if err != nil {
+ ctx.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err)
+ return nil
+ }
+
+ if !entry.IsDir() {
+ ctx.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err)
+ return nil
+ }
+
+ allEntries, err := tree.ListEntries()
+ if err != nil {
+ ctx.ServerError("ListEntries", err)
+ return nil
+ }
+ allEntries.CustomSort(base.NaturalSortLess)
+
+ commitInfoCtx := gocontext.Context(ctx)
+ if timeout > 0 {
+ var cancel gocontext.CancelFunc
+ commitInfoCtx, cancel = gocontext.WithTimeout(ctx, timeout)
+ defer cancel()
+ }
+
+ var c *git.LastCommitCache
+ if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount {
+ c = git.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, setting.LastCommitCacheTTLSeconds, cache.GetCache())
+ }
+
+ selected := map[string]bool{}
+ for _, pth := range ctx.FormStrings("f[]") {
+ selected[pth] = true
+ }
+
+ entries := allEntries
+ if len(selected) > 0 {
+ entries = make(git.Entries, 0, len(selected))
+ for _, entry := range allEntries {
+ if selected[entry.Name()] {
+ entries = append(entries, entry)
+ }
+ }
+ }
+
+ var latestCommit *git.Commit
+ ctx.Data["Files"], latestCommit, err = entries.GetCommitsInfo(commitInfoCtx, ctx.Repo.Commit, ctx.Repo.TreePath, c)
+ if err != nil {
+ ctx.ServerError("GetCommitsInfo", err)
+ return nil
+ }
+
+ // Show latest commit info of repository in table header,
+ // or of directory if not in root directory.
+ ctx.Data["LatestCommit"] = latestCommit
+ if latestCommit != nil {
+
+ verification := models.ParseCommitWithSignature(latestCommit)
+
+ if err := models.CalculateTrustStatus(verification, ctx.Repo.Repository, nil); err != nil {
+ ctx.ServerError("CalculateTrustStatus", err)
+ return nil
+ }
+ ctx.Data["LatestCommitVerification"] = verification
+ ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit)
+ }
+
+ statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), db.ListOptions{})
+ if err != nil {
+ log.Error("GetLatestCommitStatus: %v", err)
+ }
+
+ ctx.Data["LatestCommitStatus"] = models.CalcCommitStatus(statuses)
+ ctx.Data["LatestCommitStatuses"] = statuses
+
+ branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
+ treeLink := branchLink
+
+ if len(ctx.Repo.TreePath) > 0 {
+ treeLink += "/" + ctx.Repo.TreePath
+ }
+
+ ctx.Data["TreeLink"] = treeLink
+ ctx.Data["SSHDomain"] = setting.SSH.Domain
+
+ return allEntries
+}
+
func renderLanguageStats(ctx *context.Context) {
langs, err := ctx.Repo.Repository.GetTopLanguageStats(5)
if err != nil {
diff --git a/routers/web/web.go b/routers/web/web.go
index e12552f4d4..01d90d206f 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -995,6 +995,9 @@ func RegisterRoutes(m *web.Route) {
m.Get("/commit/{sha:([a-f0-9]{7,40})}.{ext:patch|diff}",
repo.MustBeNotEmpty, reqRepoCodeReader, repo.RawDiff)
}, ignSignIn, context.RepoAssignment, context.UnitTypes())
+
+ m.Post("/{username}/{reponame}/lastcommit/*", ignSignInAndCsrf, context.RepoAssignment, context.UnitTypes(), context.RepoRefByType(context.RepoRefCommit), reqRepoCodeReader, repo.LastCommit)
+
m.Group("/{username}/{reponame}", func() {
m.Get("/stars", repo.Stars)
m.Get("/watchers", repo.Watchers)
diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl
index fc66fb6b2d..3e7bcbe505 100644
--- a/templates/repo/view_list.tmpl
+++ b/templates/repo/view_list.tmpl
@@ -1,36 +1,40 @@
-<table id="repo-files-table" class="ui single line table">
+<table id="repo-files-table" class="ui single line table" data-last-commit-loader-url="{{.LastCommitLoaderURL}}">
<thead>
<tr class="commit-list">
- <th colspan="2">
- {{if .LatestCommitUser}}
- {{avatar .LatestCommitUser 24}}
- {{if .LatestCommitUser.FullName}}
- <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
- {{else}}
- <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
- {{end}}
+ <th colspan="2" {{if not .LatestCommit}}class="notready"{{end}}>
+ {{if not .LatestCommit}}
+ <div class="ui active tiny slow centered inline">…</div>
{{else}}
- {{if .LatestCommit.Author}}
- {{avatarByEmail .LatestCommit.Author.Email .LatestCommit.Author.Name 24}}
- <strong>{{.LatestCommit.Author.Name}}</strong>
- {{end}}
- {{end}}
- <a rel="nofollow" class="ui sha label {{if .LatestCommit.Signature}} isSigned {{if .LatestCommitVerification.Verified }} isVerified{{if eq .LatestCommitVerification.TrustStatus "trusted"}}{{else if eq .LatestCommitVerification.TrustStatus "untrusted"}}Untrusted{{else}}Unmatched{{end}}{{else if .LatestCommitVerification.Warning}} isWarning{{end}}{{end}}" href="{{.RepoLink}}/commit/{{.LatestCommit.ID}}">
- <span class="shortsha">{{ShortSha .LatestCommit.ID.String}}</span>
- {{if .LatestCommit.Signature}}
- {{template "repo/shabox_badge" dict "root" $ "verification" .LatestCommitVerification}}
+ {{if .LatestCommitUser}}
+ {{avatar .LatestCommitUser 24}}
+ {{if .LatestCommitUser.FullName}}
+ <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{.LatestCommitUser.FullName}}</strong></a>
+ {{else}}
+ <a href="{{AppSubUrl}}/{{.LatestCommitUser.Name}}"><strong>{{if .LatestCommit.Author}}{{.LatestCommit.Author.Name}}{{else}}{{.LatestCommitUser.Name}}{{end}}</strong></a>
+ {{end}}
+ {{else}}
+ {{if .LatestCommit.Author}}
+ {{avatarByEmail .LatestCommit.Author.Email .LatestCommit.Author.Name 24}}
+ <strong>{{.LatestCommit.Author.Name}}</strong>
+ {{end}}
{{end}}
- </a>
- {{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses "root" $}}
- {{ $commitLink:= printf "%s/commit/%s" .RepoLink .LatestCommit.ID }}
- <span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{RenderCommitMessageLinkSubject .LatestCommit.Message $.RepoLink $commitLink $.Repository.ComposeMetas}}</span>
- {{if IsMultilineCommitMessage .LatestCommit.Message}}
- <button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button>
- <pre class="commit-body" style="display: none;">{{RenderCommitBody .LatestCommit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
+ <a rel="nofollow" class="ui sha label {{if .LatestCommit.Signature}} isSigned {{if .LatestCommitVerification.Verified }} isVerified{{if eq .LatestCommitVerification.TrustStatus "trusted"}}{{else if eq .LatestCommitVerification.TrustStatus "untrusted"}}Untrusted{{else}}Unmatched{{end}}{{else if .LatestCommitVerification.Warning}} isWarning{{end}}{{end}}" href="{{.RepoLink}}/commit/{{.LatestCommit.ID}}">
+ <span class="shortsha">{{ShortSha .LatestCommit.ID.String}}</span>
+ {{if .LatestCommit.Signature}}
+ {{template "repo/shabox_badge" dict "root" $ "verification" .LatestCommitVerification}}
+ {{end}}
+ </a>
+ {{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses "root" $}}
+ {{ $commitLink:= printf "%s/commit/%s" .RepoLink .LatestCommit.ID }}
+ <span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{RenderCommitMessageLinkSubject .LatestCommit.Message $.RepoLink $commitLink $.Repository.ComposeMetas}}</span>
+ {{if IsMultilineCommitMessage .LatestCommit.Message}}
+ <button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button>
+ <pre class="commit-body" style="display: none;">{{RenderCommitBody .LatestCommit.Message $.RepoLink $.Repository.ComposeMetas}}</pre>
+ {{end}}
+ </span>
{{end}}
- </span>
</th>
- <th class="text grey right age">{{if .LatestCommit.Author}}{{TimeSince .LatestCommit.Author.When $.Lang}}{{end}}</th>
+ <th class="text grey right age">{{if .LatestCommit}}{{if .LatestCommit.Author}}{{TimeSince .LatestCommit.Author.When $.Lang}}{{end}}{{end}}</th>
</tr>
</thead>
<tbody>
@@ -43,7 +47,7 @@
{{$entry := $item.Entry}}
{{$commit := $item.Commit}}
{{$subModuleFile := $item.SubModuleFile}}
- <tr>
+ <tr data-entryname="{{$entry.Name}}" data-ready="{{if $commit}}true{{else}}false{{end}}" class="{{if not $commit}}not{{end}}ready entry">
<td class="name four wide">
<span class="truncate">
{{if $entry.IsSubModule}}
@@ -75,10 +79,14 @@
</td>
<td class="message nine wide">
<span class="truncate">
- <a href="{{$.RepoLink}}/commit/{{$commit.ID}}" title="{{$commit.Summary}}">{{$commit.Summary | RenderEmoji}}</a>
+ {{if $commit}}
+ <a href="{{$.RepoLink}}/commit/{{$commit.ID}}" title="{{$commit.Summary}}">{{$commit.Summary | RenderEmoji}}</a>
+ {{else}}
+ <div class="ui active tiny slow centered inline">…</div>
+ {{end}}
</span>
</td>
- <td class="text right age three wide">{{TimeSince $commit.Committer.When $.Lang}}</td>
+ <td class="text right age three wide">{{if $commit}}{{TimeSince $commit.Committer.When $.Lang}}{{end}}</td>
</tr>
{{end}}
</tbody>
diff --git a/web_src/js/features/lastcommitloader.js b/web_src/js/features/lastcommitloader.js
new file mode 100644
index 0000000000..964255f229
--- /dev/null
+++ b/web_src/js/features/lastcommitloader.js
@@ -0,0 +1,40 @@
+const {csrf} = window.config;
+
+export async function initLastCommitLoader() {
+ const entryMap = {};
+
+ const entries = $('table#repo-files-table tr.notready')
+ .map((_, v) => {
+ entryMap[$(v).attr('data-entryname')] = $(v);
+ return $(v).attr('data-entryname');
+ })
+ .get();
+
+ if (entries.length === 0) {
+ return;
+ }
+
+ const lastCommitLoaderURL = $('table#repo-files-table').data('lastCommitLoaderUrl');
+
+ if (entries.length > 200) {
+ $.post(lastCommitLoaderURL, {
+ _csrf: csrf,
+ }, (data) => {
+ $('table#repo-files-table').replaceWith(data);
+ });
+ return;
+ }
+
+ $.post(lastCommitLoaderURL, {
+ _csrf: csrf,
+ 'f': entries,
+ }, (data) => {
+ $(data).find('tr').each((_, row) => {
+ if (row.className === 'commit-list') {
+ $('table#repo-files-table .commit-list').replaceWith(row);
+ return;
+ }
+ entryMap[$(row).attr('data-entryname')].replaceWith(row);
+ });
+ });
+}
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 4fda303a3c..d6787e89bf 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -20,6 +20,7 @@ import initTableSort from './features/tablesort.js';
import {createCodeEditor, createMonaco} from './features/codeeditor.js';
import {initMarkupAnchors} from './markup/anchors.js';
import {initNotificationsTable, initNotificationCount} from './features/notification.js';
+import {initLastCommitLoader} from './features/lastcommitloader.js';
import {initStopwatch} from './features/stopwatch.js';
import {showLineButton} from './code/linebutton.js';
import {initMarkupContent, initCommentContent} from './markup/content.js';
@@ -2864,6 +2865,7 @@ $(document).ready(async () => {
initContextPopups();
initTableSort();
initNotificationsTable();
+ initLastCommitLoader();
initPullRequestMergeInstruction();
initFileViewToggle();
initReleaseEditor();