2018-08-06 19:31:09 +00:00
|
|
|
/*
|
|
|
|
* "git rebase" builtin command
|
|
|
|
*
|
|
|
|
* Copyright (c) 2018 Pratik Karki
|
|
|
|
*/
|
|
|
|
|
2019-01-24 08:29:12 +00:00
|
|
|
#define USE_THE_INDEX_COMPATIBILITY_MACROS
|
2018-08-06 19:31:09 +00:00
|
|
|
#include "builtin.h"
|
|
|
|
#include "run-command.h"
|
|
|
|
#include "exec-cmd.h"
|
|
|
|
#include "argv-array.h"
|
|
|
|
#include "dir.h"
|
2018-08-06 19:31:11 +00:00
|
|
|
#include "packfile.h"
|
|
|
|
#include "refs.h"
|
|
|
|
#include "quote.h"
|
|
|
|
#include "config.h"
|
|
|
|
#include "cache-tree.h"
|
|
|
|
#include "unpack-trees.h"
|
|
|
|
#include "lockfile.h"
|
2018-09-04 21:27:07 +00:00
|
|
|
#include "parse-options.h"
|
2018-09-04 21:27:09 +00:00
|
|
|
#include "commit.h"
|
2018-09-04 21:27:13 +00:00
|
|
|
#include "diff.h"
|
2018-09-04 21:27:14 +00:00
|
|
|
#include "wt-status.h"
|
2018-09-04 21:27:16 +00:00
|
|
|
#include "revision.h"
|
2018-11-02 02:04:53 +00:00
|
|
|
#include "commit-reach.h"
|
2018-08-08 15:06:17 +00:00
|
|
|
#include "rerere.h"
|
2018-11-12 23:26:01 +00:00
|
|
|
#include "branch.h"
|
2019-04-17 14:30:37 +00:00
|
|
|
#include "sequencer.h"
|
|
|
|
#include "rebase-interactive.h"
|
2018-09-04 21:27:07 +00:00
|
|
|
|
2020-04-07 14:27:59 +00:00
|
|
|
#define DEFAULT_REFLOG_ACTION "rebase"
|
|
|
|
|
2018-09-04 21:27:07 +00:00
|
|
|
static char const * const builtin_rebase_usage[] = {
|
rebase: teach rebase --keep-base
A common scenario is if a user is working on a topic branch and they
wish to make some changes to intermediate commits or autosquash, they
would run something such as
git rebase -i --onto master... master
in order to preserve the merge base. This is useful when contributing a
patch series to the Git mailing list, one often starts on top of the
current 'master'. While developing the patches, 'master' is also
developed further and it is sometimes not the best idea to keep rebasing
on top of 'master', but to keep the base commit as-is.
In addition to this, a user wishing to test individual commits in a
topic branch without changing anything may run
git rebase -x ./test.sh master... master
Since rebasing onto the merge base of the branch and the upstream is
such a common case, introduce the --keep-base option as a shortcut.
This allows us to rewrite the above as
git rebase -i --keep-base master
and
git rebase -x ./test.sh --keep-base master
respectively.
Add tests to ensure --keep-base works correctly in the normal case and
fails when there are multiple merge bases, both in regular and
interactive mode. Also, test to make sure conflicting options cause
rebase to fail. While we're adding test cases, add a missing
set_fake_editor call to 'rebase -i --onto master...side'.
While we're documenting the --keep-base option, change an instance of
"merge-base" to "merge base", which is the consistent spelling.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:38:06 +00:00
|
|
|
N_("git rebase [-i] [options] [--exec <cmd>] "
|
|
|
|
"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"),
|
2018-09-04 21:27:07 +00:00
|
|
|
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
|
|
|
|
"--root [<branch>]"),
|
|
|
|
N_("git rebase --continue | --abort | --skip | --edit-todo"),
|
|
|
|
NULL
|
|
|
|
};
|
2018-08-06 19:31:11 +00:00
|
|
|
|
2019-04-17 14:30:37 +00:00
|
|
|
static GIT_PATH_FUNC(path_squash_onto, "rebase-merge/squash-onto")
|
|
|
|
static GIT_PATH_FUNC(path_interactive, "rebase-merge/interactive")
|
2018-08-06 19:31:11 +00:00
|
|
|
static GIT_PATH_FUNC(apply_dir, "rebase-apply")
|
|
|
|
static GIT_PATH_FUNC(merge_dir, "rebase-merge")
|
|
|
|
|
|
|
|
enum rebase_type {
|
|
|
|
REBASE_UNSPECIFIED = -1,
|
2020-02-15 21:36:41 +00:00
|
|
|
REBASE_APPLY,
|
2018-08-06 19:31:11 +00:00
|
|
|
REBASE_MERGE,
|
|
|
|
REBASE_PRESERVE_MERGES
|
|
|
|
};
|
2018-08-06 19:31:09 +00:00
|
|
|
|
rebase (interactive-backend): fix handling of commits that become empty
As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design. This commit addresses
another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would
become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream
commits, as determined by `git log --cherry-mark ...`. Re-applying
these commits would result in an empty set of changes and a
duplicative commit message; i.e. these are commits that have
"already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean
cherry-picks of upstream commits, but which still become empty after
being rebased. This happens e.g. when a commit has changes which
are a strict subset of the changes in an upstream commit, or when
the changes of a commit can be found spread across or among several
upstream commits.
Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes. Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical). As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around. All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case. Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes. Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way. As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that
is empty from the beginning), I'd think that the current behaviour
is desireable one. "am" based rebase is solely to transplant an
existing history and want to stop much less than "interactive" one
whose purpose is to polish a series before making it publishable,
and asking for confirmation ("this has become empty--do you want to
drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases. Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}
with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:25 +00:00
|
|
|
enum empty_type {
|
|
|
|
EMPTY_UNSPECIFIED = -1,
|
|
|
|
EMPTY_DROP,
|
|
|
|
EMPTY_KEEP,
|
|
|
|
EMPTY_ASK
|
|
|
|
};
|
|
|
|
|
2018-08-06 19:31:11 +00:00
|
|
|
struct rebase_options {
|
|
|
|
enum rebase_type type;
|
rebase (interactive-backend): fix handling of commits that become empty
As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design. This commit addresses
another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would
become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream
commits, as determined by `git log --cherry-mark ...`. Re-applying
these commits would result in an empty set of changes and a
duplicative commit message; i.e. these are commits that have
"already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean
cherry-picks of upstream commits, but which still become empty after
being rebased. This happens e.g. when a commit has changes which
are a strict subset of the changes in an upstream commit, or when
the changes of a commit can be found spread across or among several
upstream commits.
Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes. Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical). As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around. All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case. Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes. Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way. As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that
is empty from the beginning), I'd think that the current behaviour
is desireable one. "am" based rebase is solely to transplant an
existing history and want to stop much less than "interactive" one
whose purpose is to polish a series before making it publishable,
and asking for confirmation ("this has become empty--do you want to
drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases. Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}
with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:25 +00:00
|
|
|
enum empty_type empty;
|
2020-02-15 21:36:39 +00:00
|
|
|
const char *default_backend;
|
2018-08-06 19:31:11 +00:00
|
|
|
const char *state_dir;
|
|
|
|
struct commit *upstream;
|
|
|
|
const char *upstream_name;
|
2018-09-04 21:27:10 +00:00
|
|
|
const char *upstream_arg;
|
2018-08-06 19:31:11 +00:00
|
|
|
char *head_name;
|
|
|
|
struct object_id orig_head;
|
|
|
|
struct commit *onto;
|
|
|
|
const char *onto_name;
|
|
|
|
const char *revisions;
|
2018-09-04 21:27:21 +00:00
|
|
|
const char *switch_to;
|
2019-07-31 15:18:49 +00:00
|
|
|
int root, root_with_onto;
|
2018-09-04 22:00:12 +00:00
|
|
|
struct object_id *squash_onto;
|
2018-08-06 19:31:11 +00:00
|
|
|
struct commit *restrict_revision;
|
|
|
|
int dont_finish_rebase;
|
2018-09-04 21:27:12 +00:00
|
|
|
enum {
|
|
|
|
REBASE_NO_QUIET = 1<<0,
|
2018-09-04 21:27:13 +00:00
|
|
|
REBASE_VERBOSE = 1<<1,
|
|
|
|
REBASE_DIFFSTAT = 1<<2,
|
2018-09-04 21:27:17 +00:00
|
|
|
REBASE_FORCE = 1<<3,
|
2018-09-04 21:27:18 +00:00
|
|
|
REBASE_INTERACTIVE_EXPLICIT = 1<<4,
|
2018-09-04 21:27:12 +00:00
|
|
|
} flags;
|
2018-11-14 16:25:29 +00:00
|
|
|
struct argv_array git_am_opts;
|
2018-08-08 15:06:16 +00:00
|
|
|
const char *action;
|
2018-09-04 21:59:50 +00:00
|
|
|
int signoff;
|
2018-09-04 21:59:52 +00:00
|
|
|
int allow_rerere_autoupdate;
|
2018-09-04 21:59:58 +00:00
|
|
|
int autosquash;
|
2018-09-04 22:00:00 +00:00
|
|
|
char *gpg_sign_opt;
|
2018-09-04 22:00:02 +00:00
|
|
|
int autostash;
|
2018-09-04 22:00:04 +00:00
|
|
|
char *cmd;
|
2018-09-04 22:00:05 +00:00
|
|
|
int allow_empty_message;
|
2018-09-04 22:00:07 +00:00
|
|
|
int rebase_merges, rebase_cousins;
|
2018-09-04 22:00:11 +00:00
|
|
|
char *strategy, *strategy_opts;
|
2018-08-08 15:36:33 +00:00
|
|
|
struct strbuf git_format_patch_opt;
|
2018-12-10 19:04:58 +00:00
|
|
|
int reschedule_failed_exec;
|
rebase: remove the rebase.useBuiltin setting
Remove the rebase.useBuiltin setting, which was added as an escape
hatch to disable the builtin version of rebase first released with Git
2.20.
See [1] for the initial implementation of rebase.useBuiltin, and [2]
and [3] for the documentation and corresponding
GIT_TEST_REBASE_USE_BUILTIN option.
Carrying the legacy version is a maintenance burden as seen in
7e097e27d3 ("legacy-rebase: backport -C<n> and --whitespace=<option>
checks", 2018-11-20) and 9aea5e9286 ("rebase: fix regression in
rebase.useBuiltin=false test mode", 2019-02-13). Since the built-in
version has been shown to be stable enough let's remove the legacy
version.
As noted in [3] having use_builtin_rebase() shell out to get its
config doesn't make any sense anymore, that was done for the purposes
of spawning the legacy rebase without having modified any global
state. Let's instead handle this case in rebase_config().
There's still a bunch of references to git-legacy-rebase in po/*.po,
but those will be dealt with in time by the i18n effort.
Even though this configuration variable only existed two releases
let's not entirely delete the entry from the docs, but note its
absence. Individual versions of git tend to be around for a while due
to distro packaging timelines, so e.g. if we're "lucky" a given
version like 2.21 might be installed on say OSX for half a decade.
That'll mean some people probably setting this in config, and then
when they later wonder if it's needed they can Google search the
config option name or check it in git-config. It also allows us to
refer to the docs from the warning for details.
1. 55071ea248 ("rebase: start implementing it as a builtin",
2018-08-07)
2. d8d0a546f0 ("rebase doc: document rebase.useBuiltin", 2018-11-14)
3. 62c23938fa ("tests: add a special setup where rebase.useBuiltin is
off", 2018-11-14)
3. https://public-inbox.org/git/nycvar.QRO.7.76.6.1903141544110.41@tvgsbejvaqbjf.bet/
Acked-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-03-18 11:01:52 +00:00
|
|
|
int use_legacy_rebase;
|
2018-08-06 19:31:11 +00:00
|
|
|
};
|
|
|
|
|
2019-04-17 14:30:41 +00:00
|
|
|
#define REBASE_OPTIONS_INIT { \
|
|
|
|
.type = REBASE_UNSPECIFIED, \
|
rebase (interactive-backend): fix handling of commits that become empty
As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design. This commit addresses
another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would
become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream
commits, as determined by `git log --cherry-mark ...`. Re-applying
these commits would result in an empty set of changes and a
duplicative commit message; i.e. these are commits that have
"already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean
cherry-picks of upstream commits, but which still become empty after
being rebased. This happens e.g. when a commit has changes which
are a strict subset of the changes in an upstream commit, or when
the changes of a commit can be found spread across or among several
upstream commits.
Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes. Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical). As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around. All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case. Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes. Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way. As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that
is empty from the beginning), I'd think that the current behaviour
is desireable one. "am" based rebase is solely to transplant an
existing history and want to stop much less than "interactive" one
whose purpose is to polish a series before making it publishable,
and asking for confirmation ("this has become empty--do you want to
drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases. Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}
with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:25 +00:00
|
|
|
.empty = EMPTY_UNSPECIFIED, \
|
2020-02-15 21:36:40 +00:00
|
|
|
.default_backend = "merge", \
|
2019-04-17 14:30:41 +00:00
|
|
|
.flags = REBASE_NO_QUIET, \
|
|
|
|
.git_am_opts = ARGV_ARRAY_INIT, \
|
|
|
|
.git_format_patch_opt = STRBUF_INIT \
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct replay_opts get_replay_opts(const struct rebase_options *opts)
|
|
|
|
{
|
|
|
|
struct replay_opts replay = REPLAY_OPTS_INIT;
|
|
|
|
|
|
|
|
replay.action = REPLAY_INTERACTIVE_REBASE;
|
|
|
|
sequencer_init_config(&replay);
|
|
|
|
|
|
|
|
replay.signoff = opts->signoff;
|
|
|
|
replay.allow_ff = !(opts->flags & REBASE_FORCE);
|
|
|
|
if (opts->allow_rerere_autoupdate)
|
|
|
|
replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
|
|
|
|
replay.allow_empty = 1;
|
|
|
|
replay.allow_empty_message = opts->allow_empty_message;
|
rebase (interactive-backend): fix handling of commits that become empty
As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design. This commit addresses
another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would
become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream
commits, as determined by `git log --cherry-mark ...`. Re-applying
these commits would result in an empty set of changes and a
duplicative commit message; i.e. these are commits that have
"already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean
cherry-picks of upstream commits, but which still become empty after
being rebased. This happens e.g. when a commit has changes which
are a strict subset of the changes in an upstream commit, or when
the changes of a commit can be found spread across or among several
upstream commits.
Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes. Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical). As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around. All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case. Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes. Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way. As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that
is empty from the beginning), I'd think that the current behaviour
is desireable one. "am" based rebase is solely to transplant an
existing history and want to stop much less than "interactive" one
whose purpose is to polish a series before making it publishable,
and asking for confirmation ("this has become empty--do you want to
drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases. Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}
with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:25 +00:00
|
|
|
replay.drop_redundant_commits = (opts->empty == EMPTY_DROP);
|
|
|
|
replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP);
|
2020-02-15 21:36:28 +00:00
|
|
|
replay.quiet = !(opts->flags & REBASE_NO_QUIET);
|
2019-04-17 14:30:41 +00:00
|
|
|
replay.verbose = opts->flags & REBASE_VERBOSE;
|
|
|
|
replay.reschedule_failed_exec = opts->reschedule_failed_exec;
|
|
|
|
replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
|
|
|
|
replay.strategy = opts->strategy;
|
2019-04-17 14:30:42 +00:00
|
|
|
if (opts->strategy_opts)
|
2020-01-12 20:27:41 +00:00
|
|
|
parse_strategy_opts(&replay, opts->strategy_opts);
|
2019-04-17 14:30:41 +00:00
|
|
|
|
2019-11-24 17:43:31 +00:00
|
|
|
if (opts->squash_onto) {
|
|
|
|
oidcpy(&replay.squash_onto, opts->squash_onto);
|
|
|
|
replay.have_squash_onto = 1;
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:41 +00:00
|
|
|
return replay;
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:43 +00:00
|
|
|
enum action {
|
|
|
|
ACTION_NONE = 0,
|
|
|
|
ACTION_CONTINUE,
|
|
|
|
ACTION_SKIP,
|
|
|
|
ACTION_ABORT,
|
|
|
|
ACTION_QUIT,
|
|
|
|
ACTION_EDIT_TODO,
|
|
|
|
ACTION_SHOW_CURRENT_PATCH,
|
|
|
|
ACTION_SHORTEN_OIDS,
|
|
|
|
ACTION_EXPAND_OIDS,
|
|
|
|
ACTION_CHECK_TODO_LIST,
|
|
|
|
ACTION_REARRANGE_SQUASH,
|
|
|
|
ACTION_ADD_EXEC
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *action_names[] = { "undefined",
|
|
|
|
"continue",
|
|
|
|
"skip",
|
|
|
|
"abort",
|
|
|
|
"quit",
|
|
|
|
"edit_todo",
|
|
|
|
"show_current_patch" };
|
|
|
|
|
2019-04-17 14:30:37 +00:00
|
|
|
static int add_exec_commands(struct string_list *commands)
|
|
|
|
{
|
|
|
|
const char *todo_file = rebase_path_todo();
|
|
|
|
struct todo_list todo_list = TODO_LIST_INIT;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
|
|
|
|
return error_errno(_("could not read '%s'."), todo_file);
|
|
|
|
|
|
|
|
if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
|
|
|
|
&todo_list)) {
|
|
|
|
todo_list_release(&todo_list);
|
|
|
|
return error(_("unusable todo list: '%s'"), todo_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
todo_list_add_exec_commands(&todo_list, commands);
|
|
|
|
res = todo_list_write_to_file(the_repository, &todo_list,
|
|
|
|
todo_file, NULL, NULL, -1, 0);
|
|
|
|
todo_list_release(&todo_list);
|
|
|
|
|
|
|
|
if (res)
|
|
|
|
return error_errno(_("could not write '%s'."), todo_file);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rearrange_squash_in_todo_file(void)
|
|
|
|
{
|
|
|
|
const char *todo_file = rebase_path_todo();
|
|
|
|
struct todo_list todo_list = TODO_LIST_INIT;
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
|
|
|
|
return error_errno(_("could not read '%s'."), todo_file);
|
|
|
|
if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
|
|
|
|
&todo_list)) {
|
|
|
|
todo_list_release(&todo_list);
|
|
|
|
return error(_("unusable todo list: '%s'"), todo_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
res = todo_list_rearrange_squash(&todo_list);
|
|
|
|
if (!res)
|
|
|
|
res = todo_list_write_to_file(the_repository, &todo_list,
|
|
|
|
todo_file, NULL, NULL, -1, 0);
|
|
|
|
|
|
|
|
todo_list_release(&todo_list);
|
|
|
|
|
|
|
|
if (res)
|
|
|
|
return error_errno(_("could not write '%s'."), todo_file);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int transform_todo_file(unsigned flags)
|
|
|
|
{
|
|
|
|
const char *todo_file = rebase_path_todo();
|
|
|
|
struct todo_list todo_list = TODO_LIST_INIT;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
|
|
|
|
return error_errno(_("could not read '%s'."), todo_file);
|
|
|
|
|
|
|
|
if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
|
|
|
|
&todo_list)) {
|
|
|
|
todo_list_release(&todo_list);
|
|
|
|
return error(_("unusable todo list: '%s'"), todo_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
res = todo_list_write_to_file(the_repository, &todo_list, todo_file,
|
|
|
|
NULL, NULL, -1, flags);
|
|
|
|
todo_list_release(&todo_list);
|
|
|
|
|
|
|
|
if (res)
|
|
|
|
return error_errno(_("could not write '%s'."), todo_file);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int edit_todo_file(unsigned flags)
|
|
|
|
{
|
|
|
|
const char *todo_file = rebase_path_todo();
|
|
|
|
struct todo_list todo_list = TODO_LIST_INIT,
|
|
|
|
new_todo = TODO_LIST_INIT;
|
|
|
|
int res = 0;
|
|
|
|
|
|
|
|
if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
|
|
|
|
return error_errno(_("could not read '%s'."), todo_file);
|
|
|
|
|
|
|
|
strbuf_stripspace(&todo_list.buf, 1);
|
|
|
|
res = edit_todo_list(the_repository, &todo_list, &new_todo, NULL, NULL, flags);
|
|
|
|
if (!res && todo_list_write_to_file(the_repository, &new_todo, todo_file,
|
|
|
|
NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS)))
|
|
|
|
res = error_errno(_("could not write '%s'"), todo_file);
|
|
|
|
|
|
|
|
todo_list_release(&todo_list);
|
|
|
|
todo_list_release(&new_todo);
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:39 +00:00
|
|
|
static int get_revision_ranges(struct commit *upstream, struct commit *onto,
|
rebase -i: stop checking out the tip of the branch to rebase
One of the first things done when using a sequencer-based
rebase (ie. `rebase -i', `rebase -r', or `rebase -m') is to make a todo
list. This requires knowledge of the commit range to rebase. To get
the oid of the last commit of the range, the tip of the branch to rebase
is checked out with prepare_branch_to_be_rebased(), then the oid of the
head is read. After this, the tip of the branch is not even modified.
The `am' backend, on the other hand, does not check out the branch.
On big repositories, it's a performance penalty: with `rebase -i', the
user may have to wait before editing the todo list while git is
extracting the branch silently, and "quiet" rebases will be slower than
`am'.
Since we already have the oid of the tip of the branch in
`opts->orig_head', it's useless to switch to this commit.
This removes the call to prepare_branch_to_be_rebased() in
do_interactive_rebase(), and adds a `orig_head' parameter to
get_revision_ranges(). prepare_branch_to_be_rebased() is removed as it
is no longer used.
This introduces a visible change: as we do not switch on the tip of the
branch to rebase, no reflog entry is created at the beginning of the
rebase for it.
Unscientific performance measurements, performed on linux.git, are as
follow:
Before this patch:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50
real 0m8,940s
user 0m6,830s
sys 0m2,121s
After this patch:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50
real 0m1,834s
user 0m0,916s
sys 0m0,206s
Reported-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-24 15:05:00 +00:00
|
|
|
struct object_id *orig_head, const char **head_hash,
|
2019-04-17 14:30:37 +00:00
|
|
|
char **revisions, char **shortrevisions)
|
|
|
|
{
|
2019-04-17 14:30:39 +00:00
|
|
|
struct commit *base_rev = upstream ? upstream : onto;
|
|
|
|
const char *shorthead;
|
2019-04-17 14:30:37 +00:00
|
|
|
|
rebase -i: stop checking out the tip of the branch to rebase
One of the first things done when using a sequencer-based
rebase (ie. `rebase -i', `rebase -r', or `rebase -m') is to make a todo
list. This requires knowledge of the commit range to rebase. To get
the oid of the last commit of the range, the tip of the branch to rebase
is checked out with prepare_branch_to_be_rebased(), then the oid of the
head is read. After this, the tip of the branch is not even modified.
The `am' backend, on the other hand, does not check out the branch.
On big repositories, it's a performance penalty: with `rebase -i', the
user may have to wait before editing the todo list while git is
extracting the branch silently, and "quiet" rebases will be slower than
`am'.
Since we already have the oid of the tip of the branch in
`opts->orig_head', it's useless to switch to this commit.
This removes the call to prepare_branch_to_be_rebased() in
do_interactive_rebase(), and adds a `orig_head' parameter to
get_revision_ranges(). prepare_branch_to_be_rebased() is removed as it
is no longer used.
This introduces a visible change: as we do not switch on the tip of the
branch to rebase, no reflog entry is created at the beginning of the
rebase for it.
Unscientific performance measurements, performed on linux.git, are as
follow:
Before this patch:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50
real 0m8,940s
user 0m6,830s
sys 0m2,121s
After this patch:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50
real 0m1,834s
user 0m0,916s
sys 0m0,206s
Reported-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-24 15:05:00 +00:00
|
|
|
*head_hash = find_unique_abbrev(orig_head, GIT_MAX_HEXSZ);
|
2019-04-17 14:30:39 +00:00
|
|
|
*revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
|
|
|
|
*head_hash);
|
2019-04-17 14:30:37 +00:00
|
|
|
|
rebase -i: stop checking out the tip of the branch to rebase
One of the first things done when using a sequencer-based
rebase (ie. `rebase -i', `rebase -r', or `rebase -m') is to make a todo
list. This requires knowledge of the commit range to rebase. To get
the oid of the last commit of the range, the tip of the branch to rebase
is checked out with prepare_branch_to_be_rebased(), then the oid of the
head is read. After this, the tip of the branch is not even modified.
The `am' backend, on the other hand, does not check out the branch.
On big repositories, it's a performance penalty: with `rebase -i', the
user may have to wait before editing the todo list while git is
extracting the branch silently, and "quiet" rebases will be slower than
`am'.
Since we already have the oid of the tip of the branch in
`opts->orig_head', it's useless to switch to this commit.
This removes the call to prepare_branch_to_be_rebased() in
do_interactive_rebase(), and adds a `orig_head' parameter to
get_revision_ranges(). prepare_branch_to_be_rebased() is removed as it
is no longer used.
This introduces a visible change: as we do not switch on the tip of the
branch to rebase, no reflog entry is created at the beginning of the
rebase for it.
Unscientific performance measurements, performed on linux.git, are as
follow:
Before this patch:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50
real 0m8,940s
user 0m6,830s
sys 0m2,121s
After this patch:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50
real 0m1,834s
user 0m0,916s
sys 0m0,206s
Reported-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-24 15:05:00 +00:00
|
|
|
shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV);
|
2019-04-17 14:30:37 +00:00
|
|
|
|
|
|
|
if (upstream) {
|
|
|
|
const char *shortrev;
|
|
|
|
|
2019-04-17 14:30:39 +00:00
|
|
|
shortrev = find_unique_abbrev(&base_rev->object.oid,
|
|
|
|
DEFAULT_ABBREV);
|
2019-04-17 14:30:37 +00:00
|
|
|
|
|
|
|
*shortrevisions = xstrfmt("%s..%s", shortrev, shorthead);
|
|
|
|
} else
|
|
|
|
*shortrevisions = xstrdup(shorthead);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int init_basic_state(struct replay_opts *opts, const char *head_name,
|
2019-04-17 14:30:39 +00:00
|
|
|
struct commit *onto, const char *orig_head)
|
2019-04-17 14:30:37 +00:00
|
|
|
{
|
|
|
|
FILE *interactive;
|
|
|
|
|
2019-04-17 14:30:38 +00:00
|
|
|
if (!is_directory(merge_dir()) && mkdir_in_gitdir(merge_dir()))
|
|
|
|
return error_errno(_("could not create temporary %s"), merge_dir());
|
2019-04-17 14:30:37 +00:00
|
|
|
|
|
|
|
delete_reflog("REBASE_HEAD");
|
|
|
|
|
|
|
|
interactive = fopen(path_interactive(), "w");
|
|
|
|
if (!interactive)
|
|
|
|
return error_errno(_("could not mark as interactive"));
|
|
|
|
fclose(interactive);
|
|
|
|
|
|
|
|
return write_basic_state(opts, head_name, onto, orig_head);
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:42 +00:00
|
|
|
static void split_exec_commands(const char *cmd, struct string_list *commands)
|
|
|
|
{
|
|
|
|
if (cmd && *cmd) {
|
|
|
|
string_list_split(commands, cmd, '\n', -1);
|
|
|
|
|
|
|
|
/* rebase.c adds a new line to cmd after every command,
|
|
|
|
* so here the last command is always empty */
|
|
|
|
string_list_remove_empty_items(commands, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
|
2019-04-17 14:30:37 +00:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
const char *head_hash = NULL;
|
|
|
|
char *revisions = NULL, *shortrevisions = NULL;
|
|
|
|
struct argv_array make_script_args = ARGV_ARRAY_INIT;
|
|
|
|
struct todo_list todo_list = TODO_LIST_INIT;
|
2019-04-17 14:30:42 +00:00
|
|
|
struct replay_opts replay = get_replay_opts(opts);
|
|
|
|
struct string_list commands = STRING_LIST_INIT_DUP;
|
2019-04-17 14:30:37 +00:00
|
|
|
|
rebase -i: stop checking out the tip of the branch to rebase
One of the first things done when using a sequencer-based
rebase (ie. `rebase -i', `rebase -r', or `rebase -m') is to make a todo
list. This requires knowledge of the commit range to rebase. To get
the oid of the last commit of the range, the tip of the branch to rebase
is checked out with prepare_branch_to_be_rebased(), then the oid of the
head is read. After this, the tip of the branch is not even modified.
The `am' backend, on the other hand, does not check out the branch.
On big repositories, it's a performance penalty: with `rebase -i', the
user may have to wait before editing the todo list while git is
extracting the branch silently, and "quiet" rebases will be slower than
`am'.
Since we already have the oid of the tip of the branch in
`opts->orig_head', it's useless to switch to this commit.
This removes the call to prepare_branch_to_be_rebased() in
do_interactive_rebase(), and adds a `orig_head' parameter to
get_revision_ranges(). prepare_branch_to_be_rebased() is removed as it
is no longer used.
This introduces a visible change: as we do not switch on the tip of the
branch to rebase, no reflog entry is created at the beginning of the
rebase for it.
Unscientific performance measurements, performed on linux.git, are as
follow:
Before this patch:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50
real 0m8,940s
user 0m6,830s
sys 0m2,121s
After this patch:
$ time git rebase -m --onto v4.18 463fa44eec2fef50~ 463fa44eec2fef50
real 0m1,834s
user 0m0,916s
sys 0m0,206s
Reported-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Alban Gruin <alban.gruin@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-01-24 15:05:00 +00:00
|
|
|
if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head,
|
|
|
|
&head_hash, &revisions, &shortrevisions))
|
2019-04-17 14:30:37 +00:00
|
|
|
return -1;
|
|
|
|
|
2019-04-17 14:30:44 +00:00
|
|
|
if (init_basic_state(&replay,
|
|
|
|
opts->head_name ? opts->head_name : "detached HEAD",
|
|
|
|
opts->onto, head_hash)) {
|
2019-04-17 14:30:37 +00:00
|
|
|
free(revisions);
|
|
|
|
free(shortrevisions);
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:42 +00:00
|
|
|
if (!opts->upstream && opts->squash_onto)
|
2019-04-17 14:30:40 +00:00
|
|
|
write_file(path_squash_onto(), "%s\n",
|
2019-04-17 14:30:42 +00:00
|
|
|
oid_to_hex(opts->squash_onto));
|
2019-04-17 14:30:37 +00:00
|
|
|
|
|
|
|
argv_array_pushl(&make_script_args, "", revisions, NULL);
|
2019-04-17 14:30:42 +00:00
|
|
|
if (opts->restrict_revision)
|
2020-02-15 21:36:29 +00:00
|
|
|
argv_array_pushf(&make_script_args, "^%s",
|
|
|
|
oid_to_hex(&opts->restrict_revision->object.oid));
|
2019-04-17 14:30:37 +00:00
|
|
|
|
|
|
|
ret = sequencer_make_script(the_repository, &todo_list.buf,
|
|
|
|
make_script_args.argc, make_script_args.argv,
|
|
|
|
flags);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
error(_("could not generate todo list"));
|
|
|
|
else {
|
|
|
|
discard_cache();
|
|
|
|
if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
|
|
|
|
&todo_list))
|
|
|
|
BUG("unusable todo list");
|
|
|
|
|
2019-04-17 14:30:42 +00:00
|
|
|
split_exec_commands(opts->cmd, &commands);
|
|
|
|
ret = complete_action(the_repository, &replay, flags,
|
|
|
|
shortrevisions, opts->onto_name, opts->onto, head_hash,
|
|
|
|
&commands, opts->autosquash, &todo_list);
|
2019-04-17 14:30:37 +00:00
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:42 +00:00
|
|
|
string_list_clear(&commands, 0);
|
2019-04-17 14:30:37 +00:00
|
|
|
free(revisions);
|
|
|
|
free(shortrevisions);
|
|
|
|
todo_list_release(&todo_list);
|
|
|
|
argv_array_clear(&make_script_args);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
static int run_sequencer_rebase(struct rebase_options *opts,
|
2019-04-17 14:30:44 +00:00
|
|
|
enum action command)
|
|
|
|
{
|
|
|
|
unsigned flags = 0;
|
|
|
|
int abbreviate_commands = 0, ret = 0;
|
|
|
|
|
|
|
|
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
|
|
|
|
|
|
|
|
flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
|
|
|
|
flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
|
|
|
|
flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
|
2019-07-31 15:18:49 +00:00
|
|
|
flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0;
|
2019-04-17 14:30:44 +00:00
|
|
|
flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
|
|
|
|
|
|
|
|
switch (command) {
|
|
|
|
case ACTION_NONE: {
|
|
|
|
if (!opts->onto && !opts->upstream)
|
|
|
|
die(_("a base commit must be provided with --upstream or --onto"));
|
|
|
|
|
|
|
|
ret = do_interactive_rebase(opts, flags);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ACTION_SKIP: {
|
|
|
|
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
|
|
|
|
|
|
|
rerere_clear(the_repository, &merge_rr);
|
|
|
|
}
|
|
|
|
/* fallthrough */
|
|
|
|
case ACTION_CONTINUE: {
|
|
|
|
struct replay_opts replay_opts = get_replay_opts(opts);
|
|
|
|
|
|
|
|
ret = sequencer_continue(the_repository, &replay_opts);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ACTION_EDIT_TODO:
|
|
|
|
ret = edit_todo_file(flags);
|
|
|
|
break;
|
|
|
|
case ACTION_SHOW_CURRENT_PATCH: {
|
|
|
|
struct child_process cmd = CHILD_PROCESS_INIT;
|
|
|
|
|
|
|
|
cmd.git_cmd = 1;
|
|
|
|
argv_array_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL);
|
|
|
|
ret = run_command(&cmd);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ACTION_SHORTEN_OIDS:
|
|
|
|
case ACTION_EXPAND_OIDS:
|
|
|
|
ret = transform_todo_file(flags);
|
|
|
|
break;
|
|
|
|
case ACTION_CHECK_TODO_LIST:
|
|
|
|
ret = check_todo_list_from_file(the_repository);
|
|
|
|
break;
|
|
|
|
case ACTION_REARRANGE_SQUASH:
|
|
|
|
ret = rearrange_squash_in_todo_file();
|
|
|
|
break;
|
|
|
|
case ACTION_ADD_EXEC: {
|
|
|
|
struct string_list commands = STRING_LIST_INIT_DUP;
|
|
|
|
|
|
|
|
split_exec_commands(opts->cmd, &commands);
|
|
|
|
ret = add_exec_commands(&commands);
|
|
|
|
string_list_clear(&commands, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
BUG("invalid command '%d'", command);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
rebase (interactive-backend): make --keep-empty the default
Different rebase backends have different treatment for commits which
start empty (i.e. have no changes relative to their parent), and the
--keep-empty option was added at some point to allow adjusting behavior.
The handling of commits which start empty is actually quite similar to
commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,
2018-06-27), which pointed out that the behavior for various backends is
often more happenstance than design. The specific change made in that
commit is actually quite relevant as well and much of the logic there
directly applies here.
It makes a lot of sense in 'git commit' to error out on the creation of
empty commits, unless an override flag is provided. However, once
someone determines that there is a rare case that merits using the
manual override to create such a commit, it is somewhere between
annoying and harmful to have to take extra steps to keep such
intentional commits around. Granted, empty commits are quite rare,
which is why handling of them doesn't get considered much and folks tend
to defer to existing (accidental) behavior and assume there was a reason
for it, leading them to just add flags (--keep-empty in this case) that
allow them to override the bad defaults. Fix the interactive backend so
that --keep-empty is the default, much like we did with
--allow-empty-message. The am backend should also be fixed to have
--keep-empty semantics for commits that start empty, but that is not
included in this patch other than a testcase documenting the failure.
Note that there was one test in t3421 which appears to have been written
expecting --keep-empty to not be the default as correct behavior. This
test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
empty commits", 2013-06-06), which was part of a series focusing on
rebase topology and which had an interesting original cover letter at
https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
which noted
Your input especially appreciated on whether you agree with the
intent of the test cases.
and then went into a long example about how one of the many tests added
had several questions about whether it was correct. As such, I believe
most the tests in that series were about testing rebase topology with as
many different flags as possible and were not trying to state in general
how those flags should behave otherwise.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:24 +00:00
|
|
|
static int parse_opt_keep_empty(const struct option *opt, const char *arg,
|
|
|
|
int unset)
|
|
|
|
{
|
|
|
|
struct rebase_options *opts = opt->value;
|
|
|
|
|
|
|
|
BUG_ON_OPT_ARG(arg);
|
|
|
|
|
rebase (interactive-backend): fix handling of commits that become empty
As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design. This commit addresses
another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would
become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream
commits, as determined by `git log --cherry-mark ...`. Re-applying
these commits would result in an empty set of changes and a
duplicative commit message; i.e. these are commits that have
"already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean
cherry-picks of upstream commits, but which still become empty after
being rebased. This happens e.g. when a commit has changes which
are a strict subset of the changes in an upstream commit, or when
the changes of a commit can be found spread across or among several
upstream commits.
Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes. Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical). As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around. All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case. Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes. Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way. As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that
is empty from the beginning), I'd think that the current behaviour
is desireable one. "am" based rebase is solely to transplant an
existing history and want to stop much less than "interactive" one
whose purpose is to polish a series before making it publishable,
and asking for confirmation ("this has become empty--do you want to
drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases. Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}
with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:25 +00:00
|
|
|
/*
|
|
|
|
* If we ever want to remap --keep-empty to --empty=keep, insert:
|
|
|
|
* opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP;
|
|
|
|
*/
|
2020-02-15 21:36:41 +00:00
|
|
|
opts->type = REBASE_MERGE;
|
rebase (interactive-backend): make --keep-empty the default
Different rebase backends have different treatment for commits which
start empty (i.e. have no changes relative to their parent), and the
--keep-empty option was added at some point to allow adjusting behavior.
The handling of commits which start empty is actually quite similar to
commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,
2018-06-27), which pointed out that the behavior for various backends is
often more happenstance than design. The specific change made in that
commit is actually quite relevant as well and much of the logic there
directly applies here.
It makes a lot of sense in 'git commit' to error out on the creation of
empty commits, unless an override flag is provided. However, once
someone determines that there is a rare case that merits using the
manual override to create such a commit, it is somewhere between
annoying and harmful to have to take extra steps to keep such
intentional commits around. Granted, empty commits are quite rare,
which is why handling of them doesn't get considered much and folks tend
to defer to existing (accidental) behavior and assume there was a reason
for it, leading them to just add flags (--keep-empty in this case) that
allow them to override the bad defaults. Fix the interactive backend so
that --keep-empty is the default, much like we did with
--allow-empty-message. The am backend should also be fixed to have
--keep-empty semantics for commits that start empty, but that is not
included in this patch other than a testcase documenting the failure.
Note that there was one test in t3421 which appears to have been written
expecting --keep-empty to not be the default as correct behavior. This
test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
empty commits", 2013-06-06), which was part of a series focusing on
rebase topology and which had an interesting original cover letter at
https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
which noted
Your input especially appreciated on whether you agree with the
intent of the test cases.
and then went into a long example about how one of the many tests added
had several questions about whether it was correct. As such, I believe
most the tests in that series were about testing rebase topology with as
many different flags as possible and were not trying to state in general
how those flags should behave otherwise.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:24 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:37 +00:00
|
|
|
static const char * const builtin_rebase_interactive_usage[] = {
|
|
|
|
N_("git rebase--interactive [<options>]"),
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
|
|
|
|
{
|
2019-04-17 14:30:41 +00:00
|
|
|
struct rebase_options opts = REBASE_OPTIONS_INIT;
|
2019-04-17 14:30:40 +00:00
|
|
|
struct object_id squash_onto = null_oid;
|
2019-04-17 14:30:43 +00:00
|
|
|
enum action command = ACTION_NONE;
|
2019-04-17 14:30:37 +00:00
|
|
|
struct option options[] = {
|
2019-04-17 14:30:41 +00:00
|
|
|
OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
|
|
|
|
REBASE_FORCE),
|
rebase (interactive-backend): make --keep-empty the default
Different rebase backends have different treatment for commits which
start empty (i.e. have no changes relative to their parent), and the
--keep-empty option was added at some point to allow adjusting behavior.
The handling of commits which start empty is actually quite similar to
commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,
2018-06-27), which pointed out that the behavior for various backends is
often more happenstance than design. The specific change made in that
commit is actually quite relevant as well and much of the logic there
directly applies here.
It makes a lot of sense in 'git commit' to error out on the creation of
empty commits, unless an override flag is provided. However, once
someone determines that there is a rare case that merits using the
manual override to create such a commit, it is somewhere between
annoying and harmful to have to take extra steps to keep such
intentional commits around. Granted, empty commits are quite rare,
which is why handling of them doesn't get considered much and folks tend
to defer to existing (accidental) behavior and assume there was a reason
for it, leading them to just add flags (--keep-empty in this case) that
allow them to override the bad defaults. Fix the interactive backend so
that --keep-empty is the default, much like we did with
--allow-empty-message. The am backend should also be fixed to have
--keep-empty semantics for commits that start empty, but that is not
included in this patch other than a testcase documenting the failure.
Note that there was one test in t3421 which appears to have been written
expecting --keep-empty to not be the default as correct behavior. This
test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
empty commits", 2013-06-06), which was part of a series focusing on
rebase topology and which had an interesting original cover letter at
https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
which noted
Your input especially appreciated on whether you agree with the
intent of the test cases.
and then went into a long example about how one of the many tests added
had several questions about whether it was correct. As such, I believe
most the tests in that series were about testing rebase topology with as
many different flags as possible and were not trying to state in general
how those flags should behave otherwise.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:24 +00:00
|
|
|
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
|
|
|
|
N_("(DEPRECATED) keep empty commits"),
|
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
|
|
|
|
parse_opt_keep_empty },
|
2020-01-16 06:14:15 +00:00
|
|
|
OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message,
|
|
|
|
N_("allow commits with empty messages"),
|
|
|
|
PARSE_OPT_HIDDEN),
|
2019-04-17 14:30:41 +00:00
|
|
|
OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
|
|
|
|
OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
|
2019-04-17 14:30:37 +00:00
|
|
|
N_("keep original branch points of cousins")),
|
2019-04-17 14:30:41 +00:00
|
|
|
OPT_BOOL(0, "autosquash", &opts.autosquash,
|
2019-04-17 14:30:37 +00:00
|
|
|
N_("move commits that begin with squash!/fixup!")),
|
|
|
|
OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")),
|
2019-04-17 14:30:41 +00:00
|
|
|
OPT_BIT('v', "verbose", &opts.flags,
|
|
|
|
N_("display a diffstat of what changed upstream"),
|
|
|
|
REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
|
2019-04-17 14:30:37 +00:00
|
|
|
OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
|
2019-04-17 14:30:43 +00:00
|
|
|
ACTION_CONTINUE),
|
|
|
|
OPT_CMDMODE(0, "skip", &command, N_("skip commit"), ACTION_SKIP),
|
2019-04-17 14:30:37 +00:00
|
|
|
OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"),
|
2019-04-17 14:30:43 +00:00
|
|
|
ACTION_EDIT_TODO),
|
2019-04-17 14:30:37 +00:00
|
|
|
OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"),
|
2019-04-17 14:30:43 +00:00
|
|
|
ACTION_SHOW_CURRENT_PATCH),
|
2019-04-17 14:30:37 +00:00
|
|
|
OPT_CMDMODE(0, "shorten-ids", &command,
|
2019-04-17 14:30:43 +00:00
|
|
|
N_("shorten commit ids in the todo list"), ACTION_SHORTEN_OIDS),
|
2019-04-17 14:30:37 +00:00
|
|
|
OPT_CMDMODE(0, "expand-ids", &command,
|
2019-04-17 14:30:43 +00:00
|
|
|
N_("expand commit ids in the todo list"), ACTION_EXPAND_OIDS),
|
2019-04-17 14:30:37 +00:00
|
|
|
OPT_CMDMODE(0, "check-todo-list", &command,
|
2019-04-17 14:30:43 +00:00
|
|
|
N_("check the todo list"), ACTION_CHECK_TODO_LIST),
|
2019-04-17 14:30:37 +00:00
|
|
|
OPT_CMDMODE(0, "rearrange-squash", &command,
|
2019-04-17 14:30:43 +00:00
|
|
|
N_("rearrange fixup/squash lines"), ACTION_REARRANGE_SQUASH),
|
2019-04-17 14:30:37 +00:00
|
|
|
OPT_CMDMODE(0, "add-exec-commands", &command,
|
2019-04-17 14:30:43 +00:00
|
|
|
N_("insert exec commands in todo list"), ACTION_ADD_EXEC),
|
2019-04-17 14:30:41 +00:00
|
|
|
{ OPTION_CALLBACK, 0, "onto", &opts.onto, N_("onto"), N_("onto"),
|
2019-04-17 14:30:39 +00:00
|
|
|
PARSE_OPT_NONEG, parse_opt_commit, 0 },
|
2019-04-17 14:30:41 +00:00
|
|
|
{ OPTION_CALLBACK, 0, "restrict-revision", &opts.restrict_revision,
|
2019-04-17 14:30:39 +00:00
|
|
|
N_("restrict-revision"), N_("restrict revision"),
|
|
|
|
PARSE_OPT_NONEG, parse_opt_commit, 0 },
|
2019-04-17 14:30:40 +00:00
|
|
|
{ OPTION_CALLBACK, 0, "squash-onto", &squash_onto, N_("squash-onto"),
|
|
|
|
N_("squash onto"), PARSE_OPT_NONEG, parse_opt_object_id, 0 },
|
2019-04-17 14:30:41 +00:00
|
|
|
{ OPTION_CALLBACK, 0, "upstream", &opts.upstream, N_("upstream"),
|
2019-04-17 14:30:39 +00:00
|
|
|
N_("the upstream commit"), PARSE_OPT_NONEG, parse_opt_commit,
|
|
|
|
0 },
|
2019-04-17 14:30:41 +00:00
|
|
|
OPT_STRING(0, "head-name", &opts.head_name, N_("head-name"), N_("head name")),
|
|
|
|
{ OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign_opt, N_("key-id"),
|
2019-04-17 14:30:37 +00:00
|
|
|
N_("GPG-sign commits"),
|
|
|
|
PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
|
|
|
|
OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"),
|
|
|
|
N_("rebase strategy")),
|
2019-04-17 14:30:41 +00:00
|
|
|
OPT_STRING(0, "strategy-opts", &opts.strategy_opts, N_("strategy-opts"),
|
2019-04-17 14:30:37 +00:00
|
|
|
N_("strategy options")),
|
2019-04-17 14:30:41 +00:00
|
|
|
OPT_STRING(0, "switch-to", &opts.switch_to, N_("switch-to"),
|
2019-04-17 14:30:37 +00:00
|
|
|
N_("the branch or commit to checkout")),
|
2019-04-17 14:30:41 +00:00
|
|
|
OPT_STRING(0, "onto-name", &opts.onto_name, N_("onto-name"), N_("onto name")),
|
|
|
|
OPT_STRING(0, "cmd", &opts.cmd, N_("cmd"), N_("the command to run")),
|
|
|
|
OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_autoupdate),
|
2019-04-17 14:30:37 +00:00
|
|
|
OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec,
|
|
|
|
N_("automatically re-schedule any `exec` that fails")),
|
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
2019-04-17 14:30:41 +00:00
|
|
|
opts.rebase_cousins = -1;
|
2019-04-17 14:30:37 +00:00
|
|
|
|
|
|
|
if (argc == 1)
|
|
|
|
usage_with_options(builtin_rebase_interactive_usage, options);
|
|
|
|
|
2019-06-13 20:19:34 +00:00
|
|
|
argc = parse_options(argc, argv, prefix, options,
|
2019-04-17 14:30:37 +00:00
|
|
|
builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
|
|
|
|
|
2019-04-17 14:30:40 +00:00
|
|
|
if (!is_null_oid(&squash_onto))
|
2019-04-17 14:30:41 +00:00
|
|
|
opts.squash_onto = &squash_onto;
|
2019-04-17 14:30:40 +00:00
|
|
|
|
2019-04-17 14:30:41 +00:00
|
|
|
if (opts.rebase_cousins >= 0 && !opts.rebase_merges)
|
2019-04-17 14:30:37 +00:00
|
|
|
warning(_("--[no-]rebase-cousins has no effect without "
|
|
|
|
"--rebase-merges"));
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
return !!run_sequencer_rebase(&opts, command);
|
2019-04-17 14:30:37 +00:00
|
|
|
}
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
static int is_merge(struct rebase_options *opts)
|
2018-09-04 21:27:16 +00:00
|
|
|
{
|
2020-02-15 21:36:41 +00:00
|
|
|
return opts->type == REBASE_MERGE ||
|
2018-09-04 21:27:16 +00:00
|
|
|
opts->type == REBASE_PRESERVE_MERGES;
|
|
|
|
}
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
static void imply_merge(struct rebase_options *opts, const char *option)
|
2018-09-04 21:59:57 +00:00
|
|
|
{
|
|
|
|
switch (opts->type) {
|
2020-02-15 21:36:41 +00:00
|
|
|
case REBASE_APPLY:
|
2018-09-04 21:59:57 +00:00
|
|
|
die(_("%s requires an interactive rebase"), option);
|
|
|
|
break;
|
2020-02-15 21:36:41 +00:00
|
|
|
case REBASE_MERGE:
|
2018-09-04 21:59:57 +00:00
|
|
|
case REBASE_PRESERVE_MERGES:
|
|
|
|
break;
|
|
|
|
default:
|
2020-02-15 21:36:41 +00:00
|
|
|
opts->type = REBASE_MERGE; /* implied */
|
2018-09-04 21:59:57 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-06 19:31:11 +00:00
|
|
|
/* Returns the filename prefixed by the state_dir */
|
|
|
|
static const char *state_dir_path(const char *filename, struct rebase_options *opts)
|
|
|
|
{
|
|
|
|
static struct strbuf path = STRBUF_INIT;
|
|
|
|
static size_t prefix_len;
|
|
|
|
|
|
|
|
if (!prefix_len) {
|
|
|
|
strbuf_addf(&path, "%s/", opts->state_dir);
|
|
|
|
prefix_len = path.len;
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_setlen(&path, prefix_len);
|
|
|
|
strbuf_addstr(&path, filename);
|
|
|
|
return path.buf;
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:06:16 +00:00
|
|
|
/* Initialize the rebase options from the state directory. */
|
|
|
|
static int read_basic_state(struct rebase_options *opts)
|
|
|
|
{
|
|
|
|
struct strbuf head_name = STRBUF_INIT;
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
struct object_id oid;
|
|
|
|
|
2020-04-07 14:27:55 +00:00
|
|
|
if (!read_oneliner(&head_name, state_dir_path("head-name", opts),
|
|
|
|
READ_ONELINER_WARN_MISSING) ||
|
|
|
|
!read_oneliner(&buf, state_dir_path("onto", opts),
|
|
|
|
READ_ONELINER_WARN_MISSING))
|
2018-08-08 15:06:16 +00:00
|
|
|
return -1;
|
|
|
|
opts->head_name = starts_with(head_name.buf, "refs/") ?
|
|
|
|
xstrdup(head_name.buf) : NULL;
|
|
|
|
strbuf_release(&head_name);
|
|
|
|
if (get_oid(buf.buf, &oid))
|
|
|
|
return error(_("could not get 'onto': '%s'"), buf.buf);
|
|
|
|
opts->onto = lookup_commit_or_die(&oid, buf.buf);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We always write to orig-head, but interactive rebase used to write to
|
|
|
|
* head. Fall back to reading from head to cover for the case that the
|
|
|
|
* user upgraded git with an ongoing interactive rebase.
|
|
|
|
*/
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
if (file_exists(state_dir_path("orig-head", opts))) {
|
2020-04-07 14:27:55 +00:00
|
|
|
if (!read_oneliner(&buf, state_dir_path("orig-head", opts),
|
|
|
|
READ_ONELINER_WARN_MISSING))
|
2018-08-08 15:06:16 +00:00
|
|
|
return -1;
|
2020-04-07 14:27:55 +00:00
|
|
|
} else if (!read_oneliner(&buf, state_dir_path("head", opts),
|
|
|
|
READ_ONELINER_WARN_MISSING))
|
2018-08-08 15:06:16 +00:00
|
|
|
return -1;
|
|
|
|
if (get_oid(buf.buf, &opts->orig_head))
|
|
|
|
return error(_("invalid orig-head: '%s'"), buf.buf);
|
|
|
|
|
git-rebase, sequencer: extend --quiet option for the interactive machinery
While 'quiet' and 'interactive' may sound like antonyms, the interactive
machinery actually has logic that implements several
interactive_rebase=implied cases (--exec, --keep-empty, --rebase-merges)
which won't pop up an editor. The rewrite of interactive rebase in C
added a quiet option, though it only turns stats off. Since we want to
make the interactive machinery also take over for git-rebase--merge, it
should fully implement the --quiet option.
git-rebase--interactive was already somewhat quieter than
git-rebase--merge and git-rebase--am, possibly because cherry-pick has
just traditionally been quieter. As such, we only drop a few
informational messages -- "Rebasing (n/m)" and "Successfully rebased..."
Also, for simplicity, remove the differences in how quiet and verbose
options were recorded. Having one be signalled by the presence of a
"verbose" file in the state_dir, while the other was signalled by the
contents of a "quiet" file was just weirdly inconsistent. (This
inconsistency pre-dated the rewrite into C.) Make them consistent by
having them both key off the presence of the file.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-12-11 16:11:36 +00:00
|
|
|
if (file_exists(state_dir_path("quiet", opts)))
|
2018-08-08 15:06:16 +00:00
|
|
|
opts->flags &= ~REBASE_NO_QUIET;
|
|
|
|
else
|
|
|
|
opts->flags |= REBASE_NO_QUIET;
|
|
|
|
|
|
|
|
if (file_exists(state_dir_path("verbose", opts)))
|
|
|
|
opts->flags |= REBASE_VERBOSE;
|
|
|
|
|
2018-09-04 21:59:50 +00:00
|
|
|
if (file_exists(state_dir_path("signoff", opts))) {
|
|
|
|
opts->signoff = 1;
|
|
|
|
opts->flags |= REBASE_FORCE;
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:59:52 +00:00
|
|
|
if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) {
|
|
|
|
strbuf_reset(&buf);
|
2020-04-07 14:27:55 +00:00
|
|
|
if (!read_oneliner(&buf, state_dir_path("allow_rerere_autoupdate", opts),
|
|
|
|
READ_ONELINER_WARN_MISSING))
|
2018-09-04 21:59:52 +00:00
|
|
|
return -1;
|
|
|
|
if (!strcmp(buf.buf, "--rerere-autoupdate"))
|
2019-04-17 14:30:36 +00:00
|
|
|
opts->allow_rerere_autoupdate = RERERE_AUTOUPDATE;
|
2018-09-04 21:59:52 +00:00
|
|
|
else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
|
2019-04-17 14:30:36 +00:00
|
|
|
opts->allow_rerere_autoupdate = RERERE_NOAUTOUPDATE;
|
2018-09-04 21:59:52 +00:00
|
|
|
else
|
|
|
|
warning(_("ignoring invalid allow_rerere_autoupdate: "
|
|
|
|
"'%s'"), buf.buf);
|
2019-04-17 14:30:36 +00:00
|
|
|
}
|
2018-09-04 21:59:52 +00:00
|
|
|
|
2018-09-04 22:00:00 +00:00
|
|
|
if (file_exists(state_dir_path("gpg_sign_opt", opts))) {
|
|
|
|
strbuf_reset(&buf);
|
2020-04-07 14:27:55 +00:00
|
|
|
if (!read_oneliner(&buf, state_dir_path("gpg_sign_opt", opts),
|
|
|
|
READ_ONELINER_WARN_MISSING))
|
2018-09-04 22:00:00 +00:00
|
|
|
return -1;
|
|
|
|
free(opts->gpg_sign_opt);
|
|
|
|
opts->gpg_sign_opt = xstrdup(buf.buf);
|
|
|
|
}
|
|
|
|
|
2018-09-04 22:00:11 +00:00
|
|
|
if (file_exists(state_dir_path("strategy", opts))) {
|
|
|
|
strbuf_reset(&buf);
|
2020-04-07 14:27:55 +00:00
|
|
|
if (!read_oneliner(&buf, state_dir_path("strategy", opts),
|
|
|
|
READ_ONELINER_WARN_MISSING))
|
2018-09-04 22:00:11 +00:00
|
|
|
return -1;
|
|
|
|
free(opts->strategy);
|
|
|
|
opts->strategy = xstrdup(buf.buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file_exists(state_dir_path("strategy_opts", opts))) {
|
|
|
|
strbuf_reset(&buf);
|
2020-04-07 14:27:55 +00:00
|
|
|
if (!read_oneliner(&buf, state_dir_path("strategy_opts", opts),
|
|
|
|
READ_ONELINER_WARN_MISSING))
|
2018-09-04 22:00:11 +00:00
|
|
|
return -1;
|
|
|
|
free(opts->strategy_opts);
|
|
|
|
opts->strategy_opts = xstrdup(buf.buf);
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:06:16 +00:00
|
|
|
strbuf_release(&buf);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:35 +00:00
|
|
|
static int rebase_write_basic_state(struct rebase_options *opts)
|
2019-01-18 15:09:27 +00:00
|
|
|
{
|
|
|
|
write_file(state_dir_path("head-name", opts), "%s",
|
|
|
|
opts->head_name ? opts->head_name : "detached HEAD");
|
|
|
|
write_file(state_dir_path("onto", opts), "%s",
|
|
|
|
opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");
|
|
|
|
write_file(state_dir_path("orig-head", opts), "%s",
|
|
|
|
oid_to_hex(&opts->orig_head));
|
2020-02-15 21:36:27 +00:00
|
|
|
if (!(opts->flags & REBASE_NO_QUIET))
|
|
|
|
write_file(state_dir_path("quiet", opts), "%s", "");
|
2019-01-18 15:09:27 +00:00
|
|
|
if (opts->flags & REBASE_VERBOSE)
|
|
|
|
write_file(state_dir_path("verbose", opts), "%s", "");
|
|
|
|
if (opts->strategy)
|
|
|
|
write_file(state_dir_path("strategy", opts), "%s",
|
|
|
|
opts->strategy);
|
|
|
|
if (opts->strategy_opts)
|
|
|
|
write_file(state_dir_path("strategy_opts", opts), "%s",
|
|
|
|
opts->strategy_opts);
|
2019-04-17 14:30:36 +00:00
|
|
|
if (opts->allow_rerere_autoupdate > 0)
|
2019-01-18 15:09:27 +00:00
|
|
|
write_file(state_dir_path("allow_rerere_autoupdate", opts),
|
|
|
|
"-%s-rerere-autoupdate",
|
2019-04-17 14:30:36 +00:00
|
|
|
opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
|
|
|
|
"" : "-no");
|
2019-01-18 15:09:27 +00:00
|
|
|
if (opts->gpg_sign_opt)
|
|
|
|
write_file(state_dir_path("gpg_sign_opt", opts), "%s",
|
|
|
|
opts->gpg_sign_opt);
|
|
|
|
if (opts->signoff)
|
2019-12-20 18:53:56 +00:00
|
|
|
write_file(state_dir_path("signoff", opts), "--signoff");
|
2019-01-18 15:09:27 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-06 19:31:11 +00:00
|
|
|
static int finish_rebase(struct rebase_options *opts)
|
|
|
|
{
|
|
|
|
struct strbuf dir = STRBUF_INIT;
|
|
|
|
const char *argv_gc_auto[] = { "gc", "--auto", NULL };
|
2019-05-14 18:03:47 +00:00
|
|
|
int ret = 0;
|
2018-08-06 19:31:11 +00:00
|
|
|
|
|
|
|
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
|
2020-04-07 14:27:58 +00:00
|
|
|
apply_autostash(state_dir_path("autostash", opts));
|
2019-05-17 18:41:49 +00:00
|
|
|
close_object_store(the_repository->objects);
|
2018-08-06 19:31:11 +00:00
|
|
|
/*
|
|
|
|
* We ignore errors in 'gc --auto', since the
|
|
|
|
* user should see them.
|
|
|
|
*/
|
|
|
|
run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
|
2020-02-15 21:36:41 +00:00
|
|
|
if (opts->type == REBASE_MERGE) {
|
2019-05-14 18:03:49 +00:00
|
|
|
struct replay_opts replay = REPLAY_OPTS_INIT;
|
2018-08-06 19:31:11 +00:00
|
|
|
|
2019-05-14 18:03:49 +00:00
|
|
|
replay.action = REPLAY_INTERACTIVE_REBASE;
|
|
|
|
ret = sequencer_remove_state(&replay);
|
|
|
|
} else {
|
|
|
|
strbuf_addstr(&dir, opts->state_dir);
|
|
|
|
if (remove_dir_recursively(&dir, 0))
|
|
|
|
ret = error(_("could not remove '%s'"),
|
|
|
|
opts->state_dir);
|
|
|
|
strbuf_release(&dir);
|
|
|
|
}
|
2018-08-06 19:31:11 +00:00
|
|
|
|
2019-05-14 18:03:47 +00:00
|
|
|
return ret;
|
2018-08-06 19:31:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct commit *peel_committish(const char *name)
|
|
|
|
{
|
|
|
|
struct object *obj;
|
|
|
|
struct object_id oid;
|
|
|
|
|
|
|
|
if (get_oid(name, &oid))
|
|
|
|
return NULL;
|
|
|
|
obj = parse_object(the_repository, &oid);
|
|
|
|
return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void add_var(struct strbuf *buf, const char *name, const char *value)
|
|
|
|
{
|
|
|
|
if (!value)
|
|
|
|
strbuf_addf(buf, "unset %s; ", name);
|
|
|
|
else {
|
|
|
|
strbuf_addf(buf, "%s=", name);
|
|
|
|
sq_quote_buf(buf, value);
|
|
|
|
strbuf_addstr(buf, "; ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-18 15:09:24 +00:00
|
|
|
#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
|
|
|
|
|
|
|
|
#define RESET_HEAD_DETACH (1<<0)
|
|
|
|
#define RESET_HEAD_HARD (1<<1)
|
2019-02-07 06:05:22 +00:00
|
|
|
#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
|
|
|
|
#define RESET_HEAD_REFS_ONLY (1<<3)
|
2019-03-20 06:16:05 +00:00
|
|
|
#define RESET_ORIG_HEAD (1<<4)
|
2019-01-18 15:09:24 +00:00
|
|
|
|
2020-04-07 14:27:59 +00:00
|
|
|
static int reset_head(struct repository *r, struct object_id *oid, const char *action,
|
2019-01-18 15:09:24 +00:00
|
|
|
const char *switch_to_branch, unsigned flags,
|
2020-04-07 14:27:59 +00:00
|
|
|
const char *reflog_orig_head, const char *reflog_head,
|
|
|
|
const char *default_reflog_action)
|
2019-01-18 15:09:24 +00:00
|
|
|
{
|
|
|
|
unsigned detach_head = flags & RESET_HEAD_DETACH;
|
|
|
|
unsigned reset_hard = flags & RESET_HEAD_HARD;
|
2019-02-07 06:05:22 +00:00
|
|
|
unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
|
2019-01-18 15:09:26 +00:00
|
|
|
unsigned refs_only = flags & RESET_HEAD_REFS_ONLY;
|
2019-03-03 17:11:55 +00:00
|
|
|
unsigned update_orig_head = flags & RESET_ORIG_HEAD;
|
2019-01-18 15:09:24 +00:00
|
|
|
struct object_id head_oid;
|
|
|
|
struct tree_desc desc[2] = { { NULL }, { NULL } };
|
|
|
|
struct lock_file lock = LOCK_INIT;
|
|
|
|
struct unpack_trees_options unpack_tree_opts;
|
|
|
|
struct tree *tree;
|
|
|
|
const char *reflog_action;
|
|
|
|
struct strbuf msg = STRBUF_INIT;
|
|
|
|
size_t prefix_len;
|
|
|
|
struct object_id *orig = NULL, oid_orig,
|
|
|
|
*old_orig = NULL, oid_old_orig;
|
|
|
|
int ret = 0, nr = 0;
|
|
|
|
|
|
|
|
if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
|
|
|
|
BUG("Not a fully qualified branch: '%s'", switch_to_branch);
|
|
|
|
|
2020-04-07 14:27:59 +00:00
|
|
|
if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {
|
2019-01-18 15:09:24 +00:00
|
|
|
ret = -1;
|
|
|
|
goto leave_reset_head;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
|
|
|
|
ret = error(_("could not determine HEAD revision"));
|
|
|
|
goto leave_reset_head;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!oid)
|
|
|
|
oid = &head_oid;
|
|
|
|
|
2019-01-18 15:09:26 +00:00
|
|
|
if (refs_only)
|
|
|
|
goto reset_head_refs;
|
|
|
|
|
2019-01-18 15:09:24 +00:00
|
|
|
memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
|
|
|
|
setup_unpack_trees_porcelain(&unpack_tree_opts, action);
|
|
|
|
unpack_tree_opts.head_idx = 1;
|
2020-04-07 14:27:59 +00:00
|
|
|
unpack_tree_opts.src_index = r->index;
|
|
|
|
unpack_tree_opts.dst_index = r->index;
|
2019-01-18 15:09:24 +00:00
|
|
|
unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
|
|
|
|
unpack_tree_opts.update = 1;
|
|
|
|
unpack_tree_opts.merge = 1;
|
|
|
|
if (!detach_head)
|
|
|
|
unpack_tree_opts.reset = 1;
|
|
|
|
|
2020-04-07 14:27:59 +00:00
|
|
|
if (repo_read_index_unmerged(r) < 0) {
|
2019-01-18 15:09:24 +00:00
|
|
|
ret = error(_("could not read index"));
|
|
|
|
goto leave_reset_head;
|
|
|
|
}
|
|
|
|
|
2020-04-07 14:27:59 +00:00
|
|
|
if (!reset_hard && !fill_tree_descriptor(r, &desc[nr++], &head_oid)) {
|
2019-01-18 15:09:24 +00:00
|
|
|
ret = error(_("failed to find tree of %s"),
|
|
|
|
oid_to_hex(&head_oid));
|
|
|
|
goto leave_reset_head;
|
|
|
|
}
|
|
|
|
|
2020-04-07 14:27:59 +00:00
|
|
|
if (!fill_tree_descriptor(r, &desc[nr++], oid)) {
|
2019-01-18 15:09:24 +00:00
|
|
|
ret = error(_("failed to find tree of %s"), oid_to_hex(oid));
|
|
|
|
goto leave_reset_head;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unpack_trees(nr, desc, &unpack_tree_opts)) {
|
|
|
|
ret = -1;
|
|
|
|
goto leave_reset_head;
|
|
|
|
}
|
|
|
|
|
|
|
|
tree = parse_tree_indirect(oid);
|
2020-04-07 14:27:59 +00:00
|
|
|
prime_cache_tree(r, r->index, tree);
|
2019-01-18 15:09:24 +00:00
|
|
|
|
2020-04-07 14:27:59 +00:00
|
|
|
if (write_locked_index(r->index, &lock, COMMIT_LOCK) < 0) {
|
2019-01-18 15:09:24 +00:00
|
|
|
ret = error(_("could not write index"));
|
|
|
|
goto leave_reset_head;
|
|
|
|
}
|
|
|
|
|
2019-01-18 15:09:26 +00:00
|
|
|
reset_head_refs:
|
2019-01-18 15:09:24 +00:00
|
|
|
reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
|
2020-04-07 14:27:59 +00:00
|
|
|
strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action);
|
2019-01-18 15:09:24 +00:00
|
|
|
prefix_len = msg.len;
|
|
|
|
|
2019-03-03 17:11:55 +00:00
|
|
|
if (update_orig_head) {
|
|
|
|
if (!get_oid("ORIG_HEAD", &oid_old_orig))
|
|
|
|
old_orig = &oid_old_orig;
|
|
|
|
if (!get_oid("HEAD", &oid_orig)) {
|
|
|
|
orig = &oid_orig;
|
|
|
|
if (!reflog_orig_head) {
|
|
|
|
strbuf_addstr(&msg, "updating ORIG_HEAD");
|
|
|
|
reflog_orig_head = msg.buf;
|
|
|
|
}
|
|
|
|
update_ref(reflog_orig_head, "ORIG_HEAD", orig,
|
|
|
|
old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
|
|
|
|
} else if (old_orig)
|
|
|
|
delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
|
|
|
|
}
|
|
|
|
|
2019-01-18 15:09:24 +00:00
|
|
|
if (!reflog_head) {
|
|
|
|
strbuf_setlen(&msg, prefix_len);
|
|
|
|
strbuf_addstr(&msg, "updating HEAD");
|
|
|
|
reflog_head = msg.buf;
|
|
|
|
}
|
|
|
|
if (!switch_to_branch)
|
|
|
|
ret = update_ref(reflog_head, "HEAD", oid, orig,
|
|
|
|
detach_head ? REF_NO_DEREF : 0,
|
|
|
|
UPDATE_REFS_MSG_ON_ERR);
|
|
|
|
else {
|
2019-03-03 17:11:54 +00:00
|
|
|
ret = update_ref(reflog_head, switch_to_branch, oid,
|
2019-01-18 15:09:25 +00:00
|
|
|
NULL, 0, UPDATE_REFS_MSG_ON_ERR);
|
2019-01-18 15:09:24 +00:00
|
|
|
if (!ret)
|
2019-01-18 15:09:25 +00:00
|
|
|
ret = create_symref("HEAD", switch_to_branch,
|
|
|
|
reflog_head);
|
2019-01-18 15:09:24 +00:00
|
|
|
}
|
2019-02-07 06:05:22 +00:00
|
|
|
if (run_hook)
|
|
|
|
run_hook_le(NULL, "post-checkout",
|
|
|
|
oid_to_hex(orig ? orig : &null_oid),
|
|
|
|
oid_to_hex(oid), "1", NULL);
|
2019-01-18 15:09:24 +00:00
|
|
|
|
|
|
|
leave_reset_head:
|
|
|
|
strbuf_release(&msg);
|
|
|
|
rollback_lock_file(&lock);
|
|
|
|
while (nr)
|
|
|
|
free((void *)desc[--nr].buffer);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-01-18 15:09:27 +00:00
|
|
|
static int move_to_original_branch(struct rebase_options *opts)
|
|
|
|
{
|
|
|
|
struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!opts->head_name)
|
|
|
|
return 0; /* nothing to move back to */
|
|
|
|
|
|
|
|
if (!opts->onto)
|
|
|
|
BUG("move_to_original_branch without onto");
|
|
|
|
|
|
|
|
strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
|
|
|
|
opts->head_name, oid_to_hex(&opts->onto->object.oid));
|
|
|
|
strbuf_addf(&head_reflog, "rebase finished: returning to %s",
|
|
|
|
opts->head_name);
|
2020-04-07 14:27:59 +00:00
|
|
|
ret = reset_head(the_repository, NULL, "", opts->head_name,
|
|
|
|
RESET_HEAD_REFS_ONLY,
|
|
|
|
orig_head_reflog.buf, head_reflog.buf,
|
|
|
|
DEFAULT_REFLOG_ACTION);
|
2019-01-18 15:09:27 +00:00
|
|
|
|
|
|
|
strbuf_release(&orig_head_reflog);
|
|
|
|
strbuf_release(&head_reflog);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-10-05 15:54:38 +00:00
|
|
|
static const char *resolvemsg =
|
|
|
|
N_("Resolve all conflicts manually, mark them as resolved with\n"
|
|
|
|
"\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n"
|
|
|
|
"You can instead skip this commit: run \"git rebase --skip\".\n"
|
|
|
|
"To abort and get back to the state before \"git rebase\", run "
|
|
|
|
"\"git rebase --abort\".");
|
|
|
|
|
2019-01-18 15:09:27 +00:00
|
|
|
static int run_am(struct rebase_options *opts)
|
|
|
|
{
|
|
|
|
struct child_process am = CHILD_PROCESS_INIT;
|
|
|
|
struct child_process format_patch = CHILD_PROCESS_INIT;
|
|
|
|
struct strbuf revisions = STRBUF_INIT;
|
|
|
|
int status;
|
|
|
|
char *rebased_patches;
|
|
|
|
|
|
|
|
am.git_cmd = 1;
|
|
|
|
argv_array_push(&am.args, "am");
|
|
|
|
|
|
|
|
if (opts->action && !strcmp("continue", opts->action)) {
|
|
|
|
argv_array_push(&am.args, "--resolved");
|
|
|
|
argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
|
|
|
|
if (opts->gpg_sign_opt)
|
|
|
|
argv_array_push(&am.args, opts->gpg_sign_opt);
|
|
|
|
status = run_command(&am);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
return move_to_original_branch(opts);
|
|
|
|
}
|
|
|
|
if (opts->action && !strcmp("skip", opts->action)) {
|
|
|
|
argv_array_push(&am.args, "--skip");
|
|
|
|
argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
|
|
|
|
status = run_command(&am);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
return move_to_original_branch(opts);
|
|
|
|
}
|
|
|
|
if (opts->action && !strcmp("show-current-patch", opts->action)) {
|
|
|
|
argv_array_push(&am.args, "--show-current-patch");
|
|
|
|
return run_command(&am);
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_addf(&revisions, "%s...%s",
|
|
|
|
oid_to_hex(opts->root ?
|
|
|
|
/* this is now equivalent to !opts->upstream */
|
|
|
|
&opts->onto->object.oid :
|
|
|
|
&opts->upstream->object.oid),
|
|
|
|
oid_to_hex(&opts->orig_head));
|
|
|
|
|
|
|
|
rebased_patches = xstrdup(git_path("rebased-patches"));
|
|
|
|
format_patch.out = open(rebased_patches,
|
|
|
|
O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
|
|
if (format_patch.out < 0) {
|
|
|
|
status = error_errno(_("could not open '%s' for writing"),
|
|
|
|
rebased_patches);
|
|
|
|
free(rebased_patches);
|
|
|
|
argv_array_clear(&am.args);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
format_patch.git_cmd = 1;
|
|
|
|
argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
|
|
|
|
"--full-index", "--cherry-pick", "--right-only",
|
|
|
|
"--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
|
2019-12-04 21:25:11 +00:00
|
|
|
"--no-cover-letter", "--pretty=mboxrd", "--topo-order",
|
|
|
|
"--no-base", NULL);
|
2019-01-18 15:09:27 +00:00
|
|
|
if (opts->git_format_patch_opt.len)
|
|
|
|
argv_array_split(&format_patch.args,
|
|
|
|
opts->git_format_patch_opt.buf);
|
|
|
|
argv_array_push(&format_patch.args, revisions.buf);
|
|
|
|
if (opts->restrict_revision)
|
|
|
|
argv_array_pushf(&format_patch.args, "^%s",
|
|
|
|
oid_to_hex(&opts->restrict_revision->object.oid));
|
|
|
|
|
|
|
|
status = run_command(&format_patch);
|
|
|
|
if (status) {
|
|
|
|
unlink(rebased_patches);
|
|
|
|
free(rebased_patches);
|
|
|
|
argv_array_clear(&am.args);
|
|
|
|
|
2020-04-07 14:27:59 +00:00
|
|
|
reset_head(the_repository, &opts->orig_head, "checkout",
|
|
|
|
opts->head_name, 0,
|
|
|
|
"HEAD", NULL, DEFAULT_REFLOG_ACTION);
|
2019-01-18 15:09:27 +00:00
|
|
|
error(_("\ngit encountered an error while preparing the "
|
|
|
|
"patches to replay\n"
|
|
|
|
"these revisions:\n"
|
|
|
|
"\n %s\n\n"
|
|
|
|
"As a result, git cannot rebase them."),
|
|
|
|
opts->revisions);
|
|
|
|
|
|
|
|
strbuf_release(&revisions);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
strbuf_release(&revisions);
|
|
|
|
|
|
|
|
am.in = open(rebased_patches, O_RDONLY);
|
|
|
|
if (am.in < 0) {
|
|
|
|
status = error_errno(_("could not open '%s' for reading"),
|
|
|
|
rebased_patches);
|
|
|
|
free(rebased_patches);
|
|
|
|
argv_array_clear(&am.args);
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
argv_array_pushv(&am.args, opts->git_am_opts.argv);
|
|
|
|
argv_array_push(&am.args, "--rebasing");
|
|
|
|
argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
|
|
|
|
argv_array_push(&am.args, "--patch-format=mboxrd");
|
2019-04-17 14:30:36 +00:00
|
|
|
if (opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE)
|
2019-01-18 15:09:27 +00:00
|
|
|
argv_array_push(&am.args, "--rerere-autoupdate");
|
2019-04-17 14:30:36 +00:00
|
|
|
else if (opts->allow_rerere_autoupdate == RERERE_NOAUTOUPDATE)
|
2019-01-18 15:09:27 +00:00
|
|
|
argv_array_push(&am.args, "--no-rerere-autoupdate");
|
|
|
|
if (opts->gpg_sign_opt)
|
|
|
|
argv_array_push(&am.args, opts->gpg_sign_opt);
|
|
|
|
status = run_command(&am);
|
|
|
|
unlink(rebased_patches);
|
|
|
|
free(rebased_patches);
|
|
|
|
|
|
|
|
if (!status) {
|
|
|
|
return move_to_original_branch(opts);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is_directory(opts->state_dir))
|
2019-04-17 14:30:35 +00:00
|
|
|
rebase_write_basic_state(opts);
|
2019-01-18 15:09:27 +00:00
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:44 +00:00
|
|
|
static int run_specific_rebase(struct rebase_options *opts, enum action action)
|
2018-08-06 19:31:11 +00:00
|
|
|
{
|
|
|
|
const char *argv[] = { NULL, NULL };
|
2018-11-14 16:25:29 +00:00
|
|
|
struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT;
|
2018-08-06 19:31:11 +00:00
|
|
|
int status;
|
|
|
|
const char *backend, *backend_func;
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
if (opts->type == REBASE_MERGE) {
|
|
|
|
/* Run sequencer-based rebase */
|
2019-04-17 14:30:44 +00:00
|
|
|
setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1);
|
2018-10-05 15:54:38 +00:00
|
|
|
if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
|
2019-04-17 14:30:44 +00:00
|
|
|
setenv("GIT_SEQUENCE_EDITOR", ":", 1);
|
2018-10-05 15:54:38 +00:00
|
|
|
opts->autosquash = 0;
|
|
|
|
}
|
2019-04-17 14:30:44 +00:00
|
|
|
if (opts->gpg_sign_opt) {
|
|
|
|
/* remove the leading "-S" */
|
|
|
|
char *tmp = xstrdup(opts->gpg_sign_opt + 2);
|
|
|
|
free(opts->gpg_sign_opt);
|
|
|
|
opts->gpg_sign_opt = tmp;
|
|
|
|
}
|
2018-10-05 15:54:38 +00:00
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
status = run_sequencer_rebase(opts, action);
|
2018-10-05 15:54:38 +00:00
|
|
|
goto finished_rebase;
|
|
|
|
}
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
if (opts->type == REBASE_APPLY) {
|
2019-01-18 15:09:27 +00:00
|
|
|
status = run_am(opts);
|
|
|
|
goto finished_rebase;
|
|
|
|
}
|
|
|
|
|
2018-08-06 19:31:11 +00:00
|
|
|
add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir()));
|
|
|
|
add_var(&script_snippet, "state_dir", opts->state_dir);
|
|
|
|
|
|
|
|
add_var(&script_snippet, "upstream_name", opts->upstream_name);
|
2018-08-08 15:06:16 +00:00
|
|
|
add_var(&script_snippet, "upstream", opts->upstream ?
|
|
|
|
oid_to_hex(&opts->upstream->object.oid) : NULL);
|
2018-09-04 21:27:20 +00:00
|
|
|
add_var(&script_snippet, "head_name",
|
|
|
|
opts->head_name ? opts->head_name : "detached HEAD");
|
2018-08-06 19:31:11 +00:00
|
|
|
add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
|
2018-08-08 15:06:16 +00:00
|
|
|
add_var(&script_snippet, "onto", opts->onto ?
|
|
|
|
oid_to_hex(&opts->onto->object.oid) : NULL);
|
2018-08-06 19:31:11 +00:00
|
|
|
add_var(&script_snippet, "onto_name", opts->onto_name);
|
|
|
|
add_var(&script_snippet, "revisions", opts->revisions);
|
|
|
|
add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
|
|
|
|
oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
|
2018-11-14 16:25:29 +00:00
|
|
|
sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
|
|
|
|
add_var(&script_snippet, "git_am_opt", buf.buf);
|
|
|
|
strbuf_release(&buf);
|
2018-09-04 21:27:13 +00:00
|
|
|
add_var(&script_snippet, "verbose",
|
|
|
|
opts->flags & REBASE_VERBOSE ? "t" : "");
|
|
|
|
add_var(&script_snippet, "diffstat",
|
|
|
|
opts->flags & REBASE_DIFFSTAT ? "t" : "");
|
2018-09-04 21:27:17 +00:00
|
|
|
add_var(&script_snippet, "force_rebase",
|
|
|
|
opts->flags & REBASE_FORCE ? "t" : "");
|
2018-09-04 21:27:21 +00:00
|
|
|
if (opts->switch_to)
|
|
|
|
add_var(&script_snippet, "switch_to", opts->switch_to);
|
2018-08-08 15:06:16 +00:00
|
|
|
add_var(&script_snippet, "action", opts->action ? opts->action : "");
|
2018-09-04 21:59:50 +00:00
|
|
|
add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : "");
|
2018-09-04 21:59:52 +00:00
|
|
|
add_var(&script_snippet, "allow_rerere_autoupdate",
|
|
|
|
opts->allow_rerere_autoupdate ?
|
2019-04-17 14:30:36 +00:00
|
|
|
opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
|
|
|
|
"--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
|
2018-09-04 21:59:58 +00:00
|
|
|
add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
|
2018-09-04 22:00:00 +00:00
|
|
|
add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
|
2018-09-04 22:00:04 +00:00
|
|
|
add_var(&script_snippet, "cmd", opts->cmd);
|
2018-09-04 22:00:05 +00:00
|
|
|
add_var(&script_snippet, "allow_empty_message",
|
|
|
|
opts->allow_empty_message ? "--allow-empty-message" : "");
|
2018-09-04 22:00:07 +00:00
|
|
|
add_var(&script_snippet, "rebase_merges",
|
|
|
|
opts->rebase_merges ? "t" : "");
|
|
|
|
add_var(&script_snippet, "rebase_cousins",
|
|
|
|
opts->rebase_cousins ? "t" : "");
|
2018-09-04 22:00:11 +00:00
|
|
|
add_var(&script_snippet, "strategy", opts->strategy);
|
|
|
|
add_var(&script_snippet, "strategy_opts", opts->strategy_opts);
|
2018-09-04 22:00:12 +00:00
|
|
|
add_var(&script_snippet, "rebase_root", opts->root ? "t" : "");
|
|
|
|
add_var(&script_snippet, "squash_onto",
|
|
|
|
opts->squash_onto ? oid_to_hex(opts->squash_onto) : "");
|
2018-08-08 15:36:33 +00:00
|
|
|
add_var(&script_snippet, "git_format_patch_opt",
|
|
|
|
opts->git_format_patch_opt.buf);
|
2018-08-06 19:31:11 +00:00
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
if (is_merge(opts) &&
|
2018-08-08 15:36:34 +00:00
|
|
|
!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
|
|
|
|
strbuf_addstr(&script_snippet,
|
2019-01-28 10:27:56 +00:00
|
|
|
"GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; ");
|
2018-08-08 15:36:34 +00:00
|
|
|
opts->autosquash = 0;
|
|
|
|
}
|
2018-08-06 19:31:11 +00:00
|
|
|
|
|
|
|
switch (opts->type) {
|
|
|
|
case REBASE_PRESERVE_MERGES:
|
|
|
|
backend = "git-rebase--preserve-merges";
|
|
|
|
backend_func = "git_rebase__preserve_merges";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BUG("Unhandled rebase type %d", opts->type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_addf(&script_snippet,
|
2019-05-14 11:22:34 +00:00
|
|
|
". git-sh-setup && . %s && %s", backend, backend_func);
|
2018-08-06 19:31:11 +00:00
|
|
|
argv[0] = script_snippet.buf;
|
|
|
|
|
|
|
|
status = run_command_v_opt(argv, RUN_USING_SHELL);
|
2018-10-05 15:54:38 +00:00
|
|
|
finished_rebase:
|
2018-08-06 19:31:11 +00:00
|
|
|
if (opts->dont_finish_rebase)
|
|
|
|
; /* do nothing */
|
2020-02-15 21:36:41 +00:00
|
|
|
else if (opts->type == REBASE_MERGE)
|
|
|
|
; /* merge backend cleans up after itself */
|
2018-08-06 19:31:11 +00:00
|
|
|
else if (status == 0) {
|
|
|
|
if (!file_exists(state_dir_path("stopped-sha", opts)))
|
|
|
|
finish_rebase(opts);
|
|
|
|
} else if (status == 2) {
|
|
|
|
struct strbuf dir = STRBUF_INIT;
|
|
|
|
|
2020-04-07 14:27:58 +00:00
|
|
|
apply_autostash(state_dir_path("autostash", opts));
|
2018-08-06 19:31:11 +00:00
|
|
|
strbuf_addstr(&dir, opts->state_dir);
|
|
|
|
remove_dir_recursively(&dir, 0);
|
|
|
|
strbuf_release(&dir);
|
|
|
|
die("Nothing to do");
|
|
|
|
}
|
|
|
|
|
|
|
|
strbuf_release(&script_snippet);
|
|
|
|
|
|
|
|
return status ? -1 : 0;
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:27:13 +00:00
|
|
|
static int rebase_config(const char *var, const char *value, void *data)
|
|
|
|
{
|
|
|
|
struct rebase_options *opts = data;
|
|
|
|
|
|
|
|
if (!strcmp(var, "rebase.stat")) {
|
|
|
|
if (git_config_bool(var, value))
|
|
|
|
opts->flags |= REBASE_DIFFSTAT;
|
|
|
|
else
|
2019-05-21 17:50:20 +00:00
|
|
|
opts->flags &= ~REBASE_DIFFSTAT;
|
2018-09-04 21:27:13 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:59:58 +00:00
|
|
|
if (!strcmp(var, "rebase.autosquash")) {
|
|
|
|
opts->autosquash = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-09-04 22:00:00 +00:00
|
|
|
if (!strcmp(var, "commit.gpgsign")) {
|
|
|
|
free(opts->gpg_sign_opt);
|
|
|
|
opts->gpg_sign_opt = git_config_bool(var, value) ?
|
|
|
|
xstrdup("-S") : NULL;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-09-04 22:00:02 +00:00
|
|
|
if (!strcmp(var, "rebase.autostash")) {
|
|
|
|
opts->autostash = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-12-10 19:04:59 +00:00
|
|
|
if (!strcmp(var, "rebase.reschedulefailedexec")) {
|
|
|
|
opts->reschedule_failed_exec = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
rebase: remove the rebase.useBuiltin setting
Remove the rebase.useBuiltin setting, which was added as an escape
hatch to disable the builtin version of rebase first released with Git
2.20.
See [1] for the initial implementation of rebase.useBuiltin, and [2]
and [3] for the documentation and corresponding
GIT_TEST_REBASE_USE_BUILTIN option.
Carrying the legacy version is a maintenance burden as seen in
7e097e27d3 ("legacy-rebase: backport -C<n> and --whitespace=<option>
checks", 2018-11-20) and 9aea5e9286 ("rebase: fix regression in
rebase.useBuiltin=false test mode", 2019-02-13). Since the built-in
version has been shown to be stable enough let's remove the legacy
version.
As noted in [3] having use_builtin_rebase() shell out to get its
config doesn't make any sense anymore, that was done for the purposes
of spawning the legacy rebase without having modified any global
state. Let's instead handle this case in rebase_config().
There's still a bunch of references to git-legacy-rebase in po/*.po,
but those will be dealt with in time by the i18n effort.
Even though this configuration variable only existed two releases
let's not entirely delete the entry from the docs, but note its
absence. Individual versions of git tend to be around for a while due
to distro packaging timelines, so e.g. if we're "lucky" a given
version like 2.21 might be installed on say OSX for half a decade.
That'll mean some people probably setting this in config, and then
when they later wonder if it's needed they can Google search the
config option name or check it in git-config. It also allows us to
refer to the docs from the warning for details.
1. 55071ea248 ("rebase: start implementing it as a builtin",
2018-08-07)
2. d8d0a546f0 ("rebase doc: document rebase.useBuiltin", 2018-11-14)
3. 62c23938fa ("tests: add a special setup where rebase.useBuiltin is
off", 2018-11-14)
3. https://public-inbox.org/git/nycvar.QRO.7.76.6.1903141544110.41@tvgsbejvaqbjf.bet/
Acked-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-03-18 11:01:52 +00:00
|
|
|
if (!strcmp(var, "rebase.usebuiltin")) {
|
|
|
|
opts->use_legacy_rebase = !git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-02-15 21:36:39 +00:00
|
|
|
if (!strcmp(var, "rebase.backend")) {
|
|
|
|
return git_config_string(&opts->default_backend, var, value);
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:27:13 +00:00
|
|
|
return git_default_config(var, value, data);
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:27:16 +00:00
|
|
|
/*
|
|
|
|
* Determines whether the commits in from..to are linear, i.e. contain
|
|
|
|
* no merge commits. This function *expects* `from` to be an ancestor of
|
|
|
|
* `to`.
|
|
|
|
*/
|
|
|
|
static int is_linear_history(struct commit *from, struct commit *to)
|
|
|
|
{
|
|
|
|
while (to && to != from) {
|
|
|
|
parse_commit(to);
|
|
|
|
if (!to->parents)
|
|
|
|
return 1;
|
|
|
|
if (to->parents->next)
|
|
|
|
return 0;
|
|
|
|
to = to->parents->item;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
rebase: fast-forward --onto in more cases
Before, when we had the following graph,
A---B---C (master)
\
D (side)
running 'git rebase --onto master... master side' would result in D
being always rebased, no matter what. However, the desired behavior is
that rebase should notice that this is fast-forwardable and do that
instead.
Add detection to `can_fast_forward` so that this case can be detected
and a fast-forward will be performed. First of all, rewrite the function
to use gotos which simplifies the logic. Next, since the
options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)
conditions were removed in `cmd_rebase`, we reintroduce a substitute in
`can_fast_forward`. In particular, checking the merge bases of
`upstream` and `head` fixes a failing case in t3416.
The abbreviated graph for t3416 is as follows:
F---G topic
/
A---B---C---D---E master
and the failing command was
git rebase --onto master...topic F topic
Before, Git would see that there was one merge base (C), and the merge
and onto were the same so it would incorrectly return 1, indicating that
we could fast-forward. This would cause the rebased graph to be 'ABCFG'
when we were expecting 'ABCG'.
With the additional logic, we detect that upstream and head's merge base
is F. Since onto isn't F, it means we're not rebasing the full set of
commits from master..topic. Since we're excluding some commits, a
fast-forward cannot be performed and so we correctly return 0.
Add '-f' to test cases that failed as a result of this change because
they were not expecting a fast-forward so that a rebase is forced.
Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:37:59 +00:00
|
|
|
static int can_fast_forward(struct commit *onto, struct commit *upstream,
|
2019-08-27 05:38:01 +00:00
|
|
|
struct commit *restrict_revision,
|
rebase: fast-forward --onto in more cases
Before, when we had the following graph,
A---B---C (master)
\
D (side)
running 'git rebase --onto master... master side' would result in D
being always rebased, no matter what. However, the desired behavior is
that rebase should notice that this is fast-forwardable and do that
instead.
Add detection to `can_fast_forward` so that this case can be detected
and a fast-forward will be performed. First of all, rewrite the function
to use gotos which simplifies the logic. Next, since the
options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)
conditions were removed in `cmd_rebase`, we reintroduce a substitute in
`can_fast_forward`. In particular, checking the merge bases of
`upstream` and `head` fixes a failing case in t3416.
The abbreviated graph for t3416 is as follows:
F---G topic
/
A---B---C---D---E master
and the failing command was
git rebase --onto master...topic F topic
Before, Git would see that there was one merge base (C), and the merge
and onto were the same so it would incorrectly return 1, indicating that
we could fast-forward. This would cause the rebased graph to be 'ABCFG'
when we were expecting 'ABCG'.
With the additional logic, we detect that upstream and head's merge base
is F. Since onto isn't F, it means we're not rebasing the full set of
commits from master..topic. Since we're excluding some commits, a
fast-forward cannot be performed and so we correctly return 0.
Add '-f' to test cases that failed as a result of this change because
they were not expecting a fast-forward so that a rebase is forced.
Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:37:59 +00:00
|
|
|
struct object_id *head_oid, struct object_id *merge_base)
|
2018-09-04 21:27:16 +00:00
|
|
|
{
|
|
|
|
struct commit *head = lookup_commit(the_repository, head_oid);
|
2019-08-27 05:37:56 +00:00
|
|
|
struct commit_list *merge_bases = NULL;
|
|
|
|
int res = 0;
|
2018-09-04 21:27:16 +00:00
|
|
|
|
|
|
|
if (!head)
|
2019-08-27 05:37:56 +00:00
|
|
|
goto done;
|
2018-09-04 21:27:16 +00:00
|
|
|
|
|
|
|
merge_bases = get_merge_bases(onto, head);
|
2019-08-27 05:37:56 +00:00
|
|
|
if (!merge_bases || merge_bases->next) {
|
2018-09-04 21:27:16 +00:00
|
|
|
oidcpy(merge_base, &null_oid);
|
2019-08-27 05:37:56 +00:00
|
|
|
goto done;
|
2018-09-04 21:27:16 +00:00
|
|
|
}
|
2019-08-27 05:37:56 +00:00
|
|
|
|
|
|
|
oidcpy(merge_base, &merge_bases->item->object.oid);
|
|
|
|
if (!oideq(merge_base, &onto->object.oid))
|
|
|
|
goto done;
|
|
|
|
|
2019-08-27 05:38:01 +00:00
|
|
|
if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))
|
|
|
|
goto done;
|
|
|
|
|
rebase: fast-forward --onto in more cases
Before, when we had the following graph,
A---B---C (master)
\
D (side)
running 'git rebase --onto master... master side' would result in D
being always rebased, no matter what. However, the desired behavior is
that rebase should notice that this is fast-forwardable and do that
instead.
Add detection to `can_fast_forward` so that this case can be detected
and a fast-forward will be performed. First of all, rewrite the function
to use gotos which simplifies the logic. Next, since the
options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)
conditions were removed in `cmd_rebase`, we reintroduce a substitute in
`can_fast_forward`. In particular, checking the merge bases of
`upstream` and `head` fixes a failing case in t3416.
The abbreviated graph for t3416 is as follows:
F---G topic
/
A---B---C---D---E master
and the failing command was
git rebase --onto master...topic F topic
Before, Git would see that there was one merge base (C), and the merge
and onto were the same so it would incorrectly return 1, indicating that
we could fast-forward. This would cause the rebased graph to be 'ABCFG'
when we were expecting 'ABCG'.
With the additional logic, we detect that upstream and head's merge base
is F. Since onto isn't F, it means we're not rebasing the full set of
commits from master..topic. Since we're excluding some commits, a
fast-forward cannot be performed and so we correctly return 0.
Add '-f' to test cases that failed as a result of this change because
they were not expecting a fast-forward so that a rebase is forced.
Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:37:59 +00:00
|
|
|
if (!upstream)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
free_commit_list(merge_bases);
|
|
|
|
merge_bases = get_merge_bases(upstream, head);
|
|
|
|
if (!merge_bases || merge_bases->next)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
if (!oideq(&onto->object.oid, &merge_bases->item->object.oid))
|
|
|
|
goto done;
|
|
|
|
|
2019-08-27 05:37:56 +00:00
|
|
|
res = 1;
|
|
|
|
|
|
|
|
done:
|
2018-09-04 21:27:16 +00:00
|
|
|
free_commit_list(merge_bases);
|
|
|
|
return res && is_linear_history(onto, head);
|
|
|
|
}
|
|
|
|
|
2020-02-15 21:36:34 +00:00
|
|
|
static int parse_opt_am(const struct option *opt, const char *arg, int unset)
|
|
|
|
{
|
|
|
|
struct rebase_options *opts = opt->value;
|
|
|
|
|
|
|
|
BUG_ON_OPT_NEG(unset);
|
|
|
|
BUG_ON_OPT_ARG(arg);
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
opts->type = REBASE_APPLY;
|
2020-02-15 21:36:34 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:59:49 +00:00
|
|
|
/* -i followed by -m is still -i */
|
|
|
|
static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
|
|
|
|
{
|
|
|
|
struct rebase_options *opts = opt->value;
|
|
|
|
|
assert NOARG/NONEG behavior of parse-options callbacks
When we define a parse-options callback, the flags we put in the option
struct must match what the callback expects. For example, a callback
which does not handle the "unset" parameter should only be used with
PARSE_OPT_NONEG. But since the callback and the option struct are not
defined next to each other, it's easy to get this wrong (as earlier
patches in this series show).
Fortunately, the compiler can help us here: compiling with
-Wunused-parameters can show us which callbacks ignore their "unset"
parameters (and likewise, ones that ignore "arg" expect to be triggered
with PARSE_OPT_NOARG).
But after we've inspected a callback and determined that all of its
callers use the right flags, what do we do next? We'd like to silence
the compiler warning, but do so in a way that will catch any wrong calls
in the future.
We can do that by actually checking those variables and asserting that
they match our expectations. Because this is such a common pattern,
we'll introduce some helper macros. The resulting messages aren't
as descriptive as we could make them, but the file/line information from
BUG() is enough to identify the problem (and anyway, the point is that
these should never be seen).
Each of the annotated callbacks in this patch triggers
-Wunused-parameters, and was manually inspected to make sure all callers
use the correct options (so none of these BUGs should be triggerable).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-05 06:45:42 +00:00
|
|
|
BUG_ON_OPT_NEG(unset);
|
|
|
|
BUG_ON_OPT_ARG(arg);
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
if (!is_merge(opts))
|
2018-09-04 21:59:49 +00:00
|
|
|
opts->type = REBASE_MERGE;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -i followed by -p is still explicitly interactive, but -p alone is not */
|
|
|
|
static int parse_opt_interactive(const struct option *opt, const char *arg,
|
|
|
|
int unset)
|
|
|
|
{
|
|
|
|
struct rebase_options *opts = opt->value;
|
|
|
|
|
assert NOARG/NONEG behavior of parse-options callbacks
When we define a parse-options callback, the flags we put in the option
struct must match what the callback expects. For example, a callback
which does not handle the "unset" parameter should only be used with
PARSE_OPT_NONEG. But since the callback and the option struct are not
defined next to each other, it's easy to get this wrong (as earlier
patches in this series show).
Fortunately, the compiler can help us here: compiling with
-Wunused-parameters can show us which callbacks ignore their "unset"
parameters (and likewise, ones that ignore "arg" expect to be triggered
with PARSE_OPT_NOARG).
But after we've inspected a callback and determined that all of its
callers use the right flags, what do we do next? We'd like to silence
the compiler warning, but do so in a way that will catch any wrong calls
in the future.
We can do that by actually checking those variables and asserting that
they match our expectations. Because this is such a common pattern,
we'll introduce some helper macros. The resulting messages aren't
as descriptive as we could make them, but the file/line information from
BUG() is enough to identify the problem (and anyway, the point is that
these should never be seen).
Each of the annotated callbacks in this patch triggers
-Wunused-parameters, and was manually inspected to make sure all callers
use the correct options (so none of these BUGs should be triggerable).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-05 06:45:42 +00:00
|
|
|
BUG_ON_OPT_NEG(unset);
|
|
|
|
BUG_ON_OPT_ARG(arg);
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
opts->type = REBASE_MERGE;
|
2018-09-04 21:59:49 +00:00
|
|
|
opts->flags |= REBASE_INTERACTIVE_EXPLICIT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
rebase (interactive-backend): fix handling of commits that become empty
As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design. This commit addresses
another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would
become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream
commits, as determined by `git log --cherry-mark ...`. Re-applying
these commits would result in an empty set of changes and a
duplicative commit message; i.e. these are commits that have
"already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean
cherry-picks of upstream commits, but which still become empty after
being rebased. This happens e.g. when a commit has changes which
are a strict subset of the changes in an upstream commit, or when
the changes of a commit can be found spread across or among several
upstream commits.
Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes. Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical). As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around. All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case. Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes. Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way. As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that
is empty from the beginning), I'd think that the current behaviour
is desireable one. "am" based rebase is solely to transplant an
existing history and want to stop much less than "interactive" one
whose purpose is to polish a series before making it publishable,
and asking for confirmation ("this has become empty--do you want to
drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases. Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}
with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:25 +00:00
|
|
|
static enum empty_type parse_empty_value(const char *value)
|
|
|
|
{
|
|
|
|
if (!strcasecmp(value, "drop"))
|
|
|
|
return EMPTY_DROP;
|
|
|
|
else if (!strcasecmp(value, "keep"))
|
|
|
|
return EMPTY_KEEP;
|
|
|
|
else if (!strcasecmp(value, "ask"))
|
|
|
|
return EMPTY_ASK;
|
|
|
|
|
|
|
|
die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_opt_empty(const struct option *opt, const char *arg, int unset)
|
|
|
|
{
|
|
|
|
struct rebase_options *options = opt->value;
|
|
|
|
enum empty_type value = parse_empty_value(arg);
|
|
|
|
|
|
|
|
BUG_ON_OPT_NEG(unset);
|
|
|
|
|
|
|
|
options->empty = value;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:36:30 +00:00
|
|
|
static void NORETURN error_on_missing_default_upstream(void)
|
|
|
|
{
|
|
|
|
struct branch *current_branch = branch_get(NULL);
|
|
|
|
|
|
|
|
printf(_("%s\n"
|
|
|
|
"Please specify which branch you want to rebase against.\n"
|
|
|
|
"See git-rebase(1) for details.\n"
|
|
|
|
"\n"
|
|
|
|
" git rebase '<branch>'\n"
|
|
|
|
"\n"),
|
|
|
|
current_branch ? _("There is no tracking information for "
|
|
|
|
"the current branch.") :
|
|
|
|
_("You are not currently on a branch."));
|
|
|
|
|
|
|
|
if (current_branch) {
|
|
|
|
const char *remote = current_branch->remote_name;
|
|
|
|
|
|
|
|
if (!remote)
|
|
|
|
remote = _("<remote>");
|
|
|
|
|
|
|
|
printf(_("If you wish to set tracking information for this "
|
|
|
|
"branch you can do so with:\n"
|
|
|
|
"\n"
|
|
|
|
" git branch --set-upstream-to=%s/<branch> %s\n"
|
|
|
|
"\n"),
|
|
|
|
remote, current_branch->name);
|
|
|
|
}
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2018-11-29 19:09:21 +00:00
|
|
|
static void set_reflog_action(struct rebase_options *options)
|
|
|
|
{
|
|
|
|
const char *env;
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
if (!is_merge(options))
|
2018-11-29 19:09:21 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
env = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
|
|
|
|
if (env && strcmp("rebase", env))
|
|
|
|
return; /* only override it if it is "rebase" */
|
|
|
|
|
2020-02-15 21:36:36 +00:00
|
|
|
strbuf_addf(&buf, "rebase (%s)", options->action);
|
2018-11-29 19:09:21 +00:00
|
|
|
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1);
|
|
|
|
strbuf_release(&buf);
|
|
|
|
}
|
|
|
|
|
2019-01-29 18:43:27 +00:00
|
|
|
static int check_exec_cmd(const char *cmd)
|
|
|
|
{
|
|
|
|
if (strchr(cmd, '\n'))
|
|
|
|
return error(_("exec commands cannot contain newlines"));
|
|
|
|
|
|
|
|
/* Does the command consist purely of whitespace? */
|
|
|
|
if (!cmd[strspn(cmd, " \t\r\f\v")])
|
|
|
|
return error(_("empty exec command"));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-08-06 19:31:09 +00:00
|
|
|
int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|
|
|
{
|
2019-04-17 14:30:41 +00:00
|
|
|
struct rebase_options options = REBASE_OPTIONS_INIT;
|
2018-08-06 19:31:11 +00:00
|
|
|
const char *branch_name;
|
2018-08-08 15:06:16 +00:00
|
|
|
int ret, flags, total_argc, in_progress = 0;
|
rebase: teach rebase --keep-base
A common scenario is if a user is working on a topic branch and they
wish to make some changes to intermediate commits or autosquash, they
would run something such as
git rebase -i --onto master... master
in order to preserve the merge base. This is useful when contributing a
patch series to the Git mailing list, one often starts on top of the
current 'master'. While developing the patches, 'master' is also
developed further and it is sometimes not the best idea to keep rebasing
on top of 'master', but to keep the base commit as-is.
In addition to this, a user wishing to test individual commits in a
topic branch without changing anything may run
git rebase -x ./test.sh master... master
Since rebasing onto the merge base of the branch and the upstream is
such a common case, introduce the --keep-base option as a shortcut.
This allows us to rewrite the above as
git rebase -i --keep-base master
and
git rebase -x ./test.sh --keep-base master
respectively.
Add tests to ensure --keep-base works correctly in the normal case and
fails when there are multiple merge bases, both in regular and
interactive mode. Also, test to make sure conflicting options cause
rebase to fail. While we're adding test cases, add a missing
set_fake_editor call to 'rebase -i --onto master...side'.
While we're documenting the --keep-base option, change an instance of
"merge-base" to "merge base", which is the consistent spelling.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:38:06 +00:00
|
|
|
int keep_base = 0;
|
2018-09-04 21:27:10 +00:00
|
|
|
int ok_to_skip_pre_rebase = 0;
|
2018-08-06 19:31:11 +00:00
|
|
|
struct strbuf msg = STRBUF_INIT;
|
|
|
|
struct strbuf revisions = STRBUF_INIT;
|
2018-09-04 21:27:18 +00:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
2018-09-04 21:27:09 +00:00
|
|
|
struct object_id merge_base;
|
2019-04-17 14:30:43 +00:00
|
|
|
enum action action = ACTION_NONE;
|
2018-09-04 22:00:00 +00:00
|
|
|
const char *gpg_sign = NULL;
|
2018-09-04 22:00:04 +00:00
|
|
|
struct string_list exec = STRING_LIST_INIT_NODUP;
|
2018-09-04 22:00:07 +00:00
|
|
|
const char *rebase_merges = NULL;
|
2018-09-04 22:00:09 +00:00
|
|
|
int fork_point = -1;
|
2018-09-04 22:00:11 +00:00
|
|
|
struct string_list strategy_options = STRING_LIST_INIT_NODUP;
|
2018-09-04 22:00:12 +00:00
|
|
|
struct object_id squash_onto;
|
|
|
|
char *squash_onto_name = NULL;
|
2019-07-01 11:58:15 +00:00
|
|
|
int reschedule_failed_exec = -1;
|
2020-02-15 21:36:31 +00:00
|
|
|
int allow_preemptive_ff = 1;
|
2018-09-04 21:27:07 +00:00
|
|
|
struct option builtin_rebase_options[] = {
|
|
|
|
OPT_STRING(0, "onto", &options.onto_name,
|
|
|
|
N_("revision"),
|
|
|
|
N_("rebase onto given branch instead of upstream")),
|
rebase: teach rebase --keep-base
A common scenario is if a user is working on a topic branch and they
wish to make some changes to intermediate commits or autosquash, they
would run something such as
git rebase -i --onto master... master
in order to preserve the merge base. This is useful when contributing a
patch series to the Git mailing list, one often starts on top of the
current 'master'. While developing the patches, 'master' is also
developed further and it is sometimes not the best idea to keep rebasing
on top of 'master', but to keep the base commit as-is.
In addition to this, a user wishing to test individual commits in a
topic branch without changing anything may run
git rebase -x ./test.sh master... master
Since rebasing onto the merge base of the branch and the upstream is
such a common case, introduce the --keep-base option as a shortcut.
This allows us to rewrite the above as
git rebase -i --keep-base master
and
git rebase -x ./test.sh --keep-base master
respectively.
Add tests to ensure --keep-base works correctly in the normal case and
fails when there are multiple merge bases, both in regular and
interactive mode. Also, test to make sure conflicting options cause
rebase to fail. While we're adding test cases, add a missing
set_fake_editor call to 'rebase -i --onto master...side'.
While we're documenting the --keep-base option, change an instance of
"merge-base" to "merge base", which is the consistent spelling.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:38:06 +00:00
|
|
|
OPT_BOOL(0, "keep-base", &keep_base,
|
|
|
|
N_("use the merge-base of upstream and branch as the current base")),
|
2018-09-04 21:27:10 +00:00
|
|
|
OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
|
|
|
|
N_("allow pre-rebase hook to run")),
|
2018-09-04 21:27:12 +00:00
|
|
|
OPT_NEGBIT('q', "quiet", &options.flags,
|
|
|
|
N_("be quiet. implies --no-stat"),
|
2020-02-15 21:36:28 +00:00
|
|
|
REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
|
2018-09-04 21:27:13 +00:00
|
|
|
OPT_BIT('v', "verbose", &options.flags,
|
|
|
|
N_("display a diffstat of what changed upstream"),
|
|
|
|
REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
|
|
|
|
{OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL,
|
|
|
|
N_("do not show diffstat of what changed upstream"),
|
|
|
|
PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT },
|
2018-09-04 21:59:50 +00:00
|
|
|
OPT_BOOL(0, "signoff", &options.signoff,
|
|
|
|
N_("add a Signed-off-by: line to each commit")),
|
2020-01-12 20:27:41 +00:00
|
|
|
OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &options.git_am_opts,
|
|
|
|
NULL, N_("passed to 'git am'"),
|
|
|
|
PARSE_OPT_NOARG),
|
|
|
|
OPT_PASSTHRU_ARGV(0, "committer-date-is-author-date",
|
|
|
|
&options.git_am_opts, NULL,
|
|
|
|
N_("passed to 'git am'"), PARSE_OPT_NOARG),
|
|
|
|
OPT_PASSTHRU_ARGV(0, "ignore-date", &options.git_am_opts, NULL,
|
|
|
|
N_("passed to 'git am'"), PARSE_OPT_NOARG),
|
2018-11-14 16:25:29 +00:00
|
|
|
OPT_PASSTHRU_ARGV('C', NULL, &options.git_am_opts, N_("n"),
|
|
|
|
N_("passed to 'git apply'"), 0),
|
|
|
|
OPT_PASSTHRU_ARGV(0, "whitespace", &options.git_am_opts,
|
|
|
|
N_("action"), N_("passed to 'git apply'"), 0),
|
2018-09-04 21:27:17 +00:00
|
|
|
OPT_BIT('f', "force-rebase", &options.flags,
|
|
|
|
N_("cherry-pick all commits, even if unchanged"),
|
|
|
|
REBASE_FORCE),
|
|
|
|
OPT_BIT(0, "no-ff", &options.flags,
|
|
|
|
N_("cherry-pick all commits, even if unchanged"),
|
|
|
|
REBASE_FORCE),
|
2018-08-08 15:06:16 +00:00
|
|
|
OPT_CMDMODE(0, "continue", &action, N_("continue"),
|
|
|
|
ACTION_CONTINUE),
|
2018-08-08 15:06:17 +00:00
|
|
|
OPT_CMDMODE(0, "skip", &action,
|
|
|
|
N_("skip current patch and continue"), ACTION_SKIP),
|
2018-08-08 15:06:18 +00:00
|
|
|
OPT_CMDMODE(0, "abort", &action,
|
|
|
|
N_("abort and check out the original branch"),
|
|
|
|
ACTION_ABORT),
|
2018-08-08 15:06:19 +00:00
|
|
|
OPT_CMDMODE(0, "quit", &action,
|
|
|
|
N_("abort but keep HEAD where it is"), ACTION_QUIT),
|
2018-08-08 15:06:20 +00:00
|
|
|
OPT_CMDMODE(0, "edit-todo", &action, N_("edit the todo list "
|
|
|
|
"during an interactive rebase"), ACTION_EDIT_TODO),
|
|
|
|
OPT_CMDMODE(0, "show-current-patch", &action,
|
|
|
|
N_("show the patch file being applied or merged"),
|
|
|
|
ACTION_SHOW_CURRENT_PATCH),
|
2020-02-15 21:36:41 +00:00
|
|
|
{ OPTION_CALLBACK, 0, "apply", &options, NULL,
|
|
|
|
N_("use apply strategies to rebase"),
|
2020-02-15 21:36:34 +00:00
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
|
|
|
|
parse_opt_am },
|
2018-09-04 21:59:49 +00:00
|
|
|
{ OPTION_CALLBACK, 'm', "merge", &options, NULL,
|
|
|
|
N_("use merging strategies to rebase"),
|
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
|
|
|
|
parse_opt_merge },
|
|
|
|
{ OPTION_CALLBACK, 'i', "interactive", &options, NULL,
|
|
|
|
N_("let the user edit the list of commits to rebase"),
|
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
|
|
|
|
parse_opt_interactive },
|
2019-10-18 23:55:56 +00:00
|
|
|
OPT_SET_INT_F('p', "preserve-merges", &options.type,
|
|
|
|
N_("(DEPRECATED) try to recreate merges instead of "
|
|
|
|
"ignoring them"),
|
|
|
|
REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
|
2019-04-17 14:30:36 +00:00
|
|
|
OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
|
2020-03-11 06:55:27 +00:00
|
|
|
OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}",
|
rebase (interactive-backend): fix handling of commits that become empty
As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design. This commit addresses
another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would
become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream
commits, as determined by `git log --cherry-mark ...`. Re-applying
these commits would result in an empty set of changes and a
duplicative commit message; i.e. these are commits that have
"already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean
cherry-picks of upstream commits, but which still become empty after
being rebased. This happens e.g. when a commit has changes which
are a strict subset of the changes in an upstream commit, or when
the changes of a commit can be found spread across or among several
upstream commits.
Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes. Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical). As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around. All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case. Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes. Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way. As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that
is empty from the beginning), I'd think that the current behaviour
is desireable one. "am" based rebase is solely to transplant an
existing history and want to stop much less than "interactive" one
whose purpose is to polish a series before making it publishable,
and asking for confirmation ("this has become empty--do you want to
drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases. Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}
with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:25 +00:00
|
|
|
N_("how to handle commits that become empty"),
|
|
|
|
PARSE_OPT_NONEG, parse_opt_empty),
|
rebase (interactive-backend): make --keep-empty the default
Different rebase backends have different treatment for commits which
start empty (i.e. have no changes relative to their parent), and the
--keep-empty option was added at some point to allow adjusting behavior.
The handling of commits which start empty is actually quite similar to
commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,
2018-06-27), which pointed out that the behavior for various backends is
often more happenstance than design. The specific change made in that
commit is actually quite relevant as well and much of the logic there
directly applies here.
It makes a lot of sense in 'git commit' to error out on the creation of
empty commits, unless an override flag is provided. However, once
someone determines that there is a rare case that merits using the
manual override to create such a commit, it is somewhere between
annoying and harmful to have to take extra steps to keep such
intentional commits around. Granted, empty commits are quite rare,
which is why handling of them doesn't get considered much and folks tend
to defer to existing (accidental) behavior and assume there was a reason
for it, leading them to just add flags (--keep-empty in this case) that
allow them to override the bad defaults. Fix the interactive backend so
that --keep-empty is the default, much like we did with
--allow-empty-message. The am backend should also be fixed to have
--keep-empty semantics for commits that start empty, but that is not
included in this patch other than a testcase documenting the failure.
Note that there was one test in t3421 which appears to have been written
expecting --keep-empty to not be the default as correct behavior. This
test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of
empty commits", 2013-06-06), which was part of a series focusing on
rebase topology and which had an interesting original cover letter at
https://lore.kernel.org/git/1347949878-12578-1-git-send-email-martinvonz@gmail.com/
which noted
Your input especially appreciated on whether you agree with the
intent of the test cases.
and then went into a long example about how one of the many tests added
had several questions about whether it was correct. As such, I believe
most the tests in that series were about testing rebase topology with as
many different flags as possible and were not trying to state in general
how those flags should behave otherwise.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:24 +00:00
|
|
|
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
|
|
|
|
N_("(DEPRECATED) keep empty commits"),
|
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
|
|
|
|
parse_opt_keep_empty },
|
2018-09-04 21:59:58 +00:00
|
|
|
OPT_BOOL(0, "autosquash", &options.autosquash,
|
|
|
|
N_("move commits that begin with "
|
|
|
|
"squash!/fixup! under -i")),
|
2018-09-04 22:00:00 +00:00
|
|
|
{ OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"),
|
|
|
|
N_("GPG-sign commits"),
|
|
|
|
PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
|
2018-09-04 22:00:02 +00:00
|
|
|
OPT_BOOL(0, "autostash", &options.autostash,
|
|
|
|
N_("automatically stash/stash pop before and after")),
|
2018-09-04 22:00:04 +00:00
|
|
|
OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
|
|
|
|
N_("add exec lines after each commit of the "
|
|
|
|
"editable list")),
|
2020-01-16 06:14:15 +00:00
|
|
|
OPT_BOOL_F(0, "allow-empty-message",
|
|
|
|
&options.allow_empty_message,
|
|
|
|
N_("allow rebasing commits with empty messages"),
|
|
|
|
PARSE_OPT_HIDDEN),
|
2018-09-04 22:00:07 +00:00
|
|
|
{OPTION_STRING, 'r', "rebase-merges", &rebase_merges,
|
|
|
|
N_("mode"),
|
|
|
|
N_("try to rebase merges instead of skipping them"),
|
|
|
|
PARSE_OPT_OPTARG, NULL, (intptr_t)""},
|
2018-09-04 22:00:09 +00:00
|
|
|
OPT_BOOL(0, "fork-point", &fork_point,
|
|
|
|
N_("use 'merge-base --fork-point' to refine upstream")),
|
2018-09-04 22:00:11 +00:00
|
|
|
OPT_STRING('s', "strategy", &options.strategy,
|
|
|
|
N_("strategy"), N_("use the given merge strategy")),
|
|
|
|
OPT_STRING_LIST('X', "strategy-option", &strategy_options,
|
|
|
|
N_("option"),
|
|
|
|
N_("pass the argument through to the merge "
|
|
|
|
"strategy")),
|
2018-09-04 22:00:12 +00:00
|
|
|
OPT_BOOL(0, "root", &options.root,
|
|
|
|
N_("rebase all reachable commits up to the root(s)")),
|
2018-12-10 19:04:58 +00:00
|
|
|
OPT_BOOL(0, "reschedule-failed-exec",
|
2019-07-01 11:58:15 +00:00
|
|
|
&reschedule_failed_exec,
|
2018-12-10 19:04:58 +00:00
|
|
|
N_("automatically re-schedule any `exec` that fails")),
|
2018-09-04 21:27:07 +00:00
|
|
|
OPT_END(),
|
|
|
|
};
|
2018-11-14 16:25:29 +00:00
|
|
|
int i;
|
2018-08-06 19:31:11 +00:00
|
|
|
|
2018-09-04 21:27:07 +00:00
|
|
|
if (argc == 2 && !strcmp(argv[1], "-h"))
|
|
|
|
usage_with_options(builtin_rebase_usage,
|
|
|
|
builtin_rebase_options);
|
|
|
|
|
2019-04-17 14:30:41 +00:00
|
|
|
options.allow_empty_message = 1;
|
2018-09-04 21:27:13 +00:00
|
|
|
git_config(rebase_config, &options);
|
|
|
|
|
rebase: remove the rebase.useBuiltin setting
Remove the rebase.useBuiltin setting, which was added as an escape
hatch to disable the builtin version of rebase first released with Git
2.20.
See [1] for the initial implementation of rebase.useBuiltin, and [2]
and [3] for the documentation and corresponding
GIT_TEST_REBASE_USE_BUILTIN option.
Carrying the legacy version is a maintenance burden as seen in
7e097e27d3 ("legacy-rebase: backport -C<n> and --whitespace=<option>
checks", 2018-11-20) and 9aea5e9286 ("rebase: fix regression in
rebase.useBuiltin=false test mode", 2019-02-13). Since the built-in
version has been shown to be stable enough let's remove the legacy
version.
As noted in [3] having use_builtin_rebase() shell out to get its
config doesn't make any sense anymore, that was done for the purposes
of spawning the legacy rebase without having modified any global
state. Let's instead handle this case in rebase_config().
There's still a bunch of references to git-legacy-rebase in po/*.po,
but those will be dealt with in time by the i18n effort.
Even though this configuration variable only existed two releases
let's not entirely delete the entry from the docs, but note its
absence. Individual versions of git tend to be around for a while due
to distro packaging timelines, so e.g. if we're "lucky" a given
version like 2.21 might be installed on say OSX for half a decade.
That'll mean some people probably setting this in config, and then
when they later wonder if it's needed they can Google search the
config option name or check it in git-config. It also allows us to
refer to the docs from the warning for details.
1. 55071ea248 ("rebase: start implementing it as a builtin",
2018-08-07)
2. d8d0a546f0 ("rebase doc: document rebase.useBuiltin", 2018-11-14)
3. 62c23938fa ("tests: add a special setup where rebase.useBuiltin is
off", 2018-11-14)
3. https://public-inbox.org/git/nycvar.QRO.7.76.6.1903141544110.41@tvgsbejvaqbjf.bet/
Acked-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-03-18 11:01:52 +00:00
|
|
|
if (options.use_legacy_rebase ||
|
|
|
|
!git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1))
|
|
|
|
warning(_("the rebase.useBuiltin support has been removed!\n"
|
|
|
|
"See its entry in 'git help config' for details."));
|
|
|
|
|
2018-08-08 15:06:22 +00:00
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addf(&buf, "%s/applying", apply_dir());
|
|
|
|
if(file_exists(buf.buf))
|
|
|
|
die(_("It looks like 'git am' is in progress. Cannot rebase."));
|
|
|
|
|
2018-09-04 21:27:18 +00:00
|
|
|
if (is_directory(apply_dir())) {
|
2020-02-15 21:36:41 +00:00
|
|
|
options.type = REBASE_APPLY;
|
2018-09-04 21:27:18 +00:00
|
|
|
options.state_dir = apply_dir();
|
|
|
|
} else if (is_directory(merge_dir())) {
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addf(&buf, "%s/rewritten", merge_dir());
|
|
|
|
if (is_directory(buf.buf)) {
|
|
|
|
options.type = REBASE_PRESERVE_MERGES;
|
|
|
|
options.flags |= REBASE_INTERACTIVE_EXPLICIT;
|
|
|
|
} else {
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addf(&buf, "%s/interactive", merge_dir());
|
|
|
|
if(file_exists(buf.buf)) {
|
2020-02-15 21:36:41 +00:00
|
|
|
options.type = REBASE_MERGE;
|
2018-09-04 21:27:18 +00:00
|
|
|
options.flags |= REBASE_INTERACTIVE_EXPLICIT;
|
|
|
|
} else
|
|
|
|
options.type = REBASE_MERGE;
|
|
|
|
}
|
|
|
|
options.state_dir = merge_dir();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.type != REBASE_UNSPECIFIED)
|
|
|
|
in_progress = 1;
|
|
|
|
|
2018-08-08 15:06:16 +00:00
|
|
|
total_argc = argc;
|
2018-09-04 21:27:07 +00:00
|
|
|
argc = parse_options(argc, argv, prefix,
|
|
|
|
builtin_rebase_options,
|
|
|
|
builtin_rebase_usage, 0);
|
|
|
|
|
2019-04-17 14:30:43 +00:00
|
|
|
if (action != ACTION_NONE && total_argc != 2) {
|
2018-08-08 15:06:16 +00:00
|
|
|
usage_with_options(builtin_rebase_usage,
|
|
|
|
builtin_rebase_options);
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:27:07 +00:00
|
|
|
if (argc > 2)
|
|
|
|
usage_with_options(builtin_rebase_usage,
|
|
|
|
builtin_rebase_options);
|
2018-08-06 19:31:11 +00:00
|
|
|
|
2019-03-11 19:57:35 +00:00
|
|
|
if (options.type == REBASE_PRESERVE_MERGES)
|
|
|
|
warning(_("git rebase --preserve-merges is deprecated. "
|
|
|
|
"Use --rebase-merges instead."));
|
|
|
|
|
rebase: teach rebase --keep-base
A common scenario is if a user is working on a topic branch and they
wish to make some changes to intermediate commits or autosquash, they
would run something such as
git rebase -i --onto master... master
in order to preserve the merge base. This is useful when contributing a
patch series to the Git mailing list, one often starts on top of the
current 'master'. While developing the patches, 'master' is also
developed further and it is sometimes not the best idea to keep rebasing
on top of 'master', but to keep the base commit as-is.
In addition to this, a user wishing to test individual commits in a
topic branch without changing anything may run
git rebase -x ./test.sh master... master
Since rebasing onto the merge base of the branch and the upstream is
such a common case, introduce the --keep-base option as a shortcut.
This allows us to rewrite the above as
git rebase -i --keep-base master
and
git rebase -x ./test.sh --keep-base master
respectively.
Add tests to ensure --keep-base works correctly in the normal case and
fails when there are multiple merge bases, both in regular and
interactive mode. Also, test to make sure conflicting options cause
rebase to fail. While we're adding test cases, add a missing
set_fake_editor call to 'rebase -i --onto master...side'.
While we're documenting the --keep-base option, change an instance of
"merge-base" to "merge base", which is the consistent spelling.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:38:06 +00:00
|
|
|
if (keep_base) {
|
|
|
|
if (options.onto_name)
|
|
|
|
die(_("cannot combine '--keep-base' with '--onto'"));
|
|
|
|
if (options.root)
|
|
|
|
die(_("cannot combine '--keep-base' with '--root'"));
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:30:43 +00:00
|
|
|
if (action != ACTION_NONE && !in_progress)
|
2018-08-08 15:06:21 +00:00
|
|
|
die(_("No rebase in progress?"));
|
2018-11-29 19:09:21 +00:00
|
|
|
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
|
2018-08-08 15:06:21 +00:00
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
if (action == ACTION_EDIT_TODO && !is_merge(&options))
|
2018-08-08 15:06:20 +00:00
|
|
|
die(_("The --edit-todo action can only be used during "
|
|
|
|
"interactive rebase."));
|
|
|
|
|
2019-02-22 22:25:10 +00:00
|
|
|
if (trace2_is_enabled()) {
|
2020-02-15 21:36:41 +00:00
|
|
|
if (is_merge(&options))
|
2019-02-22 22:25:10 +00:00
|
|
|
trace2_cmd_mode("interactive");
|
|
|
|
else if (exec.nr)
|
|
|
|
trace2_cmd_mode("interactive-exec");
|
|
|
|
else
|
|
|
|
trace2_cmd_mode(action_names[action]);
|
|
|
|
}
|
|
|
|
|
2018-08-08 15:06:16 +00:00
|
|
|
switch (action) {
|
|
|
|
case ACTION_CONTINUE: {
|
|
|
|
struct object_id head;
|
|
|
|
struct lock_file lock_file = LOCK_INIT;
|
|
|
|
int fd;
|
|
|
|
|
|
|
|
options.action = "continue";
|
2018-11-29 19:09:21 +00:00
|
|
|
set_reflog_action(&options);
|
2018-08-08 15:06:16 +00:00
|
|
|
|
|
|
|
/* Sanity check */
|
|
|
|
if (get_oid("HEAD", &head))
|
|
|
|
die(_("Cannot read HEAD"));
|
|
|
|
|
|
|
|
fd = hold_locked_index(&lock_file, 0);
|
2019-01-12 02:13:26 +00:00
|
|
|
if (repo_read_index(the_repository) < 0)
|
2018-08-08 15:06:16 +00:00
|
|
|
die(_("could not read index"));
|
|
|
|
refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL,
|
|
|
|
NULL);
|
|
|
|
if (0 <= fd)
|
2019-01-12 02:13:27 +00:00
|
|
|
repo_update_index_if_able(the_repository, &lock_file);
|
2018-08-08 15:06:16 +00:00
|
|
|
rollback_lock_file(&lock_file);
|
|
|
|
|
2018-11-10 05:48:49 +00:00
|
|
|
if (has_unstaged_changes(the_repository, 1)) {
|
2018-08-08 15:06:16 +00:00
|
|
|
puts(_("You must edit all merge conflicts and then\n"
|
|
|
|
"mark them as resolved using git add"));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
if (read_basic_state(&options))
|
|
|
|
exit(1);
|
|
|
|
goto run_rebase;
|
|
|
|
}
|
2018-08-08 15:06:17 +00:00
|
|
|
case ACTION_SKIP: {
|
|
|
|
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
|
|
|
|
|
|
|
options.action = "skip";
|
2018-11-29 19:09:21 +00:00
|
|
|
set_reflog_action(&options);
|
2018-08-08 15:06:17 +00:00
|
|
|
|
2018-11-10 05:49:09 +00:00
|
|
|
rerere_clear(the_repository, &merge_rr);
|
2018-08-08 15:06:17 +00:00
|
|
|
string_list_clear(&merge_rr, 1);
|
|
|
|
|
2020-04-07 14:27:59 +00:00
|
|
|
if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD,
|
|
|
|
NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
|
2018-08-08 15:06:17 +00:00
|
|
|
die(_("could not discard worktree changes"));
|
2019-03-29 10:38:59 +00:00
|
|
|
remove_branch_state(the_repository, 0);
|
2018-08-08 15:06:17 +00:00
|
|
|
if (read_basic_state(&options))
|
|
|
|
exit(1);
|
|
|
|
goto run_rebase;
|
|
|
|
}
|
2018-08-08 15:06:18 +00:00
|
|
|
case ACTION_ABORT: {
|
|
|
|
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
|
|
|
options.action = "abort";
|
2018-11-29 19:09:21 +00:00
|
|
|
set_reflog_action(&options);
|
2018-08-08 15:06:18 +00:00
|
|
|
|
2018-11-10 05:49:09 +00:00
|
|
|
rerere_clear(the_repository, &merge_rr);
|
2018-08-08 15:06:18 +00:00
|
|
|
string_list_clear(&merge_rr, 1);
|
|
|
|
|
|
|
|
if (read_basic_state(&options))
|
|
|
|
exit(1);
|
2020-04-07 14:27:59 +00:00
|
|
|
if (reset_head(the_repository, &options.orig_head, "reset",
|
2018-11-12 11:44:32 +00:00
|
|
|
options.head_name, RESET_HEAD_HARD,
|
2020-04-07 14:27:59 +00:00
|
|
|
NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
|
2018-08-08 15:06:18 +00:00
|
|
|
die(_("could not move back to %s"),
|
|
|
|
oid_to_hex(&options.orig_head));
|
2019-03-29 10:38:59 +00:00
|
|
|
remove_branch_state(the_repository, 0);
|
2019-05-14 18:03:47 +00:00
|
|
|
ret = !!finish_rebase(&options);
|
2018-08-08 15:06:18 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2018-08-08 15:06:19 +00:00
|
|
|
case ACTION_QUIT: {
|
2020-02-15 21:36:41 +00:00
|
|
|
if (options.type == REBASE_MERGE) {
|
2019-05-14 18:03:49 +00:00
|
|
|
struct replay_opts replay = REPLAY_OPTS_INIT;
|
|
|
|
|
|
|
|
replay.action = REPLAY_INTERACTIVE_REBASE;
|
|
|
|
ret = !!sequencer_remove_state(&replay);
|
|
|
|
} else {
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addstr(&buf, options.state_dir);
|
|
|
|
ret = !!remove_dir_recursively(&buf, 0);
|
|
|
|
if (ret)
|
|
|
|
error(_("could not remove '%s'"),
|
|
|
|
options.state_dir);
|
|
|
|
}
|
2018-08-08 15:06:19 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2018-08-08 15:06:20 +00:00
|
|
|
case ACTION_EDIT_TODO:
|
|
|
|
options.action = "edit-todo";
|
|
|
|
options.dont_finish_rebase = 1;
|
|
|
|
goto run_rebase;
|
|
|
|
case ACTION_SHOW_CURRENT_PATCH:
|
|
|
|
options.action = "show-current-patch";
|
|
|
|
options.dont_finish_rebase = 1;
|
|
|
|
goto run_rebase;
|
2019-04-17 14:30:43 +00:00
|
|
|
case ACTION_NONE:
|
2018-08-08 15:06:20 +00:00
|
|
|
break;
|
2018-08-08 15:06:16 +00:00
|
|
|
default:
|
2018-08-08 15:06:20 +00:00
|
|
|
BUG("action: %d", action);
|
2018-08-08 15:06:16 +00:00
|
|
|
}
|
|
|
|
|
2018-09-04 21:27:18 +00:00
|
|
|
/* Make sure no rebase is in progress */
|
|
|
|
if (in_progress) {
|
|
|
|
const char *last_slash = strrchr(options.state_dir, '/');
|
|
|
|
const char *state_dir_base =
|
|
|
|
last_slash ? last_slash + 1 : options.state_dir;
|
|
|
|
const char *cmd_live_rebase =
|
|
|
|
"git rebase (--continue | --abort | --skip)";
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addf(&buf, "rm -fr \"%s\"", options.state_dir);
|
|
|
|
die(_("It seems that there is already a %s directory, and\n"
|
|
|
|
"I wonder if you are in the middle of another rebase. "
|
|
|
|
"If that is the\n"
|
|
|
|
"case, please try\n\t%s\n"
|
|
|
|
"If that is not the case, please\n\t%s\n"
|
|
|
|
"and run me again. I am stopping in case you still "
|
|
|
|
"have something\n"
|
|
|
|
"valuable there.\n"),
|
|
|
|
state_dir_base, cmd_live_rebase, buf.buf);
|
|
|
|
}
|
|
|
|
|
2020-02-15 21:36:31 +00:00
|
|
|
if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
|
|
|
|
(action != ACTION_NONE) ||
|
|
|
|
(exec.nr > 0) ||
|
|
|
|
options.autosquash) {
|
|
|
|
allow_preemptive_ff = 0;
|
|
|
|
}
|
|
|
|
|
2018-11-14 16:25:29 +00:00
|
|
|
for (i = 0; i < options.git_am_opts.argc; i++) {
|
2018-11-14 16:25:31 +00:00
|
|
|
const char *option = options.git_am_opts.argv[i], *p;
|
2020-01-12 20:27:41 +00:00
|
|
|
if (!strcmp(option, "--committer-date-is-author-date") ||
|
|
|
|
!strcmp(option, "--ignore-date") ||
|
|
|
|
!strcmp(option, "--whitespace=fix") ||
|
2018-11-14 16:25:29 +00:00
|
|
|
!strcmp(option, "--whitespace=strip"))
|
2020-02-15 21:36:31 +00:00
|
|
|
allow_preemptive_ff = 0;
|
2018-11-14 16:25:31 +00:00
|
|
|
else if (skip_prefix(option, "-C", &p)) {
|
|
|
|
while (*p)
|
|
|
|
if (!isdigit(*(p++)))
|
|
|
|
die(_("switch `C' expects a "
|
|
|
|
"numerical value"));
|
|
|
|
} else if (skip_prefix(option, "--whitespace=", &p)) {
|
|
|
|
if (*p && strcmp(p, "warn") && strcmp(p, "nowarn") &&
|
|
|
|
strcmp(p, "error") && strcmp(p, "error-all"))
|
|
|
|
die("Invalid whitespace option: '%s'", p);
|
|
|
|
}
|
2018-09-04 21:59:53 +00:00
|
|
|
}
|
|
|
|
|
2019-01-29 18:43:27 +00:00
|
|
|
for (i = 0; i < exec.nr; i++)
|
|
|
|
if (check_exec_cmd(exec.items[i].string))
|
|
|
|
exit(1);
|
|
|
|
|
2018-11-14 16:25:29 +00:00
|
|
|
if (!(options.flags & REBASE_NO_QUIET))
|
|
|
|
argv_array_push(&options.git_am_opts, "-q");
|
2018-09-04 21:59:56 +00:00
|
|
|
|
rebase (interactive-backend): fix handling of commits that become empty
As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design. This commit addresses
another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would
become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream
commits, as determined by `git log --cherry-mark ...`. Re-applying
these commits would result in an empty set of changes and a
duplicative commit message; i.e. these are commits that have
"already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean
cherry-picks of upstream commits, but which still become empty after
being rebased. This happens e.g. when a commit has changes which
are a strict subset of the changes in an upstream commit, or when
the changes of a commit can be found spread across or among several
upstream commits.
Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes. Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical). As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around. All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case. Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes. Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way. As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that
is empty from the beginning), I'd think that the current behaviour
is desireable one. "am" based rebase is solely to transplant an
existing history and want to stop much less than "interactive" one
whose purpose is to polish a series before making it publishable,
and asking for confirmation ("this has become empty--do you want to
drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases. Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}
with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:25 +00:00
|
|
|
if (options.empty != EMPTY_UNSPECIFIED)
|
2020-02-15 21:36:41 +00:00
|
|
|
imply_merge(&options, "--empty");
|
2018-09-04 21:59:57 +00:00
|
|
|
|
2018-09-04 22:00:00 +00:00
|
|
|
if (gpg_sign) {
|
|
|
|
free(options.gpg_sign_opt);
|
|
|
|
options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
|
|
|
|
}
|
|
|
|
|
2018-09-04 22:00:04 +00:00
|
|
|
if (exec.nr) {
|
|
|
|
int i;
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
imply_merge(&options, "--exec");
|
2018-09-04 22:00:04 +00:00
|
|
|
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
for (i = 0; i < exec.nr; i++)
|
|
|
|
strbuf_addf(&buf, "exec %s\n", exec.items[i].string);
|
|
|
|
options.cmd = xstrdup(buf.buf);
|
|
|
|
}
|
|
|
|
|
2018-09-04 22:00:07 +00:00
|
|
|
if (rebase_merges) {
|
|
|
|
if (!*rebase_merges)
|
|
|
|
; /* default mode; do nothing */
|
|
|
|
else if (!strcmp("rebase-cousins", rebase_merges))
|
|
|
|
options.rebase_cousins = 1;
|
|
|
|
else if (strcmp("no-rebase-cousins", rebase_merges))
|
|
|
|
die(_("Unknown mode: %s"), rebase_merges);
|
|
|
|
options.rebase_merges = 1;
|
2020-02-15 21:36:41 +00:00
|
|
|
imply_merge(&options, "--rebase-merges");
|
2018-09-04 22:00:07 +00:00
|
|
|
}
|
|
|
|
|
2018-09-04 22:00:11 +00:00
|
|
|
if (strategy_options.nr) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!options.strategy)
|
|
|
|
options.strategy = "recursive";
|
|
|
|
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
for (i = 0; i < strategy_options.nr; i++)
|
|
|
|
strbuf_addf(&buf, " --%s",
|
|
|
|
strategy_options.items[i].string);
|
|
|
|
options.strategy_opts = xstrdup(buf.buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.strategy) {
|
|
|
|
options.strategy = xstrdup(options.strategy);
|
|
|
|
switch (options.type) {
|
2020-02-15 21:36:41 +00:00
|
|
|
case REBASE_APPLY:
|
2018-09-04 22:00:11 +00:00
|
|
|
die(_("--strategy requires --merge or --interactive"));
|
|
|
|
case REBASE_MERGE:
|
|
|
|
case REBASE_PRESERVE_MERGES:
|
|
|
|
/* compatible */
|
|
|
|
break;
|
|
|
|
case REBASE_UNSPECIFIED:
|
|
|
|
options.type = REBASE_MERGE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
BUG("unhandled rebase type (%d)", options.type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
rebase: implement --merge via the interactive machinery
As part of an ongoing effort to make rebase have more uniform behavior,
modify the merge backend to behave like the interactive one, by
re-implementing it on top of the latter.
Interactive rebases are implemented in terms of cherry-pick rather than
the merge-recursive builtin, but cherry-pick also calls into the
recursive merge machinery by default and can accept special merge
strategies and/or special strategy options. As such, there really is
not any need for having both git-rebase--merge and
git-rebase--interactive anymore. Delete git-rebase--merge.sh and
instead implement it in builtin/rebase.c.
This results in a few deliberate but small user-visible changes:
* The progress output is modified (see t3406 and t3420 for examples)
* A few known test failures are now fixed (see t3421)
* bash-prompt during a rebase --merge is now REBASE-i instead of
REBASE-m. Reason: The prompt is a reflection of the backend in use;
this allows users to report an issue to the git mailing list with
the appropriate backend information, and allows advanced users to
know where to search for relevant control files. (see t9903)
testcase modification notes:
t3406: --interactive and --merge had slightly different progress output
while running; adjust a test to match the new expectation
t3420: these test precise output while running, but rebase--am,
rebase--merge, and rebase--interactive all were built on very
different commands (am, merge-recursive, cherry-pick), so the
tests expected different output for each type. Now we expect
--merge and --interactive to have the same output.
t3421: --interactive fixes some bugs in --merge! Wahoo!
t9903: --merge uses the interactive backend so the prompt expected is
now REBASE-i.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-12-11 16:11:39 +00:00
|
|
|
if (options.type == REBASE_MERGE)
|
2020-02-15 21:36:41 +00:00
|
|
|
imply_merge(&options, "--merge");
|
rebase: implement --merge via the interactive machinery
As part of an ongoing effort to make rebase have more uniform behavior,
modify the merge backend to behave like the interactive one, by
re-implementing it on top of the latter.
Interactive rebases are implemented in terms of cherry-pick rather than
the merge-recursive builtin, but cherry-pick also calls into the
recursive merge machinery by default and can accept special merge
strategies and/or special strategy options. As such, there really is
not any need for having both git-rebase--merge and
git-rebase--interactive anymore. Delete git-rebase--merge.sh and
instead implement it in builtin/rebase.c.
This results in a few deliberate but small user-visible changes:
* The progress output is modified (see t3406 and t3420 for examples)
* A few known test failures are now fixed (see t3421)
* bash-prompt during a rebase --merge is now REBASE-i instead of
REBASE-m. Reason: The prompt is a reflection of the backend in use;
this allows users to report an issue to the git mailing list with
the appropriate backend information, and allows advanced users to
know where to search for relevant control files. (see t9903)
testcase modification notes:
t3406: --interactive and --merge had slightly different progress output
while running; adjust a test to match the new expectation
t3420: these test precise output while running, but rebase--am,
rebase--merge, and rebase--interactive all were built on very
different commands (am, merge-recursive, cherry-pick), so the
tests expected different output for each type. Now we expect
--merge and --interactive to have the same output.
t3421: --interactive fixes some bugs in --merge! Wahoo!
t9903: --merge uses the interactive backend so the prompt expected is
now REBASE-i.
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-12-11 16:11:39 +00:00
|
|
|
|
2018-09-04 22:00:12 +00:00
|
|
|
if (options.root && !options.onto_name)
|
2020-02-15 21:36:41 +00:00
|
|
|
imply_merge(&options, "--root without --onto");
|
2018-09-04 22:00:12 +00:00
|
|
|
|
2018-08-08 15:36:33 +00:00
|
|
|
if (isatty(2) && options.flags & REBASE_NO_QUIET)
|
|
|
|
strbuf_addstr(&options.git_format_patch_opt, " --progress");
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
if (options.git_am_opts.argc || options.type == REBASE_APPLY) {
|
|
|
|
/* all am options except -q are compatible only with --apply */
|
2020-02-15 21:36:33 +00:00
|
|
|
for (i = options.git_am_opts.argc - 1; i >= 0; i--)
|
|
|
|
if (strcmp(options.git_am_opts.argv[i], "-q"))
|
|
|
|
break;
|
|
|
|
|
2020-02-15 21:36:39 +00:00
|
|
|
if (i >= 0) {
|
2020-02-15 21:36:41 +00:00
|
|
|
if (is_merge(&options))
|
|
|
|
die(_("cannot combine apply options with "
|
|
|
|
"merge options"));
|
2020-02-15 21:36:39 +00:00
|
|
|
else
|
2020-02-15 21:36:41 +00:00
|
|
|
options.type = REBASE_APPLY;
|
2020-02-15 21:36:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.type == REBASE_UNSPECIFIED) {
|
|
|
|
if (!strcmp(options.default_backend, "merge"))
|
2020-02-15 21:36:41 +00:00
|
|
|
imply_merge(&options, "--merge");
|
|
|
|
else if (!strcmp(options.default_backend, "apply"))
|
|
|
|
options.type = REBASE_APPLY;
|
2020-02-15 21:36:39 +00:00
|
|
|
else
|
|
|
|
die(_("Unknown rebase backend: %s"),
|
|
|
|
options.default_backend);
|
2020-02-15 21:36:33 +00:00
|
|
|
}
|
|
|
|
|
2018-08-06 19:31:11 +00:00
|
|
|
switch (options.type) {
|
|
|
|
case REBASE_MERGE:
|
|
|
|
case REBASE_PRESERVE_MERGES:
|
|
|
|
options.state_dir = merge_dir();
|
|
|
|
break;
|
2020-02-15 21:36:41 +00:00
|
|
|
case REBASE_APPLY:
|
2018-08-06 19:31:11 +00:00
|
|
|
options.state_dir = apply_dir();
|
|
|
|
break;
|
|
|
|
default:
|
2020-02-15 21:36:39 +00:00
|
|
|
BUG("options.type was just set above; should be unreachable.");
|
2018-08-06 19:31:11 +00:00
|
|
|
}
|
|
|
|
|
rebase (interactive-backend): fix handling of commits that become empty
As established in the previous commit and commit b00bf1c9a8dd
(git-rebase: make --allow-empty-message the default, 2018-06-27), the
behavior for rebase with different backends in various edge or corner
cases is often more happenstance than design. This commit addresses
another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would
become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream
commits, as determined by `git log --cherry-mark ...`. Re-applying
these commits would result in an empty set of changes and a
duplicative commit message; i.e. these are commits that have
"already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean
cherry-picks of upstream commits, but which still become empty after
being rebased. This happens e.g. when a commit has changes which
are a strict subset of the changes in an upstream commit, or when
the changes of a commit can be found spread across or among several
upstream commits.
Clearly, in both cases the changes in the commit in question are found
upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then
because of how cherry-mark works this means the upstream commit message
was about the *exact* same set of changes. Thus, the commit messages
can be assumed to be fully interchangeable (and are in fact likely to be
completely identical). As such, the clean cherry-pick case represents a
case when there is no information to be gained by keeping the extra
commit around. All rebase types have always dropped these commits, and
no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also
be able to drop the commit without loss of information -- but this isn't
quite always the case. Since these commits represent cases that were
not clean cherry-picks, there is no upstream commit message explaining
the same set of changes. Projects with good commit message hygiene will
likely have the explanation from our commit message contained within or
spread among the relevant upstream commits, but not all projects run
that way. As such, the commit message of the commit being rebased may
have reasoning that suggests additional changes that should be made to
adapt to the new base, or it may have information that someone wants to
add as a note to another commit, or perhaps someone even wants to create
an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that
is empty from the beginning), I'd think that the current behaviour
is desireable one. "am" based rebase is solely to transplant an
existing history and want to stop much less than "interactive" one
whose purpose is to polish a series before making it publishable,
and asking for confirmation ("this has become empty--do you want to
drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/xmqqfu1fswdh.fsf@gitster-ct.c.googlers.com/
I would simply add that his arguments for "am"-based rebases actually
apply to all non-explicitly-interactive rebases. Also, since we are
stating that different cases should have different defaults, it may be
worth providing a flag to allow users to select which behavior they want
for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}
with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified,
pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-02-15 21:36:25 +00:00
|
|
|
if (options.empty == EMPTY_UNSPECIFIED) {
|
|
|
|
if (options.flags & REBASE_INTERACTIVE_EXPLICIT)
|
|
|
|
options.empty = EMPTY_ASK;
|
|
|
|
else if (exec.nr > 0)
|
|
|
|
options.empty = EMPTY_KEEP;
|
|
|
|
else
|
|
|
|
options.empty = EMPTY_DROP;
|
|
|
|
}
|
2020-02-15 21:36:41 +00:00
|
|
|
if (reschedule_failed_exec > 0 && !is_merge(&options))
|
2019-07-01 11:58:15 +00:00
|
|
|
die(_("--reschedule-failed-exec requires "
|
|
|
|
"--exec or --interactive"));
|
|
|
|
if (reschedule_failed_exec >= 0)
|
|
|
|
options.reschedule_failed_exec = reschedule_failed_exec;
|
2018-12-10 19:04:58 +00:00
|
|
|
|
2018-09-04 21:59:50 +00:00
|
|
|
if (options.signoff) {
|
|
|
|
if (options.type == REBASE_PRESERVE_MERGES)
|
|
|
|
die("cannot combine '--signoff' with "
|
|
|
|
"'--preserve-merges'");
|
2018-11-14 16:25:29 +00:00
|
|
|
argv_array_push(&options.git_am_opts, "--signoff");
|
2018-09-04 21:59:50 +00:00
|
|
|
options.flags |= REBASE_FORCE;
|
|
|
|
}
|
|
|
|
|
2018-12-10 19:04:58 +00:00
|
|
|
if (options.type == REBASE_PRESERVE_MERGES) {
|
2018-08-08 15:36:35 +00:00
|
|
|
/*
|
|
|
|
* Note: incompatibility with --signoff handled in signoff block above
|
|
|
|
* Note: incompatibility with --interactive is just a strong warning;
|
|
|
|
* git-rebase.txt caveats with "unless you know what you are doing"
|
|
|
|
*/
|
|
|
|
if (options.rebase_merges)
|
2018-12-11 16:11:32 +00:00
|
|
|
die(_("cannot combine '--preserve-merges' with "
|
2018-08-08 15:36:35 +00:00
|
|
|
"'--rebase-merges'"));
|
|
|
|
|
2018-12-10 19:04:58 +00:00
|
|
|
if (options.reschedule_failed_exec)
|
|
|
|
die(_("error: cannot combine '--preserve-merges' with "
|
|
|
|
"'--reschedule-failed-exec'"));
|
|
|
|
}
|
|
|
|
|
2018-08-06 19:31:11 +00:00
|
|
|
if (!options.root) {
|
2018-08-08 15:36:30 +00:00
|
|
|
if (argc < 1) {
|
|
|
|
struct branch *branch;
|
|
|
|
|
|
|
|
branch = branch_get(NULL);
|
|
|
|
options.upstream_name = branch_get_upstream(branch,
|
|
|
|
NULL);
|
|
|
|
if (!options.upstream_name)
|
|
|
|
error_on_missing_default_upstream();
|
|
|
|
if (fork_point < 0)
|
|
|
|
fork_point = 1;
|
|
|
|
} else {
|
2018-09-04 21:27:07 +00:00
|
|
|
options.upstream_name = argv[0];
|
2018-08-06 19:31:11 +00:00
|
|
|
argc--;
|
|
|
|
argv++;
|
|
|
|
if (!strcmp(options.upstream_name, "-"))
|
|
|
|
options.upstream_name = "@{-1}";
|
|
|
|
}
|
|
|
|
options.upstream = peel_committish(options.upstream_name);
|
|
|
|
if (!options.upstream)
|
|
|
|
die(_("invalid upstream '%s'"), options.upstream_name);
|
2018-09-04 21:27:10 +00:00
|
|
|
options.upstream_arg = options.upstream_name;
|
2018-09-04 22:00:12 +00:00
|
|
|
} else {
|
|
|
|
if (!options.onto_name) {
|
|
|
|
if (commit_tree("", 0, the_hash_algo->empty_tree, NULL,
|
|
|
|
&squash_onto, NULL, NULL) < 0)
|
|
|
|
die(_("Could not create new root commit"));
|
|
|
|
options.squash_onto = &squash_onto;
|
|
|
|
options.onto_name = squash_onto_name =
|
|
|
|
xstrdup(oid_to_hex(&squash_onto));
|
2019-07-31 15:18:49 +00:00
|
|
|
} else
|
|
|
|
options.root_with_onto = 1;
|
|
|
|
|
2018-09-04 22:00:12 +00:00
|
|
|
options.upstream_name = NULL;
|
|
|
|
options.upstream = NULL;
|
|
|
|
if (argc > 1)
|
|
|
|
usage_with_options(builtin_rebase_usage,
|
|
|
|
builtin_rebase_options);
|
|
|
|
options.upstream_arg = "--root";
|
|
|
|
}
|
2018-08-06 19:31:11 +00:00
|
|
|
|
|
|
|
/* Make sure the branch to rebase onto is valid. */
|
rebase: teach rebase --keep-base
A common scenario is if a user is working on a topic branch and they
wish to make some changes to intermediate commits or autosquash, they
would run something such as
git rebase -i --onto master... master
in order to preserve the merge base. This is useful when contributing a
patch series to the Git mailing list, one often starts on top of the
current 'master'. While developing the patches, 'master' is also
developed further and it is sometimes not the best idea to keep rebasing
on top of 'master', but to keep the base commit as-is.
In addition to this, a user wishing to test individual commits in a
topic branch without changing anything may run
git rebase -x ./test.sh master... master
Since rebasing onto the merge base of the branch and the upstream is
such a common case, introduce the --keep-base option as a shortcut.
This allows us to rewrite the above as
git rebase -i --keep-base master
and
git rebase -x ./test.sh --keep-base master
respectively.
Add tests to ensure --keep-base works correctly in the normal case and
fails when there are multiple merge bases, both in regular and
interactive mode. Also, test to make sure conflicting options cause
rebase to fail. While we're adding test cases, add a missing
set_fake_editor call to 'rebase -i --onto master...side'.
While we're documenting the --keep-base option, change an instance of
"merge-base" to "merge base", which is the consistent spelling.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:38:06 +00:00
|
|
|
if (keep_base) {
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addstr(&buf, options.upstream_name);
|
|
|
|
strbuf_addstr(&buf, "...");
|
|
|
|
options.onto_name = xstrdup(buf.buf);
|
|
|
|
} else if (!options.onto_name)
|
2018-08-06 19:31:11 +00:00
|
|
|
options.onto_name = options.upstream_name;
|
|
|
|
if (strstr(options.onto_name, "...")) {
|
rebase: teach rebase --keep-base
A common scenario is if a user is working on a topic branch and they
wish to make some changes to intermediate commits or autosquash, they
would run something such as
git rebase -i --onto master... master
in order to preserve the merge base. This is useful when contributing a
patch series to the Git mailing list, one often starts on top of the
current 'master'. While developing the patches, 'master' is also
developed further and it is sometimes not the best idea to keep rebasing
on top of 'master', but to keep the base commit as-is.
In addition to this, a user wishing to test individual commits in a
topic branch without changing anything may run
git rebase -x ./test.sh master... master
Since rebasing onto the merge base of the branch and the upstream is
such a common case, introduce the --keep-base option as a shortcut.
This allows us to rewrite the above as
git rebase -i --keep-base master
and
git rebase -x ./test.sh --keep-base master
respectively.
Add tests to ensure --keep-base works correctly in the normal case and
fails when there are multiple merge bases, both in regular and
interactive mode. Also, test to make sure conflicting options cause
rebase to fail. While we're adding test cases, add a missing
set_fake_editor call to 'rebase -i --onto master...side'.
While we're documenting the --keep-base option, change an instance of
"merge-base" to "merge base", which is the consistent spelling.
Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:38:06 +00:00
|
|
|
if (get_oid_mb(options.onto_name, &merge_base) < 0) {
|
|
|
|
if (keep_base)
|
|
|
|
die(_("'%s': need exactly one merge base with branch"),
|
|
|
|
options.upstream_name);
|
|
|
|
else
|
|
|
|
die(_("'%s': need exactly one merge base"),
|
|
|
|
options.onto_name);
|
|
|
|
}
|
2018-09-04 21:27:09 +00:00
|
|
|
options.onto = lookup_commit_or_die(&merge_base,
|
|
|
|
options.onto_name);
|
2018-08-06 19:31:11 +00:00
|
|
|
} else {
|
|
|
|
options.onto = peel_committish(options.onto_name);
|
|
|
|
if (!options.onto)
|
|
|
|
die(_("Does not point to a valid commit '%s'"),
|
|
|
|
options.onto_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the branch to rebase is given, that is the branch we will rebase
|
|
|
|
* branch_name -- branch/commit being rebased, or
|
|
|
|
* HEAD (already detached)
|
|
|
|
* orig_head -- commit object name of tip of the branch before rebasing
|
2018-09-04 21:27:20 +00:00
|
|
|
* head_name -- refs/heads/<that-branch> or NULL (detached HEAD)
|
2018-08-06 19:31:11 +00:00
|
|
|
*/
|
2018-09-04 21:27:21 +00:00
|
|
|
if (argc == 1) {
|
|
|
|
/* Is it "rebase other branchname" or "rebase other commit"? */
|
|
|
|
branch_name = argv[0];
|
|
|
|
options.switch_to = argv[0];
|
|
|
|
|
|
|
|
/* Is it a local branch? */
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addf(&buf, "refs/heads/%s", branch_name);
|
2020-02-23 10:14:07 +00:00
|
|
|
if (!read_ref(buf.buf, &options.orig_head)) {
|
|
|
|
die_if_checked_out(buf.buf, 1);
|
2018-09-04 21:27:21 +00:00
|
|
|
options.head_name = xstrdup(buf.buf);
|
|
|
|
/* If not is it a valid ref (branch or commit)? */
|
2020-02-23 10:14:07 +00:00
|
|
|
} else if (!get_oid(branch_name, &options.orig_head))
|
2018-09-04 21:27:21 +00:00
|
|
|
options.head_name = NULL;
|
|
|
|
else
|
|
|
|
die(_("fatal: no such branch/commit '%s'"),
|
|
|
|
branch_name);
|
|
|
|
} else if (argc == 0) {
|
2018-08-06 19:31:11 +00:00
|
|
|
/* Do not need to switch branches, we are already on it. */
|
|
|
|
options.head_name =
|
|
|
|
xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL,
|
|
|
|
&flags));
|
|
|
|
if (!options.head_name)
|
|
|
|
die(_("No such ref: %s"), "HEAD");
|
|
|
|
if (flags & REF_ISSYMREF) {
|
|
|
|
if (!skip_prefix(options.head_name,
|
|
|
|
"refs/heads/", &branch_name))
|
|
|
|
branch_name = options.head_name;
|
|
|
|
|
|
|
|
} else {
|
2019-04-06 11:34:21 +00:00
|
|
|
FREE_AND_NULL(options.head_name);
|
2018-08-06 19:31:11 +00:00
|
|
|
branch_name = "HEAD";
|
|
|
|
}
|
|
|
|
if (get_oid("HEAD", &options.orig_head))
|
|
|
|
die(_("Could not resolve HEAD to a revision"));
|
2018-09-04 21:27:21 +00:00
|
|
|
} else
|
|
|
|
BUG("unexpected number of arguments left to parse");
|
2018-08-06 19:31:11 +00:00
|
|
|
|
2018-09-04 22:00:09 +00:00
|
|
|
if (fork_point > 0) {
|
|
|
|
struct commit *head =
|
|
|
|
lookup_commit_reference(the_repository,
|
|
|
|
&options.orig_head);
|
|
|
|
options.restrict_revision =
|
|
|
|
get_fork_point(options.upstream_name, head);
|
|
|
|
}
|
|
|
|
|
2019-01-12 02:13:26 +00:00
|
|
|
if (repo_read_index(the_repository) < 0)
|
2018-09-04 21:27:14 +00:00
|
|
|
die(_("could not read index"));
|
|
|
|
|
2018-09-04 22:00:02 +00:00
|
|
|
if (options.autostash) {
|
|
|
|
struct lock_file lock_file = LOCK_INIT;
|
|
|
|
int fd;
|
|
|
|
|
|
|
|
fd = hold_locked_index(&lock_file, 0);
|
|
|
|
refresh_cache(REFRESH_QUIET);
|
|
|
|
if (0 <= fd)
|
2019-01-12 02:13:27 +00:00
|
|
|
repo_update_index_if_able(the_repository, &lock_file);
|
2018-09-04 22:00:02 +00:00
|
|
|
rollback_lock_file(&lock_file);
|
|
|
|
|
2018-11-10 05:48:49 +00:00
|
|
|
if (has_unstaged_changes(the_repository, 1) ||
|
|
|
|
has_uncommitted_changes(the_repository, 1)) {
|
2018-09-04 22:00:02 +00:00
|
|
|
const char *autostash =
|
|
|
|
state_dir_path("autostash", &options);
|
|
|
|
struct child_process stash = CHILD_PROCESS_INIT;
|
|
|
|
struct object_id oid;
|
|
|
|
|
|
|
|
argv_array_pushl(&stash.args,
|
|
|
|
"stash", "create", "autostash", NULL);
|
|
|
|
stash.git_cmd = 1;
|
|
|
|
stash.no_stdin = 1;
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
if (capture_command(&stash, &buf, GIT_MAX_HEXSZ))
|
|
|
|
die(_("Cannot autostash"));
|
|
|
|
strbuf_trim_trailing_newline(&buf);
|
|
|
|
if (get_oid(buf.buf, &oid))
|
|
|
|
die(_("Unexpected stash response: '%s'"),
|
|
|
|
buf.buf);
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);
|
|
|
|
|
|
|
|
if (safe_create_leading_directories_const(autostash))
|
|
|
|
die(_("Could not create directory for '%s'"),
|
|
|
|
options.state_dir);
|
2018-10-22 22:15:03 +00:00
|
|
|
write_file(autostash, "%s", oid_to_hex(&oid));
|
2018-09-04 22:00:02 +00:00
|
|
|
printf(_("Created autostash: %s\n"), buf.buf);
|
2020-04-07 14:27:59 +00:00
|
|
|
if (reset_head(the_repository, NULL, "reset --hard",
|
|
|
|
NULL, RESET_HEAD_HARD, NULL, NULL,
|
|
|
|
DEFAULT_REFLOG_ACTION) < 0)
|
2018-09-04 22:00:02 +00:00
|
|
|
die(_("could not reset --hard"));
|
|
|
|
|
|
|
|
if (discard_index(the_repository->index) < 0 ||
|
2019-01-12 02:13:26 +00:00
|
|
|
repo_read_index(the_repository) < 0)
|
2018-09-04 22:00:02 +00:00
|
|
|
die(_("could not read index"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-10 05:48:49 +00:00
|
|
|
if (require_clean_work_tree(the_repository, "rebase",
|
2018-09-04 21:27:14 +00:00
|
|
|
_("Please commit or stash them."), 1, 1)) {
|
|
|
|
ret = 1;
|
|
|
|
goto cleanup;
|
2018-08-06 19:31:11 +00:00
|
|
|
}
|
|
|
|
|
2018-09-04 21:27:16 +00:00
|
|
|
/*
|
|
|
|
* Now we are rebasing commits upstream..orig_head (or with --root,
|
|
|
|
* everything leading up to orig_head) on top of onto.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if we are already based on onto with linear history,
|
rebase: fast-forward --onto in more cases
Before, when we had the following graph,
A---B---C (master)
\
D (side)
running 'git rebase --onto master... master side' would result in D
being always rebased, no matter what. However, the desired behavior is
that rebase should notice that this is fast-forwardable and do that
instead.
Add detection to `can_fast_forward` so that this case can be detected
and a fast-forward will be performed. First of all, rewrite the function
to use gotos which simplifies the logic. Next, since the
options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)
conditions were removed in `cmd_rebase`, we reintroduce a substitute in
`can_fast_forward`. In particular, checking the merge bases of
`upstream` and `head` fixes a failing case in t3416.
The abbreviated graph for t3416 is as follows:
F---G topic
/
A---B---C---D---E master
and the failing command was
git rebase --onto master...topic F topic
Before, Git would see that there was one merge base (C), and the merge
and onto were the same so it would incorrectly return 1, indicating that
we could fast-forward. This would cause the rebased graph to be 'ABCFG'
when we were expecting 'ABCG'.
With the additional logic, we detect that upstream and head's merge base
is F. Since onto isn't F, it means we're not rebasing the full set of
commits from master..topic. Since we're excluding some commits, a
fast-forward cannot be performed and so we correctly return 0.
Add '-f' to test cases that failed as a result of this change because
they were not expecting a fast-forward so that a rebase is forced.
Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-08-27 05:37:59 +00:00
|
|
|
* in which case we could fast-forward without replacing the commits
|
2020-02-15 21:36:31 +00:00
|
|
|
* with new commits recreated by replaying their changes.
|
|
|
|
*
|
|
|
|
* Note that can_fast_forward() initializes merge_base, so we have to
|
|
|
|
* call it before checking allow_preemptive_ff.
|
2018-09-04 21:27:16 +00:00
|
|
|
*/
|
2019-08-27 05:38:01 +00:00
|
|
|
if (can_fast_forward(options.onto, options.upstream, options.restrict_revision,
|
|
|
|
&options.orig_head, &merge_base) &&
|
2020-02-15 21:36:31 +00:00
|
|
|
allow_preemptive_ff) {
|
2018-09-04 21:27:16 +00:00
|
|
|
int flag;
|
|
|
|
|
2018-09-04 21:27:17 +00:00
|
|
|
if (!(options.flags & REBASE_FORCE)) {
|
2018-09-04 21:27:21 +00:00
|
|
|
/* Lazily switch to the target branch if needed... */
|
|
|
|
if (options.switch_to) {
|
|
|
|
strbuf_reset(&buf);
|
2018-11-29 19:09:21 +00:00
|
|
|
strbuf_addf(&buf, "%s: checkout %s",
|
|
|
|
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
|
2018-09-04 21:27:21 +00:00
|
|
|
options.switch_to);
|
2020-04-07 14:27:59 +00:00
|
|
|
if (reset_head(the_repository,
|
|
|
|
&options.orig_head, "checkout",
|
2018-12-29 21:37:59 +00:00
|
|
|
options.head_name,
|
|
|
|
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
|
2020-04-07 14:27:59 +00:00
|
|
|
NULL, buf.buf,
|
|
|
|
DEFAULT_REFLOG_ACTION) < 0) {
|
2018-09-04 21:27:21 +00:00
|
|
|
ret = !!error(_("could not switch to "
|
|
|
|
"%s"),
|
|
|
|
options.switch_to);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:27:17 +00:00
|
|
|
if (!(options.flags & REBASE_NO_QUIET))
|
|
|
|
; /* be quiet */
|
|
|
|
else if (!strcmp(branch_name, "HEAD") &&
|
|
|
|
resolve_ref_unsafe("HEAD", 0, NULL, &flag))
|
|
|
|
puts(_("HEAD is up to date."));
|
|
|
|
else
|
|
|
|
printf(_("Current branch %s is up to date.\n"),
|
|
|
|
branch_name);
|
|
|
|
ret = !!finish_rebase(&options);
|
|
|
|
goto cleanup;
|
|
|
|
} else if (!(options.flags & REBASE_NO_QUIET))
|
2018-09-04 21:27:16 +00:00
|
|
|
; /* be quiet */
|
|
|
|
else if (!strcmp(branch_name, "HEAD") &&
|
|
|
|
resolve_ref_unsafe("HEAD", 0, NULL, &flag))
|
|
|
|
puts(_("HEAD is up to date, rebase forced."));
|
|
|
|
else
|
|
|
|
printf(_("Current branch %s is up to date, rebase "
|
|
|
|
"forced.\n"), branch_name);
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:27:10 +00:00
|
|
|
/* If a hook exists, give it a chance to interrupt*/
|
|
|
|
if (!ok_to_skip_pre_rebase &&
|
|
|
|
run_hook_le(NULL, "pre-rebase", options.upstream_arg,
|
|
|
|
argc ? argv[0] : NULL, NULL))
|
|
|
|
die(_("The pre-rebase hook refused to rebase."));
|
|
|
|
|
2018-09-04 21:27:13 +00:00
|
|
|
if (options.flags & REBASE_DIFFSTAT) {
|
|
|
|
struct diff_options opts;
|
|
|
|
|
2018-11-29 13:01:54 +00:00
|
|
|
if (options.flags & REBASE_VERBOSE) {
|
|
|
|
if (is_null_oid(&merge_base))
|
|
|
|
printf(_("Changes to %s:\n"),
|
|
|
|
oid_to_hex(&options.onto->object.oid));
|
|
|
|
else
|
|
|
|
printf(_("Changes from %s to %s:\n"),
|
|
|
|
oid_to_hex(&merge_base),
|
|
|
|
oid_to_hex(&options.onto->object.oid));
|
|
|
|
}
|
2018-09-04 21:27:13 +00:00
|
|
|
|
|
|
|
/* We want color (if set), but no pager */
|
|
|
|
diff_setup(&opts);
|
|
|
|
opts.stat_width = -1; /* use full terminal width */
|
|
|
|
opts.stat_graph_width = -1; /* respect statGraphWidth config */
|
|
|
|
opts.output_format |=
|
|
|
|
DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
|
|
|
|
opts.detect_rename = DIFF_DETECT_RENAME;
|
|
|
|
diff_setup_done(&opts);
|
2018-11-29 13:01:54 +00:00
|
|
|
diff_tree_oid(is_null_oid(&merge_base) ?
|
|
|
|
the_hash_algo->empty_tree : &merge_base,
|
|
|
|
&options.onto->object.oid, "", &opts);
|
2018-09-04 21:27:13 +00:00
|
|
|
diffcore_std(&opts);
|
|
|
|
diff_flush(&opts);
|
|
|
|
}
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
if (is_merge(&options))
|
2018-09-04 21:59:49 +00:00
|
|
|
goto run_rebase;
|
|
|
|
|
2018-09-04 21:27:13 +00:00
|
|
|
/* Detach HEAD and reset the tree */
|
|
|
|
if (options.flags & REBASE_NO_QUIET)
|
|
|
|
printf(_("First, rewinding head to replay your work on top of "
|
|
|
|
"it...\n"));
|
|
|
|
|
2018-11-29 19:09:21 +00:00
|
|
|
strbuf_addf(&msg, "%s: checkout %s",
|
|
|
|
getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
|
2020-04-07 14:27:59 +00:00
|
|
|
if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL,
|
2019-07-24 21:14:59 +00:00
|
|
|
RESET_HEAD_DETACH | RESET_ORIG_HEAD |
|
2019-03-20 06:16:05 +00:00
|
|
|
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
|
2020-04-07 14:27:59 +00:00
|
|
|
NULL, msg.buf, DEFAULT_REFLOG_ACTION))
|
2018-08-06 19:31:11 +00:00
|
|
|
die(_("Could not detach HEAD"));
|
|
|
|
strbuf_release(&msg);
|
|
|
|
|
2018-08-08 15:36:32 +00:00
|
|
|
/*
|
|
|
|
* If the onto is a proper descendant of the tip of the branch, then
|
|
|
|
* we just fast-forwarded.
|
|
|
|
*/
|
|
|
|
strbuf_reset(&msg);
|
2019-04-06 11:34:22 +00:00
|
|
|
if (oideq(&merge_base, &options.orig_head)) {
|
2018-11-30 18:11:45 +00:00
|
|
|
printf(_("Fast-forwarded %s to %s.\n"),
|
2018-08-08 15:36:32 +00:00
|
|
|
branch_name, options.onto_name);
|
|
|
|
strbuf_addf(&msg, "rebase finished: %s onto %s",
|
|
|
|
options.head_name ? options.head_name : "detached HEAD",
|
|
|
|
oid_to_hex(&options.onto->object.oid));
|
2020-04-07 14:27:59 +00:00
|
|
|
reset_head(the_repository, NULL, "Fast-forwarded", options.head_name,
|
|
|
|
RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
|
|
|
|
DEFAULT_REFLOG_ACTION);
|
2018-08-08 15:36:32 +00:00
|
|
|
strbuf_release(&msg);
|
|
|
|
ret = !!finish_rebase(&options);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2018-08-06 19:31:11 +00:00
|
|
|
strbuf_addf(&revisions, "%s..%s",
|
|
|
|
options.root ? oid_to_hex(&options.onto->object.oid) :
|
|
|
|
(options.restrict_revision ?
|
|
|
|
oid_to_hex(&options.restrict_revision->object.oid) :
|
|
|
|
oid_to_hex(&options.upstream->object.oid)),
|
|
|
|
oid_to_hex(&options.orig_head));
|
|
|
|
|
|
|
|
options.revisions = revisions.buf;
|
|
|
|
|
2018-08-08 15:06:16 +00:00
|
|
|
run_rebase:
|
2019-04-17 14:30:44 +00:00
|
|
|
ret = !!run_specific_rebase(&options, action);
|
2018-08-06 19:31:11 +00:00
|
|
|
|
2018-09-04 21:27:14 +00:00
|
|
|
cleanup:
|
2019-05-14 18:03:46 +00:00
|
|
|
strbuf_release(&buf);
|
2018-08-06 19:31:11 +00:00
|
|
|
strbuf_release(&revisions);
|
|
|
|
free(options.head_name);
|
2018-09-04 22:00:00 +00:00
|
|
|
free(options.gpg_sign_opt);
|
2018-09-04 22:00:04 +00:00
|
|
|
free(options.cmd);
|
2018-09-04 22:00:12 +00:00
|
|
|
free(squash_onto_name);
|
2018-08-06 19:31:11 +00:00
|
|
|
return ret;
|
2018-08-06 19:31:09 +00:00
|
|
|
}
|