Skip to content

Commit e05c054

Browse files
checkout: add --autostash option for branch switching
When switching branches, local modifications in the working tree can prevent the checkout from succeeding. While "git rebase" and "git merge" already support --autostash to handle this case automatically, "git checkout" and "git switch" require users to manually stash and unstash their changes. Teach "git checkout" and "git switch" to accept --autostash and --no-autostash options that automatically create a temporary stash entry before the branch switch begins and apply it after the switch completes. If the stash application results in conflicts, the stash entry is saved to the stash list so the user can resolve them later. Also add a checkout.autoStash configuration option that enables this behavior by default, which can be overridden with --no-autostash on the command line. Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
1 parent 67006b9 commit e05c054

7 files changed

Lines changed: 277 additions & 101 deletions

File tree

Documentation/git-checkout.adoc

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,16 +255,19 @@ working tree, by copying them from elsewhere, extracting a tarball, etc.
255255
branch, your working tree contents, and the new branch
256256
is done, and you will be on the new branch.
257257
+
258-
When a merge conflict happens, the index entries for conflicting
259-
paths are left unmerged, and you need to resolve the conflicts
260-
and mark the resolved paths with `git add` (or `git rm` if the merge
261-
should result in deletion of the path).
258+
When switching branches, if any of the paths that differ between
259+
the current branch and the target branch have local changes (in
260+
the index or the working tree), a temporary stash entry is
261+
automatically created before the switch and applied after it
262+
completes. If the local changes do not overlap with the branch
263+
difference, the switch proceeds normally without stashing. When a
264+
stash entry is created and the subsequent application results in
265+
conflicts, the stash entry is saved so that you can use `git stash
266+
pop` to recover and `git stash drop` when done.
262267
+
263268
When checking out paths from the index, this option lets you recreate
264269
the conflicted merge in the specified paths. This option cannot be
265270
used when checking out paths from a tree-ish.
266-
+
267-
When switching branches with `--merge`, staged changes may be lost.
268271
269272
`--conflict=<style>`::
270273
The same as `--merge` option above, but changes the way the

Documentation/git-switch.adoc

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,16 @@ variable.
127127
different between the current branch and the branch to which
128128
you are switching, the command refuses to switch branches in
129129
order to preserve your modifications in context. However,
130-
with this option, a three-way merge between the current
131-
branch, your working tree contents, and the new branch is
132-
done, and you will be on the new branch.
133-
+
134-
When a merge conflict happens, the index entries for conflicting
135-
paths are left unmerged, and you need to resolve the conflicts
136-
and mark the resolved paths with `git add` (or `git rm` if the merge
137-
should result in deletion of the path).
130+
with this option, when any of the paths that differ between
131+
the current branch and the target branch have local changes
132+
(in the index or the working tree), a temporary stash entry
133+
is automatically created before the switch and applied after
134+
it completes. If the local changes do not overlap with the
135+
branch difference, the switch proceeds normally without
136+
stashing. When a stash entry is created and the subsequent
137+
application results in conflicts, the stash entry is saved
138+
so that you can use `git stash pop` to recover and `git stash
139+
drop` when done.
138140

139141
`--conflict=<style>`::
140142
The same as `--merge` option above, but changes the way the

builtin/checkout.c

Lines changed: 79 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
#include "merge-ll.h"
1818
#include "lockfile.h"
1919
#include "mem-pool.h"
20-
#include "merge-ort-wrappers.h"
2120
#include "object-file.h"
2221
#include "object-name.h"
2322
#include "odb.h"
@@ -30,6 +29,7 @@
3029
#include "repo-settings.h"
3130
#include "resolve-undo.h"
3231
#include "revision.h"
32+
#include "sequencer.h"
3333
#include "setup.h"
3434
#include "submodule.h"
3535
#include "symlinks.h"
@@ -845,83 +845,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
845845

