From patchwork Wed May 6 15:29:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2273 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081431; bh=GuT7wuHLZcK5UO8HFFn55t+fZ1sjz3DwnkN5nAA83ZI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=BQa4OBYJTs3BatnbxH8pKxdmfBo5gqjvuZ8wYLkV2mw6G6uYlqNMhJmlzDfCR3Mjg FiG40BKgjpd6SHBAWDTki9FPaok4Z2O/dfRTZ8ayJ21HefXN6xrUFoP8jLMy2/Oh4r qevdepRVQ51Daq4LoEmgDhGzRnYNmhRPdmTt1NFk= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 7711D6A949 for ; Wed, 6 May 2026 09:30:31 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id JolTw_OdY4Lf for ; Wed, 6 May 2026 09:30:31 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081431; bh=GuT7wuHLZcK5UO8HFFn55t+fZ1sjz3DwnkN5nAA83ZI=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=BQa4OBYJTs3BatnbxH8pKxdmfBo5gqjvuZ8wYLkV2mw6G6uYlqNMhJmlzDfCR3Mjg FiG40BKgjpd6SHBAWDTki9FPaok4Z2O/dfRTZ8ayJ21HefXN6xrUFoP8jLMy2/Oh4r qevdepRVQ51Daq4LoEmgDhGzRnYNmhRPdmTt1NFk= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 65ADF6A937 for ; Wed, 6 May 2026 09:30:31 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081429; bh=2tCk8Rmfb+jQjn7CPuPHxmaEhYJkZTvg4mL2N4iGP5w=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CSfvm+iSNFChKWPIrKVgpwupqXiSjVuy2vSHg1wIN8+ekY9aBvC+kNPEo0P/+NoCq Zfa+OrPLtMmRuKxOoALK2pFEyJ3+quQE35d1qT1UxgoZdMuQWUlAZLuOoDel1ovzkd qRMfPSALlnGLT2YpSUL1KaV1op26uPxfTfmstwNA= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AB9EA6A92E; Wed, 6 May 2026 09:30:29 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id vQxDQ_25I89l; Wed, 6 May 2026 09:30:29 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081425; bh=33Mlk4Uw3wqwfFtDfhhwhL3zap5/PHZlpEGHJcbF9Ao=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=N92PbbNhNsX/zRy66BkBSUUKIA8qEXLNPKvSHI1cwuzE4vwAWcFjj/BT5TnB8pi7J MNA7h3s5hyzqtbTvRpos+mG5VDalB6pQT9MB90fsea7YNtY06eFmz0Z/giPs4nKJWa djIBqPcxl2lM5nP4mEetjhYmYqDeS592CDqwbjvQ= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 4A51A6A87B; Wed, 6 May 2026 09:30:25 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Wed, 6 May 2026 09:29:50 -0600 Message-ID: <20260506153006.529909-2-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260506153006.529909-1-sjg@u-boot.org> References: <20260506153006.529909-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: UCRUQ4O53KVYH3C2AHAKLCK56363WQDT X-Message-ID-Hash: UCRUQ4O53KVYH3C2AHAKLCK56363WQDT X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 1/6] u_boot_pylib: Add worktree helpers for branch-aware worktrees List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass 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 --- tools/u_boot_pylib/gitutil.py | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) 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 '. + + 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. From patchwork Wed May 6 15:29:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2274 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081436; bh=Q70UswbR9i3ldHYmIyQ98ShoL34zRBOvVhA8YFoDf64=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=E6N+wYBXP5ykCNGZ5d8QNM696ySvPsxZo9CVuI5ILBL5lRe5loRf4vpfv1hPKohYR ONn68SLwc6rx5SjlhhKpFuPmMLIy36dELplo/Iqd1BPGCIPuvl2QCdaXfrvzGXNl1k yTEdhcdarakTHRCHxNV57Vrl/uYgb/yl5rQZi/l4= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id F25C26A949 for ; Wed, 6 May 2026 09:30:36 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 2DRQf4aO7F8V for ; Wed, 6 May 2026 09:30:36 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081436; bh=Q70UswbR9i3ldHYmIyQ98ShoL34zRBOvVhA8YFoDf64=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=E6N+wYBXP5ykCNGZ5d8QNM696ySvPsxZo9CVuI5ILBL5lRe5loRf4vpfv1hPKohYR ONn68SLwc6rx5SjlhhKpFuPmMLIy36dELplo/Iqd1BPGCIPuvl2QCdaXfrvzGXNl1k yTEdhcdarakTHRCHxNV57Vrl/uYgb/yl5rQZi/l4= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DFAEC6A92E for ; Wed, 6 May 2026 09:30:36 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081435; bh=99/BgGrFE7PGkdUHDa9ZFo0cxDXpq+ccEnRXmZOTe8I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NidyBIu1RrkAEMT3ZPEhJZ0/7xA8LP27ijrbPdt7mSBXq3HdDwowshuHyVKcwOGHv S4TuymQGuDfP2VWn7sWNVH8S/9NOXril1Aos8qx1V0QRs1XdnwPixOqLEpnE0VxuZ4 ORWtESFvdOk77rxTU+pmmFdALA8WiW0wFgGTiAlo= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 332E36A92E; Wed, 6 May 2026 09:30:35 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id gXdYNsgPEatO; Wed, 6 May 2026 09:30:35 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081431; bh=eCHDcPJbz7AvQlVGX8lOdZhnpC8t67xg3OXCMzatkus=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KDs8CJZUONf05A9kFr4KtMKocmKwAWc0Wtr5RviPFojL4HcIVEUbxzyngLEfAsv0j CsR/4w1fl03PGtqd1YdyMraj0xxTigg1VH1eKJ8n/hbmQyQgRSMuvyErkidAvJyJtA WpDO0ii4ZQSXozLj+WztpT0kc/IuPCrjAG/GsuXw= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id C3D8E6A87B; Wed, 6 May 2026 09:30:30 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Wed, 6 May 2026 09:29:51 -0600 Message-ID: <20260506153006.529909-3-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260506153006.529909-1-sjg@u-boot.org> References: <20260506153006.529909-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 2PB5QSM3VAP6A2MJI2USPRI6WS6EJI3M X-Message-ID-Hash: 2PB5QSM3VAP6A2MJI2USPRI6WS6EJI3M X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 2/6] patman: Enable WAL mode for concurrent database access List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The SQLite database is opened with default settings, so a single writer holds an exclusive lock that blocks readers, and the default busy timeout is short enough that two patman invocations running at once trip 'database is locked'. Switch the connection to WAL journalling so readers and a writer can coexist across processes, set synchronous=NORMAL (the recommended pairing for WAL), and raise the busy timeout to 30 seconds to ride out brief contention. Document the new behaviour in patman.rst, including the .patman.db-wal/-shm sidecar files WAL creates so users know to copy them together when backing the database up by hand. Signed-off-by: Simon Glass --- tools/patman/database.py | 7 ++++++- tools/patman/patman.rst | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/patman/database.py b/tools/patman/database.py index 2d6e27ef6e7..2b05a6c4cfe 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -122,8 +122,13 @@ class Database: # pylint:disable=R0904 raise ValueError('Already open') if not os.path.exists(self.db_path): tout.warning(f'Creating new database {self.db_path}') - self.con = sqlite3.connect(self.db_path) + self.con = sqlite3.connect(self.db_path, timeout=30) self.cur = self.con.cursor() + # WAL lets readers and a writer coexist across processes; the busy + # timeout above rides out brief contention before raising 'database + # is locked' + self.cur.execute('PRAGMA journal_mode=WAL') + self.cur.execute('PRAGMA synchronous=NORMAL') self.is_open = True def close(self): diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst index 39fc04deec8..ef8e2e2025c 100644 --- a/tools/patman/patman.rst +++ b/tools/patman/patman.rst @@ -1465,6 +1465,16 @@ Patman stores series tracking, review and workflow state in a SQLite database (``.patman.db``) in the top-level git directory. The schema is versioned and auto-migrated on startup (currently at v10). +The connection is opened in WAL mode (``journal_mode=WAL``, +``synchronous=NORMAL``) with a 30-second busy timeout, which lets multiple +patman invocations share the database safely: concurrent readers do not block +each other, and a writer only briefly excludes other writers. This means it is +fine to run, for example, ``patman status`` in one terminal while a long +``patman review`` is running in another. WAL creates ``.patman.db-wal`` and +``.patman.db-shm`` sidecar files alongside the database; they are managed by +SQLite and removed on a clean close, so copy or sync all three together if you +back the database up by hand. + The database allows patman to track a patch series across multiple versions, recording which patches belong to each version and how they map to patchwork entries. This means patman can detect when a new From patchwork Wed May 6 15:29:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2275 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081439; bh=GgiuS0OZV92ahw2ch8YDmRtEl59PXGGP3aRsaxw+HJ8=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=QFQw7O5eOn9Sy0NE7K5tK1F5rXVP858c+0RTwPFD+T3rc+mSEmBpHjSchF5+2xfHe D73U53me5xucfZ7jY42/IPh2vUHKL33riPHDPl/HPWf8V0Nh7B0bDaM7EGfct2XuPC lQWqJXh7O+N+4YYhcXBnBrdY6ukcZpM7ap8jjsUs= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6CCEB6A950 for ; Wed, 6 May 2026 09:30:39 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 83HH6SmmNQ4x for ; Wed, 6 May 2026 09:30:39 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081438; bh=GgiuS0OZV92ahw2ch8YDmRtEl59PXGGP3aRsaxw+HJ8=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=ZzC4Cl22ni6feUPY3OCv6Xrkf6lLf2POY8hoHd6EUCCm0hhFyr4wn6leGQyN/+aQZ SS0SFyLxLwvXai/aUjqE0y6jykUMrACBYj7SpX8jTt+s7PQP/v6Y9YNlWsYYWls4UX 1Y2xFamNmbcoFEf66Pe5GBi0rhBp+UIHK6JGo6OM= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 639386A949 for ; Wed, 6 May 2026 09:30:38 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081436; bh=DjxCuNpMSHKN4ysSaf31ytzbk1Jusn1PI9QCywyGyyw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cN1hLFZnML/VZbvr8RQ9iqxTu58zt1NZUjbT/fNlVB7wH7WAzwtua0Hd0ll/iPy/f x0OgjuBEgQBBHFjf4eD4G0EwIvUWJ/4zHmD8ZzcxXq+w9DtQxntmB0rCgIYR80T9Sh uZuJ4zxwKdEn/eQ84LwTgV7/iOv1rumbEpVofnUY= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id CC24B6A937; Wed, 6 May 2026 09:30:36 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 8KSR2YAp3v4R; Wed, 6 May 2026 09:30:36 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081436; bh=gwIHDwE/jF9QIrDB8qspF6QQW1HOVH7ty+G4kbg+Ngo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=n9748C9m+ubMkuFknZxIKhtqImee8YFBTQY89HMWrnvn3/dYyBf3mKrbkM/Sa0nJD 3/Al0ImGy2l5Wwqcaj7s3BpQDbMgG7ozOgS5B5KAYxx76wEa8pCu3FxTDUboDwbv/L B1/8wCk3lJPBOtTu4ikRL7F1AuTr2+Buibc6+n+k= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 35C0E6A87B; Wed, 6 May 2026 09:30:36 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Wed, 6 May 2026 09:29:52 -0600 Message-ID: <20260506153006.529909-4-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260506153006.529909-1-sjg@u-boot.org> References: <20260506153006.529909-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: RNRPPHK65JLPYKGY3HUPWFQ3AYGW7LWW X-Message-ID-Hash: RNRPPHK65JLPYKGY3HUPWFQ3AYGW7LWW X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 3/6] patman: Allow overriding the database directory with PATMAN_DB_DIR List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The database location is hardcoded to .patman.db in the top of the current git repo, so running 'patman review' (or any other patman command) in a second worktree or unrelated repo creates and uses a fresh database there. Combined with the new WAL support, it would be nice to keep a single 'main' database and reach it from anywhere. Provide a PATMAN_DB_DIR environment variable that, when set, names the directory in which patman looks for .patman.db, mirroring the default layout. The override applies only to the database; git operations still run against the current repo, so for example:: export PATMAN_DB_DIR=~/u-boot cd ~/some-other-tree patman review -s my-series uses the main database while applying patches in the other tree. The value is passed through os.path.expanduser() so '~' works. Signed-off-by: Simon Glass --- tools/patman/cser_helper.py | 9 ++++++++- tools/patman/patman.rst | 11 +++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tools/patman/cser_helper.py b/tools/patman/cser_helper.py index 0ce59e8ecc0..0274e28f42b 100644 --- a/tools/patman/cser_helper.py +++ b/tools/patman/cser_helper.py @@ -106,7 +106,14 @@ class CseriesHelper: if not self.topdir: raise ValueError('No git repo detected in current directory') self.gitdir = os.path.join(self.topdir, '.git') - fname = f'{self.topdir}/.patman.db' + + # PATMAN_DB_DIR lets the user keep a single 'main' database while + # running patman in other repos. Only the DB path is overridden; + # git operations still run against the current repo. Use 'or' (not + # os.environ.get(key, default)) so an empty PATMAN_DB_DIR='' falls + # back to topdir rather than producing '/.patman.db' + db_dir = os.environ.get('PATMAN_DB_DIR') or self.topdir + fname = os.path.join(os.path.expanduser(db_dir), '.patman.db') # For the first instance, start it up with the expected schema self.db, is_new = Database.get_instance(fname) diff --git a/tools/patman/patman.rst b/tools/patman/patman.rst index ef8e2e2025c..98a51bcfc6f 100644 --- a/tools/patman/patman.rst +++ b/tools/patman/patman.rst @@ -1465,6 +1465,17 @@ Patman stores series tracking, review and workflow state in a SQLite database (``.patman.db``) in the top-level git directory. The schema is versioned and auto-migrated on startup (currently at v10). +Set the ``PATMAN_DB_DIR`` environment variable to use a database elsewhere +(``~`` is expanded). The variable names a *directory* in which patman looks +for ``.patman.db``, mirroring the default layout. This is useful if you keep +a single 'main' patman database in one repo but want to run, for example, +``patman review`` in another worktree or repo: the DB path is overridden but +git operations still run against the current repo. For instance:: + + export PATMAN_DB_DIR=~/u-boot + cd ~/some-other-tree + patman review -s my-series + The connection is opened in WAL mode (``journal_mode=WAL``, ``synchronous=NORMAL``) with a 30-second busy timeout, which lets multiple patman invocations share the database safely: concurrent readers do not block From patchwork Wed May 6 15:29:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2276 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081444; bh=RGh4Jv/frzWepVxGRLbAdvW+qxejL064rKC1odZq48Y=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=QbJeH6eVwP1vHsIjvqU95KJhjX9SOJbSb1ueACc0Zije/8VF2uKB8WdnMZ1bNKu/4 qcfwL9bMmIllRy7H86GWdq6WSqrzob/E+0vpfi7bYGujPCmY5S5kWHUVuSUq4FzZXq AU7VJqofRlpXkitzlhFIvTkVRn+8YiHrVFuSgEJw= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5020F6A949 for ; Wed, 6 May 2026 09:30:44 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 1sMiaa37L14Y for ; Wed, 6 May 2026 09:30:44 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081444; bh=RGh4Jv/frzWepVxGRLbAdvW+qxejL064rKC1odZq48Y=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=QbJeH6eVwP1vHsIjvqU95KJhjX9SOJbSb1ueACc0Zije/8VF2uKB8WdnMZ1bNKu/4 qcfwL9bMmIllRy7H86GWdq6WSqrzob/E+0vpfi7bYGujPCmY5S5kWHUVuSUq4FzZXq AU7VJqofRlpXkitzlhFIvTkVRn+8YiHrVFuSgEJw= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3D0FB6A937 for ; Wed, 6 May 2026 09:30:44 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081442; bh=S08rYYeolYID/uOAPh1xQ6wyx8Ie44btB+oc1+fM+38=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ctY17RkiVeZxcprVNFclxFaCP6KWY4ZpeO/ZRzsQu5ViR2hc4weymbOukKbdJm8vw NftU25JuxP9TXERIJUjhSLPolM7oHQeWG0plQXeZ9u3+iFkibZtqmGFRquL/W78BqS MeFxcpmAruLkRyhUfbDNrOQsS6lqx2uRB5cFzAa8= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 465E56A87B; Wed, 6 May 2026 09:30:42 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id 2x0jzoX0vM0y; Wed, 6 May 2026 09:30:42 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081438; bh=Cknkko8D6yJ0RgUzWEwl/teaAFQp3bNUO48uCitajio=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kWrXpq5Hqhdd5eyn31MB8WmYpV1G8x7HYVppwX3zGR74fqYtiV/ma44oMLtZQcZva vvFGpfnHn2gAd236EesoJ6NCwa1vh5wfEF46YDp5NbK2ZCJM8Dcz9fEtbCWa3MnDs0 ZCcCTsE93teK1vZPIF0LyUMXg5wOFdzd8FjpQLs0= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id D84FC6A937; Wed, 6 May 2026 09:30:37 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Wed, 6 May 2026 09:29:53 -0600 Message-ID: <20260506153006.529909-5-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260506153006.529909-1-sjg@u-boot.org> References: <20260506153006.529909-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: B3A4QATJMG5P2M3CYX7YQBT2W6IF23X3 X-Message-ID-Hash: B3A4QATJMG5P2M3CYX7YQBT2W6IF23X3 X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 4/6] patman: Serialise schema migrations across processes List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Now that PATMAN_DB_DIR lets multiple repos share one database, two patman invocations can race on first launch after a schema bump: both read the old schema_version, both run the same _migrate_to_vN() step, and the second crashes on a duplicate-column ALTER TABLE. Even within a single repo the same race exists; PATMAN_DB_DIR just makes it more visible. Wrap the migration loop in an advisory file lock on .patman.db.lock, acquired with fcntl.flock(LOCK_EX). The first process performs the migration; peers block until it finishes, re-read the (now current) version inside the loop, and exit immediately. The lock fd is independent of the SQLite connection, so the existing close-backup-reopen flow inside the loop still works. Signed-off-by: Simon Glass --- tools/patman/database.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/patman/database.py b/tools/patman/database.py index 2b05a6c4cfe..49b932320b2 100644 --- a/tools/patman/database.py +++ b/tools/patman/database.py @@ -12,6 +12,7 @@ and write some code in migrate_to() to call it. """ from collections import namedtuple, OrderedDict +import fcntl import os import sqlite3 @@ -275,7 +276,20 @@ class Database: # pylint:disable=R0904 Args: dest_version (int): Version to migrate to """ + # Serialise migrations across processes via an advisory lock on a + # sentinel file beside the DB. Without this, two patman processes + # starting against the same out-of-date DB can both decide they + # need to run the next migration step, and the second one crashes + # on a duplicate-column ALTER TABLE + with open(f'{self.db_path}.lock', 'w', encoding='utf-8') as lock_fd: + fcntl.flock(lock_fd, fcntl.LOCK_EX) + self._migrate_locked(dest_version) + + def _migrate_locked(self, dest_version): + """Run the migration loop; caller holds the migration lock""" while True: + # Re-read each iteration: a peer process may have advanced the + # version while we were waiting for the lock version = self.get_schema_version() if version == dest_version: break From patchwork Wed May 6 15:29:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2277 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081446; bh=LokLv1WQFFFBObzJxzxvPSVEmqqzWQF1g+wS0MhLAvc=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=sFAEYdcjRXzXha8FXVrYeIb+FFH4GPrjEbfnyclRNAGHqao0XwxxfacIBirNz1CbO RTvboEkwz5HYeWLqdYM5hhEGnA+X1pKTXTtermZHFttiFa/x9DXeyljRiU/H78ce4U SnYE5smlQc2VIhf8b9HXCSYuCXL1QJkvI6gz+WHw= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A8F6B6A87B for ; Wed, 6 May 2026 09:30:46 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id LyX25Bt2KTtn for ; Wed, 6 May 2026 09:30:46 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081445; bh=LokLv1WQFFFBObzJxzxvPSVEmqqzWQF1g+wS0MhLAvc=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=jW29fI7qdVoK9n+pQfab6lfHQKJow9Tz53uzOSXdtScaDJfqOFZh/OMoB67kWv+KO wNz3fDyRyc1H/uc4MVyo5CD6gZIvGopTecgAJPtIgZx8kGMaXlcu5otQIfuxh/+1WX MyW1oqMHVMh6k29uw1Lp93qeX4sC9L2B3Gu0wmJ8= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A821B6A949 for ; Wed, 6 May 2026 09:30:45 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081444; bh=ztGla4SxUm/eW3u6P03kIg8XsDNQWeYxv2pGKp0s75s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qpj7TZDTi1MMnpVLBElzGCdrAT7+bi0cDxWOukq5SoF6Gs0A20JngsGCRJKHGUKJf NvhZ4DwmyYPLaWWRJxOx+UfY5CtpcQSKuTtRCx8H0YURlXtVNtks4iGdoVrlnEaLbf Ym2sPCYjtziDezhDPtREHRl8RHzv0NkoOGPpxqHU= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 215CF6A92E; Wed, 6 May 2026 09:30:44 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id MPUWRy5AnDJ0; Wed, 6 May 2026 09:30:44 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081443; bh=1cc86kGTZRmO6FQ6cLJrffifQ38V9V9u469YKjFDmRc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=s0XaStlxEl/IrPnUYul5D6Ydop96vWLrPJxX3wXW2ypzNO7QWQ/a6WA25Dn/UFYc5 goruNXBbO4I8irkEuTaR3cmNvjtSd6HMUTyNHre3gpgDER+35fPMd7J49iukeu3PIA 6GlCr5qbr9I71+MbD5GO3/+R8uclPkseBMmcUebI= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 421326A87B; Wed, 6 May 2026 09:30:43 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Wed, 6 May 2026 09:29:54 -0600 Message-ID: <20260506153006.529909-6-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260506153006.529909-1-sjg@u-boot.org> References: <20260506153006.529909-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: 6QYEW2YEFQHXC7DYMEJPXLOT7DU6QW2C X-Message-ID-Hash: 6QYEW2YEFQHXC7DYMEJPXLOT7DU6QW2C X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 5/6] patman: Run reviews in a per-series git worktree List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass The review code currently applies the patch series onto the user's main checkout: it captures the original branch, stashes any local work (including untracked files), creates the review branch, applies, runs the agent, then tries to restore the original state on exit. This has a long tail of failure modes: Ctrl-C in the wrong place leaves the tree dirty, untracked build artefacts can leak in or out, two reviews cannot run at once, and the user cannot do other work in the same checkout while a review is in progress. Run each review in its own worktree under
/.git/patman/worktrees/. The worktree shares the .git directory of the main repo (so refs and objects are free) but has its own working tree and HEAD, completely isolated from whatever the user is doing. Use the new gitutil.ensure_worktree() helper to create or reset the worktree on demand, with the patman-specific path policy living in cser_helper.review_worktree_path(). Thread the two paths through ReviewContext: ctx.repo_path is the worktree (used as cwd for apply/build/agent) and ctx.main_repo is the user's main checkout (used when a real .git directory is needed for ref-based operations like 'git log upstream..branch'). Drop the 'git checkout -b' step from the apply agent's prompt since the worktree is already on the right branch when the agent starts. The user's main checkout is never touched, so the original-branch and stash dance is no longer needed. Remove the now-unused _git_restore() helper, the 'from u_boot_pylib import command' import that only fed it, and the matching mocks from the cseries test suite. While here, initialise patch_selection in ReviewContext.__init__ so the assignment in do_review() does not trip pylint's W0201 (attribute-defined-outside-init). Signed-off-by: Simon Glass --- tools/patman/cser_helper.py | 13 +++ tools/patman/review.py | 149 +++++++++++++++-------------------- tools/patman/test_cseries.py | 14 ++-- 3 files changed, 83 insertions(+), 93 deletions(-) -- 2.43.0 diff --git a/tools/patman/cser_helper.py b/tools/patman/cser_helper.py index 0274e28f42b..00dd80906e7 100644 --- a/tools/patman/cser_helper.py +++ b/tools/patman/cser_helper.py @@ -49,6 +49,19 @@ SHORTEN_STATE = { AUTOLINK = namedtuple('autolink', 'name,version,link,desc,result') +def review_worktree_path(repo, branch_name): + """Get the on-disk path for a per-review worktree + + Args: + repo (str): Top-level dir of the main checkout + branch_name (str): Review branch name + + Return: + str: Path where the review worktree lives (may not yet exist) + """ + return os.path.join(repo, '.git', 'patman', 'worktrees', branch_name) + + def oid(oid_val): """Convert a hash string into a shortened hash diff --git a/tools/patman/review.py b/tools/patman/review.py index 524a06df3a0..2842bb4ab00 100644 --- a/tools/patman/review.py +++ b/tools/patman/review.py @@ -23,12 +23,12 @@ import tempfile import aiohttp from u_boot_pylib import claude as claude_mod -from u_boot_pylib import command from u_boot_pylib import gitutil from u_boot_pylib import terminal from u_boot_pylib import tools from u_boot_pylib import tout +from patman import cser_helper from patman import database from patman import gmail from patman import patchstream @@ -46,7 +46,10 @@ class ReviewContext: # pylint: disable=R0902 reviewer_name (str): Reviewer's name reviewer_email (str): Reviewer's email series_data (dict): Series data from patchwork - repo_path (str): Path to the git repository + main_repo (str): Top-level of the user's main checkout (where + the .git dir lives and where shared refs are resolved) + repo_path (str): Path to the per-review worktree (used as cwd + for apply/build/agent) signoff (str or None): Sign-off text for reviews with comments spelling (str): Spelling convention comments_path (str or None): Path to existing patchwork comments @@ -58,6 +61,8 @@ class ReviewContext: # pylint: disable=R0902 branch_name (str or None): Branch with applied patches upstream_branch (str or None): Upstream branch ref patch_count (int or None): Number of patches + patch_selection (set of int or None): Patch indices to review + (None means all) """ def __init__(self, pwork, cser, series_data): @@ -66,6 +71,7 @@ class ReviewContext: # pylint: disable=R0902 self.series_data = series_data self.reviewer_name = None self.reviewer_email = None + self.main_repo = None self.repo_path = None self.signoff = None self.spelling = 'British' @@ -76,6 +82,7 @@ class ReviewContext: # pylint: disable=R0902 self.branch_name = None self.upstream_branch = None self.patch_count = None + self.patch_selection = None self.cover_content = None self.previous_reviews = {} self.diffstat = None @@ -130,28 +137,29 @@ async def fetch_mbox(pwork, link): def _build_apply_prompt(mbox_path, branch_name, upstream_branch): """Build the Claude agent prompt for applying patches - The agent will create a branch and apply the mbox using git am, - handling any conflicts that arise. + The cwd is already a per-review worktree on branch '{branch_name}' + reset to '{upstream_branch}', so the agent just needs to run git am + and handle any conflicts. Args: mbox_path (str): Path to the downloaded mbox file - branch_name (str): Name for the new branch - upstream_branch (str): Branch to start from (e.g. 'origin/master') + branch_name (str): Name of the (already-checked-out) review branch + upstream_branch (str): Upstream ref the branch is based on Returns: str: The prompt text for the agent """ return f'''Apply a patch series from a patchwork mbox \ -file to a new git branch. +file to the current branch. -TASK: -1. Create a new branch called '{branch_name}' from '{upstream_branch}': - git checkout -b {branch_name} {upstream_branch} +You are in a git worktree on branch '{branch_name}', already reset to +'{upstream_branch}'. Do NOT create a new branch. -2. Apply the patches from the mbox file: +TASK: +1. Apply the patches from the mbox file: git am {mbox_path} -3. If git am fails, try this recovery sequence: +2. If git am fails, try this recovery sequence: a. First, abort the failed git am: git am --abort @@ -183,10 +191,10 @@ TASK: If a patch is completely irrelevant (e.g. already applied), skip it and note which patch was skipped. -4. After all patches are applied (or skipped), run: +3. After all patches are applied (or skipped), run: git log --oneline {upstream_branch}..HEAD -5. Report the result: +4. Report the result: - How many patches were applied successfully - Which patches (if any) were skipped and why - The branch name: {branch_name} @@ -1079,7 +1087,9 @@ async def review_patches(ctx): return {} commit_range = f'{ctx.upstream_branch}..{ctx.branch_name}' - git_dir = os.path.join(ctx.repo_path, '.git') + # The branch is a shared ref, so use the main repo's .git directory + # — the worktree's .git is a pointer file, not a real git_dir + git_dir = os.path.join(ctx.main_repo, '.git') series = patchstream.get_metadata_for_list(commit_range, git_dir=git_dir) all_commits = [(i + 1, cmt.hash, cmt.subject) for i, cmt in enumerate(series.commits)] @@ -1283,24 +1293,6 @@ def search_series(pwork, title): return str(best['id']) -def _git_restore(orig_branch, had_stash, repo_path): - """Restore git branch and stash after review - - Args: - orig_branch (str or None): Branch to restore - had_stash (bool): Whether changes were stashed - repo_path (str or None): Repository path - """ - - try: - if orig_branch and repo_path: - gitutil.checkout_branch(orig_branch, repo_path) - if had_stash: - gitutil.stash_pop(repo_path) - except command.CommandExc: - pass - - def create_drafts(ctx, args, review_bodies, review_ids): """Create Gmail drafts for review emails @@ -1776,29 +1768,30 @@ def _get_upstream_branch(args, cser): def _apply_and_check(ctx, link): """Download, apply patches and verify they applied + Runs in the per-review worktree at ctx.repo_path; the branch + ctx.branch_name has already been created and reset to upstream by + worktree.ensure_worktree(), so the agent only needs to 'git am'. + Args: ctx (ReviewContext): Review context (uses pwork, cser, series_id, - version, upstream_branch) + version, upstream_branch, branch_name, repo_path) link (str): Patchwork series link/ID Returns: - str or None: Branch name, or None on failure + bool: True if the patches applied cleanly """ - ups = ctx.pwork.upstream if ctx.pwork else None - branch_name = _make_review_name(link, ups) - repo_path = gitutil.get_top_level() - success, branch_name = apply_series_sync(ctx.pwork, link, branch_name, - ctx.upstream_branch, repo_path) + success, _ = apply_series_sync(ctx.pwork, link, ctx.branch_name, + ctx.upstream_branch, ctx.repo_path) if success: applied = gitutil.count_revs( - repo_path, f'{ctx.upstream_branch}..{branch_name}') + ctx.repo_path, f'{ctx.upstream_branch}..{ctx.branch_name}') if not applied: # Zero commits, or branch missing because apply was interrupted success = False elif applied != ctx.patch_count: tout.error(f'Only {applied} of {ctx.patch_count} patches ' - f'applied to {branch_name}; aborting. Fix the ' + f'applied to {ctx.branch_name}; aborting. Fix the ' 'conflicts manually and retry.') success = False @@ -1806,9 +1799,9 @@ def _apply_and_check(ctx, link): tout.error('Failed to apply patches to branch') ctx.cser.db.ser_ver_remove(ctx.series_id, ctx.version) ctx.cser.commit() - return None - tout.notice(f'Patches applied to branch: {branch_name}') - return branch_name + return False + tout.notice(f'Patches applied to branch: {ctx.branch_name}') + return True def _fetch_cover_content(pwork, series_data): @@ -1979,47 +1972,33 @@ def do_review(args, pwork, cser): return 0 ctx.series_id, ctx.svid = result - orig_branch = None - had_stash = False + ctx.upstream_branch = _get_upstream_branch(args, cser) + ctx.main_repo = gitutil.get_top_level() + ups = pwork.upstream if pwork else None + ctx.branch_name = _make_review_name(link, ups) + wt_path = cser_helper.review_worktree_path(ctx.main_repo, ctx.branch_name) + tout.notice(f'Using review worktree {wt_path}') + ctx.repo_path = gitutil.ensure_worktree( + ctx.main_repo, wt_path, ctx.branch_name, ctx.upstream_branch) - try: - ctx.upstream_branch = _get_upstream_branch(args, cser) - ctx.repo_path = gitutil.get_top_level() - orig_branch = gitutil.current_branch(ctx.repo_path) - try: - # Stash untracked files too, so build artefacts from the - # current branch don't leak into the review branch - stash_out = gitutil.stash_save(ctx.repo_path, - include_untracked=True) - if 'No local changes' not in stash_out: - had_stash = True - except command.CommandExc: - pass - - ctx.branch_name = _apply_and_check(ctx, link) - if not ctx.branch_name: - return 1 - - if args.apply_only: - tout.notice('Apply-only mode; skipping review') - return 0 - - ctx.patch_selection = parse_patch_selection(args.patches) - ctx.reviewer_name, ctx.reviewer_email = _parse_reviewer(args) - ctx.signoff = args.signoff or None - if ctx.signoff: - ctx.signoff = ctx.signoff.replace('\\n', '\n') - ctx.spelling = args.spelling - ctx.comments_path = _write_comments_file(series_data, pwork) - - _run_and_store_reviews(ctx, args) - workflow.reviewed(cser, ctx.series_id, ctx.svid) - - _git_restore(orig_branch, had_stash, ctx.repo_path) - orig_branch = None - - finally: - _git_restore(orig_branch, had_stash, ctx.repo_path) + if not _apply_and_check(ctx, link): + return 1 + + if args.apply_only: + tout.notice('Apply-only mode; skipping review') + return 0 + + ctx.patch_selection = parse_patch_selection(args.patches) + ctx.reviewer_name, ctx.reviewer_email = _parse_reviewer(args) + ctx.signoff = args.signoff or None + if ctx.signoff: + ctx.signoff = ctx.signoff.replace('\\n', '\n') + ctx.spelling = args.spelling + ctx.comments_path = _write_comments_file(series_data, pwork) + + _run_and_store_reviews(ctx, args) + workflow.reviewed(cser, ctx.series_id, ctx.svid) + gitutil.remove_worktree(ctx.main_repo, wt_path) return 0 diff --git a/tools/patman/test_cseries.py b/tools/patman/test_cseries.py index 7d6f99b3536..c21a0357d1d 100644 --- a/tools/patman/test_cseries.py +++ b/tools/patman/test_cseries.py @@ -4642,16 +4642,15 @@ Date: .* """Context manager to mock apply, upstream, git and AI review""" fake_review = {1: f'Reviewed-by: {self.REVIEWER}'} return (mock.patch('patman.review._apply_and_check', - return_value='review1'), + return_value=True), mock.patch('patman.review._get_upstream_branch', return_value='origin/master'), mock.patch('patman.review.review_patches_sync', return_value=fake_review), mock.patch('patman.review.gitutil.get_top_level', return_value=self.tmpdir), - mock.patch('patman.review.command.output', - return_value='pati'), - mock.patch('patman.review._git_restore')) + mock.patch('patman.review.gitutil.ensure_worktree', + return_value=self.tmpdir)) def test_review_new_series(self): """Test reviewing a new series creates database records""" @@ -4772,14 +4771,13 @@ Date: .* pwork.project_set(self.PROJ_ID, self.PROJ_LINK_NAME) with mock.patch('patman.review._apply_and_check', - return_value=None), \ + return_value=False), \ mock.patch('patman.review._get_upstream_branch', return_value='origin/master'), \ mock.patch('patman.review.gitutil.get_top_level', return_value=self.tmpdir), \ - mock.patch('patman.review.command.output', - return_value='test'), \ - mock.patch('patman.review._git_restore'): + mock.patch('patman.review.gitutil.ensure_worktree', + return_value=self.tmpdir): with terminal.capture() as _: self.run_review('-s', str(self.REVIEW_LINK), pwork=pwork, expect_ret=1) From patchwork Wed May 6 15:29:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 2278 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081451; bh=/9fntw4gN4rNky8ym6zj77381nc0ZQ9ingTWemuSf2g=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=bZ6KJfAMyKaihASgmOMCQsD6YVkGjuflHUd1UuFkctuh7JAS7NZgn++L9C519RPmD dHVXcJpp+lPSGwsGNe0UGi0NWD32vi43j3dc2QH/Jfz7y7PYQJXVPO+Ql62cZrD3s5 H5h3K4ulACMaJstMXYcv9DLY1rwzBT44u46nwXBw= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2B7B06A947 for ; Wed, 6 May 2026 09:30:51 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id SexyXF0fJ_VK for ; Wed, 6 May 2026 09:30:51 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081451; bh=/9fntw4gN4rNky8ym6zj77381nc0ZQ9ingTWemuSf2g=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=bZ6KJfAMyKaihASgmOMCQsD6YVkGjuflHUd1UuFkctuh7JAS7NZgn++L9C519RPmD dHVXcJpp+lPSGwsGNe0UGi0NWD32vi43j3dc2QH/Jfz7y7PYQJXVPO+Ql62cZrD3s5 H5h3K4ulACMaJstMXYcv9DLY1rwzBT44u46nwXBw= Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1B2316A92E for ; Wed, 6 May 2026 09:30:51 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081449; bh=5U3Ve6deYo2MfH+hEqbqcm4WU2tcQdOqXe4xSq93/BY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=s3S0xNRtiZPf1Y1kakoJzjQCRkCX9oioLybaTUgPgPIUjOm0w8J3tur0veBLdSOtx TYE+tSFDjevEkNbks/n4EALuTuTBp5Pt3qHz7aFHkUo1eke46dH33EYXOy1mYQDxS4 Pmnhym3dEL2RnDE5CbfEoh7WCh+l18RYEUkltl9I= Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 44DE06A87B; Wed, 6 May 2026 09:30:49 -0600 (MDT) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id eIrPa0fyhuEU; Wed, 6 May 2026 09:30:49 -0600 (MDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1778081445; bh=4IeWFy5ugMJDgkF4wlJnqdrYh+S7WI52i5Ewtb2dqvA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=JrRwwFIofv58hhKBaTs6H7d9V0O9VeAoCHs3Wvgif3ILODdNw9UN53Knyvan2r58B WXXU7ATIm+0wjsqcGkWQ7uMk84xp5Eaxv/p3dUF2uontAtXgcapFnWACt36vOwoAU7 Bx0OC5gKoqBMzrxJ8VemfVmUiPH5t33ztT6EDMTg= Received: from u-boot.org (unknown [174.51.25.52]) by mail.u-boot.org (Postfix) with ESMTPSA id 179166A92E; Wed, 6 May 2026 09:30:45 -0600 (MDT) From: Simon Glass To: U-Boot Concept Date: Wed, 6 May 2026 09:29:55 -0600 Message-ID: <20260506153006.529909-7-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260506153006.529909-1-sjg@u-boot.org> References: <20260506153006.529909-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: CLNS76TYR4H2JXLNNPCZWRMN6XX3HAYF X-Message-ID-Hash: CLNS76TYR4H2JXLNNPCZWRMN6XX3HAYF X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 6/6] patman: Clean up review worktrees on series rm and archive List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass A worktree created by 'patman review' lives on disk until something removes it. With nothing to trigger that today, abandoned reviews accumulate under .git/patman/worktrees and the user has no obvious way to reclaim the space. Hook the worktree removal into the two paths that already invalidate the underlying branch: - 'series rm' deletes the database record. The worktree has no value without it, so drop it after the commit succeeds. - 'series archive' deletes each version's branch. A checked-out branch cannot be deleted, so the worktree must go first; otherwise pygit2's branches.delete() fails. Use gitutil.remove_worktree() with the path from cser_helper.review_worktree_path(); both are no-ops when the worktree is not registered, so this is safe for non-review series whose branches were never reviewed. Signed-off-by: Simon Glass --- tools/patman/cseries.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/patman/cseries.py b/tools/patman/cseries.py index fb3f5a6c49a..1149eef44e1 100644 --- a/tools/patman/cseries.py +++ b/tools/patman/cseries.py @@ -830,6 +830,11 @@ class Cseries(cser_helper.CseriesHelper): self.db.ser_ver_remove(ser.idnum, None) if not dry_run: self.commit() + # The review worktree (if any) has no value once the db row + # is gone; v1 review series share their branch name with the + # series name, so review_worktree_path() resolves it directly + gitutil.remove_worktree(self.topdir, + cser_helper.review_worktree_path(self.topdir, name)) else: self.rollback() @@ -1252,6 +1257,11 @@ class Cseries(cser_helper.CseriesHelper): # Delete the branches for idnum, name, tag_name in tag_info.values(): + # Drop any review worktree first; a checked-out branch + # cannot be deleted + gitutil.remove_worktree(self.topdir, + cser_helper.review_worktree_path(self.topdir, name)) + # Detach HEAD from the branch if pointing to this branch commit = repo.revparse_single(name) if repo.head.target == commit.oid: