summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--integrations/api_repo_test.go79
-rw-r--r--modules/convert/repository.go30
-rw-r--r--modules/structs/repo.go8
-rw-r--r--routers/api/v1/api.go2
-rw-r--r--routers/api/v1/repo/transfer.go102
-rw-r--r--templates/swagger/v1_json.tmpl101
6 files changed, 322 insertions, 0 deletions
diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go
index 1e443362f6..c909e96f06 100644
--- a/integrations/api_repo_test.go
+++ b/integrations/api_repo_test.go
@@ -498,6 +498,85 @@ func TestAPIRepoTransfer(t *testing.T) {
_ = models.DeleteRepository(user, repo.OwnerID, repo.ID)
}
+func transfer(t *testing.T) *repo_model.Repository {
+ //create repo to move
+ user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
+ session := loginUser(t, user.Name)
+ token := getTokenForLoggedInUser(t, session)
+ repoName := "moveME"
+ apiRepo := new(api.Repository)
+ req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/repos?token=%s", token), &api.CreateRepoOption{
+ Name: repoName,
+ Description: "repo move around",
+ Private: false,
+ Readme: "Default",
+ AutoInit: true,
+ })
+
+ resp := session.MakeRequest(t, req, http.StatusCreated)
+ DecodeJSON(t, resp, apiRepo)
+
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}).(*repo_model.Repository)
+ req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer?token=%s", repo.OwnerName, repo.Name, token), &api.TransferRepoOption{
+ NewOwner: "user4",
+ })
+ session.MakeRequest(t, req, http.StatusCreated)
+
+ return repo
+}
+
+func TestAPIAcceptTransfer(t *testing.T) {
+ defer prepareTestEnv(t)()
+
+ repo := transfer(t)
+
+ // try to accept with not authorized user
+ session := loginUser(t, "user2")
+ token := getTokenForLoggedInUser(t, session)
+ req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token))
+ session.MakeRequest(t, req, http.StatusForbidden)
+
+ // try to accept repo that's not marked as transferred
+ req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept?token=%s", "user2", "repo1", token))
+ session.MakeRequest(t, req, http.StatusNotFound)
+
+ // accept transfer
+ session = loginUser(t, "user4")
+ token = getTokenForLoggedInUser(t, session)
+
+ req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept?token=%s", repo.OwnerName, repo.Name, token))
+ resp := session.MakeRequest(t, req, http.StatusAccepted)
+ apiRepo := new(api.Repository)
+ DecodeJSON(t, resp, apiRepo)
+ assert.Equal(t, "user4", apiRepo.Owner.UserName)
+}
+
+func TestAPIRejectTransfer(t *testing.T) {
+ defer prepareTestEnv(t)()
+
+ repo := transfer(t)
+
+ // try to reject with not authorized user
+ session := loginUser(t, "user2")
+ token := getTokenForLoggedInUser(t, session)
+ req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token))
+ session.MakeRequest(t, req, http.StatusForbidden)
+
+ // try to reject repo that's not marked as transferred
+ req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", "user2", "repo1", token))
+ session.MakeRequest(t, req, http.StatusNotFound)
+
+ // reject transfer
+ session = loginUser(t, "user4")
+ token = getTokenForLoggedInUser(t, session)
+
+ req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token))
+ resp := session.MakeRequest(t, req, http.StatusOK)
+ apiRepo := new(api.Repository)
+ DecodeJSON(t, resp, apiRepo)
+ assert.Equal(t, "user2", apiRepo.Owner.UserName)
+}
+
func TestAPIGenerateRepo(t *testing.T) {
defer prepareTestEnv(t)()
diff --git a/modules/convert/repository.go b/modules/convert/repository.go
index 9859ea2674..725b04e2ca 100644
--- a/modules/convert/repository.go
+++ b/modules/convert/repository.go
@@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
+ "code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
)
@@ -106,6 +107,20 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
}
}
+ var transfer *api.RepoTransfer
+ if repo.Status == repo_model.RepositoryPendingTransfer {
+ t, err := models.GetPendingRepositoryTransfer(repo)
+ if err != nil && !models.IsErrNoPendingTransfer(err) {
+ log.Warn("GetPendingRepositoryTransfer: %v", err)
+ } else {
+ if err := t.LoadAttributes(); err != nil {
+ log.Warn("LoadAttributes of RepoTransfer: %v", err)
+ } else {
+ transfer = ToRepoTransfer(t)
+ }
+ }
+ }
+
return &api.Repository{
ID: repo.ID,
Owner: ToUserWithAccessMode(repo.Owner, mode),
@@ -151,5 +166,20 @@ func innerToRepo(repo *repo_model.Repository, mode perm.AccessMode, isParent boo
AvatarURL: repo.AvatarLink(),
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
MirrorInterval: mirrorInterval,
+ RepoTransfer: transfer,
+ }
+}
+
+// ToRepoTransfer convert a models.RepoTransfer to a structs.RepeTransfer
+func ToRepoTransfer(t *models.RepoTransfer) *api.RepoTransfer {
+ var teams []*api.Team
+ for _, v := range t.Teams {
+ teams = append(teams, ToTeam(v))
+ }
+
+ return &api.RepoTransfer{
+ Doer: ToUser(t.Doer, nil),
+ Recipient: ToUser(t.Recipient, nil),
+ Teams: teams,
}
}
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index b1a3781d05..38d80db704 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -93,6 +93,7 @@ type Repository struct {
AvatarURL string `json:"avatar_url"`
Internal bool `json:"internal"`
MirrorInterval string `json:"mirror_interval"`
+ RepoTransfer *RepoTransfer `json:"repo_transfer"`
}
// CreateRepoOption options when creating repository
@@ -336,3 +337,10 @@ var (
CodebaseService,
}
)
+
+// RepoTransfer represents a pending repo transfer
+type RepoTransfer struct {
+ Doer *User `json:"doer"`
+ Recipient *User `json:"recipient"`
+ Teams []*Team `json:"teams"`
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index af5ab96d05..c587907d4b 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -736,6 +736,8 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
m.Post("/generate", reqToken(), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate)
m.Post("/transfer", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
+ m.Post("/transfer/accept", reqToken(), repo.AcceptTransfer)
+ m.Post("/transfer/reject", reqToken(), repo.RejectTransfer)
m.Combo("/notifications").
Get(reqToken(), notify.ListRepoNotifications).
Put(reqToken(), notify.ReadRepoNotifications)
diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go
index dd7730b42c..a997444f49 100644
--- a/routers/api/v1/repo/transfer.go
+++ b/routers/api/v1/repo/transfer.go
@@ -127,3 +127,105 @@ func Transfer(ctx *context.APIContext) {
log.Trace("Repository transferred: %s -> %s", ctx.Repo.Repository.FullName(), newOwner.Name)
ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx.Repo.Repository, perm.AccessModeAdmin))
}
+
+// AcceptTransfer accept a repo transfer
+func AcceptTransfer(ctx *context.APIContext) {
+ // swagger:operation POST /repos/{owner}/{repo}/transfer/accept repository acceptRepoTransfer
+ // ---
+ // summary: Accept a repo transfer
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo to transfer
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo to transfer
+ // type: string
+ // required: true
+ // responses:
+ // "202":
+ // "$ref": "#/responses/Repository"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ err := acceptOrRejectRepoTransfer(ctx, true)
+ if ctx.Written() {
+ return
+ }
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
+ return
+ }
+
+ ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx.Repo.Repository, ctx.Repo.AccessMode))
+}
+
+// RejectTransfer reject a repo transfer
+func RejectTransfer(ctx *context.APIContext) {
+ // swagger:operation POST /repos/{owner}/{repo}/transfer/reject repository rejectRepoTransfer
+ // ---
+ // summary: Reject a repo transfer
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo to transfer
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo to transfer
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/Repository"
+ // "403":
+ // "$ref": "#/responses/forbidden"
+ // "404":
+ // "$ref": "#/responses/notFound"
+
+ err := acceptOrRejectRepoTransfer(ctx, false)
+ if ctx.Written() {
+ return
+ }
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
+ return
+ }
+
+ ctx.JSON(http.StatusOK, convert.ToRepo(ctx.Repo.Repository, ctx.Repo.AccessMode))
+}
+
+func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
+ repoTransfer, err := models.GetPendingRepositoryTransfer(ctx.Repo.Repository)
+ if err != nil {
+ if models.IsErrNoPendingTransfer(err) {
+ ctx.NotFound()
+ return nil
+ }
+ return err
+ }
+
+ if err := repoTransfer.LoadAttributes(); err != nil {
+ return err
+ }
+
+ if !repoTransfer.CanUserAcceptTransfer(ctx.User) {
+ ctx.Error(http.StatusForbidden, "CanUserAcceptTransfer", nil)
+ return fmt.Errorf("user does not have permissions to do this")
+ }
+
+ if accept {
+ return repo_service.TransferOwnership(repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
+ }
+
+ return models.CancelRepositoryTransfer(ctx.Repo.Repository)
+}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index b016ad22a2..96dd262301 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -9895,6 +9895,84 @@
}
}
},
+ "/repos/{owner}/{repo}/transfer/accept": {
+ "post": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Accept a repo transfer",
+ "operationId": "acceptRepoTransfer",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "owner of the repo to transfer",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repo to transfer",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "202": {
+ "$ref": "#/responses/Repository"
+ },
+ "403": {
+ "$ref": "#/responses/forbidden"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
+ "/repos/{owner}/{repo}/transfer/reject": {
+ "post": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Reject a repo transfer",
+ "operationId": "rejectRepoTransfer",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "owner of the repo to transfer",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repo to transfer",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "$ref": "#/responses/Repository"
+ },
+ "403": {
+ "$ref": "#/responses/forbidden"
+ },
+ "404": {
+ "$ref": "#/responses/notFound"
+ }
+ }
+ }
+ },
"/repos/{owner}/{repo}/wiki/new": {
"post": {
"consumes": [
@@ -16890,6 +16968,26 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "RepoTransfer": {
+ "description": "RepoTransfer represents a pending repo transfer",
+ "type": "object",
+ "properties": {
+ "doer": {
+ "$ref": "#/definitions/User"
+ },
+ "recipient": {
+ "$ref": "#/definitions/User"
+ },
+ "teams": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Team"
+ },
+ "x-go-name": "Teams"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"Repository": {
"description": "Repository represents a repository",
"type": "object",
@@ -17042,6 +17140,9 @@
"format": "int64",
"x-go-name": "Releases"
},
+ "repo_transfer": {
+ "$ref": "#/definitions/RepoTransfer"
+ },
"size": {
"type": "integer",
"format": "int64",