Skip to content

Commit 571cba5

Browse files
Gitealafriks
authored andcommitted
Create new branch from branch selection dropdown and rewrite it to VueJS
1 parent c25303b commit 571cba5

File tree

13 files changed

+563
-69
lines changed

13 files changed

+563
-69
lines changed

integrations/repo_branch_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright 2017 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"net/http"
9+
"path"
10+
"strings"
11+
"testing"
12+
13+
"github.com/Unknwon/i18n"
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func testCreateBranch(t *testing.T, session *TestSession, user, repo, oldRefName, newBranchName string, expectedStatus int) string {
18+
var csrf string
19+
if expectedStatus == http.StatusNotFound {
20+
csrf = GetCSRF(t, session, path.Join(user, repo, "src/master"))
21+
} else {
22+
csrf = GetCSRF(t, session, path.Join(user, repo, "src", oldRefName))
23+
}
24+
req := NewRequestWithValues(t, "POST", path.Join(user, repo, "branches/_new", oldRefName), map[string]string{
25+
"_csrf": csrf,
26+
"new_branch_name": newBranchName,
27+
})
28+
resp := session.MakeRequest(t, req, expectedStatus)
29+
if expectedStatus != http.StatusFound {
30+
return ""
31+
}
32+
return RedirectURL(t, resp)
33+
}
34+
35+
func TestCreateBranch(t *testing.T) {
36+
tests := []struct {
37+
OldBranchOrCommit string
38+
NewBranch string
39+
CreateRelease string
40+
FlashMessage string
41+
ExpectedStatus int
42+
}{
43+
{
44+
OldBranchOrCommit: "master",
45+
NewBranch: "feature/test1",
46+
ExpectedStatus: http.StatusFound,
47+
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test1"),
48+
},
49+
{
50+
OldBranchOrCommit: "master",
51+
NewBranch: "",
52+
ExpectedStatus: http.StatusFound,
53+
FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.require_error"),
54+
},
55+
{
56+
OldBranchOrCommit: "master",
57+
NewBranch: "feature=test1",
58+
ExpectedStatus: http.StatusFound,
59+
FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.git_ref_name_error"),
60+
},
61+
{
62+
OldBranchOrCommit: "master",
63+
NewBranch: strings.Repeat("b", 101),
64+
ExpectedStatus: http.StatusFound,
65+
FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.max_size_error", "100"),
66+
},
67+
{
68+
OldBranchOrCommit: "master",
69+
NewBranch: "master",
70+
ExpectedStatus: http.StatusFound,
71+
FlashMessage: i18n.Tr("en", "repo.branch.branch_already_exists", "master"),
72+
},
73+
{
74+
OldBranchOrCommit: "master",
75+
NewBranch: "master/test",
76+
ExpectedStatus: http.StatusFound,
77+
FlashMessage: i18n.Tr("en", "repo.branch.branch_name_conflict", "master/test", "master"),
78+
},
79+
{
80+
OldBranchOrCommit: "acd1d892867872cb47f3993468605b8aa59aa2e0",
81+
NewBranch: "feature/test2",
82+
ExpectedStatus: http.StatusNotFound,
83+
},
84+
{
85+
OldBranchOrCommit: "65f1bf27bc3bf70f64657658635e66094edbcb4d",
86+
NewBranch: "feature/test3",
87+
ExpectedStatus: http.StatusFound,
88+
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test3"),
89+
},
90+
{
91+
OldBranchOrCommit: "master",
92+
NewBranch: "v1.0.0",
93+
CreateRelease: "v1.0.0",
94+
ExpectedStatus: http.StatusFound,
95+
FlashMessage: i18n.Tr("en", "repo.branch.tag_collision", "v1.0.0"),
96+
},
97+
{
98+
OldBranchOrCommit: "v1.0.0",
99+
NewBranch: "feature/test4",
100+
CreateRelease: "v1.0.0",
101+
ExpectedStatus: http.StatusFound,
102+
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test4"),
103+
},
104+
}
105+
for _, test := range tests {
106+
prepareTestEnv(t)
107+
session := loginUser(t, "user2")
108+
if test.CreateRelease != "" {
109+
createNewRelease(t, session, "/user2/repo1", test.CreateRelease, test.CreateRelease, false, false)
110+
}
111+
redirectURL := testCreateBranch(t, session, "user2", "repo1", test.OldBranchOrCommit, test.NewBranch, test.ExpectedStatus)
112+
if test.ExpectedStatus == http.StatusFound {
113+
req := NewRequest(t, "GET", redirectURL)
114+
resp := session.MakeRequest(t, req, http.StatusOK)
115+
htmlDoc := NewHTMLParser(t, resp.Body)
116+
assert.Equal(t,
117+
test.FlashMessage,
118+
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
119+
)
120+
}
121+
}
122+
}
123+
124+
func TestCreateBranchInvalidCSRF(t *testing.T) {
125+
prepareTestEnv(t)
126+
session := loginUser(t, "user2")
127+
req := NewRequestWithValues(t, "POST", "user2/repo1/branches/_new/master", map[string]string{
128+
"_csrf": "fake_csrf",
129+
"new_branch_name": "test",
130+
})
131+
session.MakeRequest(t, req, http.StatusBadRequest)
132+
}

