Skip to content

checkout: 'autostash' for branch switching

Harald Nordgren via GitGitGadget git 15 versions – In review

Get series with b4
b4 am [email protected]

Versions newest first · linked by author and title

  1. v16 Latest5 patches 2 repliesIn review
  2. v155 patches 6 repliesSuperseded
  3. v145 patches 6 repliesSuperseded
Earlier versions · 12
VersionPostedPatchesRepliesStatus
v135 patches0 repliesSuperseded
v124 patches25 repliesSuperseded
v114 patches0 repliesSuperseded
v104 patches6 repliesSuperseded
v94 patches5 repliesSuperseded
v84 patches11 repliesSuperseded
v74 patches8 repliesSuperseded
v64 patches0 repliesSuperseded
v54 patches0 repliesSuperseded
v31 patch3 repliesSuperseded
v21 patch2 repliesSuperseded
v11 patch15 repliesSuperseded

Patches in v16 No applies recorded yet

#Subject and filesSizeRepliesApplied
1/5stash: add --label-ours, --label-theirs, --label-base for applyDocumentation/git-stash.adoc builtin/stash.c t/t3903-stash.sh +1 more+57 -120 replies
2/5sequencer: allow create_autostash to run silentlybuiltin/merge.c sequencer.c sequencer.h+17 -90 replies
3/5sequencer: teach autostash apply to take optional conflict marker labelsbuiltin/commit.c builtin/merge.c sequencer.c +1 more+40 -140 replies
4/5checkout: rollback lock on early returns in merge_working_treebuiltin/checkout.c+22 -70 replies
5/5checkout -m: autostash when switching branchesDocumentation/git-checkout.adoc Documentation/git-switch.adoc builtin/checkout.c +6 more+219 -1501 reply

Reviews trailers per patch

No Reviewed-by, Acked-by or Tested-by trailers for v16 yet.

Read the v16 thread

Cover letter [PATCH v16 0/5] checkout: 'autostash' for branch switching

* Updated the git checkout and git switch docs to show the actual output when using -m to carry local changes across a branch switch, and changed "would carry" to "will carry".
* Rewrote the merge-conflict example in both docs to match the new, more concise message printed on autostash conflicts.
* Replaced the show_unpack_errors flag and old_commit parameter in merge_working_tree()/init_topts() with a plain quiet boolean, so the caller decides directly whether to suppress unpack errors.
* Tightened the 'checkout -m with dirty tree' test by replacing a printf with a heredoc.
* Made the 'checkout -m creates a recoverable stash on conflict' test also assert the reflog subject of the new stash entry.
* Replaced two test_grep calls in the 'checkout -m which would overwrite untracked file' test with a single test_cmp, which also catches a regression where the "would be overwritten" message could end up printed twice.

Harald Nordgren (5):
stash: add --label-ours, --label-theirs, --label-base for apply
sequencer: allow create_autostash to run silently
sequencer: teach autostash apply to take optional conflict marker
labels
checkout: rollback lock on early returns in merge_working_tree checkout -m: autostash when switching branches

 Documentation/git-checkout.adoc |  55 +++++------
 Documentation/git-stash.adoc    |  11 ++-
 Documentation/git-switch.adoc   |  36 ++++---
 builtin/checkout.c              | 166 +++++++++++++++-----------------
 builtin/commit.c                |   3 +-
 builtin/merge.c                 |  15 ++-
 builtin/stash.c                 |  28 ++++--
 sequencer.c                     |  69 +++++++++----
 sequencer.h                     |   7 +-
 t/t3420-rebase-autostash.sh     |  16 +--
 t/t3903-stash.sh                |  24 +++++
 t/t7201-co.sh                   |  71 +++++++++++++-
 t/t7600-merge.sh                |   3 +-
 xdiff-interface.c               |  12 +++
 xdiff-interface.h               |   1 +
 xdiff/xmerge.c                  |   6 +-
 16 files changed, 343 insertions(+), 180 deletions(-)

base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2234%2FHaraldNordgren%2Fcheckout_autostash-v16
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2234/HaraldNordgren/checkout_autostash-v16
Pull-Request: https://github.com/git/git/pull/2234

