diff --git a/cmd/web.go b/cmd/web.go index 73cec968d0..289e07285f 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -370,7 +370,7 @@ func runWeb(*cli.Context) { m.Group("/:username/:reponame", func() { m.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost) m.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost) - }, reqSignIn, middleware.RepoAssignment(true, true)) + }, reqSignIn, middleware.RepoAssignment(true)) m.Group("/:username/:reponame", func() { m.Get("/issues", repo.Issues) @@ -382,20 +382,18 @@ func runWeb(*cli.Context) { }, ignSignIn, middleware.RepoAssignment(true)) m.Group("/:username/:reponame", func() { - m.Get("/src/:branchname", repo.Home) - m.Get("/src/:branchname/*", repo.Home) - m.Get("/raw/:branchname/*", repo.SingleDownload) - m.Get("/commits/:branchname", repo.Commits) - m.Get("/commits/:branchname/search", repo.SearchCommits) - m.Get("/commits/:branchname/*", repo.FileHistory) - m.Get("/commit/:branchname", repo.Diff) - m.Get("/commit/:branchname/*", repo.Diff) + m.Group("", func() { + m.Get("/src/*", repo.Home) + m.Get("/raw/*", repo.SingleDownload) + m.Get("/commits/*", repo.RefCommits) + m.Get("/commit/*", repo.Diff) + }, middleware.RepoRef()) m.Get("/releases", repo.Releases) m.Get("/compare/:before([a-z0-9]+)...:after([a-z0-9]+)", repo.CompareDiff) - }, ignSignIn, middleware.RepoAssignment(true, true)) + }, ignSignIn, middleware.RepoAssignment(true)) m.Group("/:username", func() { - m.Get("/:reponame", ignSignIn, middleware.RepoAssignment(true, true, true), repo.Home) + m.Get("/:reponame", ignSignIn, middleware.RepoAssignment(true, true), middleware.RepoRef(), repo.Home) m.Any("/:reponame/*", ignSignInAndCsrf, repo.Http) }) diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index 5380ce8245..6d4c13c5ae 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -168,6 +168,8 @@ org_still_own_repo = This organization still have ownership of repository, you h still_own_user = This authentication still has used by some users, you should move them and then delete again. +target_branch_not_exist = Target branch does not exist + [user] change_avatar = Change your avatar at gravatar.com join_on = Joined on diff --git a/gogs.go b/gogs.go index 97dd40bdac..70379d2f9e 100644 --- a/gogs.go +++ b/gogs.go @@ -17,7 +17,7 @@ import ( "github.com/gogits/gogs/modules/setting" ) -const APP_VER = "0.5.7.1105 Beta" +const APP_VER = "0.5.7.1106 Beta" func init() { runtime.GOMAXPROCS(runtime.NumCPU()) diff --git a/modules/middleware/context.go b/modules/middleware/context.go index c45206a988..d2620fed12 100644 --- a/modules/middleware/context.go +++ b/modules/middleware/context.go @@ -53,6 +53,7 @@ type Context struct { GitRepo *git.Repository BranchName string TagName string + TreeName string CommitId string RepoLink string CloneLink struct { @@ -176,7 +177,10 @@ func Contexter() macaron.Handler { ctx.IsSigned = true ctx.Data["IsSigned"] = ctx.IsSigned ctx.Data["SignedUser"] = ctx.User + ctx.Data["SignedUserName"] = ctx.User.Name ctx.Data["IsAdmin"] = ctx.User.IsAdmin + } else { + ctx.Data["SignedUserName"] = "" } // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index fec9c54161..3d435a0540 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -18,17 +18,110 @@ import ( "github.com/gogits/gogs/modules/setting" ) +// RepoRef handles repository reference name including those contain `/`. +func RepoRef() macaron.Handler { + return func(ctx *Context) { + var ( + refName string + err error + ) + + // Get default branch. + if len(ctx.Params("*")) == 0 { + refName = ctx.Repo.Repository.DefaultBranch + if !ctx.Repo.GitRepo.IsBranchExist(refName) { + brs, err := ctx.Repo.GitRepo.GetBranches() + if err != nil { + ctx.Handle(500, "GetBranches", err) + return + } + refName = brs[0] + } + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfBranch(refName) + if err != nil { + ctx.Handle(500, "GetCommitOfBranch", err) + return + } + ctx.Repo.CommitId = ctx.Repo.Commit.Id.String() + ctx.Repo.IsBranch = true + ctx.Repo.BranchName = refName + + } else { + hasMatched := false + parts := strings.Split(ctx.Params("*"), "/") + for i, part := range parts { + refName = strings.TrimPrefix(refName+"/"+part, "/") + + if ctx.Repo.GitRepo.IsBranchExist(refName) || + ctx.Repo.GitRepo.IsTagExist(refName) { + if i < len(parts)-1 { + ctx.Repo.TreeName = strings.Join(parts[i+1:], "/") + } + hasMatched = true + break + } + } + if !hasMatched && len(parts[0]) == 40 { + refName = parts[0] + ctx.Repo.TreeName = strings.Join(parts[1:], "/") + } + + if ctx.Repo.GitRepo.IsBranchExist(refName) { + ctx.Repo.IsBranch = true + + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfBranch(refName) + if err != nil { + ctx.Handle(500, "GetCommitOfBranch", err) + return + } + ctx.Repo.CommitId = ctx.Repo.Commit.Id.String() + + } else if ctx.Repo.GitRepo.IsTagExist(refName) { + ctx.Repo.IsTag = true + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfTag(refName) + if err != nil { + ctx.Handle(500, "GetCommitOfTag", err) + return + } + ctx.Repo.CommitId = ctx.Repo.Commit.Id.String() + } else if len(refName) == 40 { + ctx.Repo.IsCommit = true + ctx.Repo.CommitId = refName + + ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName) + if err != nil { + ctx.Handle(404, "GetCommit", nil) + return + } + } else { + ctx.Handle(404, "RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName)) + return + } + } + + ctx.Repo.BranchName = refName + ctx.Data["BranchName"] = ctx.Repo.BranchName + ctx.Data["CommitId"] = ctx.Repo.CommitId + ctx.Data["IsBranch"] = ctx.Repo.IsBranch + ctx.Data["IsTag"] = ctx.Repo.IsTag + ctx.Data["IsCommit"] = ctx.Repo.IsCommit + + ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount() + if err != nil { + ctx.Handle(500, "CommitsCount", err) + return + } + ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount + } +} + func RepoAssignment(redirect bool, args ...bool) macaron.Handler { return func(ctx *Context) { var ( - validBranch bool // To valid brach name. displayBare bool // To display bare page if it is a bare repo. ) if len(args) >= 1 { - validBranch = args[0] - } - if len(args) >= 2 { - displayBare = args[1] + displayBare = args[0] } var ( @@ -201,71 +294,71 @@ func RepoAssignment(redirect bool, args ...bool) macaron.Handler { } // when repo is bare, not valid branch - if !ctx.Repo.Repository.IsBare && validBranch { - detect: - if len(refName) > 0 { - if gitRepo.IsBranchExist(refName) { - ctx.Repo.IsBranch = true - ctx.Repo.BranchName = refName + // if !ctx.Repo.Repository.IsBare && validBranch { + // detect: + // if len(refName) > 0 { + // if gitRepo.IsBranchExist(refName) { + // ctx.Repo.IsBranch = true + // ctx.Repo.BranchName = refName - ctx.Repo.Commit, err = gitRepo.GetCommitOfBranch(refName) - if err != nil { - ctx.Handle(500, "RepoAssignment invalid branch", err) - return - } - ctx.Repo.CommitId = ctx.Repo.Commit.Id.String() + // ctx.Repo.Commit, err = gitRepo.GetCommitOfBranch(refName) + // if err != nil { + // ctx.Handle(500, "RepoAssignment invalid branch", err) + // return + // } + // ctx.Repo.CommitId = ctx.Repo.Commit.Id.String() - } else if gitRepo.IsTagExist(refName) { - ctx.Repo.IsTag = true - ctx.Repo.BranchName = refName - ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfTag(refName) - if err != nil { - ctx.Handle(500, "Fail to get tag commit", err) - return - } - ctx.Repo.CommitId = ctx.Repo.Commit.Id.String() - } else if len(refName) == 40 { - ctx.Repo.IsCommit = true - ctx.Repo.CommitId = refName - ctx.Repo.BranchName = refName + // } else if gitRepo.IsTagExist(refName) { + // ctx.Repo.IsTag = true + // ctx.Repo.BranchName = refName + // ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfTag(refName) + // if err != nil { + // ctx.Handle(500, "Fail to get tag commit", err) + // return + // } + // ctx.Repo.CommitId = ctx.Repo.Commit.Id.String() + // } else if len(refName) == 40 { + // ctx.Repo.IsCommit = true + // ctx.Repo.CommitId = refName + // ctx.Repo.BranchName = refName - ctx.Repo.Commit, err = gitRepo.GetCommit(refName) - if err != nil { - ctx.Handle(404, "RepoAssignment invalid commit", nil) - return - } - } else { - ctx.Handle(404, "RepoAssignment invalid repo", fmt.Errorf("branch or tag not exist: %s", refName)) - return - } + // ctx.Repo.Commit, err = gitRepo.GetCommit(refName) + // if err != nil { + // ctx.Handle(404, "RepoAssignment invalid commit", nil) + // return + // } + // } else { + // ctx.Handle(404, "RepoAssignment invalid repo", fmt.Errorf("branch or tag not exist: %s", refName)) + // return + // } - } else { - if len(refName) == 0 { - if gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) { - refName = ctx.Repo.Repository.DefaultBranch - } else { - brs, err := gitRepo.GetBranches() - if err != nil { - ctx.Handle(500, "GetBranches", err) - return - } - refName = brs[0] - } - } - goto detect - } + // } else { + // if len(refName) == 0 { + // if gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) { + // refName = ctx.Repo.Repository.DefaultBranch + // } else { + // brs, err := gitRepo.GetBranches() + // if err != nil { + // ctx.Handle(500, "GetBranches", err) + // return + // } + // refName = brs[0] + // } + // } + // goto detect + // } - ctx.Data["IsBranch"] = ctx.Repo.IsBranch - ctx.Data["IsTag"] = ctx.Repo.IsTag - ctx.Data["IsCommit"] = ctx.Repo.IsCommit + // ctx.Data["IsBranch"] = ctx.Repo.IsBranch + // ctx.Data["IsTag"] = ctx.Repo.IsTag + // ctx.Data["IsCommit"] = ctx.Repo.IsCommit - ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount() - if err != nil { - ctx.Handle(500, "CommitsCount", err) - return - } - ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount - } + // ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount() + // if err != nil { + // ctx.Handle(500, "CommitsCount", err) + // return + // } + // ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount + // } // repo is bare and display enable if ctx.Repo.Repository.IsBare { diff --git a/public/ng/css/gogs.css b/public/ng/css/gogs.css index 91588d9916..df881a8393 100644 --- a/public/ng/css/gogs.css +++ b/public/ng/css/gogs.css @@ -240,6 +240,9 @@ img.avatar-100 { .text-black { color: #444444; } +.text-gold { + color: #a1882b; +} .table { width: 100%; max-width: 100%; diff --git a/public/ng/less/gogs/base.less b/public/ng/less/gogs/base.less index 49389b06ae..ed5f2fee59 100644 --- a/public/ng/less/gogs/base.less +++ b/public/ng/less/gogs/base.less @@ -259,6 +259,9 @@ clear: both; .text-black { color: #444444; } +.text-gold { + color: #a1882b; +} .table { width: 100%; max-width: 100%; diff --git a/routers/repo/commit.go b/routers/repo/commit.go index b2c2e0f9ac..f060133aba 100644 --- a/routers/repo/commit.go +++ b/routers/repo/commit.go @@ -20,6 +20,17 @@ const ( DIFF base.TplName = "repo/diff" ) +func RefCommits(ctx *middleware.Context) { + switch { + case len(ctx.Repo.TreeName) == 0: + Commits(ctx) + case ctx.Repo.TreeName == "search": + SearchCommits(ctx) + default: + FileHistory(ctx) + } +} + func Commits(ctx *middleware.Context) { ctx.Data["IsRepoToolbarCommits"] = true @@ -109,6 +120,69 @@ func SearchCommits(ctx *middleware.Context) { ctx.HTML(200, COMMITS) } +func FileHistory(ctx *middleware.Context) { + ctx.Data["IsRepoToolbarCommits"] = true + + fileName := ctx.Repo.TreeName + if len(fileName) == 0 { + Commits(ctx) + return + } + + userName := ctx.Repo.Owner.Name + repoName := ctx.Repo.Repository.Name + branchName := ctx.Repo.BranchName + + brs, err := ctx.Repo.GitRepo.GetBranches() + if err != nil { + ctx.Handle(500, "GetBranches", err) + return + } else if len(brs) == 0 { + ctx.Handle(404, "GetBranches", nil) + return + } + + commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(branchName, fileName) + if err != nil { + ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err) + return + } else if commitsCount == 0 { + ctx.Handle(404, "repo.FileHistory", nil) + return + } + + // Calculate and validate page number. + page := com.StrTo(ctx.Query("p")).MustInt() + if page < 1 { + page = 1 + } + lastPage := page - 1 + if lastPage < 0 { + lastPage = 0 + } + nextPage := page + 1 + if nextPage*50 > commitsCount { + nextPage = 0 + } + + commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange( + branchName, fileName, page) + if err != nil { + ctx.Handle(500, "repo.FileHistory(CommitsByRange)", err) + return + } + commits = models.ValidateCommitsWithEmails(commits) + + ctx.Data["Commits"] = commits + ctx.Data["Username"] = userName + ctx.Data["Reponame"] = repoName + ctx.Data["FileName"] = fileName + ctx.Data["CommitCount"] = commitsCount + ctx.Data["LastPageNum"] = lastPage + ctx.Data["NextPageNum"] = nextPage + ctx.HTML(200, COMMITS) +} + func Diff(ctx *middleware.Context) { ctx.Data["IsRepoToolbarCommits"] = true @@ -230,66 +304,3 @@ func CompareDiff(ctx *middleware.Context) { ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", afterCommitId) ctx.HTML(200, DIFF) } - -func FileHistory(ctx *middleware.Context) { - ctx.Data["IsRepoToolbarCommits"] = true - - fileName := ctx.Params("*") - if len(fileName) == 0 { - Commits(ctx) - return - } - - userName := ctx.Repo.Owner.Name - repoName := ctx.Repo.Repository.Name - branchName := ctx.Params(":branchname") - - brs, err := ctx.Repo.GitRepo.GetBranches() - if err != nil { - ctx.Handle(500, "GetBranches", err) - return - } else if len(brs) == 0 { - ctx.Handle(404, "GetBranches", nil) - return - } - - commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(branchName, fileName) - if err != nil { - ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err) - return - } else if commitsCount == 0 { - ctx.Handle(404, "repo.FileHistory", nil) - return - } - - // Calculate and validate page number. - page := com.StrTo(ctx.Query("p")).MustInt() - if page < 1 { - page = 1 - } - lastPage := page - 1 - if lastPage < 0 { - lastPage = 0 - } - nextPage := page + 1 - if nextPage*50 > commitsCount { - nextPage = 0 - } - - commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange( - branchName, fileName, page) - if err != nil { - ctx.Handle(500, "repo.FileHistory(CommitsByRange)", err) - return - } - commits = models.ValidateCommitsWithEmails(commits) - - ctx.Data["Commits"] = commits - ctx.Data["Username"] = userName - ctx.Data["Reponame"] = repoName - ctx.Data["FileName"] = fileName - ctx.Data["CommitCount"] = commitsCount - ctx.Data["LastPageNum"] = lastPage - ctx.Data["NextPageNum"] = nextPage - ctx.HTML(200, COMMITS) -} diff --git a/routers/repo/download.go b/routers/repo/download.go index abb9b06292..17642a57ea 100644 --- a/routers/repo/download.go +++ b/routers/repo/download.go @@ -13,9 +13,7 @@ import ( ) func SingleDownload(ctx *middleware.Context) { - treename := ctx.Params("*") - - blob, err := ctx.Repo.Commit.GetBlobByPath(treename) + blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreeName) if err != nil { ctx.Handle(500, "GetBlobByPath", err) return @@ -23,7 +21,7 @@ func SingleDownload(ctx *middleware.Context) { dataRc, err := blob.Data() if err != nil { - ctx.Handle(500, "repo.SingleDownload(Data)", err) + ctx.Handle(500, "Data", err) return } @@ -37,7 +35,7 @@ func SingleDownload(ctx *middleware.Context) { _, isImageFile := base.IsImageFile(buf) ctx.Resp.Header().Set("Content-Type", contentType) if !isTextFile && !isImageFile { - ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+path.Base(treename)) + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+path.Base(ctx.Repo.TreeName)) ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary") } ctx.Resp.Write(buf) diff --git a/routers/repo/release.go b/routers/repo/release.go index addeb1ce5f..cc9864fe7a 100644 --- a/routers/repo/release.go +++ b/routers/repo/release.go @@ -22,21 +22,38 @@ func Releases(ctx *middleware.Context) { ctx.Data["Title"] = "Releases" ctx.Data["IsRepoToolbarReleases"] = true ctx.Data["IsRepoReleaseNew"] = false + rawTags, err := ctx.Repo.GitRepo.GetTags() if err != nil { - ctx.Handle(500, "release.Releases(GetTags)", err) + ctx.Handle(500, "GetTags", err) return } rels, err := models.GetReleasesByRepoId(ctx.Repo.Repository.Id) if err != nil { - ctx.Handle(500, "release.Releases(GetReleasesByRepoId)", err) + ctx.Handle(500, "GetReleasesByRepoId", err) return } - commitsCount, err := ctx.Repo.Commit.CommitsCount() + // Get default branch. + refName := ctx.Repo.Repository.DefaultBranch + if !ctx.Repo.GitRepo.IsBranchExist(refName) { + brs, err := ctx.Repo.GitRepo.GetBranches() + if err != nil { + ctx.Handle(500, "GetBranches", err) + return + } + refName = brs[0] + } + commit, err := ctx.Repo.GitRepo.GetCommitOfBranch(refName) if err != nil { - ctx.Handle(500, "release.Releases(CommitsCount)", err) + ctx.Handle(500, "GetCommitOfBranch", err) + return + } + + commitsCount, err := commit.CommitsCount() + if err != nil { + ctx.Handle(500, "CommitsCount", err) return } @@ -59,18 +76,18 @@ func Releases(ctx *middleware.Context) { if ctx.Repo.BranchName != rel.Target { // Get count if not exists. if _, ok := countCache[rel.Target]; !ok { - commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rel.TagName) + commit, err := ctx.Repo.GitRepo.GetCommitOfBranch(ctx.Repo.BranchName) if err != nil { - ctx.Handle(500, "GetCommitOfTag", err) + ctx.Handle(500, "GetCommitOfBranch", err) return } - countCache[rel.Target], err = commit.CommitsCount() + countCache[ctx.Repo.BranchName], err = commit.CommitsCount() if err != nil { ctx.Handle(500, "CommitsCount2", err) return } } - rel.NumCommitsBehind = countCache[rel.Target] - rel.NumCommits + rel.NumCommitsBehind = countCache[ctx.Repo.BranchName] - rel.NumCommits } else { rel.NumCommitsBehind = commitsCount - rel.NumCommits } @@ -134,14 +151,20 @@ func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) { return } - commitsCount, err := ctx.Repo.Commit.CommitsCount() - if err != nil { - ctx.Handle(500, "release.ReleasesNewPost(CommitsCount)", err) + if !ctx.Repo.GitRepo.IsBranchExist(form.Target) { + ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), RELEASE_NEW, &form) return } - if !ctx.Repo.GitRepo.IsBranchExist(form.Target) { - ctx.RenderWithErr("Target branch does not exist", "release/new", &form) + commit, err := ctx.Repo.GitRepo.GetCommitOfBranch(form.Target) + if err != nil { + ctx.Handle(500, "GetCommitOfBranch", err) + return + } + + commitsCount, err := commit.CommitsCount() + if err != nil { + ctx.Handle(500, "CommitsCount", err) return } @@ -151,7 +174,7 @@ func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) { Title: form.Title, TagName: form.TagName, Target: form.Target, - Sha1: ctx.Repo.Commit.Id.String(), + Sha1: commit.Id.String(), NumCommits: commitsCount, Note: form.Content, IsDraft: len(form.Draft) > 0, diff --git a/routers/repo/view.go b/routers/repo/view.go index 26fa0b4c76..162279acd1 100644 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -34,7 +34,7 @@ func Home(ctx *middleware.Context) { rawLink := ctx.Repo.RepoLink + "/raw/" + branchName // Get tree path - treename := ctx.Params("*") + treename := ctx.Repo.TreeName if len(treename) > 0 && treename[len(treename)-1] == '/' { ctx.Redirect(repoLink + "/src/" + branchName + "/" + treename[:len(treename)-1]) diff --git a/templates/.VERSION b/templates/.VERSION index f2ebc3fb97..7c013be746 100644 --- a/templates/.VERSION +++ b/templates/.VERSION @@ -1 +1 @@ -0.5.7.1105 Beta \ No newline at end of file +0.5.7.1106 Beta \ No newline at end of file diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl index 49bf1fd14a..4928033fc2 100644 --- a/templates/repo/home.tmpl +++ b/templates/repo/home.tmpl @@ -54,7 +54,7 @@ {{end}} {{end}} -
  • + {{if .IsFile}} {{template "repo/view_file" .}} diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index af3d11a034..46bc99bda4 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -70,13 +70,18 @@
    {{range .Repos}} - {{if not .IsPrivate}} + {{if or (not .IsPrivate) (.HasAccess $.SignedUserName)}}
    • {{.NumStars}}
    • {{.NumForks}}
    -

    {{.Name}}

    +

    + {{.Name}} + {{if .IsPrivate}} + + {{end}} +

    {{.Description}}

    {{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}