models/repo.go

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2426,38 +2426,3 @@ func (repo *Repository) GetUserFork(userID int64) (*Repository, error) {
24262426
}
24272427
return &forkedRepo, nil
24282428
}
2429-
2430-
// __________ .__
2431-
// \______ \____________ ____ ____ | |__
2432-
// | | _/\_ __ \__ \ / \_/ ___\| | \
2433-
// | | \ | | \// __ \| | \ \___| Y \
2434-
// |______ / |__| (____ /___| /\___ >___| /
2435-
// \/ \/ \/ \/ \/
2436-
//
2437-
2438-
// CreateNewBranch creates a new repository branch
2439-
func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName string) (err error) {
2440-
repoWorkingPool.CheckIn(com.ToStr(repo.ID))
2441-
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
2442-
2443-
localPath := repo.LocalCopyPath()
2444-
2445-
if err = discardLocalRepoBranchChanges(localPath, oldBranchName); err != nil {
2446-
return fmt.Errorf("discardLocalRepoChanges: %v", err)
2447-
} else if err = repo.UpdateLocalCopyBranch(oldBranchName); err != nil {
2448-
return fmt.Errorf("UpdateLocalCopyBranch: %v", err)
2449-
}
2450-
2451-
if err = repo.CheckoutNewBranch(oldBranchName, branchName); err != nil {
2452-
return fmt.Errorf("CreateNewBranch: %v", err)
2453-
}
2454-
2455-
if err = git.Push(localPath, git.PushOptions{
2456-
Remote: "origin",
2457-
Branch: branchName,
2458-
}); err != nil {
2459-
return fmt.Errorf("Push: %v", err)
2460-
}
2461-
2462-
return nil
2463-
}

models/repo_branch.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
package models
66

77
import (
8+
"fmt"
9+
"time"
10+
811
"code.gitea.io/git"
12+
"code.gitea.io/gitea/modules/setting"
13+
14+
"github.com/Unknwon/com"
915
)
1016

1117
// Branch holds the branch information
@@ -36,6 +42,11 @@ func GetBranchesByPath(path string) ([]*Branch, error) {
3642
return branches, nil
3743
}
3844

