Make logging and exec members of the git struct

Otherwise they are floating global pointers, which sucks in tests.
This commit is contained in:
Tim Hockin 2021-08-16 22:58:28 -07:00
parent 0075df238c
commit dc02fd98c0
2 changed files with 101 additions and 96 deletions

View File

@ -155,9 +155,6 @@ func init() {
pflag.CommandLine.MarkDeprecated("sync-hook-command", "use --exechook-command instead") pflag.CommandLine.MarkDeprecated("sync-hook-command", "use --exechook-command instead")
} }
var cmdRunner *cmd.Runner
var log *logging.Logger
// Total pull/error, summary on pull duration // Total pull/error, summary on pull duration
var ( var (
// TODO: have a marker for "which" servergroup // TODO: have a marker for "which" servergroup
@ -252,11 +249,11 @@ func envDuration(key string, def time.Duration) time.Duration {
return def return def
} }
func setGlogFlags() { func setGlogFlags(v int, log *logging.Logger) {
// Force logging to stderr. // Force logging to stderr.
stderrFlag := stdflag.Lookup("logtostderr") stderrFlag := stdflag.Lookup("logtostderr")
if stderrFlag == nil { if stderrFlag == nil {
handleError(false, "ERROR: can't find glog flag 'logtostderr'") handleError(log, false, "ERROR: can't find glog flag 'logtostderr'")
} }
stderrFlag.Value.Set("true") stderrFlag.Value.Set("true")
@ -266,7 +263,7 @@ func setGlogFlags() {
fmt.Fprintf(os.Stderr, "ERROR: can't find glog flag 'v'\n") fmt.Fprintf(os.Stderr, "ERROR: can't find glog flag 'v'\n")
os.Exit(1) os.Exit(1)
} }
vFlag.Value.Set(strconv.Itoa(*flVerbose)) vFlag.Value.Set(strconv.Itoa(v))
} }
// repoSync represents the remote repo and the local sync of it. // repoSync represents the remote repo and the local sync of it.
@ -283,6 +280,8 @@ type repoSync struct {
authURL string // a URL to re-fetch credentials, or "" authURL string // a URL to re-fetch credentials, or ""
sparseFile string // path to a sparse-checkout file sparseFile string // path to a sparse-checkout file
syncHookCmd string // command to run after each sync syncHookCmd string // command to run after each sync
log *logging.Logger
run *cmd.Runner
} }
func main() { func main() {
@ -303,10 +302,12 @@ func main() {
pflag.Parse() pflag.Parse()
stdflag.CommandLine.Parse(nil) // Otherwise glog complains stdflag.CommandLine.Parse(nil) // Otherwise glog complains
setGlogFlags()
log = logging.New(*flRoot, *flErrorFile) // Needs to happen very early for errors to be written to a file.
cmdRunner = cmd.NewRunner(log) log := logging.New(*flRoot, *flErrorFile)
cmdRunner := cmd.NewRunner(log)
setGlogFlags(*flVerbose, log)
if *flVersion { if *flVersion {
fmt.Println(version.VERSION) fmt.Println(version.VERSION)
@ -323,21 +324,21 @@ func main() {
} }
if *flRepo == "" { if *flRepo == "" {
handleError(true, "ERROR: --repo must be specified") handleError(log, true, "ERROR: --repo must be specified")
} }
if *flDepth < 0 { // 0 means "no limit" if *flDepth < 0 { // 0 means "no limit"
handleError(true, "ERROR: --depth must be greater than or equal to 0") handleError(log, true, "ERROR: --depth must be greater than or equal to 0")
} }
switch submodulesMode(*flSubmodules) { switch submodulesMode(*flSubmodules) {
case submodulesRecursive, submodulesShallow, submodulesOff: case submodulesRecursive, submodulesShallow, submodulesOff:
default: default:
handleError(true, "ERROR: --submodules must be one of %q, %q, or %q", submodulesRecursive, submodulesShallow, submodulesOff) handleError(log, true, "ERROR: --submodules must be one of %q, %q, or %q", submodulesRecursive, submodulesShallow, submodulesOff)
} }
if *flRoot == "" { if *flRoot == "" {
handleError(true, "ERROR: --root must be specified") handleError(log, true, "ERROR: --root must be specified")
} }
if *flDest != "" { if *flDest != "" {
@ -348,24 +349,24 @@ func main() {
*flLink = parts[len(parts)-1] *flLink = parts[len(parts)-1]
} }
if strings.Contains(*flLink, "/") { if strings.Contains(*flLink, "/") {
handleError(true, "ERROR: --link must not contain '/'") handleError(log, true, "ERROR: --link must not contain '/'")
} }
if strings.HasPrefix(*flLink, ".") { if strings.HasPrefix(*flLink, ".") {
handleError(true, "ERROR: --link must not start with '.'") handleError(log, true, "ERROR: --link must not start with '.'")
} }
if *flWait != 0 { if *flWait != 0 {
*flPeriod = time.Duration(int(*flWait*1000)) * time.Millisecond *flPeriod = time.Duration(int(*flWait*1000)) * time.Millisecond
} }
if *flPeriod < 10*time.Millisecond { if *flPeriod < 10*time.Millisecond {
handleError(true, "ERROR: --period must be at least 10ms") handleError(log, true, "ERROR: --period must be at least 10ms")
} }
if *flTimeout != 0 { if *flTimeout != 0 {
*flSyncTimeout = time.Duration(*flTimeout) * time.Second *flSyncTimeout = time.Duration(*flTimeout) * time.Second
} }
if *flSyncTimeout < 10*time.Millisecond { if *flSyncTimeout < 10*time.Millisecond {
handleError(true, "ERROR: --sync-timeout must be at least 10ms") handleError(log, true, "ERROR: --sync-timeout must be at least 10ms")
} }
if *flSyncHookCommand != "" { if *flSyncHookCommand != "" {
@ -373,56 +374,56 @@ func main() {
} }
if *flExechookCommand != "" { if *flExechookCommand != "" {
if *flExechookTimeout < time.Second { if *flExechookTimeout < time.Second {
handleError(true, "ERROR: --exechook-timeout must be at least 1s") handleError(log, true, "ERROR: --exechook-timeout must be at least 1s")
} }
if *flExechookBackoff < time.Second { if *flExechookBackoff < time.Second {
handleError(true, "ERROR: --exechook-backoff must be at least 1s") handleError(log, true, "ERROR: --exechook-backoff must be at least 1s")
} }
} }
if *flWebhookURL != "" { if *flWebhookURL != "" {
if *flWebhookStatusSuccess < -1 { if *flWebhookStatusSuccess < -1 {
handleError(true, "ERROR: --webhook-success-status must be a valid HTTP code or -1") handleError(log, true, "ERROR: --webhook-success-status must be a valid HTTP code or -1")
} }
if *flWebhookTimeout < time.Second { if *flWebhookTimeout < time.Second {
handleError(true, "ERROR: --webhook-timeout must be at least 1s") handleError(log, true, "ERROR: --webhook-timeout must be at least 1s")
} }
if *flWebhookBackoff < time.Second { if *flWebhookBackoff < time.Second {
handleError(true, "ERROR: --webhook-backoff must be at least 1s") handleError(log, true, "ERROR: --webhook-backoff must be at least 1s")
} }
} }
if *flPassword != "" && *flPasswordFile != "" { if *flPassword != "" && *flPasswordFile != "" {
handleError(false, "ERROR: only one of --password and --password-file may be specified") handleError(log, false, "ERROR: only one of --password and --password-file may be specified")
} }
if *flUsername != "" { if *flUsername != "" {
if *flPassword == "" && *flPasswordFile == "" { if *flPassword == "" && *flPasswordFile == "" {
handleError(true, "ERROR: --password or --password-file must be set when --username is specified") handleError(log, true, "ERROR: --password or --password-file must be set when --username is specified")
} }
} }
if *flSSH { if *flSSH {
if *flUsername != "" { if *flUsername != "" {
handleError(false, "ERROR: only one of --ssh and --username may be specified") handleError(log, false, "ERROR: only one of --ssh and --username may be specified")
} }
if *flPassword != "" { if *flPassword != "" {
handleError(false, "ERROR: only one of --ssh and --password may be specified") handleError(log, false, "ERROR: only one of --ssh and --password may be specified")
} }
if *flPasswordFile != "" { if *flPasswordFile != "" {
handleError(false, "ERROR: only one of --ssh and --password-file may be specified") handleError(log, false, "ERROR: only one of --ssh and --password-file may be specified")
} }
if *flAskPassURL != "" { if *flAskPassURL != "" {
handleError(false, "ERROR: only one of --ssh and --askpass-url may be specified") handleError(log, false, "ERROR: only one of --ssh and --askpass-url may be specified")
} }
if *flCookieFile { if *flCookieFile {
handleError(false, "ERROR: only one of --ssh and --cookie-file may be specified") handleError(log, false, "ERROR: only one of --ssh and --cookie-file may be specified")
} }
if *flSSHKeyFile == "" { if *flSSHKeyFile == "" {
handleError(true, "ERROR: --ssh-key-file must be specified when --ssh is specified") handleError(log, true, "ERROR: --ssh-key-file must be specified when --ssh is specified")
} }
if *flSSHKnownHosts { if *flSSHKnownHosts {
if *flSSHKnownHostsFile == "" { if *flSSHKnownHostsFile == "" {
handleError(true, "ERROR: --ssh-known-hosts-file must be specified when --ssh-known-hosts is specified") handleError(log, true, "ERROR: --ssh-known-hosts-file must be specified when --ssh-known-hosts is specified")
} }
} }
} }
@ -477,6 +478,8 @@ func main() {
authURL: *flAskPassURL, authURL: *flAskPassURL,
sparseFile: *flSparseCheckoutFile, sparseFile: *flSparseCheckoutFile,
syncHookCmd: *flSyncHookCommand, syncHookCmd: *flSyncHookCommand,
log: log,
run: cmdRunner,
} }
// This context is used only for git credentials initialization. There are no long-running operations like // This context is used only for git credentials initialization. There are no long-running operations like
@ -499,7 +502,7 @@ func main() {
} }
if *flSSH { if *flSSH {
if err := setupGitSSH(*flSSHKnownHosts, *flSSHKeyFile, *flSSHKnownHostsFile); err != nil { if err := git.SetupGitSSH(*flSSHKnownHosts, *flSSHKeyFile, *flSSHKnownHostsFile); err != nil {
log.Error(err, "ERROR: can't set up git SSH") log.Error(err, "ERROR: can't set up git SSH")
os.Exit(1) os.Exit(1)
} }
@ -683,17 +686,17 @@ func (git *repoSync) InitRepo(ctx context.Context) error {
// Make sure the directory we found is actually usable. // Make sure the directory we found is actually usable.
if git.SanityCheck(ctx) { if git.SanityCheck(ctx) {
log.V(0).Info("root directory is valid", "path", git.root) git.log.V(0).Info("root directory is valid", "path", git.root)
return nil return nil
} }
// Maybe a previous run crashed? Git won't use this dir. // Maybe a previous run crashed? Git won't use this dir.
log.V(0).Info("root directory exists but failed checks, cleaning up", "path", git.root) git.log.V(0).Info("root directory exists but failed checks, cleaning up", "path", git.root)
// We remove the contents rather than the dir itself, because a common // We remove the contents rather than the dir itself, because a common
// use-case is to have a volume mounted at git.root, which makes removing // use-case is to have a volume mounted at git.root, which makes removing
// it impossible. // it impossible.
if err := removeDirContents(git.root); err != nil { if err := removeDirContents(git.root, git.log); err != nil {
return fmt.Errorf("can't remove unusable git root: %w", err) return fmt.Errorf("can't remove unusable git root: %w", err)
} }
@ -702,32 +705,32 @@ func (git *repoSync) InitRepo(ctx context.Context) error {
// sanityCheck tries to make sure that the dir is a valid git repository. // sanityCheck tries to make sure that the dir is a valid git repository.
func (git *repoSync) SanityCheck(ctx context.Context) bool { func (git *repoSync) SanityCheck(ctx context.Context) bool {
log.V(0).Info("sanity-checking git repo", "repo", git.root) git.log.V(0).Info("sanity-checking git repo", "repo", git.root)
// If it is empty, we are done. // If it is empty, we are done.
if empty, err := dirIsEmpty(git.root); err != nil { if empty, err := dirIsEmpty(git.root); err != nil {
log.Error(err, "can't list repo directory", "repo", git.root) git.log.Error(err, "can't list repo directory", "repo", git.root)
return false return false
} else if empty { } else if empty {
log.V(0).Info("git repo is empty", "repo", git.root) git.log.V(0).Info("git repo is empty", "repo", git.root)
return true return true
} }
// Check that this is actually the root of the repo. // Check that this is actually the root of the repo.
if root, err := cmdRunner.Run(ctx, git.root, git.cmd, "rev-parse", "--show-toplevel"); err != nil { if root, err := git.run.Run(ctx, git.root, git.cmd, "rev-parse", "--show-toplevel"); err != nil {
log.Error(err, "can't get repo toplevel", "repo", git.root) git.log.Error(err, "can't get repo toplevel", "repo", git.root)
return false return false
} else { } else {
root = strings.TrimSpace(root) root = strings.TrimSpace(root)
if root != git.root { if root != git.root {
log.V(0).Info("git repo is under another repo", "repo", git.root, "parent", root) git.log.V(0).Info("git repo is under another repo", "repo", git.root, "parent", root)
return false return false
} }
} }
// Consistency-check the repo. // Consistency-check the repo.
if _, err := cmdRunner.Run(ctx, git.root, git.cmd, "fsck", "--no-progress", "--connectivity-only"); err != nil { if _, err := git.run.Run(ctx, git.root, git.cmd, "fsck", "--no-progress", "--connectivity-only"); err != nil {
log.Error(err, "repo sanity check failed", "repo", git.root) git.log.Error(err, "repo sanity check failed", "repo", git.root)
return false return false
} }
@ -742,7 +745,7 @@ func dirIsEmpty(dir string) (bool, error) {
return len(dirents) == 0, nil return len(dirents) == 0, nil
} }
func removeDirContents(dir string) error { func removeDirContents(dir string, log *logging.Logger) error {
dirents, err := ioutil.ReadDir(dir) dirents, err := ioutil.ReadDir(dir)
if err != nil { if err != nil {
return err return err
@ -750,7 +753,9 @@ func removeDirContents(dir string) error {
for _, fi := range dirents { for _, fi := range dirents {
p := filepath.Join(dir, fi.Name()) p := filepath.Join(dir, fi.Name())
log.V(2).Info("removing path recursively", "path", p, "isDir", fi.IsDir()) if log != nil {
log.V(2).Info("removing path recursively", "path", p, "isDir", fi.IsDir())
}
if err := os.RemoveAll(p); err != nil { if err := os.RemoveAll(p); err != nil {
return err return err
} }
@ -775,7 +780,7 @@ func sleepForever() {
// handleError prints the error to the standard error, prints the usage if the `printUsage` flag is true, // handleError prints the error to the standard error, prints the usage if the `printUsage` flag is true,
// exports the error to the error file and exits the process with the exit code. // exports the error to the error file and exits the process with the exit code.
func handleError(printUsage bool, format string, a ...interface{}) { func handleError(log *logging.Logger, printUsage bool, format string, a ...interface{}) {
s := fmt.Sprintf(format, a...) s := fmt.Sprintf(format, a...)
fmt.Fprintln(os.Stderr, s) fmt.Fprintln(os.Stderr, s)
if printUsage { if printUsage {
@ -827,13 +832,13 @@ func (git *repoSync) UpdateSymlink(ctx context.Context, newDir string) (string,
} }
const tmplink = "tmp-link" const tmplink = "tmp-link"
log.V(1).Info("creating tmp symlink", "root", git.root, "dst", newDirRelative, "src", tmplink) git.log.V(1).Info("creating tmp symlink", "root", git.root, "dst", newDirRelative, "src", tmplink)
if _, err := cmdRunner.Run(ctx, git.root, "ln", "-snf", newDirRelative, tmplink); err != nil { if _, err := git.run.Run(ctx, git.root, "ln", "-snf", newDirRelative, tmplink); err != nil {
return "", fmt.Errorf("error creating symlink: %v", err) return "", fmt.Errorf("error creating symlink: %v", err)
} }
log.V(1).Info("renaming symlink", "root", git.root, "oldName", tmplink, "newName", git.link) git.log.V(1).Info("renaming symlink", "root", git.root, "oldName", tmplink, "newName", git.link)
if _, err := cmdRunner.Run(ctx, git.root, "mv", "-T", tmplink, git.link); err != nil { if _, err := git.run.Run(ctx, git.root, "mv", "-T", tmplink, git.link); err != nil {
return "", fmt.Errorf("error replacing symlink: %v", err) return "", fmt.Errorf("error replacing symlink: %v", err)
} }
@ -856,13 +861,13 @@ func setRepoReady() {
repoReady = true repoReady = true
} }
// cleanupWorkTree() is used to remove a worktree and its folder // CleanupWorkTree() is used to remove a worktree and its folder
func cleanupWorkTree(ctx context.Context, gitRoot, worktree string) error { func (git *repoSync) CleanupWorkTree(ctx context.Context, gitRoot, worktree string) error {
// Clean up worktree(s) // Clean up worktree(s)
log.V(1).Info("removing worktree", "path", worktree) git.log.V(1).Info("removing worktree", "path", worktree)
if err := os.RemoveAll(worktree); err != nil { if err := os.RemoveAll(worktree); err != nil {
return fmt.Errorf("error removing directory: %v", err) return fmt.Errorf("error removing directory: %v", err)
} else if _, err := cmdRunner.Run(ctx, gitRoot, *flGitCmd, "worktree", "prune"); err != nil { } else if _, err := git.run.Run(ctx, gitRoot, git.cmd, "worktree", "prune"); err != nil {
return err return err
} }
return nil return nil
@ -870,7 +875,7 @@ func cleanupWorkTree(ctx context.Context, gitRoot, worktree string) error {
// AddWorktreeAndSwap creates a new worktree and calls UpdateSymlink to swap the symlink to point to the new worktree // AddWorktreeAndSwap creates a new worktree and calls UpdateSymlink to swap the symlink to point to the new worktree
func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error { func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error {
log.V(0).Info("syncing git", "rev", git.rev, "hash", hash) git.log.V(0).Info("syncing git", "rev", git.rev, "hash", hash)
args := []string{"fetch", "-f", "--tags"} args := []string{"fetch", "-f", "--tags"}
if git.depth != 0 { if git.depth != 0 {
@ -879,7 +884,7 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
args = append(args, "origin", git.branch) args = append(args, "origin", git.branch)
// Update from the remote. // Update from the remote.
if _, err := cmdRunner.Run(ctx, git.root, git.cmd, args...); err != nil { if _, err := git.run.Run(ctx, git.root, git.cmd, args...); err != nil {
return err return err
} }
@ -887,12 +892,12 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
// end up NOT fetching the hash we wanted. If we can't resolve that hash // end up NOT fetching the hash we wanted. If we can't resolve that hash
// to a commit we can just end early and leave it for the next sync period. // to a commit we can just end early and leave it for the next sync period.
if _, err := git.ResolveRef(ctx, hash); err != nil { if _, err := git.ResolveRef(ctx, hash); err != nil {
log.Error(err, "can't resolve commit, will retry", "rev", git.rev, "hash", hash) git.log.Error(err, "can't resolve commit, will retry", "rev", git.rev, "hash", hash)
return nil return nil
} }
// GC clone // GC clone
if _, err := cmdRunner.Run(ctx, git.root, git.cmd, "gc", "--prune=all"); err != nil { if _, err := git.run.Run(ctx, git.root, git.cmd, "gc", "--prune=all"); err != nil {
return err return err
} }
@ -903,12 +908,12 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
// error'd without cleaning up. The next time thru the sync loop fails to // error'd without cleaning up. The next time thru the sync loop fails to
// create the worktree and bails out. This manifests as: // create the worktree and bails out. This manifests as:
// "fatal: '/repo/root/rev-nnnn' already exists" // "fatal: '/repo/root/rev-nnnn' already exists"
if err := cleanupWorkTree(ctx, git.root, worktreePath); err != nil { if err := git.CleanupWorkTree(ctx, git.root, worktreePath); err != nil {
return err return err
} }
_, err := cmdRunner.Run(ctx, git.root, git.cmd, "worktree", "add", worktreePath, "origin/"+git.branch, "--no-checkout") _, err := git.run.Run(ctx, git.root, git.cmd, "worktree", "add", worktreePath, "origin/"+git.branch, "--no-checkout")
log.V(0).Info("adding worktree", "path", worktreePath, "branch", fmt.Sprintf("origin/%s", git.branch)) git.log.V(0).Info("adding worktree", "path", worktreePath, "branch", fmt.Sprintf("origin/%s", git.branch))
if err != nil { if err != nil {
return err return err
} }
@ -930,7 +935,7 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
if git.sparseFile != "" { if git.sparseFile != "" {
// This is required due to the undocumented behavior outlined here: // This is required due to the undocumented behavior outlined here:
// https://public-inbox.org/git/CAPig+cSP0UiEBXSCi7Ua099eOdpMk8R=JtAjPuUavRF4z0R0Vg@mail.gmail.com/t/ // https://public-inbox.org/git/CAPig+cSP0UiEBXSCi7Ua099eOdpMk8R=JtAjPuUavRF4z0R0Vg@mail.gmail.com/t/
log.V(0).Info("configuring worktree sparse checkout") git.log.V(0).Info("configuring worktree sparse checkout")
checkoutFile := git.sparseFile checkoutFile := git.sparseFile
gitInfoPath := filepath.Join(git.root, fmt.Sprintf(".git/worktrees/%s/info", hash)) gitInfoPath := filepath.Join(git.root, fmt.Sprintf(".git/worktrees/%s/info", hash))
@ -962,23 +967,23 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
} }
args := []string{"sparse-checkout", "init"} args := []string{"sparse-checkout", "init"}
_, err = cmdRunner.Run(ctx, worktreePath, git.cmd, args...) _, err = git.run.Run(ctx, worktreePath, git.cmd, args...)
if err != nil { if err != nil {
return err return err
} }
} }
// Reset the worktree's working copy to the specific rev. // Reset the worktree's working copy to the specific rev.
_, err = cmdRunner.Run(ctx, worktreePath, git.cmd, "reset", "--hard", hash, "--") _, err = git.run.Run(ctx, worktreePath, git.cmd, "reset", "--hard", hash, "--")
if err != nil { if err != nil {
return err return err
} }
log.V(0).Info("reset worktree to hash", "path", worktreePath, "hash", hash) git.log.V(0).Info("reset worktree to hash", "path", worktreePath, "hash", hash)
// Update submodules // Update submodules
// NOTE: this works for repo with or without submodules. // NOTE: this works for repo with or without submodules.
if git.submodules != submodulesOff { if git.submodules != submodulesOff {
log.V(0).Info("updating submodules") git.log.V(0).Info("updating submodules")
submodulesArgs := []string{"submodule", "update", "--init"} submodulesArgs := []string{"submodule", "update", "--init"}
if git.submodules == submodulesRecursive { if git.submodules == submodulesRecursive {
submodulesArgs = append(submodulesArgs, "--recursive") submodulesArgs = append(submodulesArgs, "--recursive")
@ -986,7 +991,7 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
if git.depth != 0 { if git.depth != 0 {
submodulesArgs = append(submodulesArgs, "--depth", strconv.Itoa(git.depth)) submodulesArgs = append(submodulesArgs, "--depth", strconv.Itoa(git.depth))
} }
_, err = cmdRunner.Run(ctx, worktreePath, git.cmd, submodulesArgs...) _, err = git.run.Run(ctx, worktreePath, git.cmd, submodulesArgs...)
if err != nil { if err != nil {
return err return err
} }
@ -995,19 +1000,19 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
// Change the file permissions, if requested. // Change the file permissions, if requested.
if git.chmod != 0 { if git.chmod != 0 {
mode := fmt.Sprintf("%#o", git.chmod) mode := fmt.Sprintf("%#o", git.chmod)
log.V(0).Info("changing file permissions", "mode", mode) git.log.V(0).Info("changing file permissions", "mode", mode)
_, err = cmdRunner.Run(ctx, "", "chmod", "-R", mode, worktreePath) _, err = git.run.Run(ctx, "", "chmod", "-R", mode, worktreePath)
if err != nil { if err != nil {
return err return err
} }
} }
// Reset the root's rev (so we can prune and so we can rely on it later). // Reset the root's rev (so we can prune and so we can rely on it later).
_, err = cmdRunner.Run(ctx, git.root, git.cmd, "reset", "--hard", hash, "--") _, err = git.run.Run(ctx, git.root, git.cmd, "reset", "--hard", hash, "--")
if err != nil { if err != nil {
return err return err
} }
log.V(0).Info("reset root to hash", "path", git.root, "hash", hash) git.log.V(0).Info("reset root to hash", "path", git.root, "hash", hash)
// Flip the symlink. // Flip the symlink.
oldWorktree, err := git.UpdateSymlink(ctx, worktreePath) oldWorktree, err := git.UpdateSymlink(ctx, worktreePath)
@ -1021,7 +1026,7 @@ func (git *repoSync) AddWorktreeAndSwap(ctx context.Context, hash string) error
// Clean up previous worktrees. // Clean up previous worktrees.
var cleanupErr error var cleanupErr error
if oldWorktree != "" { if oldWorktree != "" {
cleanupErr = cleanupWorkTree(ctx, git.root, oldWorktree) cleanupErr = git.CleanupWorkTree(ctx, git.root, oldWorktree)
} }
if cleanupErr != nil { if cleanupErr != nil {
@ -1037,18 +1042,18 @@ func (git *repoSync) CloneRepo(ctx context.Context) error {
args = append(args, "--depth", strconv.Itoa(git.depth)) args = append(args, "--depth", strconv.Itoa(git.depth))
} }
args = append(args, git.repo, git.root) args = append(args, git.repo, git.root)
log.V(0).Info("cloning repo", "origin", git.repo, "path", git.root) git.log.V(0).Info("cloning repo", "origin", git.repo, "path", git.root)
_, err := cmdRunner.Run(ctx, "", git.cmd, args...) _, err := git.run.Run(ctx, "", git.cmd, args...)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "already exists and is not an empty directory") { if strings.Contains(err.Error(), "already exists and is not an empty directory") {
// Maybe a previous run crashed? Git won't use this dir. // Maybe a previous run crashed? Git won't use this dir.
log.V(0).Info("git root exists and is not empty (previous crash?), cleaning up", "path", git.root) git.log.V(0).Info("git root exists and is not empty (previous crash?), cleaning up", "path", git.root)
err := os.RemoveAll(git.root) err := os.RemoveAll(git.root)
if err != nil { if err != nil {
return err return err
} }
_, err = cmdRunner.Run(ctx, "", git.cmd, args...) _, err = git.run.Run(ctx, "", git.cmd, args...)
if err != nil { if err != nil {
return err return err
} }
@ -1059,7 +1064,7 @@ func (git *repoSync) CloneRepo(ctx context.Context) error {
// If sparse checkout is requested, configure git for it. // If sparse checkout is requested, configure git for it.
if git.sparseFile != "" { if git.sparseFile != "" {
log.V(0).Info("configuring sparse checkout") git.log.V(0).Info("configuring sparse checkout")
checkoutFile := git.sparseFile checkoutFile := git.sparseFile
// TODO: capture this as a function (mostly duplicated above) // TODO: capture this as a function (mostly duplicated above)
@ -1093,7 +1098,7 @@ func (git *repoSync) CloneRepo(ctx context.Context) error {
} }
args := []string{"sparse-checkout", "init"} args := []string{"sparse-checkout", "init"}
_, err = cmdRunner.Run(ctx, git.root, git.cmd, args...) _, err = git.run.Run(ctx, git.root, git.cmd, args...)
if err != nil { if err != nil {
return err return err
} }
@ -1104,7 +1109,7 @@ func (git *repoSync) CloneRepo(ctx context.Context) error {
// LocalHashForRev returns the locally known hash for a given rev. // LocalHashForRev returns the locally known hash for a given rev.
func (git *repoSync) LocalHashForRev(ctx context.Context, rev string) (string, error) { func (git *repoSync) LocalHashForRev(ctx context.Context, rev string) (string, error) {
output, err := cmdRunner.Run(ctx, git.root, git.cmd, "rev-parse", rev) output, err := git.run.Run(ctx, git.root, git.cmd, "rev-parse", rev)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -1113,7 +1118,7 @@ func (git *repoSync) LocalHashForRev(ctx context.Context, rev string) (string, e
// RemoteHashForRef returns the upstream hash for a given ref. // RemoteHashForRef returns the upstream hash for a given ref.
func (git *repoSync) RemoteHashForRef(ctx context.Context, ref string) (string, error) { func (git *repoSync) RemoteHashForRef(ctx context.Context, ref string) (string, error) {
output, err := cmdRunner.Run(ctx, git.root, git.cmd, "ls-remote", "-q", "origin", ref) output, err := git.run.Run(ctx, git.root, git.cmd, "ls-remote", "-q", "origin", ref)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -1131,7 +1136,7 @@ func (git *repoSync) RevIsHash(ctx context.Context) (bool, error) {
func (git *repoSync) ResolveRef(ctx context.Context, ref string) (string, error) { func (git *repoSync) ResolveRef(ctx context.Context, ref string) (string, error) {
// If git doesn't identify rev as a commit, we're done. // If git doesn't identify rev as a commit, we're done.
output, err := cmdRunner.Run(ctx, git.root, git.cmd, "cat-file", "-t", ref) output, err := git.run.Run(ctx, git.root, git.cmd, "cat-file", "-t", ref)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -1188,10 +1193,10 @@ func (git *repoSync) SyncRepo(ctx context.Context) (bool, string, error) {
return false, "", err return false, "", err
} }
if local == remote { if local == remote {
log.V(1).Info("no update required", "rev", git.rev, "local", local, "remote", remote) git.log.V(1).Info("no update required", "rev", git.rev, "local", local, "remote", remote)
return false, "", nil return false, "", nil
} }
log.V(0).Info("update required", "rev", git.rev, "local", local, "remote", remote) git.log.V(0).Info("update required", "rev", git.rev, "local", local, "remote", remote)
hash = remote hash = remote
} }
@ -1226,15 +1231,15 @@ func (git *repoSync) GetRevs(ctx context.Context) (string, string, error) {
// SetupAuth configures the local git repo to use a username and password when // SetupAuth configures the local git repo to use a username and password when
// accessing the repo. // accessing the repo.
func (git *repoSync) SetupAuth(ctx context.Context, username, password string) error { func (git *repoSync) SetupAuth(ctx context.Context, username, password string) error {
log.V(1).Info("setting up git credential store") git.log.V(1).Info("setting up git credential store")
_, err := cmdRunner.Run(ctx, "", git.cmd, "config", "--global", "credential.helper", "store") _, err := git.run.Run(ctx, "", git.cmd, "config", "--global", "credential.helper", "store")
if err != nil { if err != nil {
return fmt.Errorf("can't configure git credential helper: %w", err) return fmt.Errorf("can't configure git credential helper: %w", err)
} }
creds := fmt.Sprintf("url=%v\nusername=%v\npassword=%v\n", git.repo, username, password) creds := fmt.Sprintf("url=%v\nusername=%v\npassword=%v\n", git.repo, username, password)
_, err = cmdRunner.RunWithStdin(ctx, "", creds, git.cmd, "credential", "approve") _, err = git.run.RunWithStdin(ctx, "", creds, git.cmd, "credential", "approve")
if err != nil { if err != nil {
return fmt.Errorf("can't configure git credentials: %w", err) return fmt.Errorf("can't configure git credentials: %w", err)
} }
@ -1242,8 +1247,8 @@ func (git *repoSync) SetupAuth(ctx context.Context, username, password string) e
return nil return nil
} }
func setupGitSSH(setupKnownHosts bool, pathToSSHSecret, pathToSSHKnownHosts string) error { func (git *repoSync) SetupGitSSH(setupKnownHosts bool, pathToSSHSecret, pathToSSHKnownHosts string) error {
log.V(1).Info("setting up git SSH credentials") git.log.V(1).Info("setting up git SSH credentials")
_, err := os.Stat(pathToSSHSecret) _, err := os.Stat(pathToSSHSecret)
if err != nil { if err != nil {
@ -1269,7 +1274,7 @@ func setupGitSSH(setupKnownHosts bool, pathToSSHSecret, pathToSSHKnownHosts stri
} }
func (git *repoSync) SetupCookieFile(ctx context.Context) error { func (git *repoSync) SetupCookieFile(ctx context.Context) error {
log.V(1).Info("configuring git cookie file") git.log.V(1).Info("configuring git cookie file")
var pathToCookieFile = "/etc/git-secret/cookie_file" var pathToCookieFile = "/etc/git-secret/cookie_file"
@ -1278,7 +1283,7 @@ func (git *repoSync) SetupCookieFile(ctx context.Context) error {
return fmt.Errorf("can't access git cookiefile: %w", err) return fmt.Errorf("can't access git cookiefile: %w", err)
} }
if _, err = cmdRunner.Run(ctx, "", git.cmd, "config", "--global", "http.cookiefile", pathToCookieFile); err != nil { if _, err = git.run.Run(ctx, "", git.cmd, "config", "--global", "http.cookiefile", pathToCookieFile); err != nil {
return fmt.Errorf("can't configure git cookiefile: %w", err) return fmt.Errorf("can't configure git cookiefile: %w", err)
} }
@ -1293,7 +1298,7 @@ func (git *repoSync) SetupCookieFile(ctx context.Context) error {
// username=xxx@example.com // username=xxx@example.com
// password=xxxyyyzzz // password=xxxyyyzzz
func (git *repoSync) CallAskPassURL(ctx context.Context) error { func (git *repoSync) CallAskPassURL(ctx context.Context) error {
log.V(1).Info("calling GIT_ASKPASS URL to get credentials") git.log.V(1).Info("calling GIT_ASKPASS URL to get credentials")
var netClient = &http.Client{ var netClient = &http.Client{
Timeout: time.Second * 1, Timeout: time.Second * 1,
@ -1347,14 +1352,14 @@ func (git *repoSync) CallAskPassURL(ctx context.Context) error {
} }
func (git *repoSync) setupExtraGitConfigs(ctx context.Context, configsFlag string) error { func (git *repoSync) setupExtraGitConfigs(ctx context.Context, configsFlag string) error {
log.V(1).Info("setting additional git configs") git.log.V(1).Info("setting additional git configs")
configs, err := parseGitConfigs(configsFlag) configs, err := parseGitConfigs(configsFlag)
if err != nil { if err != nil {
return fmt.Errorf("can't parse --git-config flag: %v", err) return fmt.Errorf("can't parse --git-config flag: %v", err)
} }
for _, kv := range configs { for _, kv := range configs {
if _, err := cmdRunner.Run(ctx, "", git.cmd, "config", "--global", kv.key, kv.val); err != nil { if _, err := git.run.Run(ctx, "", git.cmd, "config", "--global", kv.key, kv.val); err != nil {
return fmt.Errorf("error configuring additional git configs %q %q: %v", kv.key, kv.val, err) return fmt.Errorf("error configuring additional git configs %q %q: %v", kv.key, kv.val, err)
} }
} }

View File

@ -326,7 +326,7 @@ func TestRemoveDirContents(t *testing.T) {
} }
// Test removal. // Test removal.
if err := removeDirContents(root); err != nil { if err := removeDirContents(root, nil); err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -352,12 +352,12 @@ func TestRemoveDirContents(t *testing.T) {
} }
// Test removal. // Test removal.
if err := removeDirContents(root); err != nil { if err := removeDirContents(root, nil); err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
// Test error path. // Test error path.
if err := removeDirContents(filepath.Join(root, "does-not-exist")); err == nil { if err := removeDirContents(filepath.Join(root, "does-not-exist"), nil); err == nil {
t.Errorf("unexpected success for non-existent dir") t.Errorf("unexpected success for non-existent dir")
} }
} }