2014-03-24 14:25:15 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2018-11-28 14:26:14 +03:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2014-03-24 14:25:15 +04:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
2020-06-12 02:49:47 +03:00
"fmt"
2017-10-26 03:49:16 +03:00
"strings"
2017-10-15 22:59:24 +03:00
"code.gitea.io/gitea/models"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2021-01-26 18:36:53 +03:00
auth "code.gitea.io/gitea/modules/forms"
2019-03-27 12:33:00 +03:00
"code.gitea.io/gitea/modules/git"
2017-10-26 03:49:16 +03:00
"code.gitea.io/gitea/modules/log"
2019-05-05 19:25:25 +03:00
"code.gitea.io/gitea/modules/repofiles"
2020-01-14 06:38:04 +03:00
repo_module "code.gitea.io/gitea/modules/repository"
2019-03-26 22:59:48 +03:00
"code.gitea.io/gitea/modules/util"
2021-01-26 18:36:53 +03:00
"code.gitea.io/gitea/modules/web"
2020-03-28 07:13:18 +03:00
"code.gitea.io/gitea/routers/utils"
2021-02-28 22:57:45 +03:00
release_service "code.gitea.io/gitea/services/release"
2020-09-11 17:14:48 +03:00
repo_service "code.gitea.io/gitea/services/repository"
2014-03-24 14:25:15 +04:00
)
2014-06-23 07:11:12 +04:00
const (
2017-10-26 03:49:16 +03:00
tplBranch base . TplName = "repo/branch/list"
2014-06-23 07:11:12 +04:00
)
2017-10-26 03:49:16 +03:00
// Branch contains the branch information
type Branch struct {
2019-06-27 17:15:30 +03:00
Name string
Commit * git . Commit
IsProtected bool
IsDeleted bool
2019-10-15 01:40:17 +03:00
IsIncluded bool
2019-06-27 17:15:30 +03:00
DeletedBranch * models . DeletedBranch
CommitsAhead int
CommitsBehind int
LatestPullRequest * models . PullRequest
2020-01-07 20:06:14 +03:00
MergeMovedOn bool
2017-10-26 03:49:16 +03:00
}
2016-11-22 11:32:00 +03:00
// Branches render repository branch page
2016-03-11 19:56:52 +03:00
func Branches ( ctx * context . Context ) {
2014-05-26 04:11:25 +04:00
ctx . Data [ "Title" ] = "Branches"
ctx . Data [ "IsRepoToolbarBranches" ] = true
2017-10-26 03:49:16 +03:00
ctx . Data [ "DefaultBranch" ] = ctx . Repo . Repository . DefaultBranch
2019-07-11 23:21:16 +03:00
ctx . Data [ "AllowsPulls" ] = ctx . Repo . Repository . AllowsPulls ( )
2018-11-28 14:26:14 +03:00
ctx . Data [ "IsWriter" ] = ctx . Repo . CanWrite ( models . UnitTypeCode )
2017-10-26 03:49:16 +03:00
ctx . Data [ "IsMirror" ] = ctx . Repo . Repository . IsMirror
2020-05-05 01:44:30 +03:00
ctx . Data [ "CanPull" ] = ctx . Repo . CanWrite ( models . UnitTypeCode ) || ( ctx . IsSigned && ctx . User . HasForkedRepo ( ctx . Repo . Repository . ID ) )
2017-10-26 03:49:16 +03:00
ctx . Data [ "PageIsViewCode" ] = true
ctx . Data [ "PageIsBranches" ] = true
2021-01-19 07:07:38 +03:00
page := ctx . QueryInt ( "page" )
if page <= 1 {
page = 1
}
2021-02-03 22:06:13 +03:00
limit := ctx . QueryInt ( "limit" )
if limit <= 0 || limit > git . BranchesRangeSize {
limit = git . BranchesRangeSize
2021-01-19 07:07:38 +03:00
}
2021-02-03 22:06:13 +03:00
skip := ( page - 1 ) * limit
log . Debug ( "Branches: skip: %d limit: %d" , skip , limit )
branches , branchesCount := loadBranches ( ctx , skip , limit )
2021-01-19 07:07:38 +03:00
if ctx . Written ( ) {
return
}
ctx . Data [ "Branches" ] = branches
pager := context . NewPagination ( int ( branchesCount ) , git . BranchesRangeSize , page , 5 )
pager . SetDefaultParams ( ctx )
ctx . Data [ "Page" ] = pager
2017-10-26 03:49:16 +03:00
ctx . HTML ( 200 , tplBranch )
}
2014-05-26 04:11:25 +04:00
2017-10-26 03:49:16 +03:00
// DeleteBranchPost responses for delete merged branch
func DeleteBranchPost ( ctx * context . Context ) {
defer redirect ( ctx )
branchName := ctx . Query ( "name" )
2020-05-06 14:08:45 +03:00
if branchName == ctx . Repo . Repository . DefaultBranch {
2021-02-03 22:06:13 +03:00
log . Debug ( "DeleteBranch: Can't delete default branch '%s'" , branchName )
2020-05-06 14:08:45 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.default_deletion_failed" , branchName ) )
return
}
2017-10-26 03:49:16 +03:00
isProtected , err := ctx . Repo . Repository . IsProtectedBranch ( branchName , ctx . User )
2014-03-24 14:25:15 +04:00
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "DeleteBranch: %v" , err )
2017-10-26 03:49:16 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.deletion_failed" , branchName ) )
2014-03-24 14:25:15 +04:00
return
2017-10-26 03:49:16 +03:00
}
if isProtected {
2021-02-03 22:06:13 +03:00
log . Debug ( "DeleteBranch: Can't delete protected branch '%s'" , branchName )
2017-10-26 03:49:16 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.protected_deletion_failed" , branchName ) )
2014-03-24 14:25:15 +04:00
return
}
2021-02-03 22:06:13 +03:00
if ! ctx . Repo . GitRepo . IsBranchExist ( branchName ) {
log . Debug ( "DeleteBranch: Can't delete non existing branch '%s'" , branchName )
2017-10-26 03:49:16 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.deletion_failed" , branchName ) )
return
}
if err := deleteBranch ( ctx , branchName ) ; err != nil {
2021-02-03 22:06:13 +03:00
log . Error ( "DeleteBranch: %v" , err )
2017-10-26 03:49:16 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.deletion_failed" , branchName ) )
return
}
ctx . Flash . Success ( ctx . Tr ( "repo.branch.deletion_success" , branchName ) )
}
// RestoreBranchPost responses for delete merged branch
func RestoreBranchPost ( ctx * context . Context ) {
defer redirect ( ctx )
branchID := ctx . QueryInt64 ( "branch_id" )
branchName := ctx . Query ( "name" )
deletedBranch , err := ctx . Repo . Repository . GetDeletedBranchByID ( branchID )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetDeletedBranchByID: %v" , err )
2017-10-26 03:49:16 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.restore_failed" , branchName ) )
return
}
2020-06-12 02:49:47 +03:00
if err := git . Push ( ctx . Repo . Repository . RepoPath ( ) , git . PushOptions {
Remote : ctx . Repo . Repository . RepoPath ( ) ,
Branch : fmt . Sprintf ( "%s:%s%s" , deletedBranch . Commit , git . BranchPrefix , deletedBranch . Name ) ,
Env : models . PushingEnvironment ( ctx . User , ctx . Repo . Repository ) ,
} ) ; err != nil {
2017-10-26 03:49:16 +03:00
if strings . Contains ( err . Error ( ) , "already exists" ) {
2021-02-03 22:06:13 +03:00
log . Debug ( "RestoreBranch: Can't restore branch '%s', since one with same name already exist" , deletedBranch . Name )
2017-10-26 03:49:16 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.already_exists" , deletedBranch . Name ) )
return
}
2021-02-03 22:06:13 +03:00
log . Error ( "RestoreBranch: CreateBranch: %v" , err )
2017-10-26 03:49:16 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.restore_failed" , deletedBranch . Name ) )
return
}
2019-12-26 16:17:31 +03:00
// Don't return error below this
2020-09-11 17:14:48 +03:00
if err := repo_service . PushUpdate (
2020-10-31 00:59:02 +03:00
& repo_module . PushUpdateOptions {
2019-12-26 16:17:31 +03:00
RefFullName : git . BranchPrefix + deletedBranch . Name ,
OldCommitID : git . EmptySHA ,
NewCommitID : deletedBranch . Commit ,
PusherID : ctx . User . ID ,
PusherName : ctx . User . Name ,
RepoUserName : ctx . Repo . Owner . Name ,
RepoName : ctx . Repo . Repository . Name ,
} ) ; err != nil {
2021-02-03 22:06:13 +03:00
log . Error ( "RestoreBranch: Update: %v" , err )
2019-12-26 16:17:31 +03:00
}
2017-10-26 03:49:16 +03:00
ctx . Flash . Success ( ctx . Tr ( "repo.branch.restore_success" , deletedBranch . Name ) )
}
func redirect ( ctx * context . Context ) {
ctx . JSON ( 200 , map [ string ] interface { } {
"redirect" : ctx . Repo . RepoLink + "/branches" ,
} )
}
func deleteBranch ( ctx * context . Context , branchName string ) error {
commit , err := ctx . Repo . GitRepo . GetBranchCommit ( branchName )
if err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "GetBranchCommit: %v" , err )
2017-10-26 03:49:16 +03:00
return err
}
if err := ctx . Repo . GitRepo . DeleteBranch ( branchName , git . DeleteBranchOptions {
Force : true ,
} ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "DeleteBranch: %v" , err )
2017-10-26 03:49:16 +03:00
return err
}
2019-01-02 15:56:58 +03:00
// Don't return error below this
2020-09-11 17:14:48 +03:00
if err := repo_service . PushUpdate (
2020-10-31 00:59:02 +03:00
& repo_module . PushUpdateOptions {
2019-06-10 14:35:13 +03:00
RefFullName : git . BranchPrefix + branchName ,
OldCommitID : commit . ID . String ( ) ,
NewCommitID : git . EmptySHA ,
PusherID : ctx . User . ID ,
PusherName : ctx . User . Name ,
RepoUserName : ctx . Repo . Owner . Name ,
RepoName : ctx . Repo . Repository . Name ,
} ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "Update: %v" , err )
2019-01-02 15:56:58 +03:00
}
2017-10-26 03:49:16 +03:00
if err := ctx . Repo . Repository . AddDeletedBranch ( branchName , commit . ID . String ( ) , ctx . User . ID ) ; err != nil {
log . Warn ( "AddDeletedBranch: %v" , err )
}
return nil
}
2021-01-19 07:07:38 +03:00
// loadBranches loads branches from the repository limited by page & pageSize.
2021-02-03 22:06:13 +03:00
// NOTE: May write to context on error.
func loadBranches ( ctx * context . Context , skip , limit int ) ( [ ] * Branch , int ) {
2021-01-19 07:07:38 +03:00
defaultBranch , err := repo_module . GetBranch ( ctx . Repo . Repository , ctx . Repo . Repository . DefaultBranch )
if err != nil {
2021-02-03 22:06:13 +03:00
log . Error ( "loadBranches: get default branch: %v" , err )
2021-01-19 07:07:38 +03:00
ctx . ServerError ( "GetDefaultBranch" , err )
return nil , 0
}
2021-02-03 22:06:13 +03:00
rawBranches , totalNumOfBranches , err := repo_module . GetBranches ( ctx . Repo . Repository , skip , limit )
2017-10-26 03:49:16 +03:00
if err != nil {
2021-02-03 22:06:13 +03:00
log . Error ( "GetBranches: %v" , err )
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetBranches" , err )
2021-01-19 07:07:38 +03:00
return nil , 0
2017-10-26 03:49:16 +03:00
}
2019-08-01 17:50:06 +03:00
protectedBranches , err := ctx . Repo . Repository . GetProtectedBranches ( )
if err != nil {
ctx . ServerError ( "GetProtectedBranches" , err )
2021-01-19 07:07:38 +03:00
return nil , 0
2019-08-01 17:50:06 +03:00
}
2020-01-07 20:06:14 +03:00
repoIDToRepo := map [ int64 ] * models . Repository { }
repoIDToRepo [ ctx . Repo . Repository . ID ] = ctx . Repo . Repository
repoIDToGitRepo := map [ int64 ] * git . Repository { }
repoIDToGitRepo [ ctx . Repo . Repository . ID ] = ctx . Repo . GitRepo
2021-01-19 07:07:38 +03:00
var branches [ ] * Branch
2021-02-03 22:06:13 +03:00
for i := range rawBranches {
if rawBranches [ i ] . Name == defaultBranch . Name {
// Skip default branch
continue
}
2021-01-19 07:07:38 +03:00
var branch = loadOneBranch ( ctx , rawBranches [ i ] , protectedBranches , repoIDToRepo , repoIDToGitRepo )
if branch == nil {
return nil , 0
}
branches = append ( branches , branch )
}
// Always add the default branch
2021-02-03 22:06:13 +03:00
log . Debug ( "loadOneBranch: load default: '%s'" , defaultBranch . Name )
2021-01-19 07:07:38 +03:00
branches = append ( branches , loadOneBranch ( ctx , defaultBranch , protectedBranches , repoIDToRepo , repoIDToGitRepo ) )
if ctx . Repo . CanWrite ( models . UnitTypeCode ) {
deletedBranches , err := getDeletedBranches ( ctx )
2017-10-26 03:49:16 +03:00
if err != nil {
2021-01-19 07:07:38 +03:00
ctx . ServerError ( "getDeletedBranches" , err )
return nil , 0
2017-10-26 03:49:16 +03:00
}
2021-01-19 07:07:38 +03:00
branches = append ( branches , deletedBranches ... )
}
2017-10-26 03:49:16 +03:00
2021-02-03 22:06:13 +03:00
return branches , totalNumOfBranches - 1
2021-01-19 07:07:38 +03:00
}
func loadOneBranch ( ctx * context . Context , rawBranch * git . Branch , protectedBranches [ ] * models . ProtectedBranch ,
repoIDToRepo map [ int64 ] * models . Repository ,
repoIDToGitRepo map [ int64 ] * git . Repository ) * Branch {
2021-02-03 22:06:13 +03:00
log . Trace ( "loadOneBranch: '%s'" , rawBranch . Name )
2021-01-19 07:07:38 +03:00
commit , err := rawBranch . GetCommit ( )
if err != nil {
ctx . ServerError ( "GetCommit" , err )
return nil
}
branchName := rawBranch . Name
var isProtected bool
for _ , b := range protectedBranches {
if b . BranchName == branchName {
isProtected = true
break
2017-10-26 03:49:16 +03:00
}
2021-01-19 07:07:38 +03:00
}
divergence , divergenceError := repofiles . CountDivergingCommits ( ctx . Repo . Repository , git . BranchPrefix + branchName )
if divergenceError != nil {
ctx . ServerError ( "CountDivergingCommits" , divergenceError )
return nil
}
2017-10-26 03:49:16 +03:00
2021-01-19 07:07:38 +03:00
pr , err := models . GetLatestPullRequestByHeadInfo ( ctx . Repo . Repository . ID , branchName )
if err != nil {
ctx . ServerError ( "GetLatestPullRequestByHeadInfo" , err )
return nil
}
headCommit := commit . ID . String ( )
mergeMovedOn := false
if pr != nil {
pr . HeadRepo = ctx . Repo . Repository
if err := pr . LoadIssue ( ) ; err != nil {
ctx . ServerError ( "pr.LoadIssue" , err )
2019-05-05 19:25:25 +03:00
return nil
}
2021-01-19 07:07:38 +03:00
if repo , ok := repoIDToRepo [ pr . BaseRepoID ] ; ok {
pr . BaseRepo = repo
} else if err := pr . LoadBaseRepo ( ) ; err != nil {
ctx . ServerError ( "pr.LoadBaseRepo" , err )
2019-06-27 17:15:30 +03:00
return nil
2021-01-19 07:07:38 +03:00
} else {
repoIDToRepo [ pr . BaseRepoID ] = pr . BaseRepo
2019-06-27 17:15:30 +03:00
}
2021-01-19 07:07:38 +03:00
pr . Issue . Repo = pr . BaseRepo
if pr . HasMerged {
baseGitRepo , ok := repoIDToGitRepo [ pr . BaseRepoID ]
if ! ok {
baseGitRepo , err = git . OpenRepository ( pr . BaseRepo . RepoPath ( ) )
if err != nil {
ctx . ServerError ( "OpenRepository" , err )
return nil
}
defer baseGitRepo . Close ( )
repoIDToGitRepo [ pr . BaseRepoID ] = baseGitRepo
2019-06-27 17:15:30 +03:00
}
2021-01-19 07:07:38 +03:00
pullCommit , err := baseGitRepo . GetRefCommitID ( pr . GetGitRefName ( ) )
if err != nil && ! git . IsErrNotExist ( err ) {
ctx . ServerError ( "GetBranchCommitID" , err )
2020-01-07 20:06:14 +03:00
return nil
}
2021-01-19 07:07:38 +03:00
if err == nil && headCommit != pullCommit {
// the head has moved on from the merge - we shouldn't delete
mergeMovedOn = true
2020-01-07 20:06:14 +03:00
}
2019-06-27 17:15:30 +03:00
}
2017-10-26 03:49:16 +03:00
}
2021-01-19 07:07:38 +03:00
isIncluded := divergence . Ahead == 0 && ctx . Repo . Repository . DefaultBranch != branchName
return & Branch {
Name : branchName ,
Commit : commit ,
IsProtected : isProtected ,
IsIncluded : isIncluded ,
CommitsAhead : divergence . Ahead ,
CommitsBehind : divergence . Behind ,
LatestPullRequest : pr ,
MergeMovedOn : mergeMovedOn ,
2017-10-26 03:49:16 +03:00
}
}
func getDeletedBranches ( ctx * context . Context ) ( [ ] * Branch , error ) {
branches := [ ] * Branch { }
deletedBranches , err := ctx . Repo . Repository . GetDeletedBranches ( )
if err != nil {
return branches , err
}
for i := range deletedBranches {
deletedBranches [ i ] . LoadUser ( )
branches = append ( branches , & Branch {
Name : deletedBranches [ i ] . Name ,
IsDeleted : true ,
DeletedBranch : deletedBranches [ i ] ,
} )
}
return branches , nil
2014-03-24 14:25:15 +04:00
}
2017-10-15 22:59:24 +03:00
// CreateBranch creates new branch in repository
2021-01-26 18:36:53 +03:00
func CreateBranch ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * auth . NewBranchForm )
2017-10-15 22:59:24 +03:00
if ! ctx . Repo . CanCreateBranch ( ) {
2018-01-11 00:34:17 +03:00
ctx . NotFound ( "CreateBranch" , nil )
2017-10-15 22:59:24 +03:00
return
}
if ctx . HasError ( ) {
ctx . Flash . Error ( ctx . GetErrMsg ( ) )
2017-10-30 05:04:25 +03:00
ctx . Redirect ( ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( ) )
2017-10-15 22:59:24 +03:00
return
}
var err error
2021-02-28 22:57:45 +03:00
if form . CreateTag {
if ctx . Repo . IsViewTag {
err = release_service . CreateNewTag ( ctx . User , ctx . Repo . Repository , ctx . Repo . CommitID , form . NewBranchName , "" )
} else {
err = release_service . CreateNewTag ( ctx . User , ctx . Repo . Repository , ctx . Repo . BranchName , form . NewBranchName , "" )
}
} else if ctx . Repo . IsViewBranch {
2020-01-14 06:38:04 +03:00
err = repo_module . CreateNewBranch ( ctx . User , ctx . Repo . Repository , ctx . Repo . BranchName , form . NewBranchName )
2020-06-12 02:49:47 +03:00
} else if ctx . Repo . IsViewTag {
err = repo_module . CreateNewBranchFromCommit ( ctx . User , ctx . Repo . Repository , ctx . Repo . CommitID , form . NewBranchName )
2017-10-15 22:59:24 +03:00
} else {
2020-01-14 06:38:04 +03:00
err = repo_module . CreateNewBranchFromCommit ( ctx . User , ctx . Repo . Repository , ctx . Repo . BranchName , form . NewBranchName )
2017-10-15 22:59:24 +03:00
}
if err != nil {
if models . IsErrTagAlreadyExists ( err ) {
e := err . ( models . ErrTagAlreadyExists )
ctx . Flash . Error ( ctx . Tr ( "repo.branch.tag_collision" , e . TagName ) )
2017-10-30 05:04:25 +03:00
ctx . Redirect ( ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( ) )
2017-10-15 22:59:24 +03:00
return
}
2020-03-28 07:13:18 +03:00
if models . IsErrBranchAlreadyExists ( err ) || git . IsErrPushOutOfDate ( err ) {
ctx . Flash . Error ( ctx . Tr ( "repo.branch.branch_already_exists" , form . NewBranchName ) )
2017-10-30 05:04:25 +03:00
ctx . Redirect ( ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( ) )
2017-10-15 22:59:24 +03:00
return
}
if models . IsErrBranchNameConflict ( err ) {
e := err . ( models . ErrBranchNameConflict )
ctx . Flash . Error ( ctx . Tr ( "repo.branch.branch_name_conflict" , form . NewBranchName , e . BranchName ) )
2017-10-30 05:04:25 +03:00
ctx . Redirect ( ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( ) )
2017-10-15 22:59:24 +03:00
return
}
2020-03-28 07:13:18 +03:00
if git . IsErrPushRejected ( err ) {
e := err . ( * git . ErrPushRejected )
if len ( e . Message ) == 0 {
ctx . Flash . Error ( ctx . Tr ( "repo.editor.push_rejected_no_message" ) )
} else {
2020-10-21 02:50:10 +03:00
flashError , err := ctx . HTMLString ( string ( tplAlertDetails ) , map [ string ] interface { } {
"Message" : ctx . Tr ( "repo.editor.push_rejected" ) ,
"Summary" : ctx . Tr ( "repo.editor.push_rejected_summary" ) ,
"Details" : utils . SanitizeFlashErrorString ( e . Message ) ,
} )
if err != nil {
ctx . ServerError ( "UpdatePullRequest.HTMLString" , err )
return
}
ctx . Flash . Error ( flashError )
2020-03-28 07:13:18 +03:00
}
ctx . Redirect ( ctx . Repo . RepoLink + "/src/" + ctx . Repo . BranchNameSubURL ( ) )
return
}
2017-10-15 22:59:24 +03:00
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "CreateNewBranch" , err )
2017-10-15 22:59:24 +03:00
return
}
2021-02-28 22:57:45 +03:00
if form . CreateTag {
ctx . Flash . Success ( ctx . Tr ( "repo.tag.create_success" , form . NewBranchName ) )
ctx . Redirect ( ctx . Repo . RepoLink + "/src/tag/" + util . PathEscapeSegments ( form . NewBranchName ) )
return
}
2017-10-15 22:59:24 +03:00
ctx . Flash . Success ( ctx . Tr ( "repo.branch.create_success" , form . NewBranchName ) )
2019-03-26 22:59:48 +03:00
ctx . Redirect ( ctx . Repo . RepoLink + "/src/branch/" + util . PathEscapeSegments ( form . NewBranchName ) )
2017-10-15 22:59:24 +03:00
}