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"
|
2020-07-28 20:23:39 +00:00
|
|
|
#include "strvec.h"
|
2018-08-06 19:31:09 +00:00
|
|
|
#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"
|
2020-04-07 14:28:00 +00:00
|
|
|
#include "reset.h"
|
2021-12-22 03:59:32 +00:00
|
|
|
#include "hook.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>]"),
|
2022-01-31 22:07:48 +00:00
|
|
|
"git rebase --continue | --abort | --skip | --edit-todo",
|
2018-09-04 21:27:07 +00:00
|
|
|
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,
|
2021-09-07 21:05:06 +00:00
|
|
|
REBASE_MERGE
|
2018-08-06 19:31:11 +00:00
|
|
|
};
|
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;
|
2020-07-28 20:24:27 +00:00
|
|
|
struct strvec 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;
|
rebase: reinstate --no-keep-empty
Commit d48e5e21da ("rebase (interactive-backend): make --keep-empty the
default", 2020-02-15) turned --keep-empty (for keeping commits which
start empty) into the default. The logic underpinning that commit was:
1) 'git commit' errors out on the creation of empty commits without an
override flag
2) Once someone determines that the override is worthwhile, it's
annoying and/or harmful to required them to take extra steps in
order to keep such commits around (and to repeat such steps with
every rebase).
While the logic on which the decision was made is sound, the result was
a bit of an overcorrection. Instead of jumping to having --keep-empty
being the default, it jumped to making --keep-empty the only available
behavior. There was a simple workaround, though, which was thought to
be good enough at the time. People could still drop commits which
started empty the same way the could drop any commits: by firing up an
interactive rebase and picking out the commits they didn't want from the
list. However, there are cases where external tools might create enough
empty commits that picking all of them out is painful. As such, having
a flag to automatically remove start-empty commits may be beneficial.
Provide users a way to drop commits which start empty using a flag that
existed for years: --no-keep-empty. Interpret --keep-empty as
countermanding any previous --no-keep-empty, but otherwise leaving
--keep-empty as the default.
This might lead to some slight weirdness since commands like
git rebase --empty=drop --keep-empty
git rebase --empty=keep --no-keep-empty
look really weird despite making perfect sense (the first will drop
commits which become empty, but keep commits that started empty; the
second will keep commits which become empty, but drop commits which
started empty). However, --no-keep-empty was named years ago and we are
predominantly keeping it for backward compatibility; also we suspect it
will only be used rarely since folks already have a simple way to drop
commits they don't want with an interactive rebase.
Reported-by: Bryan Turner <bturner@atlassian.com>
Reported-by: Sami Boukortt <sami@boukortt.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-11 02:44:25 +00:00
|
|
|
int keep_empty;
|
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;
|
2020-08-17 17:40:02 +00:00
|
|
|
int committer_date_is_author_date;
|
2020-08-17 17:40:03 +00:00
|
|
|
int ignore_date;
|
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;
|
2020-04-11 02:44:27 +00:00
|
|
|
int reapply_cherry_picks;
|
2021-02-23 07:18:40 +00:00
|
|
|
int fork_point;
|
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, \
|
rebase: reinstate --no-keep-empty
Commit d48e5e21da ("rebase (interactive-backend): make --keep-empty the
default", 2020-02-15) turned --keep-empty (for keeping commits which
start empty) into the default. The logic underpinning that commit was:
1) 'git commit' errors out on the creation of empty commits without an
override flag
2) Once someone determines that the override is worthwhile, it's
annoying and/or harmful to required them to take extra steps in
order to keep such commits around (and to repeat such steps with
every rebase).
While the logic on which the decision was made is sound, the result was
a bit of an overcorrection. Instead of jumping to having --keep-empty
being the default, it jumped to making --keep-empty the only available
behavior. There was a simple workaround, though, which was thought to
be good enough at the time. People could still drop commits which
started empty the same way the could drop any commits: by firing up an
interactive rebase and picking out the commits they didn't want from the
list. However, there are cases where external tools might create enough
empty commits that picking all of them out is painful. As such, having
a flag to automatically remove start-empty commits may be beneficial.
Provide users a way to drop commits which start empty using a flag that
existed for years: --no-keep-empty. Interpret --keep-empty as
countermanding any previous --no-keep-empty, but otherwise leaving
--keep-empty as the default.
This might lead to some slight weirdness since commands like
git rebase --empty=drop --keep-empty
git rebase --empty=keep --no-keep-empty
look really weird despite making perfect sense (the first will drop
commits which become empty, but keep commits that started empty; the
second will keep commits which become empty, but drop commits which
started empty). However, --no-keep-empty was named years ago and we are
predominantly keeping it for backward compatibility; also we suspect it
will only be used rarely since folks already have a simple way to drop
commits they don't want with an interactive rebase.
Reported-by: Bryan Turner <bturner@atlassian.com>
Reported-by: Sami Boukortt <sami@boukortt.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-11 02:44:25 +00:00
|
|
|
.keep_empty = 1, \
|
2020-02-15 21:36:40 +00:00
|
|
|
.default_backend = "merge", \
|
2019-04-17 14:30:41 +00:00
|
|
|
.flags = REBASE_NO_QUIET, \
|
2020-07-28 20:24:27 +00:00
|
|
|
.git_am_opts = STRVEC_INIT, \
|
2021-02-23 07:18:40 +00:00
|
|
|
.git_format_patch_opt = STRBUF_INIT, \
|
|
|
|
.fork_point = -1, \
|
2019-04-17 14:30:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct replay_opts get_replay_opts(const struct rebase_options *opts)
|
|
|
|
{
|
|
|
|
struct replay_opts replay = REPLAY_OPTS_INIT;
|
|
|
|
|
|
|
|
replay.action = REPLAY_INTERACTIVE_REBASE;
|
2020-11-02 23:45:34 +00:00
|
|
|
replay.strategy = NULL;
|
2019-04-17 14:30:41 +00:00
|
|
|
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;
|
2020-08-17 17:40:02 +00:00
|
|
|
replay.committer_date_is_author_date =
|
|
|
|
opts->committer_date_is_author_date;
|
2020-08-17 17:40:03 +00:00
|
|
|
replay.ignore_date = opts->ignore_date;
|
2019-04-17 14:30:41 +00:00
|
|
|
replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
|
2020-11-02 23:45:34 +00:00
|
|
|
if (opts->strategy)
|
2021-07-25 13:08:29 +00:00
|
|
|
replay.strategy = xstrdup_or_null(opts->strategy);
|
2020-11-02 23:45:34 +00:00
|
|
|
else if (!replay.strategy && replay.default_strategy) {
|
|
|
|
replay.strategy = replay.default_strategy;
|
|
|
|
replay.default_strategy = NULL;
|
|
|
|
}
|
2020-07-13 10:10:41 +00:00
|
|
|
|
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,
|
2021-09-07 21:05:08 +00:00
|
|
|
ACTION_SHOW_CURRENT_PATCH
|
2019-04-17 14:30:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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 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,
|
2020-11-04 15:29:40 +00:00
|
|
|
struct object_id *orig_head, char **revisions,
|
|
|
|
char **shortrevisions)
|
2019-04-17 14:30:37 +00:00
|
|
|
{
|
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
|
|
|
|
2019-04-17 14:30:39 +00:00
|
|
|
*revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
|
2020-11-04 15:29:40 +00:00
|
|
|
oid_to_hex(orig_head));
|
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,
|
2020-11-04 15:29:39 +00:00
|
|
|
struct commit *onto,
|
|
|
|
const struct object_id *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;
|
|
|
|
char *revisions = NULL, *shortrevisions = NULL;
|
2020-07-28 20:24:27 +00:00
|
|
|
struct strvec make_script_args = STRVEC_INIT;
|
2019-04-17 14:30:37 +00:00
|
|
|
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,
|
2020-11-04 15:29:40 +00:00
|
|
|
&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",
|
2020-11-04 15:29:39 +00:00
|
|
|
opts->onto, &opts->orig_head)) {
|
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
|
|
|
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_pushl(&make_script_args, "", revisions, NULL);
|
2019-04-17 14:30:42 +00:00
|
|
|
if (opts->restrict_revision)
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_pushf(&make_script_args, "^%s",
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 20:26:31 +00:00
|
|
|
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,
|
2020-07-29 00:37:20 +00:00
|
|
|
make_script_args.nr, make_script_args.v,
|
2019-04-17 14:30:37 +00:00
|
|
|
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,
|
2020-11-04 15:29:38 +00:00
|
|
|
shortrevisions, opts->onto_name, opts->onto,
|
|
|
|
&opts->orig_head, &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);
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_clear(&make_script_args);
|
2019-04-17 14:30:37 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
rebase: reinstate --no-keep-empty
Commit d48e5e21da ("rebase (interactive-backend): make --keep-empty the
default", 2020-02-15) turned --keep-empty (for keeping commits which
start empty) into the default. The logic underpinning that commit was:
1) 'git commit' errors out on the creation of empty commits without an
override flag
2) Once someone determines that the override is worthwhile, it's
annoying and/or harmful to required them to take extra steps in
order to keep such commits around (and to repeat such steps with
every rebase).
While the logic on which the decision was made is sound, the result was
a bit of an overcorrection. Instead of jumping to having --keep-empty
being the default, it jumped to making --keep-empty the only available
behavior. There was a simple workaround, though, which was thought to
be good enough at the time. People could still drop commits which
started empty the same way the could drop any commits: by firing up an
interactive rebase and picking out the commits they didn't want from the
list. However, there are cases where external tools might create enough
empty commits that picking all of them out is painful. As such, having
a flag to automatically remove start-empty commits may be beneficial.
Provide users a way to drop commits which start empty using a flag that
existed for years: --no-keep-empty. Interpret --keep-empty as
countermanding any previous --no-keep-empty, but otherwise leaving
--keep-empty as the default.
This might lead to some slight weirdness since commands like
git rebase --empty=drop --keep-empty
git rebase --empty=keep --no-keep-empty
look really weird despite making perfect sense (the first will drop
commits which become empty, but keep commits that started empty; the
second will keep commits which become empty, but drop commits which
started empty). However, --no-keep-empty was named years ago and we are
predominantly keeping it for backward compatibility; also we suspect it
will only be used rarely since folks already have a simple way to drop
commits they don't want with an interactive rebase.
Reported-by: Bryan Turner <bturner@atlassian.com>
Reported-by: Sami Boukortt <sami@boukortt.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-11 02:44:25 +00:00
|
|
|
flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
|
2019-04-17 14:30:44 +00:00
|
|
|
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;
|
2020-04-11 02:44:27 +00:00
|
|
|
flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0;
|
2021-08-30 21:46:02 +00:00
|
|
|
flags |= opts->flags & REBASE_NO_QUIET ? TODO_LIST_WARN_SKIPPED_CHERRY_PICKS : 0;
|
2019-04-17 14:30:44 +00:00
|
|
|
|
|
|
|
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;
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL);
|
2019-04-17 14:30:44 +00:00
|
|
|
ret = run_command(&cmd);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
BUG("invalid command '%d'", command);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
rebase: reinstate --no-keep-empty
Commit d48e5e21da ("rebase (interactive-backend): make --keep-empty the
default", 2020-02-15) turned --keep-empty (for keeping commits which
start empty) into the default. The logic underpinning that commit was:
1) 'git commit' errors out on the creation of empty commits without an
override flag
2) Once someone determines that the override is worthwhile, it's
annoying and/or harmful to required them to take extra steps in
order to keep such commits around (and to repeat such steps with
every rebase).
While the logic on which the decision was made is sound, the result was
a bit of an overcorrection. Instead of jumping to having --keep-empty
being the default, it jumped to making --keep-empty the only available
behavior. There was a simple workaround, though, which was thought to
be good enough at the time. People could still drop commits which
started empty the same way the could drop any commits: by firing up an
interactive rebase and picking out the commits they didn't want from the
list. However, there are cases where external tools might create enough
empty commits that picking all of them out is painful. As such, having
a flag to automatically remove start-empty commits may be beneficial.
Provide users a way to drop commits which start empty using a flag that
existed for years: --no-keep-empty. Interpret --keep-empty as
countermanding any previous --no-keep-empty, but otherwise leaving
--keep-empty as the default.
This might lead to some slight weirdness since commands like
git rebase --empty=drop --keep-empty
git rebase --empty=keep --no-keep-empty
look really weird despite making perfect sense (the first will drop
commits which become empty, but keep commits that started empty; the
second will keep commits which become empty, but drop commits which
started empty). However, --no-keep-empty was named years ago and we are
predominantly keeping it for backward compatibility; also we suspect it
will only be used rarely since folks already have a simple way to drop
commits they don't want with an interactive rebase.
Reported-by: Bryan Turner <bturner@atlassian.com>
Reported-by: Sami Boukortt <sami@boukortt.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-11 02:44:25 +00:00
|
|
|
static void imply_merge(struct rebase_options *opts, const char *option);
|
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: reinstate --no-keep-empty
Commit d48e5e21da ("rebase (interactive-backend): make --keep-empty the
default", 2020-02-15) turned --keep-empty (for keeping commits which
start empty) into the default. The logic underpinning that commit was:
1) 'git commit' errors out on the creation of empty commits without an
override flag
2) Once someone determines that the override is worthwhile, it's
annoying and/or harmful to required them to take extra steps in
order to keep such commits around (and to repeat such steps with
every rebase).
While the logic on which the decision was made is sound, the result was
a bit of an overcorrection. Instead of jumping to having --keep-empty
being the default, it jumped to making --keep-empty the only available
behavior. There was a simple workaround, though, which was thought to
be good enough at the time. People could still drop commits which
started empty the same way the could drop any commits: by firing up an
interactive rebase and picking out the commits they didn't want from the
list. However, there are cases where external tools might create enough
empty commits that picking all of them out is painful. As such, having
a flag to automatically remove start-empty commits may be beneficial.
Provide users a way to drop commits which start empty using a flag that
existed for years: --no-keep-empty. Interpret --keep-empty as
countermanding any previous --no-keep-empty, but otherwise leaving
--keep-empty as the default.
This might lead to some slight weirdness since commands like
git rebase --empty=drop --keep-empty
git rebase --empty=keep --no-keep-empty
look really weird despite making perfect sense (the first will drop
commits which become empty, but keep commits that started empty; the
second will keep commits which become empty, but drop commits which
started empty). However, --no-keep-empty was named years ago and we are
predominantly keeping it for backward compatibility; also we suspect it
will only be used rarely since folks already have a simple way to drop
commits they don't want with an interactive rebase.
Reported-by: Bryan Turner <bturner@atlassian.com>
Reported-by: Sami Boukortt <sami@boukortt.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-11 02:44:25 +00:00
|
|
|
imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty");
|
|
|
|
opts->keep_empty = !unset;
|
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;
|
|
|
|
}
|
|
|
|
|
2020-02-15 21:36:41 +00:00
|
|
|
static int is_merge(struct rebase_options *opts)
|
2018-09-04 21:27:16 +00:00
|
|
|
{
|
2021-09-07 21:05:06 +00:00
|
|
|
return opts->type == REBASE_MERGE;
|
2018-09-04 21:27:16 +00:00
|
|
|
}
|
|
|
|
|
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:
|
2020-04-11 02:44:26 +00:00
|
|
|
die(_("%s requires the merge backend"), option);
|
2018-09-04 21:59:57 +00:00
|
|
|
break;
|
2020-02-15 21:36:41 +00:00
|
|
|
case REBASE_MERGE:
|
2018-09-04 21:59:57 +00:00
|
|
|
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;
|
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);
|
2021-03-20 00:03:52 +00:00
|
|
|
unlink(git_path_auto_merge(the_repository));
|
2020-04-07 14:27:58 +00:00
|
|
|
apply_autostash(state_dir_path("autostash", opts));
|
2018-08-06 19:31:11 +00:00
|
|
|
/*
|
2020-09-17 18:11:44 +00:00
|
|
|
* We ignore errors in 'git maintenance run --auto', since the
|
2018-08-06 19:31:11 +00:00
|
|
|
* user should see them.
|
|
|
|
*/
|
2020-09-17 18:11:44 +00:00
|
|
|
run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE)));
|
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
|
|
|
}
|
|
|
|
|
2019-01-18 15:09:27 +00:00
|
|
|
static int move_to_original_branch(struct rebase_options *opts)
|
|
|
|
{
|
2022-01-26 13:05:47 +00:00
|
|
|
struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
|
2022-01-26 13:05:46 +00:00
|
|
|
struct reset_head_opts ropts = { 0 };
|
2019-01-18 15:09:27 +00:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!opts->head_name)
|
|
|
|
return 0; /* nothing to move back to */
|
|
|
|
|
|
|
|
if (!opts->onto)
|
|
|
|
BUG("move_to_original_branch without onto");
|
|
|
|
|
2022-01-26 13:05:47 +00:00
|
|
|
strbuf_addf(&branch_reflog, "rebase finished: %s onto %s",
|
2019-01-18 15:09:27 +00:00
|
|
|
opts->head_name, oid_to_hex(&opts->onto->object.oid));
|
|
|
|
strbuf_addf(&head_reflog, "rebase finished: returning to %s",
|
|
|
|
opts->head_name);
|
2022-01-26 13:05:46 +00:00
|
|
|
ropts.branch = opts->head_name;
|
|
|
|
ropts.flags = RESET_HEAD_REFS_ONLY;
|
2022-01-26 13:05:47 +00:00
|
|
|
ropts.branch_msg = branch_reflog.buf;
|
2022-01-26 13:05:46 +00:00
|
|
|
ropts.head_msg = head_reflog.buf;
|
|
|
|
ret = reset_head(the_repository, &ropts);
|
2019-01-18 15:09:27 +00:00
|
|
|
|
2022-01-26 13:05:47 +00:00
|
|
|
strbuf_release(&branch_reflog);
|
2019-01-18 15:09:27 +00:00
|
|
|
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;
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&am.args, "am");
|
2019-01-18 15:09:27 +00:00
|
|
|
|
|
|
|
if (opts->action && !strcmp("continue", opts->action)) {
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&am.args, "--resolved");
|
|
|
|
strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
|
2019-01-18 15:09:27 +00:00
|
|
|
if (opts->gpg_sign_opt)
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&am.args, opts->gpg_sign_opt);
|
2019-01-18 15:09:27 +00:00
|
|
|
status = run_command(&am);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
return move_to_original_branch(opts);
|
|
|
|
}
|
|
|
|
if (opts->action && !strcmp("skip", opts->action)) {
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&am.args, "--skip");
|
|
|
|
strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
|
2019-01-18 15:09:27 +00:00
|
|
|
status = run_command(&am);
|
|
|
|
if (status)
|
|
|
|
return status;
|
|
|
|
|
|
|
|
return move_to_original_branch(opts);
|
|
|
|
}
|
|
|
|
if (opts->action && !strcmp("show-current-patch", opts->action)) {
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&am.args, "--show-current-patch");
|
2019-01-18 15:09:27 +00:00
|
|
|
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);
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_clear(&am.args);
|
2019-01-18 15:09:27 +00:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
format_patch.git_cmd = 1;
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_pushl(&format_patch.args, "format-patch", "-k", "--stdout",
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 20:26:31 +00:00
|
|
|
"--full-index", "--cherry-pick", "--right-only",
|
|
|
|
"--src-prefix=a/", "--dst-prefix=b/", "--no-renames",
|
|
|
|
"--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)
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_split(&format_patch.args,
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 20:26:31 +00:00
|
|
|
opts->git_format_patch_opt.buf);
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&format_patch.args, revisions.buf);
|
2019-01-18 15:09:27 +00:00
|
|
|
if (opts->restrict_revision)
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_pushf(&format_patch.args, "^%s",
|
strvec: fix indentation in renamed calls
Code which split an argv_array call across multiple lines, like:
argv_array_pushl(&args, "one argument",
"another argument", "and more",
NULL);
was recently mechanically renamed to use strvec, which results in
mis-matched indentation like:
strvec_pushl(&args, "one argument",
"another argument", "and more",
NULL);
Let's fix these up to align the arguments with the opening paren. I did
this manually by sifting through the results of:
git jump grep 'strvec_.*,$'
and liberally applying my editor's auto-format. Most of the changes are
of the form shown above, though I also normalized a few that had
originally used a single-tab indentation (rather than our usual style of
aligning with the open paren). I also rewrapped a couple of obvious
cases (e.g., where previously too-long lines became short enough to fit
on one), but I wasn't aggressive about it. In cases broken to three or
more lines, the grouping of arguments is sometimes meaningful, and it
wasn't worth my time or reviewer time to ponder each case individually.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-07-28 20:26:31 +00:00
|
|
|
oid_to_hex(&opts->restrict_revision->object.oid));
|
2019-01-18 15:09:27 +00:00
|
|
|
|
|
|
|
status = run_command(&format_patch);
|
|
|
|
if (status) {
|
2022-01-26 13:05:46 +00:00
|
|
|
struct reset_head_opts ropts = { 0 };
|
2019-01-18 15:09:27 +00:00
|
|
|
unlink(rebased_patches);
|
|
|
|
free(rebased_patches);
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_clear(&am.args);
|
2019-01-18 15:09:27 +00:00
|
|
|
|
2022-01-26 13:05:46 +00:00
|
|
|
ropts.oid = &opts->orig_head;
|
|
|
|
ropts.branch = opts->head_name;
|
|
|
|
ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
|
|
|
|
reset_head(the_repository, &ropts);
|
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);
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_clear(&am.args);
|
2019-01-18 15:09:27 +00:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2020-07-29 00:37:20 +00:00
|
|
|
strvec_pushv(&am.args, opts->git_am_opts.v);
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&am.args, "--rebasing");
|
|
|
|
strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
|
|
|
|
strvec_push(&am.args, "--patch-format=mboxrd");
|
2019-04-17 14:30:36 +00:00
|
|
|
if (opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE)
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&am.args, "--rerere-autoupdate");
|
2019-04-17 14:30:36 +00:00
|
|
|
else if (opts->allow_rerere_autoupdate == RERERE_NOAUTOUPDATE)
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&am.args, "--no-rerere-autoupdate");
|
2019-01-18 15:09:27 +00:00
|
|
|
if (opts->gpg_sign_opt)
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&am.args, opts->gpg_sign_opt);
|
2019-01-18 15:09:27 +00:00
|
|
|
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
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
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);
|
2021-09-07 21:05:06 +00:00
|
|
|
} else if (opts->type == REBASE_APPLY)
|
2019-01-18 15:09:27 +00:00
|
|
|
status = run_am(opts);
|
2021-09-07 21:05:06 +00:00
|
|
|
else
|
2018-08-06 19:31:11 +00:00
|
|
|
BUG("Unhandled rebase type %d", opts->type);
|
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-02-23 07:18:40 +00:00
|
|
|
if (!strcmp(var, "rebase.forkpoint")) {
|
|
|
|
opts->fork_point = git_config_bool(var, value) ? -1 : 0;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-01-26 13:05:36 +00:00
|
|
|
static int checkout_up_to_date(struct rebase_options *options)
|
|
|
|
{
|
|
|
|
struct strbuf buf = STRBUF_INIT;
|
2022-01-26 13:05:46 +00:00
|
|
|
struct reset_head_opts ropts = { 0 };
|
2022-01-26 13:05:36 +00:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
strbuf_addf(&buf, "%s: checkout %s",
|
|
|
|
getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
|
|
|
|
options->switch_to);
|
2022-01-26 13:05:46 +00:00
|
|
|
ropts.oid = &options->orig_head;
|
|
|
|
ropts.branch = options->head_name;
|
|
|
|
ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
|
2022-03-18 13:54:03 +00:00
|
|
|
if (!ropts.branch)
|
|
|
|
ropts.flags |= RESET_HEAD_DETACH;
|
2022-01-26 13:05:46 +00:00
|
|
|
ropts.head_msg = buf.buf;
|
|
|
|
if (reset_head(the_repository, &ropts) < 0)
|
2022-01-26 13:05:36 +00:00
|
|
|
ret = error(_("could not switch to %s"), options->switch_to);
|
|
|
|
strbuf_release(&buf);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
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) {
|
2021-04-26 01:02:56 +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;
|
|
|
|
}
|
|
|
|
|
2021-09-07 21:05:10 +00:00
|
|
|
/* -i followed by -r is still explicitly interactive, but -r alone is not */
|
2018-09-04 21:59:49 +00:00
|
|
|
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;
|
2020-07-13 10:10:41 +00:00
|
|
|
int ignore_whitespace = 0;
|
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: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;
|
2021-09-07 21:05:06 +00:00
|
|
|
int preserve_merges_selected = 0;
|
2022-01-26 13:05:46 +00:00
|
|
|
struct reset_head_opts ropts = { 0 };
|
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,
|
Documentation: stylistically normalize references to Signed-off-by:
Ted reported an old typo in the git-commit.txt and merge-options.txt.
Namely, the phrase "Signed-off-by line" was used without either a
definite nor indefinite article.
Upon examination, it seems that the documentation (including items in
Documentation/, but also option help strings) have been quite
inconsistent on usage when referring to `Signed-off-by`.
First, very few places used a definite or indefinite article with the
phrase "Signed-off-by line", but that was the initial typo that led
to this investigation. So, normalize using either an indefinite or
definite article consistently.
The original phrasing, in Commit 3f971fc425b (Documentation updates,
2005-08-14), is "Add Signed-off-by line". Commit 6f855371a53 (Add
--signoff, --check, and long option-names. 2005-12-09) switched to
using "Add `Signed-off-by:` line", but didn't normalize the former
commit to match. Later commits seem to have cut and pasted from one
or the other, which is likely how the usage became so inconsistent.
Junio stated on the git mailing list in
<xmqqy2k1dfoh.fsf@gitster.c.googlers.com> a preference to leave off
the colon. Thus, prefer `Signed-off-by` (with backticks) for the
documentation files and Signed-off-by (without backticks) for option
help strings.
Additionally, Junio argued that "trailer" is now the standard term to
refer to `Signed-off-by`, saying that "becomes plenty clear that we
are not talking about any random line in the log message". As such,
prefer "trailer" over "line" anywhere the former word fits.
However, leave alone those few places in documentation that use
Signed-off-by to refer to the process (rather than the specific
trailer), or in places where mail headers are generally discussed in
comparison with Signed-off-by.
Reported-by: "Theodore Y. Ts'o" <tytso@mit.edu>
Signed-off-by: Bradley M. Kuhn <bkuhn@sfconservancy.org>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-20 01:03:55 +00:00
|
|
|
N_("add a Signed-off-by trailer to each commit")),
|
2020-08-17 17:40:02 +00:00
|
|
|
OPT_BOOL(0, "committer-date-is-author-date",
|
|
|
|
&options.committer_date_is_author_date,
|
|
|
|
N_("make committer date match author date")),
|
2020-08-17 17:40:04 +00:00
|
|
|
OPT_BOOL(0, "reset-author-date", &options.ignore_date,
|
2020-08-17 17:40:03 +00:00
|
|
|
N_("ignore author date and use current date")),
|
2020-08-17 17:40:04 +00:00
|
|
|
OPT_HIDDEN_BOOL(0, "ignore-date", &options.ignore_date,
|
|
|
|
N_("synonym of --reset-author-date")),
|
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),
|
2020-07-13 10:10:41 +00:00
|
|
|
OPT_BOOL(0, "ignore-whitespace", &ignore_whitespace,
|
|
|
|
N_("ignore changes in whitespace")),
|
2018-11-14 16:25:29 +00:00
|
|
|
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),
|
Use OPT_CALLBACK and OPT_CALLBACK_F
In the codebase, there are many options which use OPTION_CALLBACK in a
plain ol' struct definition. However, we have the OPT_CALLBACK and
OPT_CALLBACK_F macros which are meant to abstract these plain struct
definitions away. These macros are useful as they semantically signal to
developers that these are just normal callback option with nothing fancy
happening.
Replace plain struct definitions of OPTION_CALLBACK with OPT_CALLBACK or
OPT_CALLBACK_F where applicable. The heavy lifting was done using the
following (disgusting) shell script:
#!/bin/sh
do_replacement () {
tr '\n' '\r' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\s*0,\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK(\1,\2,\3,\4,\5,\6)/g' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK_F(\1,\2,\3,\4,\5,\6,\7)/g' |
tr '\r' '\n'
}
for f in $(git ls-files \*.c)
do
do_replacement <"$f" >"$f.tmp"
mv "$f.tmp" "$f"
done
The result was manually inspected and then reformatted to match the
style of the surrounding code. Finally, using
`git grep OPTION_CALLBACK \*.c`, leftover results which were not handled
by the script were manually transformed.
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-28 08:36:28 +00:00
|
|
|
OPT_CALLBACK_F(0, "apply", &options, NULL,
|
2020-02-15 21:36:41 +00:00
|
|
|
N_("use apply strategies to rebase"),
|
2020-02-15 21:36:34 +00:00
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
|
Use OPT_CALLBACK and OPT_CALLBACK_F
In the codebase, there are many options which use OPTION_CALLBACK in a
plain ol' struct definition. However, we have the OPT_CALLBACK and
OPT_CALLBACK_F macros which are meant to abstract these plain struct
definitions away. These macros are useful as they semantically signal to
developers that these are just normal callback option with nothing fancy
happening.
Replace plain struct definitions of OPTION_CALLBACK with OPT_CALLBACK or
OPT_CALLBACK_F where applicable. The heavy lifting was done using the
following (disgusting) shell script:
#!/bin/sh
do_replacement () {
tr '\n' '\r' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\s*0,\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK(\1,\2,\3,\4,\5,\6)/g' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK_F(\1,\2,\3,\4,\5,\6,\7)/g' |
tr '\r' '\n'
}
for f in $(git ls-files \*.c)
do
do_replacement <"$f" >"$f.tmp"
mv "$f.tmp" "$f"
done
The result was manually inspected and then reformatted to match the
style of the surrounding code. Finally, using
`git grep OPTION_CALLBACK \*.c`, leftover results which were not handled
by the script were manually transformed.
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-28 08:36:28 +00:00
|
|
|
parse_opt_am),
|
|
|
|
OPT_CALLBACK_F('m', "merge", &options, NULL,
|
2018-09-04 21:59:49 +00:00
|
|
|
N_("use merging strategies to rebase"),
|
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
|
Use OPT_CALLBACK and OPT_CALLBACK_F
In the codebase, there are many options which use OPTION_CALLBACK in a
plain ol' struct definition. However, we have the OPT_CALLBACK and
OPT_CALLBACK_F macros which are meant to abstract these plain struct
definitions away. These macros are useful as they semantically signal to
developers that these are just normal callback option with nothing fancy
happening.
Replace plain struct definitions of OPTION_CALLBACK with OPT_CALLBACK or
OPT_CALLBACK_F where applicable. The heavy lifting was done using the
following (disgusting) shell script:
#!/bin/sh
do_replacement () {
tr '\n' '\r' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\s*0,\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK(\1,\2,\3,\4,\5,\6)/g' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK_F(\1,\2,\3,\4,\5,\6,\7)/g' |
tr '\r' '\n'
}
for f in $(git ls-files \*.c)
do
do_replacement <"$f" >"$f.tmp"
mv "$f.tmp" "$f"
done
The result was manually inspected and then reformatted to match the
style of the surrounding code. Finally, using
`git grep OPTION_CALLBACK \*.c`, leftover results which were not handled
by the script were manually transformed.
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-28 08:36:28 +00:00
|
|
|
parse_opt_merge),
|
|
|
|
OPT_CALLBACK_F('i', "interactive", &options, NULL,
|
2018-09-04 21:59:49 +00:00
|
|
|
N_("let the user edit the list of commits to rebase"),
|
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
|
Use OPT_CALLBACK and OPT_CALLBACK_F
In the codebase, there are many options which use OPTION_CALLBACK in a
plain ol' struct definition. However, we have the OPT_CALLBACK and
OPT_CALLBACK_F macros which are meant to abstract these plain struct
definitions away. These macros are useful as they semantically signal to
developers that these are just normal callback option with nothing fancy
happening.
Replace plain struct definitions of OPTION_CALLBACK with OPT_CALLBACK or
OPT_CALLBACK_F where applicable. The heavy lifting was done using the
following (disgusting) shell script:
#!/bin/sh
do_replacement () {
tr '\n' '\r' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\s*0,\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK(\1,\2,\3,\4,\5,\6)/g' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK_F(\1,\2,\3,\4,\5,\6,\7)/g' |
tr '\r' '\n'
}
for f in $(git ls-files \*.c)
do
do_replacement <"$f" >"$f.tmp"
mv "$f.tmp" "$f"
done
The result was manually inspected and then reformatted to match the
style of the surrounding code. Finally, using
`git grep OPTION_CALLBACK \*.c`, leftover results which were not handled
by the script were manually transformed.
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-28 08:36:28 +00:00
|
|
|
parse_opt_interactive),
|
2021-09-07 21:05:06 +00:00
|
|
|
OPT_SET_INT_F('p', "preserve-merges", &preserve_merges_selected,
|
2022-06-04 11:17:46 +00:00
|
|
|
N_("(REMOVED) was: try to recreate merges "
|
|
|
|
"instead of ignoring them"),
|
2021-09-07 21:05:06 +00:00
|
|
|
1, 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),
|
Use OPT_CALLBACK and OPT_CALLBACK_F
In the codebase, there are many options which use OPTION_CALLBACK in a
plain ol' struct definition. However, we have the OPT_CALLBACK and
OPT_CALLBACK_F macros which are meant to abstract these plain struct
definitions away. These macros are useful as they semantically signal to
developers that these are just normal callback option with nothing fancy
happening.
Replace plain struct definitions of OPTION_CALLBACK with OPT_CALLBACK or
OPT_CALLBACK_F where applicable. The heavy lifting was done using the
following (disgusting) shell script:
#!/bin/sh
do_replacement () {
tr '\n' '\r' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\s*0,\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK(\1,\2,\3,\4,\5,\6)/g' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK_F(\1,\2,\3,\4,\5,\6,\7)/g' |
tr '\r' '\n'
}
for f in $(git ls-files \*.c)
do
do_replacement <"$f" >"$f.tmp"
mv "$f.tmp" "$f"
done
The result was manually inspected and then reformatted to match the
style of the surrounding code. Finally, using
`git grep OPTION_CALLBACK \*.c`, leftover results which were not handled
by the script were manually transformed.
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-28 08:36:28 +00:00
|
|
|
OPT_CALLBACK_F('k', "keep-empty", &options, NULL,
|
rebase: reinstate --no-keep-empty
Commit d48e5e21da ("rebase (interactive-backend): make --keep-empty the
default", 2020-02-15) turned --keep-empty (for keeping commits which
start empty) into the default. The logic underpinning that commit was:
1) 'git commit' errors out on the creation of empty commits without an
override flag
2) Once someone determines that the override is worthwhile, it's
annoying and/or harmful to required them to take extra steps in
order to keep such commits around (and to repeat such steps with
every rebase).
While the logic on which the decision was made is sound, the result was
a bit of an overcorrection. Instead of jumping to having --keep-empty
being the default, it jumped to making --keep-empty the only available
behavior. There was a simple workaround, though, which was thought to
be good enough at the time. People could still drop commits which
started empty the same way the could drop any commits: by firing up an
interactive rebase and picking out the commits they didn't want from the
list. However, there are cases where external tools might create enough
empty commits that picking all of them out is painful. As such, having
a flag to automatically remove start-empty commits may be beneficial.
Provide users a way to drop commits which start empty using a flag that
existed for years: --no-keep-empty. Interpret --keep-empty as
countermanding any previous --no-keep-empty, but otherwise leaving
--keep-empty as the default.
This might lead to some slight weirdness since commands like
git rebase --empty=drop --keep-empty
git rebase --empty=keep --no-keep-empty
look really weird despite making perfect sense (the first will drop
commits which become empty, but keep commits that started empty; the
second will keep commits which become empty, but drop commits which
started empty). However, --no-keep-empty was named years ago and we are
predominantly keeping it for backward compatibility; also we suspect it
will only be used rarely since folks already have a simple way to drop
commits they don't want with an interactive rebase.
Reported-by: Bryan Turner <bturner@atlassian.com>
Reported-by: Sami Boukortt <sami@boukortt.com>
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-11 02:44:25 +00:00
|
|
|
N_("keep commits which start 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
|
|
|
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,
|
Use OPT_CALLBACK and OPT_CALLBACK_F
In the codebase, there are many options which use OPTION_CALLBACK in a
plain ol' struct definition. However, we have the OPT_CALLBACK and
OPT_CALLBACK_F macros which are meant to abstract these plain struct
definitions away. These macros are useful as they semantically signal to
developers that these are just normal callback option with nothing fancy
happening.
Replace plain struct definitions of OPTION_CALLBACK with OPT_CALLBACK or
OPT_CALLBACK_F where applicable. The heavy lifting was done using the
following (disgusting) shell script:
#!/bin/sh
do_replacement () {
tr '\n' '\r' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\s*0,\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK(\1,\2,\3,\4,\5,\6)/g' |
sed -e 's/{\s*OPTION_CALLBACK,\s*\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\([^,]*\),\(\s*[^[:space:]}]*\)\s*}/OPT_CALLBACK_F(\1,\2,\3,\4,\5,\6,\7)/g' |
tr '\r' '\n'
}
for f in $(git ls-files \*.c)
do
do_replacement <"$f" >"$f.tmp"
mv "$f.tmp" "$f"
done
The result was manually inspected and then reformatted to match the
style of the surrounding code. Finally, using
`git grep OPTION_CALLBACK \*.c`, leftover results which were not handled
by the script were manually transformed.
Signed-off-by: Denton Liu <liu.denton@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-04-28 08:36:28 +00:00
|
|
|
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) "" },
|
2020-04-07 14:28:07 +00:00
|
|
|
OPT_AUTOSTASH(&options.autostash),
|
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)""},
|
2021-02-23 07:18:40 +00:00
|
|
|
OPT_BOOL(0, "fork-point", &options.fork_point,
|
2018-09-04 22:00:09 +00:00
|
|
|
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")),
|
2020-04-11 02:44:27 +00:00
|
|
|
OPT_BOOL(0, "reapply-cherry-picks", &options.reapply_cherry_picks,
|
|
|
|
N_("apply all changes, even those already present upstream")),
|
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);
|
|
|
|
|
2021-09-08 11:24:01 +00:00
|
|
|
prepare_repo_settings(the_repository);
|
|
|
|
the_repository->settings.command_requires_full_index = 0;
|
|
|
|
|
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);
|
2020-04-03 10:28:02 +00:00
|
|
|
/* options.gpg_sign_opt will be either "-S" or NULL */
|
|
|
|
gpg_sign = options.gpg_sign_opt ? "" : NULL;
|
|
|
|
FREE_AND_NULL(options.gpg_sign_opt);
|
2018-09-04 21:27:13 +00:00
|
|
|
|
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());
|
2022-06-04 11:17:47 +00:00
|
|
|
if (!(action == ACTION_ABORT) && is_directory(buf.buf)) {
|
2022-06-04 11:17:49 +00:00
|
|
|
die(_("`rebase --preserve-merges` (-p) is no longer supported.\n"
|
2022-06-04 11:17:47 +00:00
|
|
|
"Use `git rebase --abort` to terminate current rebase.\n"
|
2022-06-04 11:17:49 +00:00
|
|
|
"Or downgrade to v2.33, or earlier, to complete the rebase."));
|
2018-09-04 21:27:18 +00:00
|
|
|
} else {
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addf(&buf, "%s/interactive", merge_dir());
|
2022-04-18 17:27:21 +00:00
|
|
|
options.type = REBASE_MERGE;
|
|
|
|
if (file_exists(buf.buf))
|
2018-09-04 21:27:18 +00:00
|
|
|
options.flags |= REBASE_INTERACTIVE_EXPLICIT;
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
|
2021-09-07 21:05:06 +00:00
|
|
|
if (preserve_merges_selected)
|
2022-06-04 11:17:48 +00:00
|
|
|
die(_("--preserve-merges was replaced by --rebase-merges\n"
|
|
|
|
"Note: Your `pull.rebase` configuration may also be set to 'preserve',\n"
|
|
|
|
"which is no longer supported; use 'merges' instead"));
|
2021-09-07 21:05:06 +00:00
|
|
|
|
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
|
|
|
|
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)
|
2022-01-05 20:02:16 +00:00
|
|
|
die(_("options '%s' and '%s' cannot be used together"), "--keep-base", "--onto");
|
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 (options.root)
|
2022-01-05 20:02:16 +00:00
|
|
|
die(_("options '%s' and '%s' cannot be used together"), "--keep-base", "--root");
|
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
|
|
|
}
|
|
|
|
|
2021-02-23 07:18:40 +00:00
|
|
|
if (options.root && options.fork_point > 0)
|
2022-01-05 20:02:16 +00:00
|
|
|
die(_("options '%s' and '%s' cannot be used together"), "--root", "--fork-point");
|
2020-04-27 17:59:49 +00:00
|
|
|
|
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);
|
2022-01-26 13:05:46 +00:00
|
|
|
ropts.flags = RESET_HEAD_HARD;
|
|
|
|
if (reset_head(the_repository, &ropts) < 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);
|
2022-01-26 13:05:46 +00:00
|
|
|
ropts.oid = &options.orig_head;
|
|
|
|
ropts.branch = options.head_name;
|
|
|
|
ropts.flags = RESET_HEAD_HARD;
|
|
|
|
ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
|
|
|
|
if (reset_head(the_repository, &ropts) < 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);
|
2021-09-21 10:24:05 +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-04-28 09:31:31 +00:00
|
|
|
save_autostash(state_dir_path("autostash", &options));
|
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;
|
2021-09-21 10:24:05 +00:00
|
|
|
ret = sequencer_remove_state(&replay);
|
2019-05-14 18:03:49 +00:00
|
|
|
} else {
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addstr(&buf, options.state_dir);
|
2021-09-21 10:24:05 +00:00
|
|
|
ret = remove_dir_recursively(&buf, 0);
|
2019-05-14 18:03:49 +00:00
|
|
|
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;
|
|
|
|
}
|
2020-08-17 17:40:03 +00:00
|
|
|
if (options.committer_date_is_author_date || options.ignore_date)
|
2020-08-17 17:40:02 +00:00
|
|
|
options.flags |= REBASE_FORCE;
|
2020-02-15 21:36:31 +00:00
|
|
|
|
2020-07-29 00:37:20 +00:00
|
|
|
for (i = 0; i < options.git_am_opts.nr; i++) {
|
|
|
|
const char *option = options.git_am_opts.v[i], *p;
|
2020-08-17 17:40:03 +00:00
|
|
|
if (!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))
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_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
|
|
|
|
2020-04-11 02:44:27 +00:00
|
|
|
if (options.reapply_cherry_picks)
|
|
|
|
imply_merge(&options, "--reapply-cherry-picks");
|
|
|
|
|
2020-04-03 10:28:02 +00:00
|
|
|
if (gpg_sign)
|
2018-09-04 22:00:00 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-07-13 10:10:41 +00:00
|
|
|
if (options.type == REBASE_APPLY) {
|
|
|
|
if (ignore_whitespace)
|
2020-09-03 19:37:01 +00:00
|
|
|
strvec_push(&options.git_am_opts,
|
|
|
|
"--ignore-whitespace");
|
2020-08-17 17:40:02 +00:00
|
|
|
if (options.committer_date_is_author_date)
|
2020-09-03 19:37:01 +00:00
|
|
|
strvec_push(&options.git_am_opts,
|
|
|
|
"--committer-date-is-author-date");
|
2020-08-17 17:40:03 +00:00
|
|
|
if (options.ignore_date)
|
2020-09-03 19:37:01 +00:00
|
|
|
strvec_push(&options.git_am_opts, "--ignore-date");
|
2020-07-13 10:10:41 +00:00
|
|
|
} else {
|
2021-09-07 21:05:09 +00:00
|
|
|
/* REBASE_MERGE */
|
2020-07-13 10:10:41 +00:00
|
|
|
if (ignore_whitespace) {
|
|
|
|
string_list_append(&strategy_options,
|
|
|
|
"ignore-space-change");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-04 22:00:11 +00:00
|
|
|
if (strategy_options.nr) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!options.strategy)
|
Change default merge backend from recursive to ort
There are a few reasons to switch the default:
* Correctness
* Extensibility
* Performance
I'll provide some summaries about each.
=== Correctness ===
The original impetus for a new merge backend was to fix issues that were
difficult to fix within recursive's design. The success with this goal
is perhaps most easily demonstrated by running the following:
$ git grep -2 KNOWN_FAILURE t/ | grep -A 4 GIT_TEST_MERGE_ALGORITHM
$ git grep test_expect_merge_algorithm.failure.success t/
$ git grep test_expect_merge_algorithm.success.failure t/
In order, these greps show:
* Seven sets of submodule tests (10 total tests) that fail with
recursive but succeed with ort
* 22 other tests that fail with recursive, but succeed with ort
* 0 tests that pass with recursive, but fail with ort
=== Extensibility ===
Being able to perform merges without touching the working tree or index
makes it possible to create new features that were difficult with the
old backend:
* Merging, cherry-picking, rebasing, reverting in bare repositories...
or just on branches that aren't checked out.
* `git diff AUTO_MERGE` -- ability to see what changes the user has
made to resolve conflicts so far (see commit 5291828df8 ("merge-ort:
write $GIT_DIR/AUTO_MERGE whenever we hit a conflict", 2021-03-20)
* A --remerge-diff option for log/show, used to show diffs for merges
that display the difference between what an automatic merge would
have created and what was recorded in the merge. (This option will
often result in an empty diff because many merges are clean, but for
the non-clean ones it will show how conflicts were fixed including
the removal of conflict markers, and also show additional changes
made outside of conflict regions to e.g. fix semantic conflicts.)
* A --remerge-diff-only option for log/show, similar to --remerge-diff
but also showing how cherry-picks or reverts differed from what an
automatic cherry-pick or revert would provide.
The last three have been implemented already (though only one has been
submitted upstream so far; the others were waiting for performance work
to complete), and I still plan to implement the first one.
=== Performance ===
I'll quote from the summary of my final optimization for merge-ort
(while fixing the testcase name from 'no-renames' to 'few-renames'):
Timings
Infinite
merge- merge- Parallelism
recursive recursive of rename merge-ort
v2.30.0 current detection current
---------- --------- ----------- ---------
few-renames: 18.912 s 18.030 s 11.699 s 198.3 ms
mega-renames: 5964.031 s 361.281 s 203.886 s 661.8 ms
just-one-mega: 149.583 s 11.009 s 7.553 s 264.6 ms
Speedup factors
Infinite
merge- merge- Parallelism
recursive recursive of rename
v2.30.0 current detection merge-ort
---------- --------- ----------- ---------
few-renames: 1 1.05 1.6 95
mega-renames: 1 16.5 29 9012
just-one-mega: 1 13.6 20 565
And, for partial clone users:
Factor reduction in number of objects needed
Infinite
merge- merge- Parallelism
recursive recursive of rename
v2.30.0 current detection merge-ort
---------- --------- ----------- ---------
mega-renames: 1 1 1 181.3
Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-08-04 05:38:01 +00:00
|
|
|
options.strategy = "ort";
|
2018-09-04 22:00:11 +00:00
|
|
|
|
|
|
|
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:
|
|
|
|
/* 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-07-29 00:37:20 +00:00
|
|
|
if (options.git_am_opts.nr || options.type == REBASE_APPLY) {
|
2020-02-15 21:36:41 +00:00
|
|
|
/* all am options except -q are compatible only with --apply */
|
2020-07-29 00:37:20 +00:00
|
|
|
for (i = options.git_am_opts.nr - 1; i >= 0; i--)
|
|
|
|
if (strcmp(options.git_am_opts.v[i], "-q"))
|
2020-02-15 21:36:33 +00:00
|
|
|
break;
|
|
|
|
|
2020-02-15 21:36:39 +00:00
|
|
|
if (i >= 0) {
|
2020-02-15 21:36:41 +00:00
|
|
|
if (is_merge(&options))
|
2022-01-05 20:02:24 +00:00
|
|
|
die(_("apply options and merge options "
|
|
|
|
"cannot be used together"));
|
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
|
|
|
}
|
|
|
|
|
2020-11-02 23:45:34 +00:00
|
|
|
if (options.type == REBASE_MERGE &&
|
|
|
|
!options.strategy &&
|
|
|
|
getenv("GIT_TEST_MERGE_ALGORITHM"))
|
|
|
|
options.strategy = xstrdup(getenv("GIT_TEST_MERGE_ALGORITHM"));
|
|
|
|
|
2018-08-06 19:31:11 +00:00
|
|
|
switch (options.type) {
|
|
|
|
case REBASE_MERGE:
|
|
|
|
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) {
|
2020-07-28 20:24:27 +00:00
|
|
|
strvec_push(&options.git_am_opts, "--signoff");
|
2018-09-04 21:59:50 +00:00
|
|
|
options.flags |= REBASE_FORCE;
|
|
|
|
}
|
|
|
|
|
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();
|
2021-02-23 07:18:40 +00:00
|
|
|
if (options.fork_point < 0)
|
|
|
|
options.fork_point = 1;
|
2018-08-08 15:36:30 +00:00
|
|
|
} 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}";
|
|
|
|
}
|
2021-09-21 10:24:06 +00:00
|
|
|
options.upstream =
|
|
|
|
lookup_commit_reference_by_name(options.upstream_name);
|
2018-08-06 19:31:11 +00:00
|
|
|
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
|
|
|
|
|
|
|
/*
|
|
|
|
* 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)? */
|
2021-09-21 10:24:07 +00:00
|
|
|
} else {
|
|
|
|
struct commit *commit =
|
|
|
|
lookup_commit_reference_by_name(branch_name);
|
|
|
|
if (!commit)
|
|
|
|
die(_("no such branch/commit '%s'"),
|
|
|
|
branch_name);
|
|
|
|
oidcpy(&options.orig_head, &commit->object.oid);
|
2018-09-04 21:27:21 +00:00
|
|
|
options.head_name = NULL;
|
2021-09-21 10:24:07 +00:00
|
|
|
}
|
2018-09-04 21:27:21 +00:00
|
|
|
} 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
|
|
|
|
2022-04-21 04:42:33 +00:00
|
|
|
/* Make sure the branch to rebase onto is valid. */
|
|
|
|
if (keep_base) {
|
|
|
|
strbuf_reset(&buf);
|
|
|
|
strbuf_addstr(&buf, options.upstream_name);
|
|
|
|
strbuf_addstr(&buf, "...");
|
|
|
|
strbuf_addstr(&buf, branch_name);
|
|
|
|
options.onto_name = xstrdup(buf.buf);
|
|
|
|
} else if (!options.onto_name)
|
|
|
|
options.onto_name = options.upstream_name;
|
|
|
|
if (strstr(options.onto_name, "...")) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
options.onto = lookup_commit_or_die(&merge_base,
|
|
|
|
options.onto_name);
|
|
|
|
} else {
|
|
|
|
options.onto =
|
|
|
|
lookup_commit_reference_by_name(options.onto_name);
|
|
|
|
if (!options.onto)
|
|
|
|
die(_("Does not point to a valid commit '%s'"),
|
|
|
|
options.onto_name);
|
|
|
|
}
|
|
|
|
|
2021-02-23 07:18:40 +00:00
|
|
|
if (options.fork_point > 0) {
|
2018-09-04 22:00:09 +00:00
|
|
|
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"));
|
|
|
|
|
2022-01-26 13:05:44 +00:00
|
|
|
if (options.autostash)
|
|
|
|
create_autostash(the_repository,
|
|
|
|
state_dir_path("autostash", &options));
|
|
|
|
|
2018-09-04 22:00:02 +00:00
|
|
|
|
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)) {
|
2021-09-21 10:24:05 +00:00
|
|
|
ret = -1;
|
2018-09-04 21:27:14 +00:00
|
|
|
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) {
|
2022-01-26 13:05:36 +00:00
|
|
|
ret = checkout_up_to_date(&options);
|
|
|
|
if (ret)
|
2018-09-04 21:27:21 +00:00
|
|
|
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);
|
2021-09-21 10:24:05 +00:00
|
|
|
ret = finish_rebase(&options);
|
2018-09-04 21:27:17 +00:00
|
|
|
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 &&
|
2021-12-22 03:59:32 +00:00
|
|
|
run_hooks_l("pre-rebase", options.upstream_arg,
|
2018-09-04 21:27:10 +00:00
|
|
|
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);
|
2022-01-26 13:05:46 +00:00
|
|
|
ropts.oid = &options.onto->object.oid;
|
2022-01-26 13:05:48 +00:00
|
|
|
ropts.orig_head = &options.orig_head,
|
2022-01-26 13:05:46 +00:00
|
|
|
ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
|
|
|
|
RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
|
|
|
|
ropts.head_msg = msg.buf;
|
|
|
|
ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
|
|
|
|
if (reset_head(the_repository, &ropts))
|
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));
|
2022-01-26 13:05:46 +00:00
|
|
|
memset(&ropts, 0, sizeof(ropts));
|
|
|
|
ropts.branch = options.head_name;
|
|
|
|
ropts.flags = RESET_HEAD_REFS_ONLY;
|
|
|
|
ropts.head_msg = msg.buf;
|
|
|
|
reset_head(the_repository, &ropts);
|
2018-08-08 15:36:32 +00:00
|
|
|
strbuf_release(&msg);
|
2021-09-21 10:24:05 +00:00
|
|
|
ret = finish_rebase(&options);
|
2018-08-08 15:36:32 +00:00
|
|
|
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:
|
2021-09-21 10:24:05 +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);
|
2021-07-25 13:08:29 +00:00
|
|
|
free(options.strategy);
|
2021-04-25 14:16:18 +00:00
|
|
|
strbuf_release(&options.git_format_patch_opt);
|
2018-09-04 22:00:12 +00:00
|
|
|
free(squash_onto_name);
|
2021-09-21 10:24:05 +00:00
|
|
|
return !!ret;
|
2018-08-06 19:31:09 +00:00
|
|
|
}
|