{"thread":{"id":"21430","subject":"[PATCH] Teach 'git merge' and 'git pull' the option --ff-only","startedAt":"2009-10-29T22:08:31Z","lastAt":"2009-10-29T22:08:31Z","messageCount":1,"participants":["Björn Gustavsson"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"126342","messageId":"4AEA125F.9070704@gmail.com","threadId":"21430","inReplyTo":null,"subject":"[PATCH] Teach 'git merge' and 'git pull' the option --ff-only","fromName":"Björn Gustavsson","fromEmail":"bgustavsson@gmail.com","sentAt":"2009-10-29T22:08:31Z","receivedAt":"2009-10-29T22:08:31Z","isPatch":true,"sender":{"key":"bgustavsson@gmail.com","avatar":"https://avatars.githubusercontent.com/u/74840?v=4"},"body":"For convenience in scripts and aliases, add the option\n--ff-only to only allow fast-forwards (and up-to-date,\ndespite the name).\n\nDisallow combining --ff-only and --no-ff, since they\nflatly contradict each other.\n\nAllow all other options to be combined with --ff-only\n(i.e. do not add any code to handle them specially),\nincluding the following options:\n\n* --strategy (one or more): As long as the chosen merge\n  strategy results in up-to-date or fast-forward, the\n  command will succeed.\n\n* --squash: I cannot imagine why anyone would want to\n  squash commits only if fast-forward is possible, but I\n  also see no reason why it should not be allowed.\n\n* --message: The message will always be ignored, but I see\n  no need to explicitly disallow providing a redundant message.\n\nAcknowledgements: I did look at Yuval Kogman's earlier\npatch (107768 in gmane), mainly as shortcut to find my\nway in the code, but I did not copy anything directly.\n\nSigned-off-by: Björn Gustavsson <bgustavsson@gmail.com>\n---\nHere is a re-roll of my previous patch with improvements\nsuggested by Junio's review. Compared to the previous version,\nI have changed the following:\n\n* The documentation now clearly states that up-to-date\n  is also allowed.\n\n* The redundant and incorrect test to reject merge strategies\n  that do not allow fast-forward has been removed (along\n  with the corresponding test case).\n\n* I have added some background on the design decisions to\n  the commit message.\n\n* I have added two test cases to test --squash in combination\n  with --ff-only.\n\n Documentation/merge-options.txt |    5 ++++\n builtin-merge.c                 |   11 +++++++++-\n git-pull.sh                     |    7 ++++-\n t/t7600-merge.sh                |   41 +++++++++++++++++++++++++++++++++++++++\n 4 files changed, 61 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt\nindex adadf8e..27a9a84 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -60,6 +60,11 @@\n \ta fast-forward, only update the branch pointer. This is\n \tthe default behavior of git-merge.\n \n+--ff-only::\n+\tRefuse to merge and exit with a non-zero status unless the\n+\tcurrent `HEAD` is already up-to-date or the merge can be\n+\tresolved as a fast-forward.\n+\n -s <strategy>::\n --strategy=<strategy>::\n \tUse the given merge strategy; can be supplied more than\ndiff --git a/builtin-merge.c b/builtin-merge.c\nindex b6b8428..5e8c4b5 100644\n--- a/builtin-merge.c\n+++ b/builtin-merge.c\n@@ -43,6 +43,7 @@ static const char * const builtin_merge_usage[] = {\n \n static int show_diffstat = 1, option_log, squash;\n static int option_commit = 1, allow_fast_forward = 1;\n+static int fast_forward_only;\n static int allow_trivial = 1, have_message;\n static struct strbuf merge_msg;\n static struct commit_list *remoteheads;\n@@ -167,6 +168,8 @@ static struct option builtin_merge_options[] = {\n \t\t\"perform a commit if the merge succeeds (default)\"),\n \tOPT_BOOLEAN(0, \"ff\", &allow_fast_forward,\n \t\t\"allow fast forward (default)\"),\n+\tOPT_BOOLEAN(0, \"ff-only\", &fast_forward_only,\n+\t\t\"abort if fast forward is not possible\"),\n \tOPT_CALLBACK('s', \"strategy\", &use_strategies, \"strategy\",\n \t\t\"merge strategy to use\", option_parse_strategy),\n \tOPT_CALLBACK('m', \"message\", &merge_msg, \"message\",\n@@ -874,6 +877,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\toption_commit = 0;\n \t}\n \n+\tif (!allow_fast_forward && fast_forward_only)\n+\t\tdie(\"You cannot combine --no-ff with --ff-only.\");\n+\n \tif (!argc)\n \t\tusage_with_options(builtin_merge_usage,\n \t\t\tbuiltin_merge_options);\n@@ -1040,7 +1046,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t * only one common.\n \t\t */\n \t\trefresh_cache(REFRESH_QUIET);\n-\t\tif (allow_trivial) {\n+\t\tif (allow_trivial && !fast_forward_only) {\n \t\t\t/* See if it is really trivial. */\n \t\t\tgit_committer_info(IDENT_ERROR_ON_NO_NAME);\n \t\t\tprintf(\"Trying really trivial in-index merge...\\n\");\n@@ -1079,6 +1085,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t}\n \t}\n \n+\tif (fast_forward_only)\n+\t\tdie(\"Not possible to fast forward, aborting.\");\n+\n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_ERROR_ON_NO_NAME);\n \ndiff --git a/git-pull.sh b/git-pull.sh\nindex fc78592..37f3d93 100755\n--- a/git-pull.sh\n+++ b/git-pull.sh\n@@ -16,7 +16,8 @@ cd_to_toplevel\n test -z \"$(git ls-files -u)\" ||\n \tdie \"You are in the middle of a conflicted merge.\"\n \n-strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity=\n+strategy_args= diffstat= no_commit= squash= no_ff= ff_only=\n+log_arg= verbosity=\n curr_branch=$(git symbolic-ref -q HEAD)\n curr_branch_short=$(echo \"$curr_branch\" | sed \"s|refs/heads/||\")\n rebase=$(git config --bool branch.$curr_branch_short.rebase)\n@@ -45,6 +46,8 @@ do\n \t\tno_ff=--ff ;;\n \t--no-ff)\n \t\tno_ff=--no-ff ;;\n+\t--ff-only)\n+\t\tff_only=--ff-only ;;\n \t-s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\\\n \t\t--strateg=*|--strategy=*|\\\n \t-s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)\n@@ -215,5 +218,5 @@ merge_name=$(git fmt-merge-msg $log_arg <\"$GIT_DIR/FETCH_HEAD\") || exit\n test true = \"$rebase\" &&\n \texec git-rebase $diffstat $strategy_args --onto $merge_head \\\n \t${oldremoteref:-$merge_head}\n-exec git-merge $diffstat $no_commit $squash $no_ff $log_arg $strategy_args \\\n+exec git-merge $diffstat $no_commit $squash $no_ff $ff_only $log_arg $strategy_args \\\n \t\"$merge_name\" HEAD $merge_head $verbosity\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex e5b210b..57f6d2b 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -243,6 +243,16 @@ test_expect_success 'merge c0 with c1' '\n \n test_debug 'gitk --all'\n \n+test_expect_success 'merge c0 with c1 with --ff-only' '\n+\tgit reset --hard c0 &&\n+\tgit merge --ff-only c1 &&\n+\tgit merge --ff-only HEAD c0 c1 &&\n+\tverify_merge file result.1 &&\n+\tverify_head \"$c1\"\n+'\n+\n+test_debug 'gitk --all'\n+\n test_expect_success 'merge c1 with c2' '\n \tgit reset --hard c1 &&\n \ttest_tick &&\n@@ -263,6 +273,14 @@ test_expect_success 'merge c1 with c2 and c3' '\n \n test_debug 'gitk --all'\n \n+test_expect_success 'failing merges with --ff-only' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\ttest_must_fail git merge --ff-only c2 &&\n+\ttest_must_fail git merge --ff-only c3 &&\n+\ttest_must_fail git merge --ff-only c2 c3\n+'\n+\n test_expect_success 'merge c0 with c1 (no-commit)' '\n \tgit reset --hard c0 &&\n \tgit merge --no-commit c1 &&\n@@ -303,6 +321,17 @@ test_expect_success 'merge c0 with c1 (squash)' '\n \n test_debug 'gitk --all'\n \n+test_expect_success 'merge c0 with c1 (squash, ff-only)' '\n+\tgit reset --hard c0 &&\n+\tgit merge --squash --ff-only c1 &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c0 &&\n+\tverify_no_mergehead &&\n+\tverify_diff squash.1 .git/SQUASH_MSG \"[OOPS] bad squash message\"\n+'\n+\n+test_debug 'gitk --all'\n+\n test_expect_success 'merge c1 with c2 (squash)' '\n \tgit reset --hard c1 &&\n \tgit merge --squash c2 &&\n@@ -314,6 +343,13 @@ test_expect_success 'merge c1 with c2 (squash)' '\n \n test_debug 'gitk --all'\n \n+test_expect_success 'unsuccesful merge of c1 with c2 (squash, ff-only)' '\n+\tgit reset --hard c1 &&\n+\ttest_must_fail git merge --squash --ff-only c2\n+'\n+\n+test_debug 'gitk --all'\n+\n test_expect_success 'merge c1 with c2 and c3 (squash)' '\n \tgit reset --hard c1 &&\n \tgit merge --squash c2 c3 &&\n@@ -432,6 +468,11 @@ test_expect_success 'combining --squash and --no-ff is refused' '\n \ttest_must_fail git merge --no-ff --squash c1\n '\n \n+test_expect_success 'combining --ff-only and --no-ff is refused' '\n+\ttest_must_fail git merge --ff-only --no-ff c1 &&\n+\ttest_must_fail git merge --no-ff --ff-only c1\n+'\n+\n test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '\n \tgit reset --hard c0 &&\n \tgit config branch.master.mergeoptions \"--no-ff\" &&\n-- \n1.6.5.1.69.g36942\n"}]}