"""Prepare the working directory for a thread.
This clones or fetches the repo into the thread's work directory.
+ Optionally, it can create a linked working tree of the repo in the
+ thread's work directory instead.
Args:
thread_num: Thread number (0, 1, ...)
- setup_git: True to set up a git repo clone
+ setup_git:
+ 'clone' to set up a git clone
+ 'worktree' to set up a git worktree
"""
thread_dir = self.GetThreadDir(thread_num)
builderthread.Mkdir(thread_dir)
git_dir = os.path.join(thread_dir, '.git')
- # Clone the repo if it doesn't already exist
- # TODO(sjg@chromium): Perhaps some git hackery to symlink instead, so
- # we have a private index but uses the origin repo's contents?
+ # Create a worktree or a git repo clone for this thread if it
+ # doesn't already exist
if setup_git and self.git_dir:
src_dir = os.path.abspath(self.git_dir)
- if os.path.exists(git_dir):
+ if os.path.isdir(git_dir):
+ # This is a clone of the src_dir repo, we can keep using
+ # it but need to fetch from src_dir.
Print('\rFetching repo for thread %d' % thread_num,
newline=False)
gitutil.Fetch(git_dir, thread_dir)
terminal.PrintClear()
- else:
+ elif os.path.isfile(git_dir):
+ # This is a worktree of the src_dir repo, we don't need to
+ # create it again or update it in any way.
+ pass
+ elif os.path.exists(git_dir):
+ # Don't know what could trigger this, but we probably
+ # can't create a git worktree/clone here.
+ raise ValueError('Git dir %s exists, but is not a file '
+ 'or a directory.' % git_dir)
+ elif setup_git == 'worktree':
+ Print('\rChecking out worktree for thread %d' % thread_num,
+ newline=False)
+ gitutil.AddWorktree(src_dir, thread_dir)
+ terminal.PrintClear()
+ elif setup_git == 'clone' or setup_git == True:
Print('\rCloning repo for thread %d' % thread_num,
newline=False)
gitutil.Clone(src_dir, thread_dir)
terminal.PrintClear()
+ else:
+ raise ValueError("Can't setup git repo with %s." % setup_git)
def _PrepareWorkingSpace(self, max_threads, setup_git):
"""Prepare the working directory for use.
- Set up the git repo for each thread.
+ Set up the git repo for each thread. Creates a linked working tree
+ if git-worktree is available, or clones the repo if it isn't.
Args:
max_threads: Maximum number of threads we expect to need.
- setup_git: True to set up a git repo clone
+ setup_git: True to set up a git worktree or a git clone
"""
builderthread.Mkdir(self._working_dir)
+ if setup_git and self.git_dir:
+ src_dir = os.path.abspath(self.git_dir)
+ if gitutil.CheckWorktreeIsAvailable(src_dir):
+ setup_git = 'worktree'
+ # If we previously added a worktree but the directory for it
+ # got deleted, we need to prune its files from the repo so
+ # that we can check out another in its place.
+ gitutil.PruneWorktrees(src_dir)
+ else:
+ setup_git = 'clone'
for thread in range(max_threads):
self._PrepareThread(thread, setup_git)
if result.return_code != 0:
raise OSError('git fetch: %s' % result.stderr)
+def CheckWorktreeIsAvailable(git_dir):
+ """Check if git-worktree functionality is available
+
+ Args:
+ git_dir: The repository to test in
+
+ Returns:
+ True if git-worktree commands will work, False otherwise.
+ """
+ pipe = ['git', '--git-dir', git_dir, 'worktree', 'list']
+ result = command.RunPipe([pipe], capture=True, capture_stderr=True,
+ raise_on_error=False)
+ return result.return_code == 0
+
+def AddWorktree(git_dir, output_dir, commit_hash=None):
+ """Create and checkout a new git worktree for this build
+
+ Args:
+ git_dir: The repository to checkout the worktree from
+ output_dir: Path for the new worktree
+ commit_hash: Commit hash to checkout
+ """
+ # We need to pass --detach to avoid creating a new branch
+ pipe = ['git', '--git-dir', git_dir, 'worktree', 'add', '.', '--detach']
+ if commit_hash:
+ pipe.append(commit_hash)
+ result = command.RunPipe([pipe], capture=True, cwd=output_dir,
+ capture_stderr=True)
+ if result.return_code != 0:
+ raise OSError('git worktree add: %s' % result.stderr)
+
+def PruneWorktrees(git_dir):
+ """Remove administrative files for deleted worktrees
+
+ Args:
+ git_dir: The repository whose deleted worktrees should be pruned
+ """
+ pipe = ['git', '--git-dir', git_dir, 'worktree', 'prune']
+ result = command.RunPipe([pipe], capture=True, capture_stderr=True)
+ if result.return_code != 0:
+ raise OSError('git worktree prune: %s' % result.stderr)
+
def CreatePatches(branch, start, count, ignore_binary, series):
"""Create a series of patches from the top of the current branch.