846846
ret = unpack_trees(2, trees, &topts);
847847
clear_unpack_trees_porcelain(&topts);
848-
if (ret == -1) {
849-
/*
850-
* Unpack couldn't do a trivial merge; either
851-
* give up or do a real merge, depending on
852-
* whether the merge flag was used.
853-
*/
854-
struct tree *work;
855-
struct tree *old_tree;
856-
struct merge_options o;
857-
struct strbuf sb = STRBUF_INIT;
858-
struct strbuf old_commit_shortname = STRBUF_INIT;
859-
860-
if (!opts->merge)
861-
return 1;
862-
863-
/*
864-
* Without old_branch_info->commit, the below is the same as
865-
* the two-tree unpack we already tried and failed.
866-
*/
867-
if (!old_branch_info->commit)
868-
return 1;
869-
old_tree = repo_get_commit_tree(the_repository,
870-
old_branch_info->commit);
871-
872-
if (repo_index_has_changes(the_repository, old_tree, &sb))
873-
die(_("cannot continue with staged changes in "
874-
"the following files:\n%s"), sb.buf);
875-
strbuf_release(&sb);
876-
877-
/* Do more real merge */
878-
879-
/*
880-
* We update the index fully, then write the
881-
* tree from the index, then merge the new
882-
* branch with the current tree, with the old
883-
* branch as the base. Then we reset the index
884-
* (but not the working tree) to the new
885-
* branch, leaving the working tree as the
886-
* merged version, but skipping unmerged
887-
* entries in the index.
888-
*/
889-
890-
add_files_to_cache(the_repository, NULL, NULL, NULL, 0,
891-
0, 0);
892-
init_ui_merge_options(&o, the_repository);
893-
o.verbosity = 0;
894-
work = write_in_core_index_as_tree(the_repository);
895-
896-
ret = reset_tree(new_tree,
897-
opts, 1,
898-
writeout_error, new_branch_info);
899-
if (ret)
900-
return ret;
901-
o.ancestor = old_branch_info->name;
902-
if (!old_branch_info->name) {
903-
strbuf_add_unique_abbrev(&old_commit_shortname,
904-
&old_branch_info->commit->object.oid,
905-
DEFAULT_ABBREV);
906-
o.ancestor = old_commit_shortname.buf;
907-
}
908-
o.branch1 = new_branch_info->name;
909-
o.branch2 = "local";
910-
o.conflict_style = opts->conflict_style;
911-
ret = merge_ort_nonrecursive(&o,
912-
new_tree,
913-
work,
914-
old_tree);
915-
if (ret < 0)
916-
die(NULL);
917-
ret = reset_tree(new_tree,
918-
opts, 0,
919-
writeout_error, new_branch_info);
920-
strbuf_release(&o.obuf);
921-
strbuf_release(&old_commit_shortname);
922-
if (ret)
923-
return ret;
924-
}
848+
if (ret == -1)
849+
return 1;
925850
}
926851

927852
if (!cache_tree_fully_valid(the_repository->index->cache_tree))
@@ -930,9 +855,6 @@ static int merge_working_tree(const struct checkout_opts *opts,
930855
if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK))
931856
die(_("unable to write new index file"));
932857

933-
if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
934-
show_local_changes(&new_branch_info->commit->object, &opts->diff_options);
935-
936858
return 0;
937859
}
938860

@@ -1157,6 +1079,55 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
11571079
release_revisions(&revs);
11581080
}
11591081

