drone/git/api/branch.go

160 lines
4.0 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package api
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/harness/gitness/git/command"
"github.com/harness/gitness/git/sha"
)
type Branch struct {
Name string
SHA sha.SHA
Commit *Commit
}
type BranchFilter struct {
Query string
Page int32
PageSize int32
Sort GitReferenceField
Order SortOrder
IncludeCommit bool
}
// BranchPrefix base dir of the branch information file store on git.
const BranchPrefix = "refs/heads/"
// GetBranch gets an existing branch.
func (g *Git) GetBranch(
ctx context.Context,
repoPath string,
branchName string,
) (*Branch, error) {
if repoPath == "" {
return nil, ErrRepositoryPathEmpty
}
if branchName == "" {
return nil, ErrBranchNameEmpty
}
ref := GetReferenceFromBranchName(branchName)
commit, err := GetCommit(ctx, repoPath, ref+"^{commit}") //nolint:goconst
if err != nil {
return nil, fmt.Errorf("failed to find the commit for the branch: %w", err)
}
return &Branch{
Name: branchName,
SHA: commit.SHA,
Commit: commit,
}, nil
}
// HasBranches returns true iff there's at least one branch in the repo (any branch)
// NOTE: This is different from repo.Empty(),
// as it doesn't care whether the existing branch is the default branch or not.
func (g *Git) HasBranches(
ctx context.Context,
repoPath string,
) (bool, error) {
if repoPath == "" {
return false, ErrRepositoryPathEmpty
}
// repo has branches IFF there's at least one commit that is reachable via a branch
// (every existing branch points to a commit)
cmd := command.New("rev-list",
command.WithFlag("--max-count", "1"),
command.WithFlag("--branches"),
)
output := &bytes.Buffer{}
if err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(output)); err != nil {
return false, processGitErrorf(err, "failed to trigger rev-list command")
}
return strings.TrimSpace(output.String()) == "", nil
}
func (g *Git) IsBranchExist(ctx context.Context, repoPath, name string) (bool, error) {
cmd := command.New("show-ref",
command.WithFlag("--exists", BranchPrefix+name),
)
err := cmd.Run(ctx,
command.WithDir(repoPath),
)
if cmdERR := command.AsError(err); cmdERR != nil && cmdERR.IsExitCode(2) {
// git returns exit code 2 in case the ref doesn't exist.
// On success it would be 0 and no error would be returned in the first place.
// Any other exit code we fall through to default error handling.
// https://git-scm.com/docs/git-show-ref#Documentation/git-show-ref.txt---exists
return false, nil
}
if err != nil {
return false, processGitErrorf(err, "failed to check if branch %q exist", name)
}
return true, nil
}
func (g *Git) GetBranchCount(
ctx context.Context,
repoPath string,
) (int, error) {
if repoPath == "" {
return 0, ErrRepositoryPathEmpty
}
pipeOut, pipeIn := io.Pipe()
defer pipeOut.Close()
cmd := command.New("branch",
command.WithFlag("--list"),
command.WithFlag("--format=%(refname:short)"),
)
go func() {
err := cmd.Run(ctx, command.WithDir(repoPath), command.WithStdout(pipeIn))
if err != nil {
_ = pipeIn.CloseWithError(
processGitErrorf(err, "failed to trigger branch command"),
)
return
}
_ = pipeIn.Close()
}()
return countLines(pipeOut)
}
func countLines(pipe io.Reader) (int, error) {
scanner := bufio.NewScanner(pipe)
count := 0
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
return 0, err
}
return count, nil
}