118 lines
3 KiB
Go
118 lines
3 KiB
Go
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Copyright © 2023 Thorsten Schubert <tschubert@bafh.org>
|
|
|
|
package repo
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
type Gitrepo struct {
|
|
Path string
|
|
Name string
|
|
Branch string
|
|
Remote string
|
|
TrackingBranch string
|
|
IsBare bool
|
|
cmdGrace time.Duration
|
|
}
|
|
|
|
func (r *Gitrepo) resolveTracking() (string, string, error) {
|
|
// First check for remote
|
|
cmd := r.prepareGitCmd(false, "remote")
|
|
remoteOutput, err := cmd.Output()
|
|
if err != nil || strings.TrimSpace(string(remoteOutput)) == "" {
|
|
return "", "", errors.New("no remote")
|
|
}
|
|
|
|
remote := strings.TrimSpace(string(remoteOutput))
|
|
|
|
var tracking string
|
|
var errBuf bytes.Buffer
|
|
|
|
cmd = r.prepareGitCmd(false, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
|
|
cmd.Stderr = &errBuf
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
// Might be a bare repo or a mirror
|
|
if strings.HasPrefix(errBuf.String(), "fatal: no upstream configured") {
|
|
log.Trace().Str("repo", r.Name).Msg("Remote but no tracking branch")
|
|
return remote, "", nil
|
|
}
|
|
return remote, "", err
|
|
}
|
|
|
|
// Get tracking branch and also extract default remote
|
|
tracking = strings.TrimSpace(string(output))
|
|
parts := strings.SplitN(tracking, "/", 2)
|
|
if len(parts) != 2 {
|
|
return remote, tracking, errors.New("unexpected format")
|
|
}
|
|
// Associated remote
|
|
remote = parts[0]
|
|
log.Trace().Str("repo", r.Name).Str("tracking", tracking).Str("remote", remote).Send()
|
|
|
|
return remote, tracking, nil
|
|
}
|
|
|
|
func (r *Gitrepo) resolveLocal() (string, error) {
|
|
cmd := r.prepareGitCmd(true, "symbolic-ref", "--short", "HEAD")
|
|
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
currentBranch := strings.TrimSpace(string(output))
|
|
log.Trace().Str("repo", r.Name).Str("branch", currentBranch).Send()
|
|
|
|
return currentBranch, nil
|
|
}
|
|
|
|
func (r *Gitrepo) isBareRepo() bool {
|
|
cmd := r.prepareGitCmd(true, "rev-parse", "--is-bare-repository")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
bare := strings.Contains(string(out), "true")
|
|
log.Trace().Str("repo", r.Name).Bool("bare", bare).Send()
|
|
|
|
return bare
|
|
}
|
|
|
|
func (r *Gitrepo) prepareGitCmd(pgroup bool, args ...string) *exec.Cmd {
|
|
cmd := exec.Command("git", append([]string{"-C", r.GetPath()}, args...)...)
|
|
cmd.Stdin = nil
|
|
|
|
if pgroup {
|
|
// We don't want the subprocess to receive the SIGINT from the shell, to
|
|
// not poison the failed repo collection with invalid entries. Create a new
|
|
// process group for each spawned subprocess.
|
|
cmd.SysProcAttr = &unix.SysProcAttr{
|
|
Setpgid: true,
|
|
Pgid: 0,
|
|
}
|
|
}
|
|
log.Trace().Str("name", r.Name).Str("cmd", cmd.String()).Send()
|
|
|
|
return cmd
|
|
}
|
|
|
|
func (r *Gitrepo) prepareGitCmdWithCtx(ctx context.Context, args ...string) *exec.Cmd {
|
|
cmd := exec.CommandContext(ctx, "git", append([]string{"-C", r.GetPath()}, args...)...)
|
|
cmd.Stdin = nil
|
|
|
|
log.Trace().Str("name", r.Name).Str("cmd", cmd.String()).Send()
|
|
|
|
return cmd
|
|
}
|