45+
// CanCreateBranch returns true if repository meets the requirements for creating new branches.
46+
func (repo *Repository) CanCreateBranch() bool {
47+
return !repo.IsMirror
48+
}
49+
3950
// GetBranch returns a branch by it's name
4051
func (repo *Repository) GetBranch(branch string) (*Branch, error) {
4152
if !git.IsBranchExist(repo.RepoPath(), branch) {
@@ -52,6 +63,91 @@ func (repo *Repository) GetBranches() ([]*Branch, error) {
5263
return GetBranchesByPath(repo.RepoPath())
5364
}
5465

66+
// CreateNewBranch creates a new repository branch
67+
func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName string) (err error) {
68+
repoWorkingPool.CheckIn(com.ToStr(repo.ID))
69+
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
70+
71+
localPath := repo.LocalCopyPath()
72+
73+
if err = discardLocalRepoBranchChanges(localPath, oldBranchName); err != nil {
74+
return fmt.Errorf("discardLocalRepoChanges: %v", err)
75+
} else if err = repo.UpdateLocalCopyBranch(oldBranchName); err != nil {
76+
return fmt.Errorf("UpdateLocalCopyBranch: %v", err)
77+
}
78+
79+
if err = repo.CheckoutNewBranch(oldBranchName, branchName); err != nil {
80+
return fmt.Errorf("CreateNewBranch: %v", err)
81+
}
82+
83+
if err = git.Push(localPath, git.PushOptions{
84+
Remote: "origin",
85+
Branch: branchName,
86+
}); err != nil {
87+
return fmt.Errorf("Push: %v", err)
88+
}
89+
90+
return nil
91+
}
92+
93+
// UpdateLocalCopyToCommit pulls latest changes of given commit from repoPath to localPath.
94+
// It creates a new clone if local copy does not exist.
95+
// This function checks out target commit by default, it is safe to assume subsequent
96+
// operations are operating against target commit when caller has confidence for no race condition.
97+
func UpdateLocalCopyToCommit(repoPath, localPath, commit string) error {
98+
if !com.IsExist(localPath) {
99+
if err := git.Clone(repoPath, localPath, git.CloneRepoOptions{
100+
Timeout: time.Duration(setting.Git.Timeout.Clone) * time.Second,
101+
}); err != nil {
102+
return fmt.Errorf("git clone: %v", err)
103+
}
104+
} else {
105+
_, err := git.NewCommand("fetch", "origin").RunInDir(localPath)
106+
if err != nil {
107+
return fmt.Errorf("git fetch origin: %v", err)
108+
}
109+
if err := git.ResetHEAD(localPath, true, "HEAD"); err != nil {
110+
return fmt.Errorf("git reset --hard HEAD: %v", err)
111+
}
112+
}
113+
if err := git.Checkout(localPath, git.CheckoutOptions{
114+
Branch: commit,
115+
}); err != nil {
116+
return fmt.Errorf("git checkout %s: %v", commit, err)
117+
}
118+
return nil
119+
}
120+
121+
// UpdateLocalCopyToCommit makes sure local copy of repository is at given commit.
122+
func (repo *Repository) UpdateLocalCopyToCommit(commit string) error {
123+
return UpdateLocalCopyToCommit(repo.RepoPath(), repo.LocalCopyPath(), commit)
124+
}
125+
126+
// CreateNewBranchFromCommit creates a new repository branch
127+
func (repo *Repository) CreateNewBranchFromCommit(doer *User, commit, branchName string) (err error) {
128+
repoWorkingPool.CheckIn(com.ToStr(repo.ID))
129+
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
130+
131+
localPath := repo.LocalCopyPath()
132+
133+
if err = repo.UpdateLocalCopyToCommit(commit); err != nil {
134+
return fmt.Errorf("UpdateLocalCopyBranch: %v", err)
135+
}
136+
137+
if err = repo.CheckoutNewBranch(commit, branchName); err != nil {
138+
return fmt.Errorf("CheckoutNewBranch: %v", err)
139+
}
140+
141+
if err = git.Push(localPath, git.PushOptions{
142+
Remote: "origin",
143+
Branch: branchName,
144+
}); err != nil {
145+
return fmt.Errorf("Push: %v", err)
146+
}
147+
148+
return nil
149+
}
150+
55151
// GetCommit returns all the commits of a branch
56152
func (branch *Branch) GetCommit() (*git.Commit, error) {
57153
gitRepo, err := git.OpenRepository(branch.Path)

modules/auth/repo_branch_form.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2017 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package auth
6+
7+
import (
8+
"github.com/go-macaron/binding"
9+
macaron "gopkg.in/macaron.v1"
10+
)
11+
12+
// NewBranchForm form for creating a new branch
13+
type NewBranchForm struct {
14+
NewBranchName string `binding:"Required;MaxSize(100);GitRefName"`
15+
}
16+
17+
// Validate validates the fields
18+
func (f *NewBranchForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
19+
return validate(errs, ctx.Data, f, ctx.Locale)
20+
}

modules/context/repo.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ func (r *Repository) CanEnableEditor() bool {
7676
return r.Repository.CanEnableEditor() && r.IsViewBranch && r.IsWriter()
7777
}
7878

79+
// CanCreateBranch returns true if repository is editable and user has proper access level.
80+
func (r *Repository) CanCreateBranch() bool {
81+
return r.Repository.CanCreateBranch() && r.IsWriter()
82+
}
83+
7984
// CanCommitToBranch returns true if repository is editable and user has proper access level
8085
// and branch is not protected
8186
func (r *Repository) CanCommitToBranch(doer *models.User) (bool, error) {
@@ -528,6 +533,7 @@ func RepoRef() macaron.Handler {
528533
ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
529534
ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
530535
ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
536+
ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
531537

532538
ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
533539
if err != nil {

modules/validation/binding.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,18 @@ func addGitRefNameBindingRule() {
4444
}
4545
// Additional rules as described at https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
4646
if strings.HasPrefix(str, "/") || strings.HasSuffix(str, "/") ||
47-
strings.HasPrefix(str, ".") || strings.HasSuffix(str, ".") ||
48-
strings.HasSuffix(str, ".lock") ||
49-
strings.Contains(str, "..") || strings.Contains(str, "//") {
47+
strings.HasSuffix(str, ".") || strings.Contains(str, "..") ||
48+
strings.Contains(str, "//") {
5049
errs.Add([]string{name}, ErrGitRefName, "GitRefName")
5150
return false, errs
5251
}
52+
parts := strings.Split(str, "/")
53+
for _, part := range parts {
54+
if strings.HasSuffix(part, ".lock") || strings.HasPrefix(part, ".") {
55+
errs.Add([]string{name}, ErrGitRefName, "GitRefName")
56+
return false, errs
57+
}
58+
}
5359

5460
return true, errs
5561
},

options/locale/locale_en-US.ini

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,12 @@ branch.delete_notices_2 = - This operation will permanently delete everything in
10611061
branch.deletion_success = %s has been deleted.
10621062
branch.deletion_failed = Failed to delete branch %s.
10631063
branch.delete_branch_has_new_commits = %s cannot be deleted because new commits have been added after merging.
1064+
branch.create_branch = Create branch <strong>%s</strong>
1065+
branch.create_from = from '%s'
1066+
branch.create_success = Branch '%s' has been created successfully!
1067+
branch.branch_already_exists = Branch '%s' already exists in this repository.
1068+
branch.branch_name_conflict = Branch name '%s' conflicts with already existing branch '%s'.
1069+
branch.tag_collision = Branch '%s' can not be created as tag with same name already exists in this repository.
10641070

10651071
[org]
10661072
org_name_holder = Organization Name

public/css/index.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)