From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS31976 209.132.180.0/23 X-Spam-Status: No, score=-2.6 required=3.0 tests=AWL,BAYES_00, FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM,HEADER_FROM_DIFFERENT_DOMAINS, RCVD_IN_DNSWL_HI,T_RP_MATCHES_RCVD shortcircuit=no autolearn=no autolearn_force=no version=3.4.0 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by dcvr.yhbt.net (Postfix) with ESMTP id A368A1F404 for ; Fri, 23 Feb 2018 12:36:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751436AbeBWMgF (ORCPT ); Fri, 23 Feb 2018 07:36:05 -0500 Received: from mout.gmx.net ([212.227.17.20]:47135 "EHLO mout.gmx.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751406AbeBWMgC (ORCPT ); Fri, 23 Feb 2018 07:36:02 -0500 Received: from [192.168.0.129] ([37.201.195.115]) by mail.gmx.com (mrgmx101 [212.227.17.168]) with ESMTPSA (Nemesis) id 0MgGDK-1f48ha3Xew-00NijY; Fri, 23 Feb 2018 13:35:53 +0100 Date: Fri, 23 Feb 2018 13:35:37 +0100 (STD) From: Johannes Schindelin X-X-Sender: virtualbox@MININT-6BKU6QN.europe.corp.microsoft.com To: git@vger.kernel.org cc: Junio C Hamano , Jacob Keller , Stefan Beller , Philip Oakley , Eric Sunshine , Phillip Wood Subject: [PATCH v4 00/12] rebase -i: offer to recreate merge commits In-Reply-To: Message-ID: References: User-Agent: Alpine 2.21.1 (DEB 209 2017-03-23) MIME-Version: 1.0 Content-Type: text/plain; charset=US-ASCII X-Provags-ID: V03:K0:3ZzQcv0Vc7SVnok/GOuxQGGddkmqFPSFAaqTtaQKoq17mlf2Qtc Ueo62RobIpCiE7fNoUOhCLH5hwFnZplarNro55CWspg7e9L9i8dFnm9O5z/g6lfonOIy1M9 NFkoB4NLUIGaovMJ9uCBG+CAty4cUk23dGC74Ti4F6j7gVxCquSCOTioT25kA515f4s9WKu GfenqvicolOTx9DAYEYuw== X-UI-Out-Filterresults: notjunk:1;V01:K0:EFLAwuGanX0=:M2SYs4CscSDuvQiOkyM/FW Leen1/LTUF0pGShS7OMnx+WWDqNZkueBlPE7Pl+wmkQTAWY28XRcqKeE5R0/ERg309T04/3Ba jSv1o0vrCDmucyLHLkov8cBAAfKVNb2dItYf6kWhcIuHJMPx8dEMkK/9v3xYTVIWS5d99Eymj fT+9GjNr0lLEWlMow3kHYGYlUnrQKI2FQAVrTi+y83WzvBn3SK6wZX3vTBIkTII5k7QxQ4tdR M7byzftX2Mo0SG+yRxu0Ppux/k+2tTb1vThQ35MZxO2S5RBDVBC5Nb0rLNSH3yz6WTTIdiaQJ fHUWZIUM872oBMrnnbQ3yTky6/flQmQSMlXccV/RE4h/UYmiqShP5ndbICRSys2Z6X1PdHyTc B7rq/RoiJCoxlw64GAL2BP5P0kgEzJU5OygEkOOvMOdJ12NLvpuC6Xile7o1yFKD+Hd2r1Agv TnqIEqjAh5GHHDzrdgTdsJDLIqrHXDUf9AxYg4sMurCsvjpa9PBwn2EK9ad08n+Mud5vr5PMG l2sbjgBvmEGeftVdm9hzO+UQzwHLQHwjIKQidSyYqota5s71OiW1MJfXo1ca1EGkijqYs262/ MaAzSSI7txwIEAFyqhoGO2a4XAGPwF8Ybrz+RLRFOuEXjFlTelj6T8w8JljA6YetCKVuXEdkq XFBzOi2+X/bS0bsoExnz8rA6tlpFemrDGOB7C2Mirl1cF+RTHAYKgf76F2Ri/tL9Ud2wZMSeC G+7fMXYahU6z+N3gCqVgGDnmou6rLWvFMVydcq0PL0GHU5qMD12z39fYC4NgYlQzSgWkt940k edwP5QnCvOAvq7J/c06Qvtop2CvAtvtErp0new4wyX3xA0JtC8= Sender: git-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: git@vger.kernel.org Once upon a time, I dreamt of an interactive rebase that would not flatten branch structure, but instead recreate the commit topology faithfully. My original attempt was --preserve-merges, but that design was so limited that I did not even enable it in interactive mode. Subsequently, it *was* enabled in interactive mode, with the predictable consequences: as the --preserve-merges design does not allow for specifying the parents of merge commits explicitly, all the new commits' parents are defined *implicitly* by the previous commit history, and hence it is *not possible to even reorder commits*. This design flaw cannot be fixed. Not without a complete re-design, at least. This patch series offers such a re-design. Think of --recreate-merges as "--preserve-merges done right". It introduces new verbs for the todo list, `label`, `reset` and `merge`. For a commit topology like this: A - B - C \ / D the generated todo list would look like this: # branch D pick 0123 A label branch-point pick 1234 D label D reset branch-point pick 2345 B merge -C 3456 D # C There are more patches in the pipeline, based on this patch series, but left for later in the interest of reviewable patch series: one mini series to use the sequencer even for `git rebase -i --root`, and another one to add support for octopus merges to --recreate-merges. Changes since v3: - fixed a grammar error in "introduce the `merge` command"'s commit message. - fixed a couple of resource leaks in safe_append() and do_reset(), pointed out by Eric Sunshine. Johannes Schindelin (11): sequencer: avoid using errno clobbered by rollback_lock_file() sequencer: make rearrange_squash() a bit more obvious sequencer: introduce new commands to reset the revision sequencer: introduce the `merge` command sequencer: fast-forward merge commits, if possible rebase-helper --make-script: introduce a flag to recreate merges rebase: introduce the --recreate-merges option sequencer: make refs generated by the `label` command worktree-local sequencer: handle post-rewrite for merge commands pull: accept --rebase=recreate to recreate the branch topology rebase -i: introduce --recreate-merges=[no-]rebase-cousins Stefan Beller (1): git-rebase--interactive: clarify arguments Documentation/config.txt | 8 + Documentation/git-pull.txt | 5 +- Documentation/git-rebase.txt | 14 +- builtin/pull.c | 14 +- builtin/rebase--helper.c | 13 +- builtin/remote.c | 2 + contrib/completion/git-completion.bash | 4 +- git-rebase--interactive.sh | 22 +- git-rebase.sh | 16 + refs.c | 3 +- sequencer.c | 742 ++++++++++++++++++++++++++++++++- sequencer.h | 7 + t/t3430-rebase-recreate-merges.sh | 208 +++++++++ 13 files changed, 1027 insertions(+), 31 deletions(-) create mode 100755 t/t3430-rebase-recreate-merges.sh base-commit: e3a80781f5932f5fea12a49eb06f3ade4ed8945c Published-As: https://github.com/dscho/git/releases/tag/recreate-merges-v4 Fetch-It-Via: git fetch https://github.com/dscho/git recreate-merges-v4 Interdiff vs v3: diff --git a/Documentation/config.txt b/Documentation/config.txt index f57e9cf10ca..8c9adea0d0c 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1058,6 +1058,10 @@ branch..rebase:: "git pull" is run. See "pull.rebase" for doing this in a non branch-specific manner. + +When recreate, also pass `--recreate-merges` along to 'git rebase' +so that locally committed merge commits will not be flattened +by running 'git pull'. ++ When preserve, also pass `--preserve-merges` along to 'git rebase' so that locally committed merge commits will not be flattened by running 'git pull'. @@ -2607,6 +2611,10 @@ pull.rebase:: pull" is run. See "branch..rebase" for setting this on a per-branch basis. + +When recreate, also pass `--recreate-merges` along to 'git rebase' +so that locally committed merge commits will not be flattened +by running 'git pull'. ++ When preserve, also pass `--preserve-merges` along to 'git rebase' so that locally committed merge commits will not be flattened by running 'git pull'. diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index ce05b7a5b13..b4f9f057ea9 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -101,13 +101,16 @@ Options related to merging include::merge-options.txt[] -r:: ---rebase[=false|true|preserve|interactive]:: +--rebase[=false|true|recreate|preserve|interactive]:: When true, rebase the current branch on top of the upstream branch after fetching. If there is a remote-tracking branch corresponding to the upstream branch and the upstream branch was rebased since last fetched, the rebase uses that information to avoid rebasing non-local changes. + +When set to recreate, rebase with the `--recreate-merges` option passed +to `git rebase` so that locally created merge commits will not be flattened. ++ When set to preserve, rebase with the `--preserve-merges` option passed to `git rebase` so that locally created merge commits will not be flattened. + diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index d713951b86a..c5a77599c47 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -373,6 +373,17 @@ The commit list format can be changed by setting the configuration option rebase.instructionFormat. A customized instruction format will automatically have the long commit hash prepended to the format. +--recreate-merges[=(rebase-cousins|no-rebase-cousins)]:: + Recreate merge commits instead of flattening the history by replaying + merges. Merge conflict resolutions or manual amendments to merge + commits are not recreated automatically, but have to be recreated + manually. ++ +By default, or when `no-rebase-cousins` was specified, commits which do not +have `` as direct ancestor keep their original branch point. +If the `rebase-cousins` mode is turned on, such commits are rebased onto +`` (or ``, if specified). + -p:: --preserve-merges:: Recreate merge commits instead of flattening the history by replaying @@ -775,7 +786,8 @@ BUGS The todo list presented by `--preserve-merges --interactive` does not represent the topology of the revision graph. Editing commits and rewording their commit messages should work fine, but attempts to -reorder commits tend to produce counterintuitive results. +reorder commits tend to produce counterintuitive results. Use +--recreate-merges for a more faithful representation. For example, an attempt to rearrange ------------ diff --git a/builtin/pull.c b/builtin/pull.c index 1876271af94..9da2cfa0bd3 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -27,14 +27,16 @@ enum rebase_type { REBASE_FALSE = 0, REBASE_TRUE, REBASE_PRESERVE, + REBASE_RECREATE, REBASE_INTERACTIVE }; /** * Parses the value of --rebase. If value is a false value, returns * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is - * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with - * a fatal error if fatal is true, otherwise returns REBASE_INVALID. + * "recreate", returns REBASE_RECREATE. If value is "preserve", returns + * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if + * fatal is true, otherwise returns REBASE_INVALID. */ static enum rebase_type parse_config_rebase(const char *key, const char *value, int fatal) @@ -47,6 +49,8 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value, return REBASE_TRUE; else if (!strcmp(value, "preserve")) return REBASE_PRESERVE; + else if (!strcmp(value, "recreate")) + return REBASE_RECREATE; else if (!strcmp(value, "interactive")) return REBASE_INTERACTIVE; @@ -130,7 +134,7 @@ static struct option pull_options[] = { /* Options passed to git-merge or git-rebase */ OPT_GROUP(N_("Options related to merging")), { OPTION_CALLBACK, 'r', "rebase", &opt_rebase, - "false|true|preserve|interactive", + "false|true|recreate|preserve|interactive", N_("incorporate changes by rebasing rather than merging"), PARSE_OPT_OPTARG, parse_opt_rebase }, OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, @@ -800,7 +804,9 @@ static int run_rebase(const struct object_id *curr_head, argv_push_verbosity(&args); /* Options passed to git-rebase */ - if (opt_rebase == REBASE_PRESERVE) + if (opt_rebase == REBASE_RECREATE) + argv_array_push(&args, "--recreate-merges"); + else if (opt_rebase == REBASE_PRESERVE) argv_array_push(&args, "--preserve-merges"); else if (opt_rebase == REBASE_INTERACTIVE) argv_array_push(&args, "--interactive"); diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index ad074705bb5..5d1f12de57b 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -12,8 +12,8 @@ static const char * const builtin_rebase_helper_usage[] = { int cmd_rebase__helper(int argc, const char **argv, const char *prefix) { struct replay_opts opts = REPLAY_OPTS_INIT; - unsigned flags = 0, keep_empty = 0; - int abbreviate_commands = 0; + unsigned flags = 0, keep_empty = 0, recreate_merges = 0; + int abbreviate_commands = 0, rebase_cousins = -1; enum { CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS, CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH, @@ -24,6 +24,9 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")), OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message, N_("allow commits with empty messages")), + OPT_BOOL(0, "recreate-merges", &recreate_merges, N_("recreate merge commits")), + OPT_BOOL(0, "rebase-cousins", &rebase_cousins, + N_("keep original branch points of cousins")), OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), CONTINUE), OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), @@ -57,8 +60,14 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0; flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; + flags |= recreate_merges ? TODO_LIST_RECREATE_MERGES : 0; + flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; + if (rebase_cousins >= 0 && !recreate_merges) + warning(_("--[no-]rebase-cousins has no effect without " + "--recreate-merges")); + if (command == CONTINUE && argc == 1) return !!sequencer_continue(&opts); if (command == ABORT && argc == 1) diff --git a/builtin/remote.c b/builtin/remote.c index d95bf904c3b..b7d0f7ce596 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -306,6 +306,8 @@ static int config_read_branches(const char *key, const char *value, void *cb) info->rebase = v; else if (!strcmp(value, "preserve")) info->rebase = NORMAL_REBASE; + else if (!strcmp(value, "recreate")) + info->rebase = NORMAL_REBASE; else if (!strcmp(value, "interactive")) info->rebase = INTERACTIVE_REBASE; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 88813e91244..3d44cb6890c 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2008,7 +2008,7 @@ _git_rebase () --*) __gitcomp " --onto --merge --strategy --interactive - --preserve-merges --stat --no-stat + --recreate-merges --preserve-merges --stat --no-stat --committer-date-is-author-date --ignore-date --ignore-whitespace --whitespace= --autosquash --no-autosquash @@ -2182,7 +2182,7 @@ _git_config () return ;; branch.*.rebase) - __gitcomp "false true preserve interactive" + __gitcomp "false true recreate preserve interactive" return ;; remote.pushdefault) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index a2659fea982..679d79e0d17 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -162,6 +162,12 @@ s, squash = use commit, but meld into previous commit f, fixup = like \"squash\", but discard this commit's log message x, exec = run command (the rest of the line) using shell d, drop = remove commit +l, label