summaryrefslogtreecommitdiffstats
path: root/routers
diff options
context:
space:
mode:
authorzeripath <art27@cantab.net>2020-09-25 05:09:23 +0100
committerGitHub <noreply@github.com>2020-09-25 07:09:23 +0300
commit7a7f56044ae0b94d9f5e733d863821dbe222a395 (patch)
tree0a6ebe01ce4f48a117598c7b42e189a207314553 /routers
parent6fa19a8458e993972be2355e0575a76823a75735 (diff)
downloadgitea-7a7f56044ae0b94d9f5e733d863821dbe222a395.tar.gz
gitea-7a7f56044ae0b94d9f5e733d863821dbe222a395.zip
Adopt repositories (#12920)
* Don't automatically delete repository files if they are present Prior to this PR Gitea would delete any repository files if they are present during creation or migration. This can in certain circumstances lead to data-loss and is slightly unpleasant. This PR provides a mechanism for Gitea to adopt repositories on creation and otherwise requires an explicit flag for deletion. PushCreate is slightly different - the create will cause adoption if that is allowed otherwise it will delete the data if that is allowed. Signed-off-by: Andrew Thornton <art27@cantab.net> * Update swagger Signed-off-by: Andrew Thornton <art27@cantab.net> * Fix tests and migrate overwrite Signed-off-by: Andrew Thornton <art27@cantab.net> * as per @lunny Only offer to adopt or overwrite if the user can do that. Allow the site administrator to adopt or overwrite in all circumstances Signed-off-by: Andrew Thornton <art27@cantab.net> * Use setting.Repository.DefaultBranch for the default branch Signed-off-by: Andrew Thornton <art27@cantab.net> * Always set setting.Repository.DefaultBranch Signed-off-by: Andrew Thornton <art27@cantab.net> * update swagger Signed-off-by: Andrew Thornton <art27@cantab.net> * update templates Signed-off-by: Andrew Thornton <art27@cantab.net> * ensure repo closed Signed-off-by: Andrew Thornton <art27@cantab.net> * Rewrite of adoption as per @6543 and @lunny Signed-off-by: Andrew Thornton <art27@cantab.net> * Apply suggestions from code review * update swagger Signed-off-by: Andrew Thornton <art27@cantab.net> * missing not Signed-off-by: Andrew Thornton <art27@cantab.net> * add modals and flash reporting Signed-off-by: Andrew Thornton <art27@cantab.net> * Make the unadopted page searchable Signed-off-by: Andrew Thornton <art27@cantab.net> * Add API Signed-off-by: Andrew Thornton <art27@cantab.net> * Fix swagger Signed-off-by: Andrew Thornton <art27@cantab.net> * fix swagger Signed-off-by: Andrew Thornton <art27@cantab.net> * Handle empty and non-master branched repositories Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> * remove commented out code Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Diffstat (limited to 'routers')
-rw-r--r--routers/admin/repos.go95
-rw-r--r--routers/api/v1/admin/adopt.go164
-rw-r--r--routers/api/v1/api.go5
-rw-r--r--routers/api/v1/repo/migrate.go2
-rw-r--r--routers/repo/migrate.go14
-rw-r--r--routers/repo/repo.go12
-rw-r--r--routers/repo/setting.go12
-rw-r--r--routers/routes/routes.go2
-rw-r--r--routers/user/setting/adopt.go56
-rw-r--r--routers/user/setting/profile.go102
10 files changed, 444 insertions, 20 deletions
diff --git a/routers/admin/repos.go b/routers/admin/repos.go
index 39a1d7596c..10abaf9547 100644
--- a/routers/admin/repos.go
+++ b/routers/admin/repos.go
@@ -5,17 +5,22 @@
package admin
import (
+ "strings"
+
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers"
repo_service "code.gitea.io/gitea/services/repository"
+ "github.com/unknwon/com"
)
const (
- tplRepos base.TplName = "admin/repo/list"
+ tplRepos base.TplName = "admin/repo/list"
+ tplUnadoptedRepos base.TplName = "admin/repo/unadopted"
)
// Repos show all the repositories
@@ -50,3 +55,91 @@ func DeleteRepo(ctx *context.Context) {
"redirect": setting.AppSubURL + "/admin/repos?page=" + ctx.Query("page") + "&sort=" + ctx.Query("sort"),
})
}
+
+// UnadoptedRepos lists the unadopted repositories
+func UnadoptedRepos(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("admin.repositories")
+ ctx.Data["PageIsAdmin"] = true
+ ctx.Data["PageIsAdminRepositories"] = true
+
+ opts := models.ListOptions{
+ PageSize: setting.UI.Admin.UserPagingNum,
+ Page: ctx.QueryInt("page"),
+ }
+
+ if opts.Page <= 0 {
+ opts.Page = 1
+ }
+
+ doSearch := ctx.QueryBool("search")
+
+ ctx.Data["search"] = doSearch
+ q := ctx.Query("q")
+
+ if !doSearch {
+ pager := context.NewPagination(0, opts.PageSize, opts.Page, 5)
+ pager.SetDefaultParams(ctx)
+ ctx.Data["Page"] = pager
+ ctx.HTML(200, tplUnadoptedRepos)
+ return
+ }
+
+ ctx.Data["Keyword"] = q
+ repoNames, count, err := repository.ListUnadoptedRepositories(q, &opts)
+ if err != nil {
+ ctx.ServerError("ListUnadoptedRepositories", err)
+ }
+ ctx.Data["Dirs"] = repoNames
+ pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
+ pager.SetDefaultParams(ctx)
+ ctx.Data["Page"] = pager
+ ctx.HTML(200, tplUnadoptedRepos)
+}
+
+// AdoptOrDeleteRepository adopts or deletes a repository
+func AdoptOrDeleteRepository(ctx *context.Context) {
+ dir := ctx.Query("id")
+ action := ctx.Query("action")
+ dirSplit := strings.SplitN(dir, "/", 2)
+ if len(dirSplit) != 2 {
+ ctx.Redirect(setting.AppSubURL + "/admin/repos")
+ return
+ }
+
+ ctxUser, err := models.GetUserByName(dirSplit[0])
+ if err != nil {
+ if models.IsErrUserNotExist(err) {
+ log.Debug("User does not exist: %s", dirSplit[0])
+ ctx.Redirect(setting.AppSubURL + "/admin/repos")
+ return
+ }
+ ctx.ServerError("GetUserByName", err)
+ return
+ }
+
+ repoName := dirSplit[1]
+
+ // check not a repo
+ if has, err := models.IsRepositoryExist(ctxUser, repoName); err != nil {
+ ctx.ServerError("IsRepositoryExist", err)
+ return
+ } else if has || !com.IsDir(models.RepoPath(ctxUser.Name, repoName)) {
+ // Fallthrough to failure mode
+ } else if action == "adopt" {
+ if _, err := repository.AdoptRepository(ctx.User, ctxUser, models.CreateRepoOptions{
+ Name: dirSplit[1],
+ IsPrivate: true,
+ }); err != nil {
+ ctx.ServerError("repository.AdoptRepository", err)
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("repo.adopt_preexisting_success", dir))
+ } else if action == "delete" {
+ if err := repository.DeleteUnadoptedRepository(ctx.User, ctxUser, dirSplit[1]); err != nil {
+ ctx.ServerError("repository.AdoptRepository", err)
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("repo.delete_preexisting_success", dir))
+ }
+ ctx.Redirect(setting.AppSubURL + "/admin/repos/unadopted")
+}
diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go
new file mode 100644
index 0000000000..1a7a62a55c
--- /dev/null
+++ b/routers/api/v1/admin/adopt.go
@@ -0,0 +1,164 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package admin
+
+import (
+ "fmt"
+ "net/http"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/routers/api/v1/utils"
+ "github.com/unknwon/com"
+)
+
+// ListUnadoptedRepositories lists the unadopted repositories that match the provided names
+func ListUnadoptedRepositories(ctx *context.APIContext) {
+ // swagger:operation GET /admin/unadopted admin adminUnadoptedList
+ // ---
+ // summary: List unadopted repositories
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: page
+ // in: query
+ // description: page number of results to return (1-based)
+ // type: integer
+ // - name: limit
+ // in: query
+ // description: page size of results
+ // type: integer
+ // - name: pattern
+ // in: query
+ // description: pattern of repositories to search for
+ // type: string
+ // responses:
+ // "200":
+ // "$ref": "#/responses/StringSlice"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+
+ listOptions := utils.GetListOptions(ctx)
+ repoNames, count, err := repository.ListUnadoptedRepositories(ctx.Query("query"), &listOptions)
+ if err != nil {
+ ctx.InternalServerError(err)
+ }
+
+ ctx.Header().Set("X-Total-Count", fmt.Sprintf("%d", count))
+ ctx.Header().Set("Access-Control-Expose-Headers", "X-Total-Count")
+
+ ctx.JSON(http.StatusOK, repoNames)
+}
+
+// AdoptRepository will adopt an unadopted repository
+func AdoptRepository(ctx *context.APIContext) {
+ // swagger:operation POST /admin/unadopted/{owner}/{repo} admin adminAdoptRepository
+ // ---
+ // summary: Adopt unadopted files as a repository
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ ownerName := ctx.Params(":username")
+ repoName := ctx.Params(":reponame")
+
+ ctxUser, err := models.GetUserByName(ownerName)
+ if err != nil {
+ if models.IsErrUserNotExist(err) {
+ ctx.NotFound()
+ return
+ }
+ ctx.InternalServerError(err)
+ return
+ }
+
+ // check not a repo
+ if has, err := models.IsRepositoryExist(ctxUser, repoName); err != nil {
+ ctx.InternalServerError(err)
+ return
+ } else if has || !com.IsDir(models.RepoPath(ctxUser.Name, repoName)) {
+ ctx.NotFound()
+ return
+ }
+ if _, err := repository.AdoptRepository(ctx.User, ctxUser, models.CreateRepoOptions{
+ Name: repoName,
+ IsPrivate: true,
+ }); err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
+
+// DeleteUnadoptedRepository will delete an unadopted repository
+func DeleteUnadoptedRepository(ctx *context.APIContext) {
+ // swagger:operation DELETE /admin/unadopted/{owner}/{repo} admin adminDeleteUnadoptedRepository
+ // ---
+ // summary: Delete unadopted files
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // responses:
+ // "204":
+ // "$ref": "#/responses/empty"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ ownerName := ctx.Params(":username")
+ repoName := ctx.Params(":reponame")
+
+ ctxUser, err := models.GetUserByName(ownerName)
+ if err != nil {
+ if models.IsErrUserNotExist(err) {
+ ctx.NotFound()
+ return
+ }
+ ctx.InternalServerError(err)
+ return
+ }
+
+ // check not a repo
+ if has, err := models.IsRepositoryExist(ctxUser, repoName); err != nil {
+ ctx.InternalServerError(err)
+ return
+ } else if has || !com.IsDir(models.RepoPath(ctxUser.Name, repoName)) {
+ ctx.NotFound()
+ return
+ }
+
+ if err := repository.DeleteUnadoptedRepository(ctx.User, ctxUser, repoName); err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+
+ ctx.Status(http.StatusNoContent)
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 8b3a7545c6..3b6f8dbba3 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -957,6 +957,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
})
})
+ m.Group("/unadopted", func() {
+ m.Get("", admin.ListUnadoptedRepositories)
+ m.Post("/:username/:reponame", admin.AdoptRepository)
+ m.Delete("/:username/:reponame", admin.DeleteUnadoptedRepository)
+ })
}, reqToken(), reqSiteAdmin())
m.Group("/topics", func() {
diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go
index 019d82031c..f9cddbb7cd 100644
--- a/routers/api/v1/repo/migrate.go
+++ b/routers/api/v1/repo/migrate.go
@@ -198,6 +198,8 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteA
switch {
case models.IsErrRepoAlreadyExist(err):
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
+ case models.IsErrRepoFilesAlreadyExist(err):
+ ctx.Error(http.StatusConflict, "", "Files already exist for this repository. Adopt them or delete them.")
case migrations.IsRateLimitError(err):
ctx.Error(http.StatusUnprocessableEntity, "", "Remote visit addressed rate limitation.")
case migrations.IsTwoFactorAuthError(err):
diff --git a/routers/repo/migrate.go b/routers/repo/migrate.go
index 19dbfbab40..9b10970bf0 100644
--- a/routers/repo/migrate.go
+++ b/routers/repo/migrate.go
@@ -67,6 +67,18 @@ func handleMigrateError(ctx *context.Context, owner *models.User, err error, nam
case models.IsErrRepoAlreadyExist(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form)
+ case models.IsErrRepoFilesAlreadyExist(err):
+ ctx.Data["Err_RepoName"] = true
+ switch {
+ case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form)
+ case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form)
+ case setting.Repository.AllowDeleteOfUnadoptedRepositories:
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form)
+ default:
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form)
+ }
case models.IsErrNameReserved(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form)
@@ -159,7 +171,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
opts.Releases = false
}
- err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName)
+ err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, false)
if err != nil {
handleMigrateError(ctx, ctxUser, err, "MigratePost", tpl, &form)
return
diff --git a/routers/repo/repo.go b/routers/repo/repo.go
index 4a088ff9cd..12434747eb 100644
--- a/routers/repo/repo.go
+++ b/routers/repo/repo.go
@@ -160,6 +160,18 @@ func handleCreateError(ctx *context.Context, owner *models.User, err error, name
case models.IsErrRepoAlreadyExist(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form)
+ case models.IsErrRepoFilesAlreadyExist(err):
+ ctx.Data["Err_RepoName"] = true
+ switch {
+ case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form)
+ case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form)
+ case setting.Repository.AllowDeleteOfUnadoptedRepositories:
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form)
+ default:
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form)
+ }
case models.IsErrNameReserved(err):
ctx.Data["Err_RepoName"] = true
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form)
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
index d2c20fb03a..865dea9bca 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -88,6 +88,18 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form)
case models.IsErrNameReserved(err):
ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplSettingsOptions, &form)
+ case models.IsErrRepoFilesAlreadyExist(err):
+ ctx.Data["Err_RepoName"] = true
+ switch {
+ case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories):
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form)
+ case setting.Repository.AllowAdoptionOfUnadoptedRepositories:
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form)
+ case setting.Repository.AllowDeleteOfUnadoptedRepositories:
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form)
+ default:
+ ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form)
+ }
case models.IsErrNamePatternNotAllowed(err):
ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form)
default:
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index 5345a10171..f60af5dad0 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -402,6 +402,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/keys/delete", userSetting.DeleteKey)
m.Get("/organization", userSetting.Organization)
m.Get("/repos", userSetting.Repos)
+ m.Post("/repos/unadopted", userSetting.AdoptOrDeleteRepository)
}, reqSignIn, func(ctx *context.Context) {
ctx.Data["PageIsUserSettings"] = true
ctx.Data["AllThemes"] = setting.UI.Themes
@@ -461,6 +462,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/repos", func() {
m.Get("", admin.Repos)
+ m.Combo("/unadopted").Get(admin.UnadoptedRepos).Post(admin.AdoptOrDeleteRepository)
m.Post("/delete", admin.DeleteRepo)
})
diff --git a/routers/user/setting/adopt.go b/routers/user/setting/adopt.go
new file mode 100644
index 0000000000..6ff07d6daa
--- /dev/null
+++ b/routers/user/setting/adopt.go
@@ -0,0 +1,56 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package setting
+
+import (
+ "path/filepath"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/setting"
+ "github.com/unknwon/com"
+)
+
+// AdoptOrDeleteRepository adopts or deletes a repository
+func AdoptOrDeleteRepository(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("settings")
+ ctx.Data["PageIsSettingsRepos"] = true
+ allowAdopt := ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories
+ ctx.Data["allowAdopt"] = allowAdopt
+ allowDelete := ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories
+ ctx.Data["allowDelete"] = allowDelete
+
+ dir := ctx.Query("id")
+ action := ctx.Query("action")
+
+ ctxUser := ctx.User
+ root := filepath.Join(models.UserPath(ctxUser.LowerName))
+
+ // check not a repo
+ if has, err := models.IsRepositoryExist(ctxUser, dir); err != nil {
+ ctx.ServerError("IsRepositoryExist", err)
+ return
+ } else if has || !com.IsDir(filepath.Join(root, dir+".git")) {
+ // Fallthrough to failure mode
+ } else if action == "adopt" && allowAdopt {
+ if _, err := repository.AdoptRepository(ctxUser, ctxUser, models.CreateRepoOptions{
+ Name: dir,
+ IsPrivate: true,
+ }); err != nil {
+ ctx.ServerError("repository.AdoptRepository", err)
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("repo.adopt_preexisting_success", dir))
+ } else if action == "delete" && allowDelete {
+ if err := repository.DeleteUnadoptedRepository(ctxUser, ctxUser, dir); err != nil {
+ ctx.ServerError("repository.AdoptRepository", err)
+ return
+ }
+ ctx.Flash.Success(ctx.Tr("repo.delete_preexisting_success", dir))
+ }
+
+ ctx.Redirect(setting.AppSubURL + "/user/settings/repos")
+}
diff --git a/routers/user/setting/profile.go b/routers/user/setting/profile.go
index ba9ba2b257..fe0506946a 100644
--- a/routers/user/setting/profile.go
+++ b/routers/user/setting/profile.go
@@ -9,6 +9,8 @@ import (
"errors"
"fmt"
"io/ioutil"
+ "os"
+ "path/filepath"
"strings"
"code.gitea.io/gitea/models"
@@ -197,32 +199,96 @@ func Organization(ctx *context.Context) {
func Repos(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsRepos"] = true
- ctxUser := ctx.User
+ ctx.Data["allowAdopt"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories
+ ctx.Data["allowDelete"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories
- var err error
- if err = ctxUser.GetRepositories(models.ListOptions{Page: 1, PageSize: setting.UI.User.RepoPagingNum}); err != nil {
- ctx.ServerError("GetRepositories", err)
- return
+ opts := models.ListOptions{
+ PageSize: setting.UI.Admin.UserPagingNum,
+ Page: ctx.QueryInt("page"),
+ }
+
+ if opts.Page <= 0 {
+ opts.Page = 1
}
- repos := ctxUser.Repos
+ start := (opts.Page - 1) * opts.PageSize
+ end := start + opts.PageSize
- for i := range repos {
- if repos[i].IsFork {
- err := repos[i].GetBaseRepo()
+ adoptOrDelete := ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories)
+
+ ctxUser := ctx.User
+ count := 0
+
+ if adoptOrDelete {
+ repoNames := make([]string, 0, setting.UI.Admin.UserPagingNum)
+ repos := map[string]*models.Repository{}
+ // We're going to iterate by pagesize.
+ root := filepath.Join(models.UserPath(ctxUser.Name))
+ if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
- ctx.ServerError("GetBaseRepo", err)
- return
+ return err
}
- err = repos[i].BaseRepo.GetOwner()
- if err != nil {
- ctx.ServerError("GetOwner", err)
- return
+ if !info.IsDir() || path == root {
+ return nil
}
+ name := info.Name()
+ if !strings.HasSuffix(name, ".git") {
+ return filepath.SkipDir
+ }
+ name = name[:len(name)-4]
+ if models.IsUsableRepoName(name) != nil || strings.ToLower(name) != name {
+ return filepath.SkipDir
+ }
+ if count >= start && count < end {
+ repoNames = append(repoNames, name)
+ }
+ count++
+ return filepath.SkipDir
+ }); err != nil {
+ ctx.ServerError("filepath.Walk", err)
+ return
}
- }
- ctx.Data["Owner"] = ctxUser
- ctx.Data["Repos"] = repos
+ if err := ctxUser.GetRepositories(models.ListOptions{Page: 1, PageSize: setting.UI.Admin.UserPagingNum}, repoNames...); err != nil {
+ ctx.ServerError("GetRepositories", err)
+ return
+ }
+ for _, repo := range ctxUser.Repos {
+ if repo.IsFork {
+ if err := repo.GetBaseRepo(); err != nil {
+ ctx.ServerError("GetBaseRepo", err)
+ return
+ }
+ }
+ repos[repo.LowerName] = repo
+ }
+ ctx.Data["Dirs"] = repoNames
+ ctx.Data["ReposMap"] = repos
+ } else {
+ var err error
+ var count64 int64
+ ctxUser.Repos, count64, err = models.GetUserRepositories(&models.SearchRepoOptions{Actor: ctxUser, Private: true, ListOptions: opts})
+ if err != nil {
+ ctx.ServerError("GetRepositories", err)
+ return
+ }
+ count = int(count64)
+ repos := ctxUser.Repos
+
+ for i := range repos {
+ if repos[i].IsFork {
+ if err := repos[i].GetBaseRepo(); err != nil {
+ ctx.ServerError("GetBaseRepo", err)
+ return
+ }
+ }
+ }
+
+ ctx.Data["Repos"] = repos
+ }
+ ctx.Data["Owner"] = ctxUser
+ pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
+ pager.SetDefaultParams(ctx)
+ ctx.Data["Page"] = pager
ctx.HTML(200, tplSettingsRepositories)
}