Range-diff vs v15:

 1:  aba8e6a9dc = 1:  aba8e6a9dc stash: add --label-ours, --label-theirs, --label-base for apply
 2:  89e0bfa803 = 2:  89e0bfa803 sequencer: allow create_autostash to run silently
 3:  a428ce7328 = 3:  a428ce7328 sequencer: teach autostash apply to take optional conflict marker labels
 4:  f358424085 = 4:  f358424085 checkout: rollback lock on early returns in merge_working_tree
 5:  96b14db827 ! 5:  07d25fda91 checkout -m: autostash when switching branches

@@ Documentation/git-checkout.adoc: $ git checkout mytopic

-You can give the -m flag to the command, which would try a -three-way merge:
-+You can give the -m flag to the command, which would carry your local ++You can give the -m flag to the command, which will carry your local
+changes to the new branch:

------------
$ git checkout -m mytopic
-Auto-merging frotz
++Applied autostash.
+Switched to branch 'mytopic'
++The following paths have local changes:
++M frotz
------------

-After this three-way merge, the local modifications are _not_
@@ Documentation/git-checkout.adoc: $ git checkout mytopic

-When a merge conflict happens during switching branches with -the -m option, you would see something like this:
-+When the --merge (-m) option is in effect and the locally -+modified files overlap with files that need to be updated by the -+branch switch, the changes are stashed and reapplied after the -+switch. If this process results in conflicts, a stash entry is saved -+and made available in git stash list:
++When the --merge (-m) option is given and the local changes ++overlap with the changes in the branch we're switching to, the ++changes are stashed and reapplied after the switch. If this ++process results in conflicts, the stash entry is saved and a ++message is printed:

------------
$ git checkout -m mytopic
@@ Documentation/git-checkout.adoc: $ git checkout mytopic
-ERROR: Merge conflict in frotz
-fatal: merge program failed
-------------
-+Your local changes are stashed, however, applying it to carry -+forward your local changes resulted in conflicts:
-
+-
-At this point, git diff shows the changes cleanly merged as in -the previous example, as well as the changes in the conflicted -files. Edit and resolve the conflict and mark it resolved with -git add as usual:
-+ - You can try resolving them now. If you resolved them
-+ successfully, discard the stash entry with "git stash drop".
-
-+ - Alternatively you can "git reset --hard" if you do not want
-+ to deal with them right now, and later "git stash pop" to
-+ recover your local changes.
- ------------
+-
+-------------
-$ edit frotz
-$ git add frotz
--------------
-+
-+You can try resolving the conflicts now. Edit the conflicting files -+and mark them resolved with git add as usual, then run git stash -+drop to discard the stash entry. Alternatively, you can clear the -+working tree with git reset --hard and recover your local changes -+later with git stash pop.
++Your local changes are stashed, however applying them ++resulted in conflicts. You can either resolve the conflicts ++and then discard the stash with "git stash drop", or, if you ++do not want to resolve them now, run "git reset --hard" and ++apply the local changes later by running "git stash pop".
+ ------------

CONFIGURATION
- -------------

## Documentation/git-switch.adoc ##
@@ Documentation/git-switch.adoc: variable.
@@ Documentation/git-switch.adoc: $ git switch mytopic

-You can give the -m flag to the command, which would try a three-way -merge:
-+You can give the -m flag to the command, which would carry your local ++You can give the -m flag to the command, which will carry your local
+changes to the new branch:

------------
$ git switch -m mytopic
-Auto-merging frotz
++Applied autostash.
+Switched to branch 'mytopic'
++The following paths have local changes:
++M frotz
------------

-After this three-way merge, the local modifications are _not_
@@ builtin/checkout.c: struct checkout_opts {
char *name; /* The short name used */
char *path; /* The full name of a real branch */
@@ builtin/checkout.c: static void setup_branch_path(struct branch_info *branch)
+ branch->path = strbuf_detach(&buf, NULL);
+ }

