summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTerence Le Huu Phuong <32878496+tle-huu@users.noreply.github.com>2020-05-29 20:16:20 +0200
committerGitHub <noreply@github.com>2020-05-29 19:16:20 +0100
commit141d52cc0f356776bd6fa538dbda276c3ba44118 (patch)
tree103a5fe90e0bb23134e49bf784d566e6f9968728
parentf36104e410b3ed6b269d3b8ffb7f5247125171b6 (diff)
downloadgitea-141d52cc0f356776bd6fa538dbda276c3ba44118.tar.gz
gitea-141d52cc0f356776bd6fa538dbda276c3ba44118.zip
Add API Endpoint for Branch Creation (#11607)
* [FEATURE] [API] Add Endpoint for Branch Creation Issue: https://github.com/go-gitea/gitea/issues/11376 This commit introduces an API endpoint for branch creation. The added route is POST /repos/{owner}/{repo}/branches. A JSON with the name of the new branch and the name of the old branch is required as parameters. Signed-off-by: Terence Le Huu Phuong <terence@qwasar.io> * Put all the logic into CreateBranch and removed CreateRepoBranch * - Added the error ErrBranchDoesNotExist in error.go - Made the CreateNewBranch function return an errBranchDoesNotExist error when the OldBranch does not exist - Made the CreateBranch API function checks that the repository is not empty and that branch exists. * - Added a resetFixtures helper function in integration_test.go to fine-tune test env resetting - Added api test for CreateBranch - Used resetFixture instead of the more general prepareTestEnv in the repo_branch_test CreateBranch tests * Moved the resetFixtures call inside the loop for APICreateBranch function * Put the prepareTestEnv back in repo_branch_test * fix import order/sort api branch test Co-authored-by: zeripath <art27@cantab.net>
-rw-r--r--integrations/api_branch_test.go67
-rw-r--r--integrations/integration_test.go12
-rw-r--r--models/error.go15
-rw-r--r--modules/repository/branch.go4
-rw-r--r--modules/structs/repo.go16
-rw-r--r--routers/api/v1/api.go1
-rw-r--r--routers/api/v1/repo/branch.go90
-rw-r--r--routers/api/v1/swagger/options.go3
-rw-r--r--templates/swagger/v1_json.tmpl69
9 files changed, 276 insertions, 1 deletions
diff --git a/integrations/api_branch_test.go b/integrations/api_branch_test.go
index acf7525f80..26d8fb4b45 100644
--- a/integrations/api_branch_test.go
+++ b/integrations/api_branch_test.go
@@ -6,6 +6,7 @@ package integrations
import (
"net/http"
+ "net/url"
"testing"
api "code.gitea.io/gitea/modules/structs"
@@ -100,6 +101,72 @@ func TestAPIGetBranch(t *testing.T) {
}
}
+func TestAPICreateBranch(t *testing.T) {
+ onGiteaRun(t, testAPICreateBranches)
+}
+
+func testAPICreateBranches(t *testing.T, giteaURL *url.URL) {
+
+ username := "user2"
+ ctx := NewAPITestContext(t, username, "my-noo-repo")
+ giteaURL.Path = ctx.GitPath()
+
+ t.Run("CreateRepo", doAPICreateRepository(ctx, false))
+ tests := []struct {
+ OldBranch string
+ NewBranch string
+ ExpectedHTTPStatus int
+ }{
+ // Creating branch from default branch
+ {
+ OldBranch: "",
+ NewBranch: "new_branch_from_default_branch",
+ ExpectedHTTPStatus: http.StatusCreated,
+ },
+ // Creating branch from master
+ {
+ OldBranch: "master",
+ NewBranch: "new_branch_from_master_1",
+ ExpectedHTTPStatus: http.StatusCreated,
+ },
+ // Trying to create from master but already exists
+ {
+ OldBranch: "master",
+ NewBranch: "new_branch_from_master_1",
+ ExpectedHTTPStatus: http.StatusConflict,
+ },
+ // Trying to create from other branch (not default branch)
+ {
+ OldBranch: "new_branch_from_master_1",
+ NewBranch: "branch_2",
+ ExpectedHTTPStatus: http.StatusCreated,
+ },
+ // Trying to create from a branch which does not exist
+ {
+ OldBranch: "does_not_exist",
+ NewBranch: "new_branch_from_non_existent",
+ ExpectedHTTPStatus: http.StatusNotFound,
+ },
+ }
+ for _, test := range tests {
+ defer resetFixtures(t)
+ session := ctx.Session
+ token := getTokenForLoggedInUser(t, session)
+ req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/my-noo-repo/branches?token="+token, &api.CreateBranchRepoOption{
+ BranchName: test.NewBranch,
+ OldBranchName: test.OldBranch,
+ })
+ resp := session.MakeRequest(t, req, test.ExpectedHTTPStatus)
+
+ var branch api.Branch
+ DecodeJSON(t, resp, &branch)
+
+ if test.ExpectedHTTPStatus == http.StatusCreated {
+ assert.EqualValues(t, test.NewBranch, branch.Name)
+ }
+ }
+}
+
func TestAPIBranchProtection(t *testing.T) {
defer prepareTestEnv(t)()
diff --git a/integrations/integration_test.go b/integrations/integration_test.go
index c6a4169751..3c0125af6c 100644
--- a/integrations/integration_test.go
+++ b/integrations/integration_test.go
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/graceful"
+ "code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes"
@@ -459,3 +460,14 @@ func GetCSRF(t testing.TB, session *TestSession, urlStr string) string {
doc := NewHTMLParser(t, resp.Body)
return doc.GetCSRF()
}
+
+// resetFixtures flushes queues, reloads fixtures and resets test repositories within a single test.
+// Most tests should call defer prepareTestEnv(t)() (or have onGiteaRun do that for them) but sometimes
+// within a single test this is required
+func resetFixtures(t *testing.T) {
+ assert.NoError(t, queue.GetManager().FlushAll(context.Background(), -1))
+ assert.NoError(t, models.LoadFixtures())
+ assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
+ assert.NoError(t, com.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"),
+ setting.RepoRootPath))
+}
diff --git a/models/error.go b/models/error.go
index 3b05a7152c..e9343cbe7c 100644
--- a/models/error.go
+++ b/models/error.go
@@ -995,6 +995,21 @@ func IsErrWontSign(err error) bool {
// |______ / |__| (____ /___| /\___ >___| /
// \/ \/ \/ \/ \/
+// ErrBranchDoesNotExist represents an error that branch with such name does not exist.
+type ErrBranchDoesNotExist struct {
+ BranchName string
+}
+
+// IsErrBranchDoesNotExist checks if an error is an ErrBranchDoesNotExist.
+func IsErrBranchDoesNotExist(err error) bool {
+ _, ok := err.(ErrBranchDoesNotExist)
+ return ok
+}
+
+func (err ErrBranchDoesNotExist) Error() string {
+ return fmt.Sprintf("branch does not exist [name: %s]", err.BranchName)
+}
+
// ErrBranchAlreadyExists represents an error that branch with such name already exists.
type ErrBranchAlreadyExists struct {
BranchName string
diff --git a/modules/repository/branch.go b/modules/repository/branch.go
index 418ba25c89..94be6f0f5a 100644
--- a/modules/repository/branch.go
+++ b/modules/repository/branch.go
@@ -71,7 +71,9 @@ func CreateNewBranch(doer *models.User, repo *models.Repository, oldBranchName,
}
if !git.IsBranchExist(repo.RepoPath(), oldBranchName) {
- return fmt.Errorf("OldBranch: %s does not exist. Cannot create new branch from this", oldBranchName)
+ return models.ErrBranchDoesNotExist{
+ BranchName: oldBranchName,
+ }
}
basePath, err := models.CreateTemporaryPath("branch-maker")
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index 70de9b7469..832d330e74 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -160,6 +160,22 @@ type EditRepoOption struct {
Archived *bool `json:"archived,omitempty"`
}
+// CreateBranchRepoOption options when creating a branch in a repository
+// swagger:model
+type CreateBranchRepoOption struct {
+
+ // Name of the branch to create
+ //
+ // required: true
+ // unique: true
+ BranchName string `json:"new_branch_name" binding:"Required;GitRefName;MaxSize(100)"`
+
+ // Name of the old branch to create from
+ //
+ // unique: true
+ OldBranchName string `json:"old_branch_name" binding:"GitRefName;MaxSize(100)"`
+}
+
// TransferRepoOption options when transfer a repository's ownership
// swagger:model
type TransferRepoOption struct {
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 0d62b751cc..2eb39f6070 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -665,6 +665,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", repo.ListBranches)
m.Get("/*", context.RepoRefByType(context.RepoRefBranch), repo.GetBranch)
m.Delete("/*", reqRepoWriter(models.UnitTypeCode), context.RepoRefByType(context.RepoRefBranch), repo.DeleteBranch)
+ m.Post("", reqRepoWriter(models.UnitTypeCode), bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
}, reqRepoReader(models.UnitTypeCode))
m.Group("/branch_protections", func() {
m.Get("", repo.ListBranchProtections)
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index 57c74d7dab..90db597ef7 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -182,6 +182,96 @@ func DeleteBranch(ctx *context.APIContext) {
ctx.Status(http.StatusNoContent)
}
+// CreateBranch creates a branch for a user's repository
+func CreateBranch(ctx *context.APIContext, opt api.CreateBranchRepoOption) {
+ // swagger:operation POST /repos/{owner}/{repo}/branches repository repoCreateBranch
+ // ---
+ // summary: Create a branch
+ // consumes:
+ // - application/json
+ // 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
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/CreateBranchRepoOption"
+ // responses:
+ // "201":
+ // "$ref": "#/responses/Branch"
+ // "404":
+ // description: The old branch does not exist.
+ // "409":
+ // description: The branch with the same name already exists.
+
+ if ctx.Repo.Repository.IsEmpty {
+ ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
+ return
+ }
+
+ if len(opt.OldBranchName) == 0 {
+ opt.OldBranchName = ctx.Repo.Repository.DefaultBranch
+ }
+
+ err := repo_module.CreateNewBranch(ctx.User, ctx.Repo.Repository, opt.OldBranchName, opt.BranchName)
+
+ if err != nil {
+ if models.IsErrBranchDoesNotExist(err) {
+ ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
+ }
+ if models.IsErrTagAlreadyExists(err) {
+ ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
+
+ } else if models.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
+ ctx.Error(http.StatusConflict, "", "The branch already exists.")
+
+ } else if models.IsErrBranchNameConflict(err) {
+ ctx.Error(http.StatusConflict, "", "The branch with the same name already exists.")
+
+ } else {
+ ctx.Error(http.StatusInternalServerError, "CreateRepoBranch", err)
+
+ }
+ return
+ }
+
+ branch, err := repo_module.GetBranch(ctx.Repo.Repository, opt.BranchName)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetBranch", err)
+ return
+ }
+
+ commit, err := branch.GetCommit()
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetCommit", err)
+ return
+ }
+
+ branchProtection, err := ctx.Repo.Repository.GetBranchProtection(branch.Name)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "GetBranchProtection", err)
+ return
+ }
+
+ br, err := convert.ToBranch(ctx.Repo.Repository, branch, commit, branchProtection, ctx.User, ctx.Repo.IsAdmin())
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "convert.ToBranch", err)
+ return
+ }
+
+ ctx.JSON(http.StatusCreated, br)
+}
+
// ListBranches list all the branches of a repository
func ListBranches(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/branches repository repoListBranches
diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go
index f13dc63864..d9ef05c335 100644
--- a/routers/api/v1/swagger/options.go
+++ b/routers/api/v1/swagger/options.go
@@ -130,6 +130,9 @@ type swaggerParameterBodies struct {
EditReactionOption api.EditReactionOption
// in:body
+ CreateBranchRepoOption api.CreateBranchRepoOption
+
+ // in:body
CreateBranchProtectionOption api.CreateBranchProtectionOption
// in:body
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 0cbe33bd24..70f12b083f 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -2241,6 +2241,53 @@
"$ref": "#/responses/BranchList"
}
}
+ },
+ "post": {
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "repository"
+ ],
+ "summary": "Create a branch",
+ "operationId": "repoCreateBranch",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "owner of the repo",
+ "name": "owner",
+ "in": "path",
+ "required": true
+ },
+ {
+ "type": "string",
+ "description": "name of the repo",
+ "name": "repo",
+ "in": "path",
+ "required": true
+ },
+ {
+ "name": "body",
+ "in": "body",
+ "schema": {
+ "$ref": "#/definitions/CreateBranchRepoOption"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "$ref": "#/responses/Branch"
+ },
+ "404": {
+ "description": "The old branch does not exist."
+ },
+ "409": {
+ "description": "The branch with the same name already exists."
+ }
+ }
}
},
"/repos/{owner}/{repo}/branches/{branch}": {
@@ -10886,6 +10933,28 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
+ "CreateBranchRepoOption": {
+ "description": "CreateBranchRepoOption options when creating a branch in a repository",
+ "type": "object",
+ "required": [
+ "new_branch_name"
+ ],
+ "properties": {
+ "new_branch_name": {
+ "description": "Name of the branch to create",
+ "type": "string",
+ "uniqueItems": true,
+ "x-go-name": "BranchName"
+ },
+ "old_branch_name": {
+ "description": "Name of the old branch to create from",
+ "type": "string",
+ "uniqueItems": true,
+ "x-go-name": "OldBranchName"
+ }
+ },
+ "x-go-package": "code.gitea.io/gitea/modules/structs"
+ },
"CreateEmailOption": {
"description": "CreateEmailOption options when creating email addresses",
"type": "object",