[PATCH b4 v2 25/44] review-tui: put HEAD back where it was when it was not on a branch
Christian Brauner <[email protected]> Fri, 31 Jul 2026 23:59:06 +0200
| Newsgroups | org.kernel.linux.tools |
|---|---|
| Message-ID | <20260731-work-b4-editor-branch-guard-v2-25-243fd19d322d@kernel.org> |
Every restore remembers where to go back to by branch name, and a detached HEAD has none, so the restore was skipped and the user left wherever b4 moved them. A detached HEAD is an ordinary place to start a review from. Record the position instead: checkout arguments that name the branch when there is one and the commit when there is not. Signed-off-by: Christian Brauner (Amutable) <[email protected]> --- src/b4/__init__.py | 17 +++++++++++++++++ src/b4/review/_review.py | 6 ++++-- src/b4/review_tui/_entry.py | 16 +++++++--------- src/b4/review_tui/_review_app.py | 9 ++++++--- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/b4/__init__.py b/src/b4/__init__.py index a22c28d..2697da1 100644 --- a/src/b4/__init__.py +++ b/src/b4/__init__.py @@ -5246,6 +5246,23 @@ def git_get_current_branch( return mybranch +def git_head_restore_args(gitdir: Optional[str] = None) -> List[str]: + """git-checkout args that put HEAD back where it is standing right now. + + The branch name when HEAD is on one, the bare commit when it is not, so + that a caller which moves HEAD can undo that from a detached start too -- + a branch name is not the only starting point worth going back to. Empty + when HEAD cannot be read at all, e.g. on an unborn branch. + """ + ecode, out = git_run_command(gitdir, ['symbolic-ref', '--short', 'HEAD']) + if ecode == 0: + return ['checkout', out.strip()] + ecode, out = git_run_command(gitdir, ['rev-parse', 'HEAD']) + if ecode == 0: + return ['checkout', '--detach', out.strip()] + return [] + + def get_excluded_addrs() -> Set[str]: config = get_main_config() excludes = set() diff --git a/src/b4/review/_review.py b/src/b4/review/_review.py index 4c67387..06595fa 100644 --- a/src/b4/review/_review.py +++ b/src/b4/review/_review.py @@ -3151,8 +3151,9 @@ def _prepare_review_session(cmdargs: argparse.Namespace) -> Dict[str, Any]: topdir, change_id, series, patches ) - # Record current branch so ReviewApp can restore it if it checks - # out the review branch for shell/agent operations. + # Record where HEAD is so ReviewApp can put it back if it checks out the + # review branch for shell/agent operations. A detached HEAD is a starting + # point too, so remember the commit alongside the branch name. ecode, out = b4.git_run_command(topdir, ['symbolic-ref', '--short', 'HEAD']) current_branch = out.strip() if ecode == 0 else None @@ -3160,6 +3161,7 @@ def _prepare_review_session(cmdargs: argparse.Namespace) -> Dict[str, Any]: 'topdir': topdir, 'branch': branch, 'original_branch': current_branch, + 'original_head': b4.git_head_restore_args(topdir), 'cover_text': cover_text, 'tracking': tracking, 'series': series, diff --git a/src/b4/review_tui/_entry.py b/src/b4/review_tui/_entry.py index 6dc71a4..ab68ca4 100644 --- a/src/b4/review_tui/_entry.py +++ b/src/b4/review_tui/_entry.py @@ -69,8 +69,10 @@ def run_tracking_tui( patatt_sign = not (no_sign or _cnps.lower() in {'yes', 'true', 'y'}) use_mouse = not no_mouse and _tui_use_mouse() - # Get current branch to restore later + # Remember where HEAD is so we can put it back later. A detached HEAD is + # a starting point too, so keep the commit alongside the branch name. original_branch = b4.git_get_current_branch(topdir) + original_head = b4.git_head_restore_args(topdir) # Check if we're already on a review branch if original_branch and original_branch.startswith(b4.review.REVIEW_BRANCH_PREFIX): @@ -178,7 +180,7 @@ def run_tracking_tui( # Anything else means the user moved HEAD themselves -- possibly hours # ago, from another terminal sharing this worktree -- and the branch # recorded when we started is stale, not something to check back out. - if original_branch: + if original_head: current = b4.git_get_current_branch(topdir) if ( current @@ -186,12 +188,8 @@ def run_tracking_tui( and current.startswith(b4.review.REVIEW_BRANCH_PREFIX) ): logger.info( - 'Checking out %s and starting tracking UI...', original_branch - ) - ecode, _out = b4.git_run_command( - topdir, ['checkout', original_branch], logstderr=True + 'Checking out %s and starting tracking UI...', original_head[-1] ) + ecode, _out = b4.git_run_command(topdir, original_head, logstderr=True) if ecode != 0: - logger.warning( - 'Could not restore original branch: %s', original_branch - ) + logger.warning('Could not restore %s', original_head[-1]) diff --git a/src/b4/review_tui/_review_app.py b/src/b4/review_tui/_review_app.py index 0982f55..9c9bf17 100644 --- a/src/b4/review_tui/_review_app.py +++ b/src/b4/review_tui/_review_app.py @@ -292,6 +292,9 @@ class ReviewApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[None]): self._patatt_sign: bool = session.get('patatt_sign', True) self._branch: str = session['branch'] self._original_branch: Optional[str] = session.get('original_branch') + self._original_head: List[str] = session.get('original_head') or ( + ['checkout', self._original_branch] if self._original_branch else [] + ) self.branch_checked_out: bool = False self._has_cover: bool = NO_COVER_NOTE not in self._cover_text self._selected_idx: int = ( @@ -2161,8 +2164,8 @@ class ReviewApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[None]): return True def _restore_original_branch(self) -> None: - """Switch back to the original branch after shell/agent use.""" - if not self.branch_checked_out or not self._original_branch: + """Put HEAD back where the session found it after shell/agent use.""" + if not self.branch_checked_out or not self._original_head: return # Only undo our own checkout. If HEAD is no longer on the review # branch we left it on, something else moved it -- the shell we just @@ -2172,7 +2175,7 @@ class ReviewApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[None]): self.branch_checked_out = False return ecode, _out = b4.git_run_command( - self._topdir, ['checkout', self._original_branch], logstderr=True + self._topdir, self._original_head, logstderr=True ) if ecode == 0: self.branch_checked_out = False -- 2.53.0