2016-11-03 23:16:01 +01:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2018-11-27 23:52:20 +02:00
// Copyright 2018 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2016-11-03 23:16:01 +01:00
package git
import (
2024-08-12 17:16:55 +02:00
"bufio"
"bytes"
2021-11-30 20:06:32 +00:00
"context"
2022-07-16 08:10:02 +08:00
"errors"
2016-11-03 23:16:01 +01:00
"fmt"
2024-08-12 17:16:55 +02:00
"io"
2016-11-03 23:16:01 +01:00
"strings"
2024-08-12 17:16:55 +02:00
"code.gitea.io/gitea/modules/log"
2016-11-03 23:16:01 +01:00
)
2016-12-22 17:30:52 +08:00
// BranchPrefix base dir of the branch information file store on git
const BranchPrefix = "refs/heads/"
2016-11-03 23:16:01 +01:00
// IsReferenceExist returns true if given reference exists in the repository.
2021-11-30 20:06:32 +00:00
func IsReferenceExist ( ctx context . Context , repoPath , name string ) bool {
2022-10-23 22:44:45 +08:00
_ , _ , err := NewCommand ( ctx , "show-ref" , "--verify" ) . AddDashesAndList ( name ) . RunStdString ( & RunOpts { Dir : repoPath } )
2016-11-03 23:16:01 +01:00
return err == nil
}
// IsBranchExist returns true if given branch exists in the repository.
2021-11-30 20:06:32 +00:00
func IsBranchExist ( ctx context . Context , repoPath , name string ) bool {
return IsReferenceExist ( ctx , repoPath , BranchPrefix + name )
2016-11-03 23:16:01 +01:00
}
// Branch represents a Git branch.
type Branch struct {
Name string
Path string
2019-04-19 14:17:27 +02:00
gitRepo * Repository
2016-11-03 23:16:01 +01:00
}
// GetHEADBranch returns corresponding branch of HEAD.
func ( repo * Repository ) GetHEADBranch ( ) ( * Branch , error ) {
2020-02-26 07:32:22 +01:00
if repo == nil {
return nil , fmt . Errorf ( "nil repo" )
}
2022-04-01 10:55:30 +08:00
stdout , _ , err := NewCommand ( repo . Ctx , "symbolic-ref" , "HEAD" ) . RunStdString ( & RunOpts { Dir : repo . Path } )
2016-11-03 23:16:01 +01:00
if err != nil {
return nil , err
}
stdout = strings . TrimSpace ( stdout )
2016-12-22 17:30:52 +08:00
if ! strings . HasPrefix ( stdout , BranchPrefix ) {
2016-11-03 23:16:01 +01:00
return nil , fmt . Errorf ( "invalid HEAD branch: %v" , stdout )
}
return & Branch {
2019-04-19 14:17:27 +02:00
Name : stdout [ len ( BranchPrefix ) : ] ,
Path : stdout ,
gitRepo : repo ,
2016-11-03 23:16:01 +01:00
} , nil
}
2024-03-08 15:30:10 +08:00
func GetDefaultBranch ( ctx context . Context , repoPath string ) ( string , error ) {
stdout , _ , err := NewCommand ( ctx , "symbolic-ref" , "HEAD" ) . RunStdString ( & RunOpts { Dir : repoPath } )
2022-07-16 08:10:02 +08:00
if err != nil {
return "" , err
}
stdout = strings . TrimSpace ( stdout )
if ! strings . HasPrefix ( stdout , BranchPrefix ) {
return "" , errors . New ( "the HEAD is not a branch: " + stdout )
}
return strings . TrimPrefix ( stdout , BranchPrefix ) , nil
2020-09-25 05:09:23 +01:00
}
2019-04-19 14:17:27 +02:00
// GetBranch returns a branch by it's name
func ( repo * Repository ) GetBranch ( branch string ) ( * Branch , error ) {
if ! repo . IsBranchExist ( branch ) {
return nil , ErrBranchNotExist { branch }
}
return & Branch {
Path : repo . Path ,
Name : branch ,
gitRepo : repo ,
} , nil
}
2021-12-08 19:08:16 +00:00
// GetBranches returns a slice of *git.Branch
func ( repo * Repository ) GetBranches ( skip , limit int ) ( [ ] * Branch , int , error ) {
brs , countAll , err := repo . GetBranchNames ( skip , limit )
2019-04-19 14:17:27 +02:00
if err != nil {
2021-02-03 20:06:13 +01:00
return nil , 0 , err
2018-11-27 23:52:20 +02:00
}
2019-04-19 14:17:27 +02:00
branches := make ( [ ] * Branch , len ( brs ) )
for i := range brs {
branches [ i ] = & Branch {
2021-12-08 19:08:16 +00:00
Path : repo . Path ,
2019-04-19 14:17:27 +02:00
Name : brs [ i ] ,
2021-12-08 19:08:16 +00:00
gitRepo : repo ,
2019-04-19 14:17:27 +02:00
}
}
2021-02-03 20:06:13 +01:00
return branches , countAll , nil
2016-11-03 23:16:01 +01:00
}
2016-12-22 17:30:52 +08:00
// DeleteBranchOptions Option(s) for delete branch
2016-11-03 23:16:01 +01:00
type DeleteBranchOptions struct {
Force bool
}
// DeleteBranch delete a branch by name on repository.
func ( repo * Repository ) DeleteBranch ( name string , opts DeleteBranchOptions ) error {
2022-02-06 20:01:47 +01:00
cmd := NewCommand ( repo . Ctx , "branch" )
2016-11-03 23:16:01 +01:00
if opts . Force {
2017-08-03 16:48:36 +03:00
cmd . AddArguments ( "-D" )
} else {
cmd . AddArguments ( "-d" )
2016-11-03 23:16:01 +01:00
}
2022-10-23 22:44:45 +08:00
cmd . AddDashesAndList ( name )
2022-04-01 10:55:30 +08:00
_ , _ , err := cmd . RunStdString ( & RunOpts { Dir : repo . Path } )
2016-11-03 23:16:01 +01:00
return err
}
2017-02-05 15:43:28 +01:00
// CreateBranch create a new branch
2019-05-11 16:29:17 +01:00
func ( repo * Repository ) CreateBranch ( branch , oldbranchOrCommit string ) error {
2022-02-06 20:01:47 +01:00
cmd := NewCommand ( repo . Ctx , "branch" )
2022-10-23 22:44:45 +08:00
cmd . AddDashesAndList ( branch , oldbranchOrCommit )
2017-02-05 15:43:28 +01:00
2022-04-01 10:55:30 +08:00
_ , _ , err := cmd . RunStdString ( & RunOpts { Dir : repo . Path } )
2017-02-05 15:43:28 +01:00
return err
}
2016-11-03 23:16:01 +01:00
// AddRemote adds a new remote to repository.
func ( repo * Repository ) AddRemote ( name , url string , fetch bool ) error {
2022-02-06 20:01:47 +01:00
cmd := NewCommand ( repo . Ctx , "remote" , "add" )
2016-11-03 23:16:01 +01:00
if fetch {
cmd . AddArguments ( "-f" )
}
2022-10-23 22:44:45 +08:00
cmd . AddDynamicArguments ( name , url )
2016-11-03 23:16:01 +01:00
2022-04-01 10:55:30 +08:00
_ , _ , err := cmd . RunStdString ( & RunOpts { Dir : repo . Path } )
2016-11-03 23:16:01 +01:00
return err
}
// RemoveRemote removes a remote from repository.
func ( repo * Repository ) RemoveRemote ( name string ) error {
2022-10-23 22:44:45 +08:00
_ , _ , err := NewCommand ( repo . Ctx , "remote" , "rm" ) . AddDynamicArguments ( name ) . RunStdString ( & RunOpts { Dir : repo . Path } )
2016-11-03 23:16:01 +01:00
return err
}
2019-04-19 14:17:27 +02:00
// GetCommit returns the head commit of a branch
func ( branch * Branch ) GetCommit ( ) ( * Commit , error ) {
return branch . gitRepo . GetBranchCommit ( branch . Name )
}
2021-10-09 01:03:04 +08:00
// RenameBranch rename a branch
func ( repo * Repository ) RenameBranch ( from , to string ) error {
2022-10-23 22:44:45 +08:00
_ , _ , err := NewCommand ( repo . Ctx , "branch" , "-m" ) . AddDynamicArguments ( from , to ) . RunStdString ( & RunOpts { Dir : repo . Path } )
2021-10-09 01:03:04 +08:00
return err
}
2024-08-12 17:16:55 +02:00
// IsObjectExist returns true if given reference exists in the repository.
func ( repo * Repository ) IsObjectExist ( name string ) bool {
if name == "" {
return false
}
2024-08-21 01:04:57 +08:00
wr , rd , cancel , err := repo . CatFileBatchCheck ( repo . Ctx )
if err != nil {
log . Debug ( "Error writing to CatFileBatchCheck %v" , err )
return false
}
2024-08-12 17:16:55 +02:00
defer cancel ( )
2024-08-21 01:04:57 +08:00
_ , err = wr . Write ( [ ] byte ( name + "\n" ) )
2024-08-12 17:16:55 +02:00
if err != nil {
log . Debug ( "Error writing to CatFileBatchCheck %v" , err )
return false
}
sha , _ , _ , err := ReadBatchLine ( rd )
return err == nil && bytes . HasPrefix ( sha , [ ] byte ( strings . TrimSpace ( name ) ) )
}
// IsReferenceExist returns true if given reference exists in the repository.
func ( repo * Repository ) IsReferenceExist ( name string ) bool {
if name == "" {
return false
}
2024-08-21 01:04:57 +08:00
wr , rd , cancel , err := repo . CatFileBatchCheck ( repo . Ctx )
if err != nil {
log . Debug ( "Error writing to CatFileBatchCheck %v" , err )
return false
}
2024-08-12 17:16:55 +02:00
defer cancel ( )
2024-08-21 01:04:57 +08:00
_ , err = wr . Write ( [ ] byte ( name + "\n" ) )
2024-08-12 17:16:55 +02:00
if err != nil {
log . Debug ( "Error writing to CatFileBatchCheck %v" , err )
return false
}
_ , _ , _ , err = ReadBatchLine ( rd )
return err == nil
}
// IsBranchExist returns true if given branch exists in current repository.
func ( repo * Repository ) IsBranchExist ( name string ) bool {
if repo == nil || name == "" {
return false
}
return repo . IsReferenceExist ( BranchPrefix + name )
}
// GetBranchNames returns branches from the repository, skipping "skip" initial branches and
// returning at most "limit" branches, or all branches if "limit" is 0.
func ( repo * Repository ) GetBranchNames ( skip , limit int ) ( [ ] string , int , error ) {
return callShowRef ( repo . Ctx , repo . Path , BranchPrefix , TrustedCmdArgs { BranchPrefix , "--sort=-committerdate" } , skip , limit )
}
// WalkReferences walks all the references from the repository
// refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty.
func ( repo * Repository ) WalkReferences ( refType ObjectType , skip , limit int , walkfn func ( sha1 , refname string ) error ) ( int , error ) {
var args TrustedCmdArgs
switch refType {
case ObjectTag :
args = TrustedCmdArgs { TagPrefix , "--sort=-taggerdate" }
case ObjectBranch :
args = TrustedCmdArgs { BranchPrefix , "--sort=-committerdate" }
}
return WalkShowRef ( repo . Ctx , repo . Path , args , skip , limit , walkfn )
}
// callShowRef return refs, if limit = 0 it will not limit
func callShowRef ( ctx context . Context , repoPath , trimPrefix string , extraArgs TrustedCmdArgs , skip , limit int ) ( branchNames [ ] string , countAll int , err error ) {
countAll , err = WalkShowRef ( ctx , repoPath , extraArgs , skip , limit , func ( _ , branchName string ) error {
branchName = strings . TrimPrefix ( branchName , trimPrefix )
branchNames = append ( branchNames , branchName )
return nil
} )
return branchNames , countAll , err
}
func WalkShowRef ( ctx context . Context , repoPath string , extraArgs TrustedCmdArgs , skip , limit int , walkfn func ( sha1 , refname string ) error ) ( countAll int , err error ) {
stdoutReader , stdoutWriter := io . Pipe ( )
defer func ( ) {
_ = stdoutReader . Close ( )
_ = stdoutWriter . Close ( )
} ( )
go func ( ) {
stderrBuilder := & strings . Builder { }
args := TrustedCmdArgs { "for-each-ref" , "--format=%(objectname) %(refname)" }
args = append ( args , extraArgs ... )
err := NewCommand ( ctx , args ... ) . Run ( & RunOpts {
Dir : repoPath ,
Stdout : stdoutWriter ,
Stderr : stderrBuilder ,
} )
if err != nil {
if stderrBuilder . Len ( ) == 0 {
_ = stdoutWriter . Close ( )
return
}
_ = stdoutWriter . CloseWithError ( ConcatenateError ( err , stderrBuilder . String ( ) ) )
} else {
_ = stdoutWriter . Close ( )
}
} ( )
i := 0
bufReader := bufio . NewReader ( stdoutReader )
for i < skip {
_ , isPrefix , err := bufReader . ReadLine ( )
if err == io . EOF {
return i , nil
}
if err != nil {
return 0 , err
}
if ! isPrefix {
i ++
}
}
for limit == 0 || i < skip + limit {
// The output of show-ref is simply a list:
// <sha> SP <ref> LF
sha , err := bufReader . ReadString ( ' ' )
if err == io . EOF {
return i , nil
}
if err != nil {
return 0 , err
}
branchName , err := bufReader . ReadString ( '\n' )
if err == io . EOF {
// This shouldn't happen... but we'll tolerate it for the sake of peace
return i , nil
}
if err != nil {
return i , err
}
if len ( branchName ) > 0 {
branchName = branchName [ : len ( branchName ) - 1 ]
}
if len ( sha ) > 0 {
sha = sha [ : len ( sha ) - 1 ]
}
err = walkfn ( sha , branchName )
if err != nil {
return i , err
}
i ++
}
// count all refs
for limit != 0 {
_ , isPrefix , err := bufReader . ReadLine ( )
if err == io . EOF {
return i , nil
}
if err != nil {
return 0 , err
}
if ! isPrefix {
i ++
}
}
return i , nil
}
// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash
func ( repo * Repository ) GetRefsBySha ( sha , prefix string ) ( [ ] string , error ) {
var revList [ ] string
_ , err := WalkShowRef ( repo . Ctx , repo . Path , nil , 0 , 0 , func ( walkSha , refname string ) error {
if walkSha == sha && strings . HasPrefix ( refname , prefix ) {
revList = append ( revList , refname )
}
return nil
} )
return revList , err
}