in cmd/git-sync/main.go [713:863]
func addWorktreeAndSwap(ctx context.Context, gitRoot, dest, branch, rev string, depth int, hash string, submoduleMode string) error {
log.V(0).Info("syncing git", "rev", rev, "hash", hash)
args := []string{"fetch", "-f", "--tags"}
if depth != 0 {
args = append(args, "--depth", strconv.Itoa(depth))
}
args = append(args, "origin", branch)
// Update from the remote.
if _, err := cmdRunner.Run(ctx, gitRoot, *flGitCmd, args...); err != nil {
return err
}
// With shallow fetches, it's possible to race with the upstream repo and
// 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.
if _, err := revIsHash(ctx, hash, gitRoot); err != nil {
log.Error(err, "can't resolve commit, will retry", "rev", rev, "hash", hash)
return nil
}
// Make a worktree for this exact git hash.
worktreePath := filepath.Join(gitRoot, hash)
// Avoid wedge cases where the worktree was created but this function error'd without cleaning the worktree.
// Next timearound, the sync loop fails to create the worktree and bails out.
// Error observed:
// " Run(git worktree add /repo/root/rev-nnnn origin/develop):
// exit status 128: { stdout: \"Preparing worktree (detached HEAD nnnn)\\n\", stderr: \"fatal: '/repo/root/rev-nnnn' already exists\\n\" }"
//. "
if err := cleanupWorkTree(ctx, gitRoot, worktreePath); err != nil {
return err
}
_, err := cmdRunner.Run(ctx, gitRoot, *flGitCmd, "worktree", "add", "--detach", worktreePath, hash, "--no-checkout")
log.V(0).Info("adding worktree", "path", worktreePath, "branch", fmt.Sprintf("origin/%s", branch))
if err != nil {
return err
}
// GC clone
if _, err := cmdRunner.Run(ctx, gitRoot, *flGitCmd, "gc", "--prune=all"); err != nil {
return err
}
// The .git file in the worktree directory holds a reference to
// /git/.git/worktrees/<worktree-dir-name>. Replace it with a reference
// using relative paths, so that other containers can use a different volume
// mount name.
worktreePathRelative, err := filepath.Rel(gitRoot, worktreePath)
if err != nil {
return err
}
gitDirRef := []byte(filepath.Join("gitdir: ../.git/worktrees", worktreePathRelative) + "\n")
if err = ioutil.WriteFile(filepath.Join(worktreePath, ".git"), gitDirRef, 0644); err != nil {
return err
}
if *flSparseCheckoutFile != "" {
// This is required due to the undocumented behavior outlined here: https://public-inbox.org/git/CAPig+cSP0UiEBXSCi7Ua099eOdpMk8R=JtAjPuUavRF4z0R0Vg@mail.gmail.com/t/
log.V(0).Info("configuring worktree sparse checkout")
checkoutFile := *flSparseCheckoutFile
gitInfoPath := filepath.Join(gitRoot, fmt.Sprintf(".git/worktrees/%s/info", hash))
gitSparseConfigPath := filepath.Join(gitInfoPath, "sparse-checkout")
source, err := os.Open(checkoutFile)
if err != nil {
return err
}
defer source.Close()
if _, err := os.Stat(gitInfoPath); os.IsNotExist(err) {
fileMode := os.FileMode(int(0755))
err := os.Mkdir(gitInfoPath, fileMode)
if err != nil {
return err
}
}
destination, err := os.Create(gitSparseConfigPath)
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
if err != nil {
return err
}
args := []string{"sparse-checkout", "init"}
_, err = cmdRunner.Run(ctx, worktreePath, *flGitCmd, args...)
if err != nil {
return err
}
}
_, err = cmdRunner.Run(ctx, worktreePath, *flGitCmd, "reset", "--hard", hash)
if err != nil {
return err
}
log.V(0).Info("reset worktree to hash", "path", worktreePath, "hash", hash)
// Update submodules
// NOTE: this works for repo with or without submodules.
if submoduleMode != submodulesOff {
log.V(0).Info("updating submodules")
submodulesArgs := []string{"submodule", "update", "--init"}
if submoduleMode == submodulesRecursive {
submodulesArgs = append(submodulesArgs, "--recursive")
}
if depth != 0 {
submodulesArgs = append(submodulesArgs, "--depth", strconv.Itoa(depth))
}
_, err = cmdRunner.Run(ctx, worktreePath, *flGitCmd, submodulesArgs...)
if err != nil {
return err
}
}
// Change the file permissions, if requested.
if *flChmod != 0 {
mode := fmt.Sprintf("%#o", *flChmod)
log.V(0).Info("changing file permissions", "mode", mode)
_, err = cmdRunner.Run(ctx, "", "chmod", "-R", mode, worktreePath)
if err != nil {
return err
}
}
// Flip the symlink.
oldWorktree, err := updateSymlink(ctx, gitRoot, dest, worktreePath)
if err != nil {
return err
}
setRepoReady()
// From here on we have to save errors until the end.
// Clean up previous worktree(s).
var cleanupErr error
if oldWorktree != "" {
cleanupErr = cleanupWorkTree(ctx, gitRoot, oldWorktree)
}
if cleanupErr != nil {
return cleanupErr
}
return nil
}