diff --git a/models/fixtures/mirror.yml b/models/fixtures/mirror.yml new file mode 100644 index 00000000000..97bc4ae60dd --- /dev/null +++ b/models/fixtures/mirror.yml @@ -0,0 +1,49 @@ +- + id: 1 + repo_id: 5 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" + +- + id: 2 + repo_id: 25 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" + +- + id: 3 + repo_id: 26 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" + +- + id: 4 + repo_id: 27 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" + +- + id: 5 + repo_id: 28 + interval: 3600 + enable_prune: false + updated_unix: 0 + next_update_unix: 0 + lfs_enabled: false + lfs_endpoint: "" diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index ef7730780f7..050a9e2d06f 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -141,7 +141,7 @@ num_projects: 0 num_closed_projects: 0 is_private: true - is_empty: true + is_empty: false is_archived: false is_mirror: true status: 0 diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 5336ccb7974..04d179dfaae 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -116,6 +116,21 @@ func DeleteBranch(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" + if ctx.Repo.Repository.IsEmpty { + ctx.Error(http.StatusNotFound, "", "Git Repository is empty.") + return + } + + if ctx.Repo.Repository.IsArchived { + ctx.Error(http.StatusForbidden, "", "Git Repository is archived.") + return + } + + if ctx.Repo.Repository.IsMirror { + ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.") + return + } + branchName := ctx.Params("*") if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { @@ -162,17 +177,30 @@ func CreateBranch(ctx *context.APIContext) { // responses: // "201": // "$ref": "#/responses/Branch" + // "403": + // description: The branch is archived or a mirror. // "404": // description: The old branch does not exist. // "409": // description: The branch with the same name already exists. - opt := web.GetForm(ctx).(*api.CreateBranchRepoOption) if ctx.Repo.Repository.IsEmpty { ctx.Error(http.StatusNotFound, "", "Git Repository is empty.") return } + if ctx.Repo.Repository.IsArchived { + ctx.Error(http.StatusForbidden, "", "Git Repository is archived.") + return + } + + if ctx.Repo.Repository.IsMirror { + ctx.Error(http.StatusForbidden, "", "Git Repository is a mirror.") + return + } + + opt := web.GetForm(ctx).(*api.CreateBranchRepoOption) + var oldCommit *git.Commit var err error @@ -280,7 +308,12 @@ func ListBranches(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) - if !ctx.Repo.Repository.IsEmpty && ctx.Repo.GitRepo != nil { + if !ctx.Repo.Repository.IsEmpty { + if ctx.Repo.GitRepo == nil { + ctx.Error(http.StatusInternalServerError, "Load git repository failed", nil) + return + } + rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 11abeac77c7..e7d1cc1fe54 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -3478,6 +3478,9 @@ "201": { "$ref": "#/responses/Branch" }, + "403": { + "description": "The branch is archived or a mirror." + }, "404": { "description": "The old branch does not exist." }, diff --git a/tests/gitea-repositories-meta/user3/repo5.git/HEAD b/tests/gitea-repositories-meta/user3/repo5.git/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/gitea-repositories-meta/user3/repo5.git/config b/tests/gitea-repositories-meta/user3/repo5.git/config new file mode 100644 index 00000000000..e6da231579b --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = true diff --git a/tests/gitea-repositories-meta/user3/repo5.git/description b/tests/gitea-repositories-meta/user3/repo5.git/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive b/tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive new file mode 100755 index 00000000000..4b3d452abcc --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/post-receive.d"`; do + sh "$SHELL_FOLDER/post-receive.d/$i" +done \ No newline at end of file diff --git a/tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive.d/gitea b/tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive.d/gitea new file mode 100755 index 00000000000..43a948da3a9 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive diff --git a/tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive b/tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive new file mode 100755 index 00000000000..41270130536 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/pre-receive.d"`; do + sh "$SHELL_FOLDER/pre-receive.d/$i" +done \ No newline at end of file diff --git a/tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive.d/gitea b/tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive.d/gitea new file mode 100755 index 00000000000..49d09406364 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive diff --git a/tests/gitea-repositories-meta/user3/repo5.git/hooks/update b/tests/gitea-repositories-meta/user3/repo5.git/hooks/update new file mode 100755 index 00000000000..c186fe4a18b --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/hooks/update @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +ORI_DIR=`pwd` +SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) +cd "$ORI_DIR" +for i in `ls "$SHELL_FOLDER/update.d"`; do + sh "$SHELL_FOLDER/update.d/$i" $1 $2 $3 +done \ No newline at end of file diff --git a/tests/gitea-repositories-meta/user3/repo5.git/hooks/update.d/gitea b/tests/gitea-repositories-meta/user3/repo5.git/hooks/update.d/gitea new file mode 100755 index 00000000000..38101c24266 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/hooks/update.d/gitea @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3 diff --git a/tests/gitea-repositories-meta/user3/repo5.git/info/exclude b/tests/gitea-repositories-meta/user3/repo5.git/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests/gitea-repositories-meta/user3/repo5.git/objects/20/ade30d25e0ecaeec84e7f542a8456900858240 b/tests/gitea-repositories-meta/user3/repo5.git/objects/20/ade30d25e0ecaeec84e7f542a8456900858240 new file mode 100644 index 00000000000..9f3ffe5f27e Binary files /dev/null and b/tests/gitea-repositories-meta/user3/repo5.git/objects/20/ade30d25e0ecaeec84e7f542a8456900858240 differ diff --git a/tests/gitea-repositories-meta/user3/repo5.git/objects/27/74debeea6dc742cc4971a92db0e08b95b60588 b/tests/gitea-repositories-meta/user3/repo5.git/objects/27/74debeea6dc742cc4971a92db0e08b95b60588 new file mode 100644 index 00000000000..5d9226f7a12 Binary files /dev/null and b/tests/gitea-repositories-meta/user3/repo5.git/objects/27/74debeea6dc742cc4971a92db0e08b95b60588 differ diff --git a/tests/gitea-repositories-meta/user3/repo5.git/objects/2a/47ca4b614a9f5a43abbd5ad851a54a616ffee6 b/tests/gitea-repositories-meta/user3/repo5.git/objects/2a/47ca4b614a9f5a43abbd5ad851a54a616ffee6 new file mode 100644 index 00000000000..ca60d2314fc Binary files /dev/null and b/tests/gitea-repositories-meta/user3/repo5.git/objects/2a/47ca4b614a9f5a43abbd5ad851a54a616ffee6 differ diff --git a/tests/gitea-repositories-meta/user3/repo5.git/objects/2f/9b22fd3159a43b7b4e5dd806fcd544edf8716f b/tests/gitea-repositories-meta/user3/repo5.git/objects/2f/9b22fd3159a43b7b4e5dd806fcd544edf8716f new file mode 100644 index 00000000000..e98d752dadf Binary files /dev/null and b/tests/gitea-repositories-meta/user3/repo5.git/objects/2f/9b22fd3159a43b7b4e5dd806fcd544edf8716f differ diff --git a/tests/gitea-repositories-meta/user3/repo5.git/objects/d2/2b4d4daa5be07329fcef6ed458f00cf3392da0 b/tests/gitea-repositories-meta/user3/repo5.git/objects/d2/2b4d4daa5be07329fcef6ed458f00cf3392da0 new file mode 100644 index 00000000000..e319f8ce344 Binary files /dev/null and b/tests/gitea-repositories-meta/user3/repo5.git/objects/d2/2b4d4daa5be07329fcef6ed458f00cf3392da0 differ diff --git a/tests/gitea-repositories-meta/user3/repo5.git/objects/d5/6a3073c1dbb7b15963110a049d50cdb5db99fc b/tests/gitea-repositories-meta/user3/repo5.git/objects/d5/6a3073c1dbb7b15963110a049d50cdb5db99fc new file mode 100644 index 00000000000..eff3c9833ed Binary files /dev/null and b/tests/gitea-repositories-meta/user3/repo5.git/objects/d5/6a3073c1dbb7b15963110a049d50cdb5db99fc differ diff --git a/tests/gitea-repositories-meta/user3/repo5.git/objects/ec/f0db3c1ec806522de4b491fb9a3c7457398c61 b/tests/gitea-repositories-meta/user3/repo5.git/objects/ec/f0db3c1ec806522de4b491fb9a3c7457398c61 new file mode 100644 index 00000000000..ed431f70d3a Binary files /dev/null and b/tests/gitea-repositories-meta/user3/repo5.git/objects/ec/f0db3c1ec806522de4b491fb9a3c7457398c61 differ diff --git a/tests/gitea-repositories-meta/user3/repo5.git/objects/ee/16d127df463aa491e08958120f2108b02468df b/tests/gitea-repositories-meta/user3/repo5.git/objects/ee/16d127df463aa491e08958120f2108b02468df new file mode 100644 index 00000000000..e177f69e372 Binary files /dev/null and b/tests/gitea-repositories-meta/user3/repo5.git/objects/ee/16d127df463aa491e08958120f2108b02468df differ diff --git a/tests/gitea-repositories-meta/user3/repo5.git/refs/heads/master b/tests/gitea-repositories-meta/user3/repo5.git/refs/heads/master new file mode 100644 index 00000000000..ccee722d528 --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/refs/heads/master @@ -0,0 +1 @@ +2a47ca4b614a9f5a43abbd5ad851a54a616ffee6 diff --git a/tests/gitea-repositories-meta/user3/repo5.git/refs/heads/test_branch b/tests/gitea-repositories-meta/user3/repo5.git/refs/heads/test_branch new file mode 100644 index 00000000000..dfe0c6a128e --- /dev/null +++ b/tests/gitea-repositories-meta/user3/repo5.git/refs/heads/test_branch @@ -0,0 +1 @@ +d22b4d4daa5be07329fcef6ed458f00cf3392da0 diff --git a/tests/integration/api_repo_branch_test.go b/tests/integration/api_repo_branch_test.go new file mode 100644 index 00000000000..ef67a97cfde --- /dev/null +++ b/tests/integration/api_repo_branch_test.go @@ -0,0 +1,136 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "bytes" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "testing" + + auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIRepoBranchesPlain(t *testing.T) { + onGiteaRun(t, func(*testing.T, *url.URL) { + repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, user1.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/user3/%s/branches", repo3.Name)) // a plain repo + link.RawQuery = url.Values{"token": {token}}.Encode() + resp := MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + bs, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + + var branches []*api.Branch + assert.NoError(t, json.Unmarshal(bs, &branches)) + assert.Len(t, branches, 2) + sort.Slice(branches, func(i, j int) bool { + return branches[i].Name > branches[j].Name + }) + assert.EqualValues(t, "test_branch", branches[0].Name) + assert.EqualValues(t, "master", branches[1].Name) + + link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/user3/%s/branches/test_branch", repo3.Name)) + link2.RawQuery = url.Values{"token": {token}}.Encode() + resp = MakeRequest(t, NewRequest(t, "GET", link2.String()), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + var branch api.Branch + assert.NoError(t, json.Unmarshal(bs, &branch)) + assert.EqualValues(t, "test_branch", branch.Name) + + req := NewRequest(t, "POST", link.String()) + req.Header.Add("Content-Type", "application/json") + req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + resp = MakeRequest(t, req, http.StatusCreated) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + var branch2 api.Branch + assert.NoError(t, json.Unmarshal(bs, &branch2)) + assert.EqualValues(t, "test_branch2", branch2.Name) + assert.EqualValues(t, branch.Commit.ID, branch2.Commit.ID) + + resp = MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + + branches = []*api.Branch{} + assert.NoError(t, json.Unmarshal(bs, &branches)) + assert.Len(t, branches, 3) + sort.Slice(branches, func(i, j int) bool { + return branches[i].Name > branches[j].Name + }) + assert.EqualValues(t, "test_branch2", branches[0].Name) + assert.EqualValues(t, "test_branch", branches[1].Name) + assert.EqualValues(t, "master", branches[2].Name) + + link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/user3/%s/branches/test_branch2", repo3.Name)) + MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound) + + link3.RawQuery = url.Values{"token": {token}}.Encode() + MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNoContent) + assert.NoError(t, err) + }) +} + +func TestAPIRepoBranchesMirror(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo5 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + session := loginUser(t, user1.LowerName) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/user3/%s/branches", repo5.Name)) // a mirror repo + link.RawQuery = url.Values{"token": {token}}.Encode() + resp := MakeRequest(t, NewRequest(t, "GET", link.String()), http.StatusOK) + bs, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + + var branches []*api.Branch + assert.NoError(t, json.Unmarshal(bs, &branches)) + assert.Len(t, branches, 2) + sort.Slice(branches, func(i, j int) bool { + return branches[i].Name > branches[j].Name + }) + assert.EqualValues(t, "test_branch", branches[0].Name) + assert.EqualValues(t, "master", branches[1].Name) + + link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/user3/%s/branches/test_branch", repo5.Name)) + link2.RawQuery = url.Values{"token": {token}}.Encode() + resp = MakeRequest(t, NewRequest(t, "GET", link2.String()), http.StatusOK) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + var branch api.Branch + assert.NoError(t, json.Unmarshal(bs, &branch)) + assert.EqualValues(t, "test_branch", branch.Name) + + req := NewRequest(t, "POST", link.String()) + req.Header.Add("Content-Type", "application/json") + req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + resp = MakeRequest(t, req, http.StatusForbidden) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.EqualValues(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) + + resp = MakeRequest(t, NewRequest(t, "DELETE", link2.String()), http.StatusForbidden) + bs, err = io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.EqualValues(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) +} diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go index f0022f4db3a..d604391dab4 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -34,7 +34,7 @@ func TestEmptyRepo(t *testing.T) { "commit/1ae57b34ccf7e18373", "graph", } - emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) + emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 6}) assert.True(t, emptyRepo.IsEmpty) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: emptyRepo.OwnerID}) for _, subPath := range subPaths {