1082+
static int checkout_would_clobber_changes(struct branch_info *old_branch_info,
1083+
struct branch_info *new_branch_info)
1084+
{
1085+
struct tree_desc trees[2];
1086+
struct tree *old_tree, *new_tree;
1087+
struct unpack_trees_options topts;
1088+
struct index_state tmp_index = INDEX_STATE_INIT(the_repository);
1089+
const struct object_id *old_commit_oid;
1090+
int ret;
1091+
1092+
if (!new_branch_info->commit)
1093+
return 0;
1094+
1095+
old_commit_oid = old_branch_info->commit ?
1096+
&old_branch_info->commit->object.oid :
1097+
the_hash_algo->empty_tree;
1098+
old_tree = repo_parse_tree_indirect(the_repository, old_commit_oid);
1099+
if (!old_tree)
1100+
return 0;
1101+
1102+
new_tree = repo_get_commit_tree(the_repository,
1103+
new_branch_info->commit);
1104+
if (!new_tree)
1105+
return 0;
1106+
if (repo_parse_tree(the_repository, new_tree) < 0)
1107+
return 0;
1108+
1109+
memset(&topts, 0, sizeof(topts));
1110+
topts.head_idx = -1;
1111+
topts.src_index = the_repository->index;
1112+
topts.dst_index = &tmp_index;
1113+
topts.initial_checkout = is_index_unborn(the_repository->index);
1114+
topts.merge = 1;
1115+
topts.update = 1;
1116+
topts.dry_run = 1;
1117+
topts.quiet = 1;
1118+
topts.fn = twoway_merge;
1119+
1120+
init_tree_desc(&trees[0], &old_tree->object.oid,
1121+
old_tree->buffer, old_tree->size);
1122+
init_tree_desc(&trees[1], &new_tree->object.oid,
1123+
new_tree->buffer, new_tree->size);
1124+
1125+
ret = unpack_trees(2, trees, &topts);
1126+
discard_index(&tmp_index);
1127+
1128+
return ret != 0;
1129+
}
1130+
11601131
static int switch_branches(const struct checkout_opts *opts,
11611132
struct branch_info *new_branch_info)
11621133
{
@@ -1202,9 +1173,19 @@ static int switch_branches(const struct checkout_opts *opts,
12021173
do_merge = 0;
12031174
}
12041175

1176+
if (opts->merge) {
1177+
if (repo_read_index(the_repository) < 0)
1178+
die(_("index file corrupt"));
1179+
if (checkout_would_clobber_changes(&old_branch_info,
1180+
new_branch_info))
1181+
create_autostash_ref(the_repository,
1182+
"CHECKOUT_AUTOSTASH");
1183+
}
1184+
12051185
if (do_merge) {
12061186
ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
12071187
if (ret) {
1188+
apply_autostash_ref(the_repository, "CHECKOUT_AUTOSTASH");
12081189
branch_info_release(&old_branch_info);
12091190
return ret;
12101191
}
@@ -1215,6 +1196,23 @@ static int switch_branches(const struct checkout_opts *opts,
12151196

12161197
update_refs_for_switch(opts, &old_branch_info, new_branch_info);
12171198

1199+
if (opts->conflict_style >= 0) {
1200+
struct strbuf cfg = STRBUF_INIT;
1201+
strbuf_addf(&cfg, "merge.conflictStyle=%s",
1202+
conflict_style_name(opts->conflict_style));
1203+
git_config_push_parameter(cfg.buf);
1204+
strbuf_release(&cfg);
1205+
}
1206+
apply_autostash_ref(the_repository, "CHECKOUT_AUTOSTASH");
1207+
1208+
discard_index(the_repository->index);
1209+
if (repo_read_index(the_repository) < 0)
1210+
die(_("index file corrupt"));
1211+
1212+
if (!opts->discard_changes && !opts->quiet && new_branch_info->commit)
1213+
show_local_changes(&new_branch_info->commit->object,
1214+
&opts->diff_options);
1215+
12181216
ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);
12191217
branch_info_release(&old_branch_info);
12201218

sequencer.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4677,7 +4677,7 @@ static void create_autostash_internal(struct repository *r,
46774677
&oid, null_oid(the_hash_algo), 0, UPDATE_REFS_DIE_ON_ERR);
46784678
}
46794679

4680-
printf(_("Created autostash: %s\n"), buf.buf);
4680+
fprintf(stderr, _("Created autostash: %s\n"), buf.buf);
46814681
if (reset_head(r, &ropts) < 0)
46824682
die(_("could not reset --hard"));
46834683
discard_index(r->index);

0 commit comments

Comments
 (0)