Backport #25578 This PR added a repository's check when creating/deleting branches via API. Mirror repository and archive repository cannot do that.
This commit is contained in:
parent
e5b684e567
commit
13ffa287b1
26 changed files with 268 additions and 4 deletions
49
models/fixtures/mirror.yml
Normal file
49
models/fixtures/mirror.yml
Normal file
|
@ -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: ""
|
|
@ -141,7 +141,7 @@
|
||||||
num_projects: 0
|
num_projects: 0
|
||||||
num_closed_projects: 0
|
num_closed_projects: 0
|
||||||
is_private: true
|
is_private: true
|
||||||
is_empty: true
|
is_empty: false
|
||||||
is_archived: false
|
is_archived: false
|
||||||
is_mirror: true
|
is_mirror: true
|
||||||
status: 0
|
status: 0
|
||||||
|
|
|
@ -116,6 +116,21 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$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("*")
|
branchName := ctx.Params("*")
|
||||||
|
|
||||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
|
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:
|
// responses:
|
||||||
// "201":
|
// "201":
|
||||||
// "$ref": "#/responses/Branch"
|
// "$ref": "#/responses/Branch"
|
||||||
|
// "403":
|
||||||
|
// description: The branch is archived or a mirror.
|
||||||
// "404":
|
// "404":
|
||||||
// description: The old branch does not exist.
|
// description: The old branch does not exist.
|
||||||
// "409":
|
// "409":
|
||||||
// description: The branch with the same name already exists.
|
// description: The branch with the same name already exists.
|
||||||
|
|
||||||
opt := web.GetForm(ctx).(*api.CreateBranchRepoOption)
|
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
|
ctx.Error(http.StatusNotFound, "", "Git Repository is empty.")
|
||||||
return
|
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 oldCommit *git.Commit
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -280,7 +308,12 @@ func ListBranches(ctx *context.APIContext) {
|
||||||
|
|
||||||
listOptions := utils.GetListOptions(ctx)
|
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)
|
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
|
ctx.Error(http.StatusInternalServerError, "FindMatchedProtectedBranchRules", err)
|
||||||
|
|
3
templates/swagger/v1_json.tmpl
generated
3
templates/swagger/v1_json.tmpl
generated
|
@ -3478,6 +3478,9 @@
|
||||||
"201": {
|
"201": {
|
||||||
"$ref": "#/responses/Branch"
|
"$ref": "#/responses/Branch"
|
||||||
},
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "The branch is archived or a mirror."
|
||||||
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "The old branch does not exist."
|
"description": "The old branch does not exist."
|
||||||
},
|
},
|
||||||
|
|
1
tests/gitea-repositories-meta/user3/repo5.git/HEAD
Normal file
1
tests/gitea-repositories-meta/user3/repo5.git/HEAD
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ref: refs/heads/master
|
6
tests/gitea-repositories-meta/user3/repo5.git/config
Normal file
6
tests/gitea-repositories-meta/user3/repo5.git/config
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[core]
|
||||||
|
repositoryformatversion = 0
|
||||||
|
filemode = true
|
||||||
|
bare = true
|
||||||
|
ignorecase = true
|
||||||
|
precomposeunicode = true
|
|
@ -0,0 +1 @@
|
||||||
|
Unnamed repository; edit this file 'description' to name the repository.
|
7
tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive
Executable file
7
tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive
Executable file
|
@ -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
|
2
tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive.d/gitea
Executable file
2
tests/gitea-repositories-meta/user3/repo5.git/hooks/post-receive.d/gitea
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" post-receive
|
7
tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive
Executable file
7
tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive
Executable file
|
@ -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
|
2
tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive.d/gitea
Executable file
2
tests/gitea-repositories-meta/user3/repo5.git/hooks/pre-receive.d/gitea
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" pre-receive
|
7
tests/gitea-repositories-meta/user3/repo5.git/hooks/update
Executable file
7
tests/gitea-repositories-meta/user3/repo5.git/hooks/update
Executable file
|
@ -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
|
2
tests/gitea-repositories-meta/user3/repo5.git/hooks/update.d/gitea
Executable file
2
tests/gitea-repositories-meta/user3/repo5.git/hooks/update.d/gitea
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
"$GITEA_ROOT/gitea" hook --config="$GITEA_ROOT/$GITEA_CONF" update $1 $2 $3
|
|
@ -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]
|
||||||
|
# *~
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
2a47ca4b614a9f5a43abbd5ad851a54a616ffee6
|
|
@ -0,0 +1 @@
|
||||||
|
d22b4d4daa5be07329fcef6ed458f00cf3392da0
|
136
tests/integration/api_repo_branch_test.go
Normal file
136
tests/integration/api_repo_branch_test.go
Normal file
|
@ -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))
|
||||||
|
}
|
|
@ -34,7 +34,7 @@ func TestEmptyRepo(t *testing.T) {
|
||||||
"commit/1ae57b34ccf7e18373",
|
"commit/1ae57b34ccf7e18373",
|
||||||
"graph",
|
"graph",
|
||||||
}
|
}
|
||||||
emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5})
|
emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 6})
|
||||||
assert.True(t, emptyRepo.IsEmpty)
|
assert.True(t, emptyRepo.IsEmpty)
|
||||||
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: emptyRepo.OwnerID})
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: emptyRepo.OwnerID})
|
||||||
for _, subPath := range subPaths {
|
for _, subPath := range subPaths {
|
||||||
|
|
Loading…
Reference in a new issue