[Concept,1/6] u_boot_pylib: Add worktree helpers for branch-aware worktrees

Message ID 20260506153006.529909-2-sjg@u-boot.org
State New
Headers
Series patman: Concurrent DB access and per-series review worktrees |

Commit Message

Simon Glass May 6, 2026, 3:29 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

The existing add_worktree() always creates a detached worktree, which
suits buildman's one-off build trees but not callers that want a
worktree on a named branch they can iterate on (apply, reset, retry).

Add ensure_worktree() and remove_worktree(): the former creates the
worktree at a given path on a named branch reset to a given upstream
ref, or resets and cleans an existing one to that ref so a re-run is
recoverable; the latter removes the worktree by path. Callers own the
path policy. Patman uses these for per-series review worktrees in a
later commit.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 tools/u_boot_pylib/gitutil.py | 53 +++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)
  

Patch

diff --git a/tools/u_boot_pylib/gitutil.py b/tools/u_boot_pylib/gitutil.py
index a7db37fd7df..ec326332d9b 100644
--- a/tools/u_boot_pylib/gitutil.py
+++ b/tools/u_boot_pylib/gitutil.py
@@ -445,6 +445,59 @@  def prune_worktrees(git_dir):
         raise OSError(f'git worktree prune: {result.stderr}')
 
 
+def _is_worktree_registered(repo, path):
+    """Return True if git already has a worktree registered at path"""
+    out = command.output('git', '-C', repo, 'worktree', 'list',
+                         '--porcelain')
+    target = os.path.realpath(path)
+    for line in out.splitlines():
+        if line.startswith('worktree '):
+            registered = os.path.realpath(line[len('worktree '):])
+            if registered == target:
+                return True
+    return False
+
+
+def ensure_worktree(repo, path, branch_name, upstream_branch):
+    """Create or reuse a worktree at path on branch_name
+
+    If a worktree at path already exists, reset it to upstream_branch
+    so the next operation starts clean. Otherwise create one via
+    'git worktree add -B <branch> <path> <upstream>'.
+
+    Args:
+        repo (str): Top-level dir of the repo to add the worktree under
+        path (str): Path where the worktree should live
+        branch_name (str): Branch to create/reuse in the worktree
+        upstream_branch (str): Ref to base the branch on (e.g. 'us/next')
+
+    Return:
+        str: path, ready to be used as cwd
+    """
+    if _is_worktree_registered(repo, path):
+        command.output('git', '-C', path, 'reset', '--hard', upstream_branch)
+        command.output('git', '-C', path, 'clean', '-fdx')
+        return path
+
+    os.makedirs(os.path.dirname(path), exist_ok=True)
+    command.output('git', '-C', repo, 'worktree', 'add',
+                   '-B', branch_name, path, upstream_branch)
+    return path
+
+
+def remove_worktree(repo, path):
+    """Remove the worktree at path, if any
+
+    Args:
+        repo (str): Top-level dir of the repo to add the worktree under
+        path (str): Path of the worktree to remove
+    """
+    if not _is_worktree_registered(repo, path):
+        return
+    command.output('git', '-C', repo, 'worktree', 'remove', '--force',
+                   path)
+
+
 def create_patches(branch, start, count, ignore_binary, series, signoff=True,
                    git_dir=None, cwd=None):
     """Create a series of patches from the top of the current branch.