- static void init_topts(struct unpack_trees_options *topts, int merge, +-static void init_topts(struct unpack_trees_options *topts, int merge, ++static void init_topts(struct unpack_trees_options *topts,
int show_progress, int overwrite_ignore,
- struct commit *old_commit)
-+ struct commit *old_commit, bool show_unpack_errors)
++ bool quiet)
{
memset(topts, 0, sizeof(*topts));
topts->head_idx = -1;
@@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, i
topts->update = 1;
topts->merge = 1;
- topts->quiet = merge && old_commit;
-+ topts->quiet = merge && old_commit && !show_unpack_errors;
++ topts->quiet = quiet;
topts->verbose_update = show_progress;
topts->fn = twoway_merge;
topts->preserve_ignored = !overwrite_ignore;
@@ builtin/checkout.c: static void init_topts(struct unpack_trees_options *topts, i
static int merge_working_tree(const struct checkout_opts *opts,
struct branch_info *old_branch_info,
struct branch_info *new_branch_info,
-- int *writeout_error)
-+ int *writeout_error,
-+ bool show_unpack_errors)
++ bool quiet,
+ int *writeout_error)
{
int ret;
- struct lock_file lock_file = LOCK_INIT;
@@ builtin/checkout.c: static int merge_working_tree(const struct checkout_opts *opts,
+ }

/* 2-way merge to the new branch */
- init_topts(&topts, opts->merge, opts->show_progress,
+- init_topts(&topts, opts->merge, opts->show_progress,
- opts->overwrite_ignore, old_branch_info->commit);
-+ opts->overwrite_ignore, old_branch_info->commit,
-+ show_unpack_errors);
++ init_topts(&topts, opts->show_progress,
++ opts->overwrite_ignore, quiet);
init_checkout_metadata(&topts.meta, new_branch_info->refname,
new_branch_info->commit ? &new_branch_info->commit->object.oid :
@@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
if (do_merge) {
- ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
+ ret = merge_working_tree(opts, &old_branch_info, new_branch_info,
-+ &writeout_error, false);
++ opts->merge, &writeout_error);
+ if (ret == MERGE_WORKING_TREE_UNPACK_FAILED && opts->merge) {
+ strbuf_addf(&autostash_msg,
+ "autostash while switching to '%s'",
@@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,
+ autostash_msg.buf, true);
+ created_autostash = 1;
+ ret = merge_working_tree(opts, &old_branch_info, new_branch_info,
-+ &writeout_error, true);
++ false, &writeout_error);
+ }
+ if (created_autostash) {
+ if (opts->conflict_style >= 0) {
@@ t/t7201-co.sh: test_expect_success 'checkout -m with dirty tree' '
test "$(git symbolic-ref HEAD)" = "refs/heads/side" &&

- printf "M\t%s\n" one >expect.messages &&
-+ printf "The following paths have local changes:\nM\t%s\n" one >expect.messages &&
++ cat >expect.messages <<-\EOF &&
++ The following paths have local changes:
++ M one
++ EOF
test_cmp expect.messages messages &&

fill "M one" "A three" "D two" >expect.main &&
@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'
+ test_grep "git stash drop" actual &&
+ test_grep "git stash pop" actual &&
+ test_grep "The following paths have local changes" actual &&
-+ git show --format=%B --diff-merges=1 refs/stash >actual &&
++ git log -p -1 --format="%gs%n%B" -g --diff-merges=1 refs/stash >actual &&
+ sed /^index/d actual >actual.trimmed &&
+ cat >expect <<-EOF &&
++ autostash while switching to ${SQ}side${SQ}
+ On main: autostash while switching to ${SQ}side${SQ}
+
+ diff --git a/one b/one
@@ t/t7201-co.sh: test_expect_success 'checkout --merge --conflict=diff3 <branch>'
+ >another-file.t &&
+ fill 1 2 3 4 5 >one &&
+ test_must_fail git checkout -m @{-1} 2>err &&
-+ test_grep "would be overwritten by checkout" err &&
-+ test_grep "another-file.t" err
++ q_to_tab >expect <<-\EOF &&
++ error: The following untracked working tree files would be overwritten by checkout:
++ Qanother-file.t
++ Please move or remove them before you switch branches.
++ Aborting
++ Applied autostash.
++ EOF
++ test_cmp expect err
+'
+
test_expect_success 'switch to another branch while carrying a deletion' '

Signature · 1 line
gitgitgadget