{"thread":{"id":"24975","subject":"[RFC PATCH] Teach checkout the -n|--dry-run option","startedAt":"2010-09-04T20:19:50Z","lastAt":"2010-09-04T20:19:50Z","messageCount":1,"participants":["Jens Lehmann"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"149868","messageId":"4C82A9E6.6050407@web.de","threadId":"24975","inReplyTo":null,"subject":"[RFC PATCH] Teach checkout the -n|--dry-run option","fromName":"Jens Lehmann","fromEmail":"jens.lehmann@web.de","sentAt":"2010-09-04T20:19:50Z","receivedAt":"2010-09-04T20:19:50Z","isPatch":true,"sender":{"key":"jens.lehmann@web.de","avatar":"https://avatars.githubusercontent.com/u/135220?v=4"},"body":"This option can be used to determine if checking out a branch or commit\nwould touch files which are modified in the work tree. It doesn't change\nthe work tree contents or the index, so it can only tell if the checkout\nwould succeed using trivial merges.\n\nIt can neither be used when checking out only certain paths nor together\nwith the '-m' option.\n\nSigned-off-by: Jens Lehmann <Jens.Lehmann@web.de>\n---\n\n\nI need this new option for the recursive submodule checkout. Denying a\ncheckout inside a submodule just because it has modifications is a too\nlimiting condition (and it is way too expensive to check the whole tree,\njust looking at those paths going to be changed by the checkout should\nbe much faster, especially for large submodules).\n\nIMO the same behavior that applies for a checkout in the superproject\nshould apply for the checkout inside the submodule: no local changes\nmay be overwritten by the checkout (and the HEAD must match the commit\nrecorded in the superproject, but that is handled elsewhere).\n\nTo be able to test that, I added the -n|--dry-run option to checkout.\nI think I found all relevant places, but a few more eyeballs are highly\nappreciated as I am not very familiar with this part of the code.\n\nA thing I'm not so sure about is the output of show_local_changes(),\nwith -n it doesn't show the changes as they would have been *after* the\n- not executed - checkout but *without* having done it.\nIs that a problem here?\n\n\n Documentation/git-checkout.txt |   13 +++++++++++--\n builtin/checkout.c             |   20 +++++++++++++++-----\n t/t2018-checkout-branch.sh     |   23 +++++++++++++++++++++++\n 3 files changed, 49 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex f88e997..32c5c77 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -8,8 +8,8 @@ git-checkout - Checkout a branch or paths to the working tree\n SYNOPSIS\n --------\n [verse]\n-'git checkout' [-q] [-f] [-m] [<branch>]\n-'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n+'git checkout' [-q] [-f] [-m|-n] [<branch>]\n+'git checkout' [-q] [-f] [-m|-n] [[-b|-B|--orphan] <new_branch>] [<start_point>]\n 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...\n 'git checkout' --patch [<tree-ish>] [--] [<paths>...]\n\n@@ -77,6 +77,13 @@ OPTIONS\n When checking out paths from the index, do not fail upon unmerged\n entries; instead, unmerged entries are ignored.\n\n+-n::\n+--dry-run::\n+\tDon't really checkout the file(s), just check if it would succeed\n+\tusing only trivial merges. Using this option will not touch the work\n+\ttree or the index. The command will fail when the checkout would change\n+\tlocally modified files. This option can not be used together with `-m`.\n+\n --ours::\n --theirs::\n \tWhen checking out paths from the index, check out stage #2\n@@ -158,6 +165,8 @@ should result in deletion of the path).\n +\n When checking out paths from the index, this option lets you recreate\n the conflicted merge in the specified paths.\n++\n+This option does not work together with '-n'.\n\n --conflict=<style>::\n \tThe same as --merge option above, but changes the way the\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f365169..ba6ca24 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -32,6 +32,7 @@ struct checkout_opts {\n \tint force;\n \tint writeout_stage;\n \tint writeout_error;\n+\tint dry_run;\n\n \t/* not set by parse_options */\n \tint branch_exists;\n@@ -370,6 +371,8 @@ static int merge_working_tree(struct checkout_opts *opts,\n\n \tresolve_undo_clear();\n \tif (opts->force) {\n+\t\tif (opts->dry_run)\n+\t\t\treturn 0;\n \t\tret = reset_tree(new->commit->tree, opts, 1);\n \t\tif (ret)\n \t\t\treturn ret;\n@@ -394,7 +397,7 @@ static int merge_working_tree(struct checkout_opts *opts,\n\n \t\t/* 2-way merge to the new branch */\n \t\ttopts.initial_checkout = is_cache_unborn();\n-\t\ttopts.update = 1;\n+\t\ttopts.update = !opts->dry_run;\n \t\ttopts.merge = 1;\n \t\ttopts.gently = opts->merge && old->commit;\n \t\ttopts.verbose_update = !opts->quiet;\n@@ -469,8 +472,8 @@ static int merge_working_tree(struct checkout_opts *opts,\n \t\t}\n \t}\n\n-\tif (write_cache(newfd, active_cache, active_nr) ||\n-\t    commit_locked_index(lock_file))\n+\tif (!opts->dry_run && (write_cache(newfd, active_cache, active_nr) ||\n+\t\t\t       commit_locked_index(lock_file)))\n \t\tdie(\"unable to write new index file\");\n\n \tif (!opts->force && !opts->quiet)\n@@ -603,7 +606,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)\n \t}\n\n \tret = merge_working_tree(opts, &old, new);\n-\tif (ret)\n+\tif (ret || opts->dry_run)\n \t\treturn ret;\n\n \t/*\n@@ -704,6 +707,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOLEAN('m', \"merge\", &opts.merge, \"merge\"),\n \t\tOPT_STRING(0, \"conflict\", &conflict_style, \"style\",\n \t\t\t   \"conflict style (merge or diff3)\"),\n+\t\tOPT_BOOLEAN('n', \"dry-run\", &opts.dry_run, \"show if the checkout would fail because it touches locally modified files\"),\n \t\tOPT_BOOLEAN('p', \"patch\", &patch_mode, \"select hunks interactively\"),\n \t\t{ OPTION_BOOLEAN, 0, \"guess\", &dwim_new_local_branch, NULL,\n \t\t  \"second guess 'git checkout no-such-branch'\",\n@@ -731,7 +735,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \tif (opts.new_branch_force)\n \t\topts.new_branch = opts.new_branch_force;\n\n-\tif (patch_mode && (opts.track > 0 || opts.new_branch\n+\tif (patch_mode && (opts.track > 0 || opts.new_branch || opts.dry_run\n \t\t\t   || opts.new_branch_log || opts.merge || opts.force))\n \t\tdie (\"--patch is incompatible with all other options\");\n\n@@ -766,6 +770,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)\n \tif (opts.force && opts.merge)\n \t\tdie(\"git checkout: -f and -m are incompatible\");\n\n+\tif (opts.merge && opts.dry_run)\n+\t\tdie(\"git checkout: -m and -n are incompatible\");\n+\n \t/*\n \t * case 1: git checkout <ref> -- [<paths>]\n \t *\n@@ -890,6 +897,9 @@ no_reference:\n \t\tif (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)\n \t\t\tdie(\"git checkout: --ours/--theirs, --force and --merge are incompatible when\\nchecking out of the index.\");\n\n+\t\tif (opts.dry_run)\n+\t\t\tdie(\"git checkout: updating paths is incompatible with -n.\");\n+\n \t\treturn checkout_paths(source_tree, pathspec, &opts);\n \t}\n\ndiff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh\nindex fa69016..61c82ef 100755\n--- a/t/t2018-checkout-branch.sh\n+++ b/t/t2018-checkout-branch.sh\n@@ -169,4 +169,27 @@ test_expect_success 'checkout -f -B to an existing branch with mergeable changes\n \ttest_must_fail test_dirty_mergeable\n '\n\n+test_expect_success 'checkout -n/--dry-run does not change work tree or index' '\n+\techo precious >expect &&\n+\techo precious >file1 &&\n+\ttest_must_fail git checkout -n branch1 &&\n+\ttest_cmp expect file1 &&\n+\tgit checkout --dry-run -f branch1 &&\n+\ttest_cmp expect file1 &&\n+\ttest_must_fail git checkout -b new_branch --dry-run branch1 &&\n+\ttest_cmp expect file1 &&\n+\tgit checkout -b new_branch -n -f branch1 &&\n+\ttest_cmp expect file1 &&\n+\tgit checkout -f branch1 &&\n+\tgit status -s -uno > actual &&\n+\t! test -s actual &&\n+\tgit checkout -n HEAD^ &&\n+\tgit status -s -uno > actual &&\n+\t! test -s actual\n+'\n+\n+test_expect_success 'checkout -n/--dry-run is not allowed when checking out only certain paths' '\n+\ttest_must_fail git checkout -n branch1 file1 &&\n+\ttest_must_fail git checkout -n HEAD file1\n+'\n test_done\n-- \n1.7.2.2.570.ga3f70\n"}]}