123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- // Copyright 2015 The Gogs Authors. All rights reserved.
- // Copyright 2017 The Gitea Authors. All rights reserved.
- // Use of this source code is governed by a MIT-style
- // license that can be found in the LICENSE file.
-
- package git
-
- import (
- "bytes"
- "context"
- "fmt"
- "io"
- "net/url"
- "os"
- "path"
- "path/filepath"
- "strconv"
- "strings"
- "time"
-
- "code.gitea.io/gitea/modules/proxy"
- )
-
- // GPGSettings represents the default GPG settings for this repository
- type GPGSettings struct {
- Sign bool
- KeyID string
- Email string
- Name string
- PublicKeyContent string
- }
-
- const prettyLogFormat = `--pretty=format:%H`
-
- // GetAllCommitsCount returns count of all commits in repository
- func (repo *Repository) GetAllCommitsCount() (int64, error) {
- return AllCommitsCount(repo.Path, false)
- }
-
- func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, error) {
- var commits []*Commit
- if len(logs) == 0 {
- return commits, nil
- }
-
- parts := bytes.Split(logs, []byte{'\n'})
-
- for _, commitID := range parts {
- commit, err := repo.GetCommit(string(commitID))
- if err != nil {
- return nil, err
- }
- commits = append(commits, commit)
- }
-
- return commits, nil
- }
-
- // IsRepoURLAccessible checks if given repository URL is accessible.
- func IsRepoURLAccessible(url string) bool {
- _, err := NewCommand("ls-remote", "-q", "-h", url, "HEAD").Run()
- return err == nil
- }
-
- // InitRepository initializes a new Git repository.
- func InitRepository(repoPath string, bare bool) error {
- err := os.MkdirAll(repoPath, os.ModePerm)
- if err != nil {
- return err
- }
-
- cmd := NewCommand("init")
- if bare {
- cmd.AddArguments("--bare")
- }
- _, err = cmd.RunInDir(repoPath)
- return err
- }
-
- // IsEmpty Check if repository is empty.
- func (repo *Repository) IsEmpty() (bool, error) {
- var errbuf strings.Builder
- if err := NewCommand("log", "-1").RunInDirPipeline(repo.Path, nil, &errbuf); err != nil {
- if strings.Contains(errbuf.String(), "fatal: bad default revision 'HEAD'") ||
- strings.Contains(errbuf.String(), "fatal: your current branch 'master' does not have any commits yet") {
- return true, nil
- }
- return true, fmt.Errorf("check empty: %v - %s", err, errbuf.String())
- }
-
- return false, nil
- }
-
- // CloneRepoOptions options when clone a repository
- type CloneRepoOptions struct {
- Timeout time.Duration
- Mirror bool
- Bare bool
- Quiet bool
- Branch string
- Shared bool
- NoCheckout bool
- Depth int
- }
-
- // Clone clones original repository to target path.
- func Clone(from, to string, opts CloneRepoOptions) error {
- return CloneWithContext(DefaultContext, from, to, opts)
- }
-
- // CloneWithContext clones original repository to target path.
- func CloneWithContext(ctx context.Context, from, to string, opts CloneRepoOptions) error {
- cargs := make([]string, len(GlobalCommandArgs))
- copy(cargs, GlobalCommandArgs)
- return CloneWithArgs(ctx, from, to, cargs, opts)
- }
-
- // CloneWithArgs original repository to target path.
- func CloneWithArgs(ctx context.Context, from, to string, args []string, opts CloneRepoOptions) (err error) {
- toDir := path.Dir(to)
- if err = os.MkdirAll(toDir, os.ModePerm); err != nil {
- return err
- }
-
- cmd := NewCommandContextNoGlobals(ctx, args...).AddArguments("clone")
- if opts.Mirror {
- cmd.AddArguments("--mirror")
- }
- if opts.Bare {
- cmd.AddArguments("--bare")
- }
- if opts.Quiet {
- cmd.AddArguments("--quiet")
- }
- if opts.Shared {
- cmd.AddArguments("-s")
- }
- if opts.NoCheckout {
- cmd.AddArguments("--no-checkout")
- }
- if opts.Depth > 0 {
- cmd.AddArguments("--depth", strconv.Itoa(opts.Depth))
- }
-
- if len(opts.Branch) > 0 {
- cmd.AddArguments("-b", opts.Branch)
- }
- cmd.AddArguments("--", from, to)
-
- if opts.Timeout <= 0 {
- opts.Timeout = -1
- }
-
- var envs = os.Environ()
- u, err := url.Parse(from)
- if err == nil && (strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https")) {
- if proxy.Match(u.Host) {
- envs = append(envs, fmt.Sprintf("https_proxy=%s", proxy.GetProxyURL()))
- }
- }
-
- var stderr = new(bytes.Buffer)
- if err = cmd.RunWithContext(&RunContext{
- Timeout: opts.Timeout,
- Env: envs,
- Stdout: io.Discard,
- Stderr: stderr,
- }); err != nil {
- return ConcatenateError(err, stderr.String())
- }
- return nil
- }
-
- // PullRemoteOptions options when pull from remote
- type PullRemoteOptions struct {
- Timeout time.Duration
- All bool
- Rebase bool
- Remote string
- Branch string
- }
-
- // Pull pulls changes from remotes.
- func Pull(repoPath string, opts PullRemoteOptions) error {
- cmd := NewCommand("pull")
- if opts.Rebase {
- cmd.AddArguments("--rebase")
- }
- if opts.All {
- cmd.AddArguments("--all")
- } else {
- cmd.AddArguments("--", opts.Remote, opts.Branch)
- }
-
- if opts.Timeout <= 0 {
- opts.Timeout = -1
- }
-
- _, err := cmd.RunInDirTimeout(opts.Timeout, repoPath)
- return err
- }
-
- // PushOptions options when push to remote
- type PushOptions struct {
- Remote string
- Branch string
- Force bool
- Mirror bool
- Env []string
- Timeout time.Duration
- }
-
- // Push pushs local commits to given remote branch.
- func Push(repoPath string, opts PushOptions) error {
- cmd := NewCommand("push")
- if opts.Force {
- cmd.AddArguments("-f")
- }
- if opts.Mirror {
- cmd.AddArguments("--mirror")
- }
- cmd.AddArguments("--", opts.Remote)
- if len(opts.Branch) > 0 {
- cmd.AddArguments(opts.Branch)
- }
- var outbuf, errbuf strings.Builder
-
- if opts.Timeout == 0 {
- opts.Timeout = -1
- }
-
- err := cmd.RunInDirTimeoutEnvPipeline(opts.Env, opts.Timeout, repoPath, &outbuf, &errbuf)
- if err != nil {
- if strings.Contains(errbuf.String(), "non-fast-forward") {
- return &ErrPushOutOfDate{
- StdOut: outbuf.String(),
- StdErr: errbuf.String(),
- Err: err,
- }
- } else if strings.Contains(errbuf.String(), "! [remote rejected]") {
- err := &ErrPushRejected{
- StdOut: outbuf.String(),
- StdErr: errbuf.String(),
- Err: err,
- }
- err.GenerateMessage()
- return err
- } else if strings.Contains(errbuf.String(), "matches more than one") {
- err := &ErrMoreThanOne{
- StdOut: outbuf.String(),
- StdErr: errbuf.String(),
- Err: err,
- }
- return err
- }
- }
-
- if errbuf.Len() > 0 && err != nil {
- return fmt.Errorf("%v - %s", err, errbuf.String())
- }
-
- return err
- }
-
- // CheckoutOptions options when heck out some branch
- type CheckoutOptions struct {
- Timeout time.Duration
- Branch string
- OldBranch string
- }
-
- // Checkout checkouts a branch
- func Checkout(repoPath string, opts CheckoutOptions) error {
- cmd := NewCommand("checkout")
- if len(opts.OldBranch) > 0 {
- cmd.AddArguments("-b")
- }
-
- if opts.Timeout <= 0 {
- opts.Timeout = -1
- }
-
- cmd.AddArguments(opts.Branch)
-
- if len(opts.OldBranch) > 0 {
- cmd.AddArguments(opts.OldBranch)
- }
-
- _, err := cmd.RunInDirTimeout(opts.Timeout, repoPath)
- return err
- }
-
- // ResetHEAD resets HEAD to given revision or head of branch.
- func ResetHEAD(repoPath string, hard bool, revision string) error {
- cmd := NewCommand("reset")
- if hard {
- cmd.AddArguments("--hard")
- }
- _, err := cmd.AddArguments(revision).RunInDir(repoPath)
- return err
- }
-
- // MoveFile moves a file to another file or directory.
- func MoveFile(repoPath, oldTreeName, newTreeName string) error {
- _, err := NewCommand("mv").AddArguments(oldTreeName, newTreeName).RunInDir(repoPath)
- return err
- }
-
- // CountObject represents repository count objects report
- type CountObject struct {
- Count int64
- Size int64
- InPack int64
- Packs int64
- SizePack int64
- PrunePack int64
- Garbage int64
- SizeGarbage int64
- }
-
- const (
- statCount = "count: "
- statSize = "size: "
- statInpack = "in-pack: "
- statPacks = "packs: "
- statSizePack = "size-pack: "
- statPrunePackage = "prune-package: "
- statGarbage = "garbage: "
- statSizeGarbage = "size-garbage: "
- )
-
- // CountObjects returns the results of git count-objects on the repoPath
- func CountObjects(repoPath string) (*CountObject, error) {
- cmd := NewCommand("count-objects", "-v")
- stdout, err := cmd.RunInDir(repoPath)
- if err != nil {
- return nil, err
- }
-
- return parseSize(stdout), nil
- }
-
- // parseSize parses the output from count-objects and return a CountObject
- func parseSize(objects string) *CountObject {
- repoSize := new(CountObject)
- for _, line := range strings.Split(objects, "\n") {
- switch {
- case strings.HasPrefix(line, statCount):
- repoSize.Count, _ = strconv.ParseInt(line[7:], 10, 64)
- case strings.HasPrefix(line, statSize):
- repoSize.Size, _ = strconv.ParseInt(line[6:], 10, 64)
- repoSize.Size *= 1024
- case strings.HasPrefix(line, statInpack):
- repoSize.InPack, _ = strconv.ParseInt(line[9:], 10, 64)
- case strings.HasPrefix(line, statPacks):
- repoSize.Packs, _ = strconv.ParseInt(line[7:], 10, 64)
- case strings.HasPrefix(line, statSizePack):
- repoSize.Count, _ = strconv.ParseInt(line[11:], 10, 64)
- repoSize.Count *= 1024
- case strings.HasPrefix(line, statPrunePackage):
- repoSize.PrunePack, _ = strconv.ParseInt(line[16:], 10, 64)
- case strings.HasPrefix(line, statGarbage):
- repoSize.Garbage, _ = strconv.ParseInt(line[9:], 10, 64)
- case strings.HasPrefix(line, statSizeGarbage):
- repoSize.SizeGarbage, _ = strconv.ParseInt(line[14:], 10, 64)
- repoSize.SizeGarbage *= 1024
- }
- }
- return repoSize
- }
-
- // GetLatestCommitTime returns time for latest commit in repository (across all branches)
- func GetLatestCommitTime(repoPath string) (time.Time, error) {
- cmd := NewCommand("for-each-ref", "--sort=-committerdate", "refs/heads/", "--count", "1", "--format=%(committerdate)")
- stdout, err := cmd.RunInDir(repoPath)
- if err != nil {
- return time.Time{}, err
- }
- commitTime := strings.TrimSpace(stdout)
- return time.Parse(GitTimeLayout, commitTime)
- }
-
- // DivergeObject represents commit count diverging commits
- type DivergeObject struct {
- Ahead int
- Behind int
- }
-
- func checkDivergence(repoPath string, baseBranch string, targetBranch string) (int, error) {
- branches := fmt.Sprintf("%s..%s", baseBranch, targetBranch)
- cmd := NewCommand("rev-list", "--count", branches)
- stdout, err := cmd.RunInDir(repoPath)
- if err != nil {
- return -1, err
- }
- outInteger, errInteger := strconv.Atoi(strings.Trim(stdout, "\n"))
- if errInteger != nil {
- return -1, errInteger
- }
- return outInteger, nil
- }
-
- // GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
- func GetDivergingCommits(repoPath string, baseBranch string, targetBranch string) (DivergeObject, error) {
- // $(git rev-list --count master..feature) commits ahead of master
- ahead, errorAhead := checkDivergence(repoPath, baseBranch, targetBranch)
- if errorAhead != nil {
- return DivergeObject{}, errorAhead
- }
-
- // $(git rev-list --count feature..master) commits behind master
- behind, errorBehind := checkDivergence(repoPath, targetBranch, baseBranch)
- if errorBehind != nil {
- return DivergeObject{}, errorBehind
- }
-
- return DivergeObject{ahead, behind}, nil
- }
-
- // CreateBundle create bundle content to the target path
- func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error {
- tmp, err := os.MkdirTemp(os.TempDir(), "gitea-bundle")
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmp)
-
- tmpFile := filepath.Join(tmp, "bundle")
- args := []string{
- "bundle",
- "create",
- tmpFile,
- commit,
- }
- _, err = NewCommandContext(ctx, args...).RunInDir(repo.Path)
- if err != nil {
- return err
- }
-
- fi, err := os.Open(tmpFile)
- if err != nil {
- return err
- }
- defer fi.Close()
-
- _, err = io.Copy(out, fi)
- return err
- }
|