func addWorktreeAndSwap()

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
}