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 (
2017-10-26 03:49:16 +03:00
"strings"
2017-10-15 22:59:24 +03:00
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
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"
2019-03-26 22:59:48 +03:00
"code.gitea.io/gitea/modules/util"
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
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
ctx . Data [ "PageIsViewCode" ] = true
ctx . Data [ "PageIsBranches" ] = true
ctx . Data [ "Branches" ] = loadBranches ( ctx )
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" )
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 {
ctx . Flash . Error ( ctx . Tr ( "repo.branch.protected_deletion_failed" , branchName ) )
2014-03-24 14:25:15 +04:00
return
}
2017-10-26 03:49:16 +03:00
if ! ctx . Repo . GitRepo . IsBranchExist ( branchName ) || branchName == ctx . Repo . Repository . DefaultBranch {
ctx . Flash . Error ( ctx . Tr ( "repo.branch.deletion_failed" , branchName ) )
return
}
if err := deleteBranch ( ctx , branchName ) ; err != nil {
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
}
if err := ctx . Repo . GitRepo . CreateBranch ( deletedBranch . Name , deletedBranch . Commit ) ; err != nil {
if strings . Contains ( err . Error ( ) , "already exists" ) {
ctx . Flash . Error ( ctx . Tr ( "repo.branch.already_exists" , deletedBranch . Name ) )
return
}
2019-04-02 10:48:31 +03:00
log . Error ( "CreateBranch: %v" , err )
2017-10-26 03:49:16 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.restore_failed" , deletedBranch . Name ) )
return
}
if err := ctx . Repo . Repository . RemoveDeletedBranch ( deletedBranch . ID ) ; err != nil {
2019-04-02 10:48:31 +03:00
log . Error ( "RemoveDeletedBranch: %v" , err )
2017-10-26 03:49:16 +03:00
ctx . Flash . Error ( ctx . Tr ( "repo.branch.restore_failed" , deletedBranch . Name ) )
return
}
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
2019-06-10 14:35:13 +03:00
if err := repofiles . PushUpdate (
ctx . Repo . Repository ,
branchName ,
2019-11-29 05:21:05 +03:00
repofiles . 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
}
func loadBranches ( ctx * context . Context ) [ ] * Branch {
rawBranches , err := ctx . Repo . Repository . GetBranches ( )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetBranches" , err )
2017-10-26 03:49:16 +03:00
return nil
}
2019-08-01 17:50:06 +03:00
protectedBranches , err := ctx . Repo . Repository . GetProtectedBranches ( )
if err != nil {
ctx . ServerError ( "GetProtectedBranches" , err )
return nil
}
2017-10-26 03:49:16 +03:00
branches := make ( [ ] * Branch , len ( rawBranches ) )
for i := range rawBranches {
commit , err := rawBranches [ i ] . GetCommit ( )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "GetCommit" , err )
2017-10-26 03:49:16 +03:00
return nil
}
2019-08-01 17:50:06 +03:00
var isProtected bool
2019-05-05 19:25:25 +03:00
branchName := rawBranches [ i ] . Name
2019-08-01 17:50:06 +03:00
for _ , b := range protectedBranches {
if b . BranchName == branchName {
isProtected = true
break
}
2017-10-26 03:49:16 +03:00
}
2019-05-05 19:25:25 +03:00
divergence , divergenceError := repofiles . CountDivergingCommits ( ctx . Repo . Repository , branchName )
if divergenceError != nil {
ctx . ServerError ( "CountDivergingCommits" , divergenceError )
return nil
}
2019-06-27 17:15:30 +03:00
pr , err := models . GetLatestPullRequestByHeadInfo ( ctx . Repo . Repository . ID , branchName )
if err != nil {
ctx . ServerError ( "GetLatestPullRequestByHeadInfo" , err )
return nil
}
if pr != nil {
if err := pr . LoadIssue ( ) ; err != nil {
ctx . ServerError ( "pr.LoadIssue" , err )
return nil
}
}
2019-10-15 01:40:17 +03:00
isIncluded := divergence . Ahead == 0 && ctx . Repo . Repository . DefaultBranch != branchName
2017-10-26 03:49:16 +03:00
branches [ i ] = & Branch {
2019-06-27 17:15:30 +03:00
Name : branchName ,
Commit : commit ,
IsProtected : isProtected ,
2019-10-15 01:40:17 +03:00
IsIncluded : isIncluded ,
2019-06-27 17:15:30 +03:00
CommitsAhead : divergence . Ahead ,
CommitsBehind : divergence . Behind ,
LatestPullRequest : pr ,
2017-10-26 03:49:16 +03:00
}
}
2018-11-28 14:26:14 +03:00
if ctx . Repo . CanWrite ( models . UnitTypeCode ) {
2017-10-26 03:49:16 +03:00
deletedBranches , err := getDeletedBranches ( ctx )
if err != nil {
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "getDeletedBranches" , err )
2017-10-26 03:49:16 +03:00
return nil
}
branches = append ( branches , deletedBranches ... )
}
return branches
}
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
func CreateBranch ( ctx * context . Context , form auth . NewBranchForm ) {
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
if ctx . Repo . IsViewBranch {
err = ctx . Repo . Repository . CreateNewBranch ( ctx . User , ctx . Repo . BranchName , form . NewBranchName )
} else {
err = ctx . Repo . Repository . CreateNewBranchFromCommit ( ctx . User , ctx . Repo . BranchName , form . NewBranchName )
}
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
}
if models . IsErrBranchAlreadyExists ( err ) {
e := err . ( models . ErrBranchAlreadyExists )
ctx . Flash . Error ( ctx . Tr ( "repo.branch.branch_already_exists" , 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
}
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
}
2018-01-11 00:34:17 +03:00
ctx . ServerError ( "CreateNewBranch" , err )
2017-10-15 22:59:24 +03:00
return
}
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
}