mirror of
https://github.com/git/git.git
synced 2026-04-14 02:40:08 +02:00
Merge branch 'interactive-rebase'
This series of branches introduces the git-rebase--helper, a builtin helping to accelerate the interactive rebase dramatically. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -114,6 +114,7 @@
|
||||
/git-read-tree
|
||||
/git-rebase
|
||||
/git-rebase--am
|
||||
/git-rebase--helper
|
||||
/git-rebase--interactive
|
||||
/git-rebase--merge
|
||||
/git-receive-pack
|
||||
|
||||
@@ -32,11 +32,14 @@ OPTIONS
|
||||
--branch::
|
||||
Show the branch and tracking info even in short-format.
|
||||
|
||||
--porcelain::
|
||||
--porcelain[=<version>]::
|
||||
Give the output in an easy-to-parse format for scripts.
|
||||
This is similar to the short output, but will remain stable
|
||||
across Git versions and regardless of user configuration. See
|
||||
below for details.
|
||||
+
|
||||
The version parameter is used to specify the format version.
|
||||
This is optional and defaults to the original version 'v1' format.
|
||||
|
||||
--long::
|
||||
Give the output in the long-format. This is the default.
|
||||
@@ -96,7 +99,7 @@ configuration variable documented in linkgit:git-config[1].
|
||||
|
||||
-z::
|
||||
Terminate entries with NUL, instead of LF. This implies
|
||||
the `--porcelain` output format if no other format is given.
|
||||
the `--porcelain=v1` output format if no other format is given.
|
||||
|
||||
--column[=<options>]::
|
||||
--no-column::
|
||||
@@ -185,12 +188,12 @@ in which case `XY` are `!!`.
|
||||
|
||||
If -b is used the short-format status is preceded by a line
|
||||
|
||||
## branchname tracking info
|
||||
## branchname tracking info
|
||||
|
||||
Porcelain Format
|
||||
~~~~~~~~~~~~~~~~
|
||||
Porcelain Format Version 1
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The porcelain format is similar to the short format, but is guaranteed
|
||||
Version 1 porcelain format is similar to the short format, but is guaranteed
|
||||
not to change in a backwards-incompatible way between Git versions or
|
||||
based on user configuration. This makes it ideal for parsing by scripts.
|
||||
The description of the short format above also describes the porcelain
|
||||
@@ -212,6 +215,124 @@ field from the first filename). Third, filenames containing special
|
||||
characters are not specially formatted; no quoting or
|
||||
backslash-escaping is performed.
|
||||
|
||||
Porcelain Format Version 2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Version 2 format adds more detailed information about the state of
|
||||
the worktree and changed items. Version 2 also defines an extensible
|
||||
set of easy to parse optional headers.
|
||||
|
||||
Header lines start with "#" and are added in response to specific
|
||||
command line arguments. Parsers should ignore headers they
|
||||
don't recognize.
|
||||
|
||||
### Branch Headers
|
||||
|
||||
If `--branch` is given, a series of header lines are printed with
|
||||
information about the current branch.
|
||||
|
||||
Line Notes
|
||||
------------------------------------------------------------
|
||||
# branch.oid <commit> | (initial) Current commit.
|
||||
# branch.head <branch> | (detached) Current branch.
|
||||
# branch.upstream <upstream_branch> If upstream is set.
|
||||
# branch.ab +<ahead> -<behind> If upstream is set and
|
||||
the commit is present.
|
||||
------------------------------------------------------------
|
||||
|
||||
### Changed Tracked Entries
|
||||
|
||||
Following the headers, a series of lines are printed for tracked
|
||||
entries. One of three different line formats may be used to describe
|
||||
an entry depending on the type of change. Tracked entries are printed
|
||||
in an undefined order; parsers should allow for a mixture of the 3
|
||||
line types in any order.
|
||||
|
||||
Ordinary changed entries have the following format:
|
||||
|
||||
1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
|
||||
|
||||
Renamed or copied entries have the following format:
|
||||
|
||||
2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath>
|
||||
|
||||
Field Meaning
|
||||
--------------------------------------------------------
|
||||
<XY> A 2 character field containing the staged and
|
||||
unstaged XY values described in the short format,
|
||||
with unchanged indicated by a "." rather than
|
||||
a space.
|
||||
<sub> A 4 character field describing the submodule state.
|
||||
"N..." when the entry is not a submodule.
|
||||
"S<c><m><u>" when the entry is a submodule.
|
||||
<c> is "C" if the commit changed; otherwise ".".
|
||||
<m> is "M" if it has tracked changes; otherwise ".".
|
||||
<u> is "U" if there are untracked changes; otherwise ".".
|
||||
<mH> The octal file mode in HEAD.
|
||||
<mI> The octal file mode in the index.
|
||||
<mW> The octal file mode in the worktree.
|
||||
<hH> The object name in HEAD.
|
||||
<hI> The object name in the index.
|
||||
<X><score> The rename or copy score (denoting the percentage
|
||||
of similarity between the source and target of the
|
||||
move or copy). For example "R100" or "C75".
|
||||
<path> The pathname. In a renamed/copied entry, this
|
||||
is the path in the index and in the working tree.
|
||||
<sep> When the `-z` option is used, the 2 pathnames are separated
|
||||
with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09)
|
||||
byte separates them.
|
||||
<origPath> The pathname in the commit at HEAD. This is only
|
||||
present in a renamed/copied entry, and tells
|
||||
where the renamed/copied contents came from.
|
||||
--------------------------------------------------------
|
||||
|
||||
Unmerged entries have the following format; the first character is
|
||||
a "u" to distinguish from ordinary changed entries.
|
||||
|
||||
u <xy> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
|
||||
|
||||
Field Meaning
|
||||
--------------------------------------------------------
|
||||
<XY> A 2 character field describing the conflict type
|
||||
as described in the short format.
|
||||
<sub> A 4 character field describing the submodule state
|
||||
as described above.
|
||||
<m1> The octal file mode in stage 1.
|
||||
<m2> The octal file mode in stage 2.
|
||||
<m3> The octal file mode in stage 3.
|
||||
<mW> The octal file mode in the worktree.
|
||||
<h1> The object name in stage 1.
|
||||
<h2> The object name in stage 2.
|
||||
<h3> The object name in stage 3.
|
||||
<path> The pathname.
|
||||
--------------------------------------------------------
|
||||
|
||||
### Other Items
|
||||
|
||||
Following the tracked entries (and if requested), a series of
|
||||
lines will be printed for untracked and then ignored items
|
||||
found in the worktree.
|
||||
|
||||
Untracked items have the following format:
|
||||
|
||||
? <path>
|
||||
|
||||
Ignored items have the following format:
|
||||
|
||||
! <path>
|
||||
|
||||
### Pathname Format Notes and -z
|
||||
|
||||
When the `-z` option is given, pathnames are printed as is and
|
||||
without any quoting and lines are terminated with a NUL (ASCII 0x00)
|
||||
byte.
|
||||
|
||||
Otherwise, all pathnames will be "C-quoted" if they contain any tab,
|
||||
linefeed, double quote, or backslash characters. In C-quoting, these
|
||||
characters will be replaced with the corresponding C-style escape
|
||||
sequences and the resulting pathname will be double quoted.
|
||||
|
||||
|
||||
CONFIGURATION
|
||||
-------------
|
||||
|
||||
|
||||
1
Makefile
1
Makefile
@@ -925,6 +925,7 @@ BUILTIN_OBJS += builtin/prune.o
|
||||
BUILTIN_OBJS += builtin/pull.o
|
||||
BUILTIN_OBJS += builtin/push.o
|
||||
BUILTIN_OBJS += builtin/read-tree.o
|
||||
BUILTIN_OBJS += builtin/rebase--helper.o
|
||||
BUILTIN_OBJS += builtin/receive-pack.o
|
||||
BUILTIN_OBJS += builtin/reflog.o
|
||||
BUILTIN_OBJS += builtin/remote.o
|
||||
|
||||
@@ -102,6 +102,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_pull(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_push(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_receive_pack(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_remote(int argc, const char **argv, const char *prefix);
|
||||
|
||||
@@ -142,14 +142,24 @@ static int show_ignored_in_status, have_option_m;
|
||||
static const char *only_include_assumed;
|
||||
static struct strbuf message = STRBUF_INIT;
|
||||
|
||||
static enum status_format {
|
||||
STATUS_FORMAT_NONE = 0,
|
||||
STATUS_FORMAT_LONG,
|
||||
STATUS_FORMAT_SHORT,
|
||||
STATUS_FORMAT_PORCELAIN,
|
||||
static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
|
||||
|
||||
STATUS_FORMAT_UNSPECIFIED
|
||||
} status_format = STATUS_FORMAT_UNSPECIFIED;
|
||||
static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
|
||||
{
|
||||
enum wt_status_format *value = (enum wt_status_format *)opt->value;
|
||||
if (unset)
|
||||
*value = STATUS_FORMAT_NONE;
|
||||
else if (!arg)
|
||||
*value = STATUS_FORMAT_PORCELAIN;
|
||||
else if (!strcmp(arg, "v1") || !strcmp(arg, "1"))
|
||||
*value = STATUS_FORMAT_PORCELAIN;
|
||||
else if (!strcmp(arg, "v2") || !strcmp(arg, "2"))
|
||||
*value = STATUS_FORMAT_PORCELAIN_V2;
|
||||
else
|
||||
die("unsupported porcelain version '%s'", arg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int opt_parse_m(const struct option *opt, const char *arg, int unset)
|
||||
{
|
||||
@@ -173,7 +183,7 @@ static void determine_whence(struct wt_status *s)
|
||||
whence = FROM_MERGE;
|
||||
else if (file_exists(git_path_cherry_pick_head())) {
|
||||
whence = FROM_CHERRY_PICK;
|
||||
if (file_exists(git_path(SEQ_DIR)))
|
||||
if (file_exists(git_path_seq_dir()))
|
||||
sequencer_in_use = 1;
|
||||
}
|
||||
else
|
||||
@@ -500,24 +510,13 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
|
||||
s->fp = fp;
|
||||
s->nowarn = nowarn;
|
||||
s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
|
||||
if (!s->is_initial)
|
||||
hashcpy(s->sha1_commit, sha1);
|
||||
s->status_format = status_format;
|
||||
s->ignore_submodule_arg = ignore_submodule_arg;
|
||||
|
||||
wt_status_collect(s);
|
||||
|
||||
switch (status_format) {
|
||||
case STATUS_FORMAT_SHORT:
|
||||
wt_shortstatus_print(s);
|
||||
break;
|
||||
case STATUS_FORMAT_PORCELAIN:
|
||||
wt_porcelain_print(s);
|
||||
break;
|
||||
case STATUS_FORMAT_UNSPECIFIED:
|
||||
die("BUG: finalize_deferred_config() should have been called");
|
||||
break;
|
||||
case STATUS_FORMAT_NONE:
|
||||
case STATUS_FORMAT_LONG:
|
||||
wt_status_print(s);
|
||||
break;
|
||||
}
|
||||
wt_status_print(s);
|
||||
|
||||
return s->commitable;
|
||||
}
|
||||
@@ -1099,7 +1098,7 @@ static const char *read_commit_message(const char *name)
|
||||
* is not in effect here.
|
||||
*/
|
||||
static struct status_deferred_config {
|
||||
enum status_format status_format;
|
||||
enum wt_status_format status_format;
|
||||
int show_branch;
|
||||
} status_deferred_config = {
|
||||
STATUS_FORMAT_UNSPECIFIED,
|
||||
@@ -1109,6 +1108,7 @@ static struct status_deferred_config {
|
||||
static void finalize_deferred_config(struct wt_status *s)
|
||||
{
|
||||
int use_deferred_config = (status_format != STATUS_FORMAT_PORCELAIN &&
|
||||
status_format != STATUS_FORMAT_PORCELAIN_V2 &&
|
||||
!s->null_termination);
|
||||
|
||||
if (s->null_termination) {
|
||||
@@ -1337,9 +1337,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
|
||||
N_("show status concisely"), STATUS_FORMAT_SHORT),
|
||||
OPT_BOOL('b', "branch", &s.show_branch,
|
||||
N_("show branch information")),
|
||||
OPT_SET_INT(0, "porcelain", &status_format,
|
||||
N_("machine-readable output"),
|
||||
STATUS_FORMAT_PORCELAIN),
|
||||
{ OPTION_CALLBACK, 0, "porcelain", &status_format,
|
||||
N_("version"), N_("machine-readable output"),
|
||||
PARSE_OPT_OPTARG, opt_parse_porcelain },
|
||||
OPT_SET_INT(0, "long", &status_format,
|
||||
N_("show status in long format (default)"),
|
||||
STATUS_FORMAT_LONG),
|
||||
@@ -1384,7 +1384,13 @@ int cmd_status(int argc, const char **argv, const char *prefix)
|
||||
fd = no_lock_index ? -1 : hold_locked_index(&index_lock, 0);
|
||||
|
||||
s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
|
||||
if (!s.is_initial)
|
||||
hashcpy(s.sha1_commit, sha1);
|
||||
|
||||
s.ignore_submodule_arg = ignore_submodule_arg;
|
||||
s.status_format = status_format;
|
||||
s.verbose = verbose;
|
||||
|
||||
wt_status_collect(&s);
|
||||
|
||||
if (0 <= fd)
|
||||
@@ -1393,23 +1399,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
|
||||
if (s.relative_paths)
|
||||
s.prefix = prefix;
|
||||
|
||||
switch (status_format) {
|
||||
case STATUS_FORMAT_SHORT:
|
||||
wt_shortstatus_print(&s);
|
||||
break;
|
||||
case STATUS_FORMAT_PORCELAIN:
|
||||
wt_porcelain_print(&s);
|
||||
break;
|
||||
case STATUS_FORMAT_UNSPECIFIED:
|
||||
die("BUG: finalize_deferred_config() should have been called");
|
||||
break;
|
||||
case STATUS_FORMAT_NONE:
|
||||
case STATUS_FORMAT_LONG:
|
||||
s.verbose = verbose;
|
||||
s.ignore_submodule_arg = ignore_submodule_arg;
|
||||
wt_status_print(&s);
|
||||
break;
|
||||
}
|
||||
wt_status_print(&s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "revision.h"
|
||||
#include "tempfile.h"
|
||||
#include "lockfile.h"
|
||||
#include "wt-status.h"
|
||||
|
||||
enum rebase_type {
|
||||
REBASE_INVALID = -1,
|
||||
@@ -325,73 +326,6 @@ static int git_pull_config(const char *var, const char *value, void *cb)
|
||||
return git_default_config(var, value, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 if there are unstaged changes, 0 otherwise.
|
||||
*/
|
||||
static int has_unstaged_changes(const char *prefix)
|
||||
{
|
||||
struct rev_info rev_info;
|
||||
int result;
|
||||
|
||||
init_revisions(&rev_info, prefix);
|
||||
DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
|
||||
DIFF_OPT_SET(&rev_info.diffopt, QUICK);
|
||||
diff_setup_done(&rev_info.diffopt);
|
||||
result = run_diff_files(&rev_info, 0);
|
||||
return diff_result_code(&rev_info.diffopt, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 if there are uncommitted changes, 0 otherwise.
|
||||
*/
|
||||
static int has_uncommitted_changes(const char *prefix)
|
||||
{
|
||||
struct rev_info rev_info;
|
||||
int result;
|
||||
|
||||
if (is_cache_unborn())
|
||||
return 0;
|
||||
|
||||
init_revisions(&rev_info, prefix);
|
||||
DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
|
||||
DIFF_OPT_SET(&rev_info.diffopt, QUICK);
|
||||
add_head_to_pending(&rev_info);
|
||||
diff_setup_done(&rev_info.diffopt);
|
||||
result = run_diff_index(&rev_info, 1);
|
||||
return diff_result_code(&rev_info.diffopt, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the work tree has unstaged or uncommitted changes, dies with the
|
||||
* appropriate message.
|
||||
*/
|
||||
static void die_on_unclean_work_tree(const char *prefix)
|
||||
{
|
||||
struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
|
||||
int do_die = 0;
|
||||
|
||||
hold_locked_index(lock_file, 0);
|
||||
refresh_cache(REFRESH_QUIET);
|
||||
update_index_if_able(&the_index, lock_file);
|
||||
rollback_lock_file(lock_file);
|
||||
|
||||
if (has_unstaged_changes(prefix)) {
|
||||
error(_("Cannot pull with rebase: You have unstaged changes."));
|
||||
do_die = 1;
|
||||
}
|
||||
|
||||
if (has_uncommitted_changes(prefix)) {
|
||||
if (do_die)
|
||||
error(_("Additionally, your index contains uncommitted changes."));
|
||||
else
|
||||
error(_("Cannot pull with rebase: Your index contains uncommitted changes."));
|
||||
do_die = 1;
|
||||
}
|
||||
|
||||
if (do_die)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends merge candidates from FETCH_HEAD that are not marked not-for-merge
|
||||
* into merge_heads.
|
||||
@@ -875,7 +809,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
|
||||
die(_("Updating an unborn branch with changes added to the index."));
|
||||
|
||||
if (!autostash)
|
||||
die_on_unclean_work_tree(prefix);
|
||||
require_clean_work_tree(N_("pull with rebase"),
|
||||
_("please commit or stash them."), 1, 0);
|
||||
|
||||
if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs))
|
||||
hashclr(rebase_fork_point);
|
||||
|
||||
67
builtin/rebase--helper.c
Normal file
67
builtin/rebase--helper.c
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "builtin.h"
|
||||
#include "cache.h"
|
||||
#include "parse-options.h"
|
||||
#include "sequencer.h"
|
||||
|
||||
static const char * const builtin_rebase_helper_usage[] = {
|
||||
N_("git rebase--helper [<options>]"),
|
||||
NULL
|
||||
};
|
||||
|
||||
int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct replay_opts opts = REPLAY_OPTS_INIT;
|
||||
int keep_empty = 0;
|
||||
enum {
|
||||
CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S,
|
||||
CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH
|
||||
} command = 0;
|
||||
struct option options[] = {
|
||||
OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")),
|
||||
OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")),
|
||||
OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
|
||||
CONTINUE),
|
||||
OPT_CMDMODE(0, "abort", &command, N_("abort rebase"),
|
||||
ABORT),
|
||||
OPT_CMDMODE(0, "make-script", &command,
|
||||
N_("make rebase script"), MAKE_SCRIPT),
|
||||
OPT_CMDMODE(0, "shorten-sha1s", &command,
|
||||
N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S),
|
||||
OPT_CMDMODE(0, "expand-sha1s", &command,
|
||||
N_("expand SHA-1s in the todo list"), EXPAND_SHA1S),
|
||||
OPT_CMDMODE(0, "check-todo-list", &command,
|
||||
N_("check the todo list"), CHECK_TODO_LIST),
|
||||
OPT_CMDMODE(0, "skip-unnecessary-picks", &command,
|
||||
N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS),
|
||||
OPT_CMDMODE(0, "rearrange-squash", &command,
|
||||
N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
|
||||
OPT_END()
|
||||
};
|
||||
|
||||
git_config(git_default_config, NULL);
|
||||
|
||||
opts.action = REPLAY_INTERACTIVE_REBASE;
|
||||
opts.allow_ff = 1;
|
||||
opts.allow_empty = 1;
|
||||
|
||||
argc = parse_options(argc, argv, NULL, options,
|
||||
builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0);
|
||||
|
||||
if (command == CONTINUE && argc == 1)
|
||||
return !!sequencer_continue(&opts);
|
||||
if (command == ABORT && argc == 1)
|
||||
return !!sequencer_remove_state(&opts);
|
||||
if (command == MAKE_SCRIPT && argc > 1)
|
||||
return !!sequencer_make_script(keep_empty, stdout, argc, argv);
|
||||
if (command == SHORTEN_SHA1S && argc == 1)
|
||||
return !!transform_todo_ids(1);
|
||||
if (command == EXPAND_SHA1S && argc == 1)
|
||||
return !!transform_todo_ids(0);
|
||||
if (command == CHECK_TODO_LIST && argc == 1)
|
||||
return !!check_todo_list();
|
||||
if (command == SKIP_UNNECESSARY_PICKS && argc == 1)
|
||||
return !!skip_unnecessary_picks();
|
||||
if (command == REARRANGE_SQUASH && argc == 1)
|
||||
return !!rearrange_squash();
|
||||
usage_with_options(builtin_rebase_helper_usage, options);
|
||||
}
|
||||
@@ -71,7 +71,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...)
|
||||
die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
|
||||
}
|
||||
|
||||
static void parse_args(int argc, const char **argv, struct replay_opts *opts)
|
||||
static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
|
||||
{
|
||||
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
|
||||
const char *me = action_name(opts);
|
||||
@@ -115,25 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
|
||||
if (opts->keep_redundant_commits)
|
||||
opts->allow_empty = 1;
|
||||
|
||||
/* Set the subcommand */
|
||||
if (cmd == 'q')
|
||||
opts->subcommand = REPLAY_REMOVE_STATE;
|
||||
else if (cmd == 'c')
|
||||
opts->subcommand = REPLAY_CONTINUE;
|
||||
else if (cmd == 'a')
|
||||
opts->subcommand = REPLAY_ROLLBACK;
|
||||
else
|
||||
opts->subcommand = REPLAY_NONE;
|
||||
|
||||
/* Check for incompatible command line arguments */
|
||||
if (opts->subcommand != REPLAY_NONE) {
|
||||
if (cmd) {
|
||||
char *this_operation;
|
||||
if (opts->subcommand == REPLAY_REMOVE_STATE)
|
||||
if (cmd == 'q')
|
||||
this_operation = "--quit";
|
||||
else if (opts->subcommand == REPLAY_CONTINUE)
|
||||
else if (cmd == 'c')
|
||||
this_operation = "--continue";
|
||||
else {
|
||||
assert(opts->subcommand == REPLAY_ROLLBACK);
|
||||
assert(cmd == 'a');
|
||||
this_operation = "--abort";
|
||||
}
|
||||
|
||||
@@ -156,7 +146,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
|
||||
"--edit", opts->edit,
|
||||
NULL);
|
||||
|
||||
if (opts->subcommand != REPLAY_NONE) {
|
||||
if (cmd) {
|
||||
opts->revs = NULL;
|
||||
} else {
|
||||
struct setup_revision_opt s_r_opt;
|
||||
@@ -174,20 +164,30 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
|
||||
|
||||
if (argc > 1)
|
||||
usage_with_options(usage_str, options);
|
||||
|
||||
/* These option values will be free()d */
|
||||
opts->gpg_sign = xstrdup_or_null(opts->gpg_sign);
|
||||
opts->strategy = xstrdup_or_null(opts->strategy);
|
||||
|
||||
if (cmd == 'q')
|
||||
return sequencer_remove_state(opts);
|
||||
if (cmd == 'c')
|
||||
return sequencer_continue(opts);
|
||||
if (cmd == 'a')
|
||||
return sequencer_rollback(opts);
|
||||
return sequencer_pick_revisions(opts);
|
||||
}
|
||||
|
||||
int cmd_revert(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct replay_opts opts;
|
||||
struct replay_opts opts = REPLAY_OPTS_INIT;
|
||||
int res;
|
||||
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
if (isatty(0))
|
||||
opts.edit = 1;
|
||||
opts.action = REPLAY_REVERT;
|
||||
git_config(git_default_config, NULL);
|
||||
parse_args(argc, argv, &opts);
|
||||
res = sequencer_pick_revisions(&opts);
|
||||
res = run_sequencer(argc, argv, &opts);
|
||||
if (res < 0)
|
||||
die(_("revert failed"));
|
||||
return res;
|
||||
@@ -195,14 +195,12 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
|
||||
|
||||
int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct replay_opts opts;
|
||||
struct replay_opts opts = REPLAY_OPTS_INIT;
|
||||
int res;
|
||||
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
opts.action = REPLAY_PICK;
|
||||
git_config(git_default_config, NULL);
|
||||
parse_args(argc, argv, &opts);
|
||||
res = sequencer_pick_revisions(&opts);
|
||||
res = run_sequencer(argc, argv, &opts);
|
||||
if (res < 0)
|
||||
die(_("cherry-pick failed"));
|
||||
return res;
|
||||
|
||||
@@ -146,13 +146,13 @@ reschedule_last_action () {
|
||||
append_todo_help () {
|
||||
gettext "
|
||||
Commands:
|
||||
p, pick = use commit
|
||||
r, reword = use commit, but edit the commit message
|
||||
e, edit = use commit, but stop for amending
|
||||
s, squash = use commit, but meld into previous commit
|
||||
f, fixup = like \"squash\", but discard this commit's log message
|
||||
x, exec = run command (the rest of the line) using shell
|
||||
d, drop = remove commit
|
||||
p, pick = use commit
|
||||
r, reword = use commit, but edit the commit message
|
||||
e, edit = use commit, but stop for amending
|
||||
s, squash = use commit, but meld into previous commit
|
||||
f, fixup = like \"squash\", but discard this commit's log message
|
||||
x, exec = run command (the rest of the line) using shell
|
||||
d, drop = remove commit
|
||||
|
||||
These lines can be re-ordered; they are executed from top to bottom.
|
||||
" | git stripspace --comment-lines >>"$todo"
|
||||
@@ -703,43 +703,6 @@ do_rest () {
|
||||
done
|
||||
}
|
||||
|
||||
# skip picking commits whose parents are unchanged
|
||||
skip_unnecessary_picks () {
|
||||
fd=3
|
||||
while read -r command rest
|
||||
do
|
||||
# fd=3 means we skip the command
|
||||
case "$fd,$command" in
|
||||
3,pick|3,p)
|
||||
# pick a commit whose parent is current $onto -> skip
|
||||
sha1=${rest%% *}
|
||||
case "$(git rev-parse --verify --quiet "$sha1"^)" in
|
||||
"$onto"*)
|
||||
onto=$sha1
|
||||
;;
|
||||
*)
|
||||
fd=1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
3,"$comment_char"*|3,)
|
||||
# copy comments
|
||||
;;
|
||||
*)
|
||||
fd=1
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "$command${rest:+ }$rest" >&$fd
|
||||
done <"$todo" >"$todo.new" 3>>"$done" &&
|
||||
mv -f "$todo".new "$todo" &&
|
||||
case "$(peek_next_command)" in
|
||||
squash|s|fixup|f)
|
||||
record_in_rewritten "$onto"
|
||||
;;
|
||||
esac ||
|
||||
die "$(gettext "Could not skip unnecessary pick commands")"
|
||||
}
|
||||
|
||||
transform_todo_ids () {
|
||||
while read -r command rest
|
||||
do
|
||||
@@ -750,7 +713,12 @@ transform_todo_ids () {
|
||||
;;
|
||||
*)
|
||||
sha1=$(git rev-parse --verify --quiet "$@" ${rest%%[ ]*}) &&
|
||||
rest="$sha1 ${rest#*[ ]}"
|
||||
if test "a$rest" = "a${rest#*[ ]}"
|
||||
then
|
||||
rest=$sha1
|
||||
else
|
||||
rest="$sha1 ${rest#*[ ]}"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "$command${rest:+ }$rest"
|
||||
@@ -759,98 +727,11 @@ transform_todo_ids () {
|
||||
}
|
||||
|
||||
expand_todo_ids() {
|
||||
transform_todo_ids
|
||||
git rebase--helper --expand-sha1s
|
||||
}
|
||||
|
||||
collapse_todo_ids() {
|
||||
transform_todo_ids --short
|
||||
}
|
||||
|
||||
# Rearrange the todo list that has both "pick sha1 msg" and
|
||||
# "pick sha1 fixup!/squash! msg" appears in it so that the latter
|
||||
# comes immediately after the former, and change "pick" to
|
||||
# "fixup"/"squash".
|
||||
#
|
||||
# Note that if the config has specified a custom instruction format
|
||||
# each log message will be re-retrieved in order to normalize the
|
||||
# autosquash arrangement
|
||||
rearrange_squash () {
|
||||
# extract fixup!/squash! lines and resolve any referenced sha1's
|
||||
while read -r pick sha1 message
|
||||
do
|
||||
test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
|
||||
case "$message" in
|
||||
"squash! "*|"fixup! "*)
|
||||
action="${message%%!*}"
|
||||
rest=$message
|
||||
prefix=
|
||||
# skip all squash! or fixup! (but save for later)
|
||||
while :
|
||||
do
|
||||
case "$rest" in
|
||||
"squash! "*|"fixup! "*)
|
||||
prefix="$prefix${rest%%!*},"
|
||||
rest="${rest#*! }"
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
printf '%s %s %s %s\n' "$sha1" "$action" "$prefix" "$rest"
|
||||
# if it's a single word, try to resolve to a full sha1 and
|
||||
# emit a second copy. This allows us to match on both message
|
||||
# and on sha1 prefix
|
||||
if test "${rest#* }" = "$rest"; then
|
||||
fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)"
|
||||
if test -n "$fullsha"; then
|
||||
# prefix the action to uniquely identify this line as
|
||||
# intended for full sha1 match
|
||||
echo "$sha1 +$action $prefix $fullsha"
|
||||
fi
|
||||
fi
|
||||
esac
|
||||
done >"$1.sq" <"$1"
|
||||
test -s "$1.sq" || return
|
||||
|
||||
used=
|
||||
while read -r pick sha1 message
|
||||
do
|
||||
case " $used" in
|
||||
*" $sha1 "*) continue ;;
|
||||
esac
|
||||
printf '%s\n' "$pick $sha1 $message"
|
||||
test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1})
|
||||
used="$used$sha1 "
|
||||
while read -r squash action msg_prefix msg_content
|
||||
do
|
||||
case " $used" in
|
||||
*" $squash "*) continue ;;
|
||||
esac
|
||||
emit=0
|
||||
case "$action" in
|
||||
+*)
|
||||
action="${action#+}"
|
||||
# full sha1 prefix test
|
||||
case "$msg_content" in "$sha1"*) emit=1;; esac ;;
|
||||
*)
|
||||
# message prefix test
|
||||
case "$message" in "$msg_content"*) emit=1;; esac ;;
|
||||
esac
|
||||
if test $emit = 1; then
|
||||
if test -n "${format}"
|
||||
then
|
||||
msg_content=$(git log -n 1 --format="${format}" ${squash})
|
||||
else
|
||||
msg_content="$(echo "$msg_prefix" | sed "s/,/! /g")$msg_content"
|
||||
fi
|
||||
printf '%s\n' "$action $squash $msg_content"
|
||||
used="$used$squash "
|
||||
fi
|
||||
done <"$1.sq"
|
||||
done >"$1.rearranged" <"$1"
|
||||
cat "$1.rearranged" >"$1"
|
||||
rm -f "$1.sq" "$1.rearranged"
|
||||
git rebase--helper --shorten-sha1s
|
||||
}
|
||||
|
||||
# Add commands after a pick or after a squash/fixup serie
|
||||
@@ -874,96 +755,6 @@ add_exec_commands () {
|
||||
mv "$1.new" "$1"
|
||||
}
|
||||
|
||||
# Check if the SHA-1 passed as an argument is a
|
||||
# correct one, if not then print $2 in "$todo".badsha
|
||||
# $1: the SHA-1 to test
|
||||
# $2: the line number of the input
|
||||
# $3: the input filename
|
||||
check_commit_sha () {
|
||||
badsha=0
|
||||
if test -z "$1"
|
||||
then
|
||||
badsha=1
|
||||
else
|
||||
sha1_verif="$(git rev-parse --verify --quiet $1^{commit})"
|
||||
if test -z "$sha1_verif"
|
||||
then
|
||||
badsha=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if test $badsha -ne 0
|
||||
then
|
||||
line="$(sed -n -e "${2}p" "$3")"
|
||||
warn "$(eval_gettext "\
|
||||
Warning: the SHA-1 is missing or isn't a commit in the following line:
|
||||
- \$line")"
|
||||
warn
|
||||
fi
|
||||
|
||||
return $badsha
|
||||
}
|
||||
|
||||
# prints the bad commits and bad commands
|
||||
# from the todolist in stdin
|
||||
check_bad_cmd_and_sha () {
|
||||
retval=0
|
||||
lineno=0
|
||||
while read -r command rest
|
||||
do
|
||||
lineno=$(( $lineno + 1 ))
|
||||
case $command in
|
||||
"$comment_char"*|''|noop|x|exec)
|
||||
# Doesn't expect a SHA-1
|
||||
;;
|
||||
"$cr")
|
||||
# Work around CR left by "read" (e.g. with Git for
|
||||
# Windows' Bash).
|
||||
;;
|
||||
pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f)
|
||||
if ! check_commit_sha "${rest%%[ ]*}" "$lineno" "$1"
|
||||
then
|
||||
retval=1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
line="$(sed -n -e "${lineno}p" "$1")"
|
||||
warn "$(eval_gettext "\
|
||||
Warning: the command isn't recognized in the following line:
|
||||
- \$line")"
|
||||
warn
|
||||
retval=1
|
||||
;;
|
||||
esac
|
||||
done <"$1"
|
||||
return $retval
|
||||
}
|
||||
|
||||
# Print the list of the SHA-1 of the commits
|
||||
# from stdin to stdout
|
||||
todo_list_to_sha_list () {
|
||||
git stripspace --strip-comments |
|
||||
while read -r command sha1 rest
|
||||
do
|
||||
case $command in
|
||||
"$comment_char"*|''|noop|x|"exec")
|
||||
;;
|
||||
*)
|
||||
long_sha=$(git rev-list --no-walk "$sha1" 2>/dev/null)
|
||||
printf "%s\n" "$long_sha"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Use warn for each line in stdin
|
||||
warn_lines () {
|
||||
while read -r line
|
||||
do
|
||||
warn " - $line"
|
||||
done
|
||||
}
|
||||
|
||||
# Switch to the branch in $into and notify it in the reflog
|
||||
checkout_onto () {
|
||||
GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
|
||||
@@ -978,74 +769,6 @@ get_missing_commit_check_level () {
|
||||
printf '%s' "$check_level" | tr 'A-Z' 'a-z'
|
||||
}
|
||||
|
||||
# Check if the user dropped some commits by mistake
|
||||
# Behaviour determined by rebase.missingCommitsCheck.
|
||||
# Check if there is an unrecognized command or a
|
||||
# bad SHA-1 in a command.
|
||||
check_todo_list () {
|
||||
raise_error=f
|
||||
|
||||
check_level=$(get_missing_commit_check_level)
|
||||
|
||||
case "$check_level" in
|
||||
warn|error)
|
||||
# Get the SHA-1 of the commits
|
||||
todo_list_to_sha_list <"$todo".backup >"$todo".oldsha1
|
||||
todo_list_to_sha_list <"$todo" >"$todo".newsha1
|
||||
|
||||
# Sort the SHA-1 and compare them
|
||||
sort -u "$todo".oldsha1 >"$todo".oldsha1+
|
||||
mv "$todo".oldsha1+ "$todo".oldsha1
|
||||
sort -u "$todo".newsha1 >"$todo".newsha1+
|
||||
mv "$todo".newsha1+ "$todo".newsha1
|
||||
comm -2 -3 "$todo".oldsha1 "$todo".newsha1 >"$todo".miss
|
||||
|
||||
# Warn about missing commits
|
||||
if test -s "$todo".miss
|
||||
then
|
||||
test "$check_level" = error && raise_error=t
|
||||
|
||||
warn "$(gettext "\
|
||||
Warning: some commits may have been dropped accidentally.
|
||||
Dropped commits (newer to older):")"
|
||||
|
||||
# Make the list user-friendly and display
|
||||
opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin"
|
||||
git rev-list $opt <"$todo".miss | warn_lines
|
||||
|
||||
warn "$(gettext "\
|
||||
To avoid this message, use \"drop\" to explicitly remove a commit.
|
||||
|
||||
Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
|
||||
The possible behaviours are: ignore, warn, error.")"
|
||||
warn
|
||||
fi
|
||||
;;
|
||||
ignore)
|
||||
;;
|
||||
*)
|
||||
warn "$(eval_gettext "Unrecognized setting \$check_level for option rebase.missingCommitsCheck. Ignoring.")"
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! check_bad_cmd_and_sha "$todo"
|
||||
then
|
||||
raise_error=t
|
||||
fi
|
||||
|
||||
if test $raise_error = t
|
||||
then
|
||||
# Checkout before the first commit of the
|
||||
# rebase: this way git rebase --continue
|
||||
# will work correctly as it expects HEAD to be
|
||||
# placed before the commit of the next action
|
||||
checkout_onto
|
||||
|
||||
warn "$(gettext "You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.")"
|
||||
die "$(gettext "Or you can abort the rebase with 'git rebase --abort'.")"
|
||||
fi
|
||||
}
|
||||
|
||||
# The whole contents of this file is run by dot-sourcing it from
|
||||
# inside a shell function. It used to be that "return"s we see
|
||||
# below were not inside any function, and expected to return
|
||||
@@ -1059,6 +782,10 @@ git_rebase__interactive () {
|
||||
|
||||
case "$action" in
|
||||
continue)
|
||||
if test ! -d "$rewritten"
|
||||
then
|
||||
exec git rebase--helper ${force_rebase:+--no-ff} --continue
|
||||
fi
|
||||
# do we have anything to commit?
|
||||
if git diff-index --cached --quiet HEAD --
|
||||
then
|
||||
@@ -1118,6 +845,10 @@ first and then run 'git rebase --continue' again.")"
|
||||
skip)
|
||||
git rerere clear
|
||||
|
||||
if test ! -d "$rewritten"
|
||||
then
|
||||
exec git rebase--helper ${force_rebase:+--no-ff} --continue
|
||||
fi
|
||||
do_rest
|
||||
return 0
|
||||
;;
|
||||
@@ -1192,26 +923,27 @@ else
|
||||
revisions=$onto...$orig_head
|
||||
shortrevisions=$shorthead
|
||||
fi
|
||||
format=$(git config --get rebase.instructionFormat)
|
||||
# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
|
||||
git rev-list $merges_option --format="%m%H ${format:-%s}" \
|
||||
--reverse --left-right --topo-order \
|
||||
$revisions ${restrict_revision+^$restrict_revision} | \
|
||||
sed -n "s/^>//p" |
|
||||
while read -r sha1 rest
|
||||
do
|
||||
if test t != "$preserve_merges"
|
||||
then
|
||||
git rebase--helper --make-script ${keep_empty:+--keep-empty} \
|
||||
$revisions ${restrict_revision+^$restrict_revision} >"$todo"
|
||||
else
|
||||
format=$(git config --get rebase.instructionFormat)
|
||||
# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse
|
||||
git rev-list $merges_option --format="%m%H ${format:-%s}" \
|
||||
--reverse --left-right --topo-order \
|
||||
$revisions ${restrict_revision+^$restrict_revision} | \
|
||||
sed -n "s/^>//p" |
|
||||
while read -r sha1 rest
|
||||
do
|
||||
|
||||
if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
|
||||
then
|
||||
comment_out="$comment_char "
|
||||
else
|
||||
comment_out=
|
||||
fi
|
||||
if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1
|
||||
then
|
||||
comment_out="$comment_char "
|
||||
else
|
||||
comment_out=
|
||||
fi
|
||||
|
||||
if test t != "$preserve_merges"
|
||||
then
|
||||
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
|
||||
else
|
||||
if test -z "$rebase_root"
|
||||
then
|
||||
preserve=t
|
||||
@@ -1230,8 +962,8 @@ do
|
||||
touch "$rewritten"/$sha1
|
||||
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
# Watch for commits that been dropped by --cherry-pick
|
||||
if test t = "$preserve_merges"
|
||||
@@ -1261,7 +993,7 @@ then
|
||||
fi
|
||||
|
||||
test -s "$todo" || echo noop >> "$todo"
|
||||
test -n "$autosquash" && rearrange_squash "$todo"
|
||||
test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
|
||||
test -n "$cmd" && add_exec_commands "$todo"
|
||||
|
||||
todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
|
||||
@@ -1297,13 +1029,24 @@ git_sequence_editor "$todo" ||
|
||||
has_action "$todo" ||
|
||||
return 2
|
||||
|
||||
check_todo_list
|
||||
git rebase--helper --check-todo-list || {
|
||||
ret=$?
|
||||
checkout_onto
|
||||
exit $ret
|
||||
}
|
||||
|
||||
expand_todo_ids
|
||||
|
||||
test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
|
||||
test -d "$rewritten" || test -n "$force_rebase" ||
|
||||
onto="$(git rebase--helper --skip-unnecessary-picks)" ||
|
||||
die "Could not skip unnecessary pick commands"
|
||||
|
||||
checkout_onto
|
||||
if test -z "$rebase_root" && test ! -d "$rewritten"
|
||||
then
|
||||
require_clean_work_tree "rebase"
|
||||
exec git rebase--helper ${force_rebase:+--no-ff} --continue
|
||||
fi
|
||||
do_rest
|
||||
|
||||
}
|
||||
|
||||
1
git.c
1
git.c
@@ -451,6 +451,7 @@ static struct cmd_struct commands[] = {
|
||||
{ "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE },
|
||||
{ "push", cmd_push, RUN_SETUP },
|
||||
{ "read-tree", cmd_read_tree, RUN_SETUP },
|
||||
{ "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE },
|
||||
{ "receive-pack", cmd_receive_pack },
|
||||
{ "reflog", cmd_reflog, RUN_SETUP },
|
||||
{ "remote", cmd_remote, RUN_SETUP },
|
||||
|
||||
9
merge.c
9
merge.c
@@ -57,7 +57,8 @@ int checkout_fast_forward(const unsigned char *head,
|
||||
|
||||
refresh_cache(REFRESH_QUIET);
|
||||
|
||||
hold_locked_index(lock_file, 1);
|
||||
if (hold_locked_index(lock_file, 0) < 0)
|
||||
return -1;
|
||||
|
||||
memset(&trees, 0, sizeof(trees));
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
@@ -90,7 +91,9 @@ int checkout_fast_forward(const unsigned char *head,
|
||||
}
|
||||
if (unpack_trees(nr_trees, t, &opts))
|
||||
return -1;
|
||||
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
|
||||
die(_("unable to write new index file"));
|
||||
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) {
|
||||
rollback_lock_file(lock_file);
|
||||
return error(_("unable to write new index file"));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -575,6 +575,29 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const
|
||||
cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0;
|
||||
cmd.dir = dir;
|
||||
cmd.env = env;
|
||||
|
||||
if (opt & RUN_HIDE_STDERR_ON_SUCCESS) {
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
int res;
|
||||
|
||||
cmd.err = -1;
|
||||
if (start_command(&cmd) < 0)
|
||||
return -1;
|
||||
|
||||
if (strbuf_read(&buf, cmd.err, 0) < 0) {
|
||||
close(cmd.err);
|
||||
finish_command(&cmd); /* throw away exit code */
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(cmd.err);
|
||||
res = finish_command(&cmd);
|
||||
if (res)
|
||||
fputs(buf.buf, stderr);
|
||||
strbuf_release(&buf);
|
||||
return res;
|
||||
}
|
||||
|
||||
return run_command(&cmd);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args);
|
||||
#define RUN_SILENT_EXEC_FAILURE 8
|
||||
#define RUN_USING_SHELL 16
|
||||
#define RUN_CLEAN_ON_EXIT 32
|
||||
#define RUN_HIDE_STDERR_ON_SUCCESS 64
|
||||
int run_command_v_opt(const char **argv, int opt);
|
||||
|
||||
/*
|
||||
|
||||
2271
sequencer.c
2271
sequencer.c
File diff suppressed because it is too large
Load Diff
35
sequencer.h
35
sequencer.h
@@ -1,28 +1,18 @@
|
||||
#ifndef SEQUENCER_H
|
||||
#define SEQUENCER_H
|
||||
|
||||
#define SEQ_DIR "sequencer"
|
||||
#define SEQ_HEAD_FILE "sequencer/head"
|
||||
#define SEQ_TODO_FILE "sequencer/todo"
|
||||
#define SEQ_OPTS_FILE "sequencer/opts"
|
||||
const char *git_path_seq_dir(void);
|
||||
|
||||
#define APPEND_SIGNOFF_DEDUP (1u << 0)
|
||||
|
||||
enum replay_action {
|
||||
REPLAY_REVERT,
|
||||
REPLAY_PICK
|
||||
};
|
||||
|
||||
enum replay_subcommand {
|
||||
REPLAY_NONE,
|
||||
REPLAY_REMOVE_STATE,
|
||||
REPLAY_CONTINUE,
|
||||
REPLAY_ROLLBACK
|
||||
REPLAY_PICK,
|
||||
REPLAY_INTERACTIVE_REBASE
|
||||
};
|
||||
|
||||
struct replay_opts {
|
||||
enum replay_action action;
|
||||
enum replay_subcommand subcommand;
|
||||
|
||||
/* Boolean options */
|
||||
int edit;
|
||||
@@ -34,21 +24,34 @@ struct replay_opts {
|
||||
int allow_empty;
|
||||
int allow_empty_message;
|
||||
int keep_redundant_commits;
|
||||
int verbose;
|
||||
|
||||
int mainline;
|
||||
|
||||
const char *gpg_sign;
|
||||
char *gpg_sign;
|
||||
|
||||
/* Merge strategy */
|
||||
const char *strategy;
|
||||
const char **xopts;
|
||||
char *strategy;
|
||||
char **xopts;
|
||||
size_t xopts_nr, xopts_alloc;
|
||||
|
||||
/* Only used by REPLAY_NONE */
|
||||
struct rev_info *revs;
|
||||
};
|
||||
#define REPLAY_OPTS_INIT { -1 }
|
||||
|
||||
int sequencer_pick_revisions(struct replay_opts *opts);
|
||||
int sequencer_continue(struct replay_opts *opts);
|
||||
int sequencer_rollback(struct replay_opts *opts);
|
||||
int sequencer_remove_state(struct replay_opts *opts);
|
||||
|
||||
int sequencer_make_script(int keep_empty, FILE *out,
|
||||
int argc, const char **argv);
|
||||
|
||||
int transform_todo_ids(int shorten_sha1s);
|
||||
int check_todo_list(void);
|
||||
int skip_unnecessary_picks(void);
|
||||
int rearrange_squash(void);
|
||||
|
||||
extern const char sign_off_header[];
|
||||
|
||||
|
||||
@@ -540,7 +540,7 @@ test_expect_success 'clean error after failed "exec"' '
|
||||
echo "edited again" > file7 &&
|
||||
git add file7 &&
|
||||
test_must_fail git rebase --continue 2>error &&
|
||||
test_i18ngrep "You have staged changes in your working tree." error
|
||||
test_i18ngrep "you have staged changes in your working tree" error
|
||||
'
|
||||
|
||||
test_expect_success 'rebase a detached HEAD' '
|
||||
@@ -1215,20 +1215,13 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
|
||||
test B = $(git cat-file commit HEAD^ | sed -ne \$p)
|
||||
'
|
||||
|
||||
cat >expect <<EOF
|
||||
Warning: the command isn't recognized in the following line:
|
||||
- badcmd $(git rev-list --oneline -1 master~1)
|
||||
|
||||
You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
|
||||
Or you can abort the rebase with 'git rebase --abort'.
|
||||
EOF
|
||||
|
||||
test_expect_success 'static check of bad command' '
|
||||
rebase_setup_and_clean bad-cmd &&
|
||||
set_fake_editor &&
|
||||
test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
|
||||
git rebase -i --root 2>actual &&
|
||||
test_i18ncmp expect actual &&
|
||||
test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" actual &&
|
||||
test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual &&
|
||||
FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo &&
|
||||
git rebase --continue &&
|
||||
test E = $(git cat-file commit HEAD | sed -ne \$p) &&
|
||||
@@ -1250,20 +1243,13 @@ test_expect_success 'tabs and spaces are accepted in the todolist' '
|
||||
test E = $(git cat-file commit HEAD | sed -ne \$p)
|
||||
'
|
||||
|
||||
cat >expect <<EOF
|
||||
Warning: the SHA-1 is missing or isn't a commit in the following line:
|
||||
- edit XXXXXXX False commit
|
||||
|
||||
You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
|
||||
Or you can abort the rebase with 'git rebase --abort'.
|
||||
EOF
|
||||
|
||||
test_expect_success 'static check of bad SHA-1' '
|
||||
rebase_setup_and_clean bad-sha &&
|
||||
set_fake_editor &&
|
||||
test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
|
||||
git rebase -i --root 2>actual &&
|
||||
test_i18ncmp expect actual &&
|
||||
test_i18ngrep "edit XXXXXXX False commit" actual &&
|
||||
test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual &&
|
||||
FAKE_LINES="1 2 4 5 6" git rebase --edit-todo &&
|
||||
git rebase --continue &&
|
||||
test E = $(git cat-file commit HEAD | sed -ne \$p)
|
||||
|
||||
@@ -278,7 +278,7 @@ set_backup_editor () {
|
||||
test_set_editor "$PWD/backup-editor.sh"
|
||||
}
|
||||
|
||||
test_expect_failure 'autosquash with multiple empty patches' '
|
||||
test_expect_success 'autosquash with multiple empty patches' '
|
||||
test_tick &&
|
||||
git commit --allow-empty -m "empty" &&
|
||||
test_tick &&
|
||||
@@ -304,4 +304,18 @@ test_expect_success 'extra spaces after fixup!' '
|
||||
test $base = $parent
|
||||
'
|
||||
|
||||
test_expect_success 'wrapped original subject' '
|
||||
if test -d .git/rebase-merge; then git rebase --abort; fi &&
|
||||
base=$(git rev-parse HEAD) &&
|
||||
echo "wrapped subject" >wrapped &&
|
||||
git add wrapped &&
|
||||
test_tick &&
|
||||
git commit --allow-empty -m "$(printf "To\nfixup")" &&
|
||||
test_tick &&
|
||||
git commit --allow-empty -m "fixup! To fixup" &&
|
||||
git rebase -i --autosquash --keep-empty HEAD~2 &&
|
||||
parent=$(git rev-parse HEAD^) &&
|
||||
test $base = $parent
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
@@ -96,7 +96,7 @@ test_expect_success 'revert forbidden on dirty working tree' '
|
||||
echo content >extra_file &&
|
||||
git add extra_file &&
|
||||
test_must_fail git revert HEAD 2>errors &&
|
||||
test_i18ngrep "Your local changes would be overwritten by " errors
|
||||
test_i18ngrep "your local changes would be overwritten by " errors
|
||||
|
||||
'
|
||||
|
||||
|
||||
@@ -232,4 +232,25 @@ test_expect_success 'status --branch with detached HEAD' '
|
||||
test_i18ncmp expected actual
|
||||
'
|
||||
|
||||
## Duplicate the above test and verify --porcelain=v1 arg parsing.
|
||||
test_expect_success 'status --porcelain=v1 --branch with detached HEAD' '
|
||||
git reset --hard &&
|
||||
git checkout master^0 &&
|
||||
git status --branch --porcelain=v1 >actual &&
|
||||
cat >expected <<-EOF &&
|
||||
## HEAD (no branch)
|
||||
?? .gitconfig
|
||||
?? actual
|
||||
?? expect
|
||||
?? expected
|
||||
?? mdconflict/
|
||||
EOF
|
||||
test_i18ncmp expected actual
|
||||
'
|
||||
|
||||
## Verify parser error on invalid --porcelain argument.
|
||||
test_expect_success 'status --porcelain=bogus' '
|
||||
test_must_fail git status --porcelain=bogus
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
593
t/t7064-wtstatus-pv2.sh
Executable file
593
t/t7064-wtstatus-pv2.sh
Executable file
@@ -0,0 +1,593 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='git status --porcelain=v2
|
||||
|
||||
This test exercises porcelain V2 output for git status.'
|
||||
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
|
||||
test_expect_success setup '
|
||||
test_tick &&
|
||||
git config core.autocrlf false &&
|
||||
echo x >file_x &&
|
||||
echo y >file_y &&
|
||||
echo z >file_z &&
|
||||
mkdir dir1 &&
|
||||
echo a >dir1/file_a &&
|
||||
echo b >dir1/file_b
|
||||
'
|
||||
|
||||
test_expect_success 'before initial commit, nothing added, only untracked' '
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid (initial)
|
||||
# branch.head master
|
||||
? actual
|
||||
? dir1/
|
||||
? expect
|
||||
? file_x
|
||||
? file_y
|
||||
? file_z
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=normal >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'before initial commit, things added' '
|
||||
git add file_x file_y file_z dir1 &&
|
||||
OID_A=$(git hash-object -t blob -- dir1/file_a) &&
|
||||
OID_B=$(git hash-object -t blob -- dir1/file_b) &&
|
||||
OID_X=$(git hash-object -t blob -- file_x) &&
|
||||
OID_Y=$(git hash-object -t blob -- file_y) &&
|
||||
OID_Z=$(git hash-object -t blob -- file_z) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid (initial)
|
||||
# branch.head master
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_X file_x
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z
|
||||
? actual
|
||||
? expect
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'before initial commit, things added (-z)' '
|
||||
lf_to_nul >expect <<-EOF &&
|
||||
# branch.oid (initial)
|
||||
# branch.head master
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_X file_x
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y
|
||||
1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z
|
||||
? actual
|
||||
? expect
|
||||
EOF
|
||||
|
||||
git status -z --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'make first commit, comfirm HEAD oid and branch' '
|
||||
git commit -m initial &&
|
||||
H0=$(git rev-parse HEAD) &&
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $H0
|
||||
# branch.head master
|
||||
? actual
|
||||
? expect
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'after first commit, create unstaged changes' '
|
||||
echo x >>file_x &&
|
||||
OID_X1=$(git hash-object -t blob -- file_x) &&
|
||||
rm file_z &&
|
||||
H0=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $H0
|
||||
# branch.head master
|
||||
1 .M N... 100644 100644 100644 $OID_X $OID_X file_x
|
||||
1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z
|
||||
? actual
|
||||
? expect
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'after first commit but omit untracked files and branch' '
|
||||
cat >expect <<-EOF &&
|
||||
1 .M N... 100644 100644 100644 $OID_X $OID_X file_x
|
||||
1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --untracked-files=no >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'after first commit, stage existing changes' '
|
||||
git add file_x &&
|
||||
git rm file_z &&
|
||||
H0=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $H0
|
||||
# branch.head master
|
||||
1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
|
||||
1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z
|
||||
? actual
|
||||
? expect
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rename causes 2 path lines' '
|
||||
git mv file_y renamed_y &&
|
||||
H0=$(git rev-parse HEAD) &&
|
||||
|
||||
q_to_tab >expect <<-EOF &&
|
||||
# branch.oid $H0
|
||||
# branch.head master
|
||||
1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
|
||||
1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z
|
||||
2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y
|
||||
? actual
|
||||
? expect
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'rename causes 2 path lines (-z)' '
|
||||
H0=$(git rev-parse HEAD) &&
|
||||
|
||||
## Lines use NUL path separator and line terminator, so double transform here.
|
||||
q_to_nul <<-EOF | lf_to_nul >expect &&
|
||||
# branch.oid $H0
|
||||
# branch.head master
|
||||
1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
|
||||
1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z
|
||||
2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y
|
||||
? actual
|
||||
? expect
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all -z >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'make second commit, confirm clean and new HEAD oid' '
|
||||
git commit -m second &&
|
||||
H1=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $H1
|
||||
# branch.head master
|
||||
? actual
|
||||
? expect
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'confirm ignored files are not printed' '
|
||||
test_when_finished "rm -f x.ign .gitignore" &&
|
||||
echo x.ign >.gitignore &&
|
||||
echo "ignore me" >x.ign &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
? .gitignore
|
||||
? actual
|
||||
? expect
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'ignored files are printed with --ignored' '
|
||||
test_when_finished "rm -f x.ign .gitignore" &&
|
||||
echo x.ign >.gitignore &&
|
||||
echo "ignore me" >x.ign &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
? .gitignore
|
||||
? actual
|
||||
? expect
|
||||
! x.ign
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --ignored --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'create and commit permanent ignore file' '
|
||||
cat >.gitignore <<-EOF &&
|
||||
actual*
|
||||
expect*
|
||||
EOF
|
||||
|
||||
git add .gitignore &&
|
||||
git commit -m ignore_trash &&
|
||||
H1=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $H1
|
||||
# branch.head master
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'verify --intent-to-add output' '
|
||||
test_when_finished "git rm -f intent1.add intent2.add" &&
|
||||
touch intent1.add &&
|
||||
echo test >intent2.add &&
|
||||
|
||||
git add --intent-to-add intent1.add intent2.add &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent1.add
|
||||
1 AM N... 000000 100644 100644 $_z40 $EMPTY_BLOB intent2.add
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'verify AA (add-add) conflict' '
|
||||
test_when_finished "git reset --hard" &&
|
||||
|
||||
git branch AA_A master &&
|
||||
git checkout AA_A &&
|
||||
echo "Branch AA_A" >conflict.txt &&
|
||||
OID_AA_A=$(git hash-object -t blob -- conflict.txt) &&
|
||||
git add conflict.txt &&
|
||||
git commit -m "branch aa_a" &&
|
||||
|
||||
git branch AA_B master &&
|
||||
git checkout AA_B &&
|
||||
echo "Branch AA_B" >conflict.txt &&
|
||||
OID_AA_B=$(git hash-object -t blob -- conflict.txt) &&
|
||||
git add conflict.txt &&
|
||||
git commit -m "branch aa_b" &&
|
||||
|
||||
git branch AA_M AA_B &&
|
||||
git checkout AA_M &&
|
||||
test_must_fail git merge AA_A &&
|
||||
|
||||
HM=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HM
|
||||
# branch.head AA_M
|
||||
u AA N... 000000 100644 100644 100644 $_z40 $OID_AA_B $OID_AA_A conflict.txt
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'verify UU (edit-edit) conflict' '
|
||||
test_when_finished "git reset --hard" &&
|
||||
|
||||
git branch UU_ANC master &&
|
||||
git checkout UU_ANC &&
|
||||
echo "Ancestor" >conflict.txt &&
|
||||
OID_UU_ANC=$(git hash-object -t blob -- conflict.txt) &&
|
||||
git add conflict.txt &&
|
||||
git commit -m "UU_ANC" &&
|
||||
|
||||
git branch UU_A UU_ANC &&
|
||||
git checkout UU_A &&
|
||||
echo "Branch UU_A" >conflict.txt &&
|
||||
OID_UU_A=$(git hash-object -t blob -- conflict.txt) &&
|
||||
git add conflict.txt &&
|
||||
git commit -m "branch uu_a" &&
|
||||
|
||||
git branch UU_B UU_ANC &&
|
||||
git checkout UU_B &&
|
||||
echo "Branch UU_B" >conflict.txt &&
|
||||
OID_UU_B=$(git hash-object -t blob -- conflict.txt) &&
|
||||
git add conflict.txt &&
|
||||
git commit -m "branch uu_b" &&
|
||||
|
||||
git branch UU_M UU_B &&
|
||||
git checkout UU_M &&
|
||||
test_must_fail git merge UU_A &&
|
||||
|
||||
HM=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HM
|
||||
# branch.head UU_M
|
||||
u UU N... 100644 100644 100644 100644 $OID_UU_ANC $OID_UU_B $OID_UU_A conflict.txt
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'verify upstream fields in branch header' '
|
||||
git checkout master &&
|
||||
test_when_finished "rm -rf sub_repo" &&
|
||||
git clone . sub_repo &&
|
||||
(
|
||||
## Confirm local master tracks remote master.
|
||||
cd sub_repo &&
|
||||
HUF=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HUF
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +0 -0
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
## Test ahead/behind.
|
||||
echo xyz >file_xyz &&
|
||||
git add file_xyz &&
|
||||
git commit -m xyz &&
|
||||
|
||||
HUF=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HUF
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +1 -0
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
## Repeat the above but without --branch.
|
||||
cat >expect <<-EOF &&
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --untracked-files=all >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
## Test upstream-gone case. Fake this by pointing origin/master at
|
||||
## a non-existing commit.
|
||||
OLD=$(git rev-parse origin/master) &&
|
||||
NEW=$_z40 &&
|
||||
mv .git/packed-refs .git/old-packed-refs &&
|
||||
sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs &&
|
||||
|
||||
HUF=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HUF
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'create and add submodule, submodule appears clean (A. S...)' '
|
||||
git checkout master &&
|
||||
git clone . sub_repo &&
|
||||
git clone . super_repo &&
|
||||
( cd super_repo &&
|
||||
git submodule add ../sub_repo sub1 &&
|
||||
|
||||
## Confirm stage/add of clean submodule.
|
||||
HMOD=$(git hash-object -t blob -- .gitmodules) &&
|
||||
HSUP=$(git rev-parse HEAD) &&
|
||||
HSUB=$HSUP &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HSUP
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +0 -0
|
||||
1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
|
||||
1 A. S... 000000 160000 160000 $_z40 $HSUB sub1
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'untracked changes in added submodule (AM S..U)' '
|
||||
( cd super_repo &&
|
||||
## create untracked file in the submodule.
|
||||
( cd sub1 &&
|
||||
echo "xxxx" >file_in_sub
|
||||
) &&
|
||||
|
||||
HMOD=$(git hash-object -t blob -- .gitmodules) &&
|
||||
HSUP=$(git rev-parse HEAD) &&
|
||||
HSUB=$HSUP &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HSUP
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +0 -0
|
||||
1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
|
||||
1 AM S..U 000000 160000 160000 $_z40 $HSUB sub1
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'staged changes in added submodule (AM S.M.)' '
|
||||
( cd super_repo &&
|
||||
## stage the changes in the submodule.
|
||||
( cd sub1 &&
|
||||
git add file_in_sub
|
||||
) &&
|
||||
|
||||
HMOD=$(git hash-object -t blob -- .gitmodules) &&
|
||||
HSUP=$(git rev-parse HEAD) &&
|
||||
HSUB=$HSUP &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HSUP
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +0 -0
|
||||
1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
|
||||
1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'staged and unstaged changes in added (AM S.M.)' '
|
||||
( cd super_repo &&
|
||||
( cd sub1 &&
|
||||
## make additional unstaged changes (on the same file) in the submodule.
|
||||
## This does not cause us to get S.MU (because the submodule does not report
|
||||
## a "?" line for the unstaged changes).
|
||||
echo "more changes" >>file_in_sub
|
||||
) &&
|
||||
|
||||
HMOD=$(git hash-object -t blob -- .gitmodules) &&
|
||||
HSUP=$(git rev-parse HEAD) &&
|
||||
HSUB=$HSUP &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HSUP
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +0 -0
|
||||
1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
|
||||
1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'staged and untracked changes in added submodule (AM S.MU)' '
|
||||
( cd super_repo &&
|
||||
( cd sub1 &&
|
||||
## stage new changes in tracked file.
|
||||
git add file_in_sub &&
|
||||
## create new untracked file.
|
||||
echo "yyyy" >>another_file_in_sub
|
||||
) &&
|
||||
|
||||
HMOD=$(git hash-object -t blob -- .gitmodules) &&
|
||||
HSUP=$(git rev-parse HEAD) &&
|
||||
HSUB=$HSUP &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HSUP
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +0 -0
|
||||
1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
|
||||
1 AM S.MU 000000 160000 160000 $_z40 $HSUB sub1
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'commit within the submodule appears as new commit in super (AM SC..)' '
|
||||
( cd super_repo &&
|
||||
( cd sub1 &&
|
||||
## Make a new commit in the submodule.
|
||||
git add file_in_sub &&
|
||||
rm -f another_file_in_sub &&
|
||||
git commit -m "new commit"
|
||||
) &&
|
||||
|
||||
HMOD=$(git hash-object -t blob -- .gitmodules) &&
|
||||
HSUP=$(git rev-parse HEAD) &&
|
||||
HSUB=$HSUP &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HSUP
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +0 -0
|
||||
1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules
|
||||
1 AM SC.. 000000 160000 160000 $_z40 $HSUB sub1
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'stage submodule in super and commit' '
|
||||
( cd super_repo &&
|
||||
## Stage the new submodule commit in the super.
|
||||
git add sub1 &&
|
||||
## Commit the super so that the sub no longer appears as added.
|
||||
git commit -m "super commit" &&
|
||||
|
||||
HSUP=$(git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HSUP
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +1 -0
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'make unstaged changes in existing submodule (.M S.M.)' '
|
||||
( cd super_repo &&
|
||||
( cd sub1 &&
|
||||
echo "zzzz" >>file_in_sub
|
||||
) &&
|
||||
|
||||
HSUP=$(git rev-parse HEAD) &&
|
||||
HSUB=$(cd sub1 && git rev-parse HEAD) &&
|
||||
|
||||
cat >expect <<-EOF &&
|
||||
# branch.oid $HSUP
|
||||
# branch.head master
|
||||
# branch.upstream origin/master
|
||||
# branch.ab +1 -0
|
||||
1 .M S.M. 160000 160000 160000 $HSUB $HSUB sub1
|
||||
EOF
|
||||
|
||||
git status --porcelain=v2 --branch --untracked-files=all >actual &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
||||
@@ -81,6 +81,10 @@ test_decode_color () {
|
||||
'
|
||||
}
|
||||
|
||||
lf_to_nul () {
|
||||
perl -pe 'y/\012/\000/'
|
||||
}
|
||||
|
||||
nul_to_q () {
|
||||
perl -pe 'y/\000/Q/'
|
||||
}
|
||||
|
||||
648
wt-status.c
648
wt-status.c
@@ -16,6 +16,7 @@
|
||||
#include "strbuf.h"
|
||||
#include "utf8.h"
|
||||
#include "worktree.h"
|
||||
#include "lockfile.h"
|
||||
|
||||
static const char cut_line[] =
|
||||
"------------------------ >8 ------------------------\n";
|
||||
@@ -139,7 +140,7 @@ void wt_status_prepare(struct wt_status *s)
|
||||
s->display_comment_prefix = 0;
|
||||
}
|
||||
|
||||
static void wt_status_print_unmerged_header(struct wt_status *s)
|
||||
static void wt_longstatus_print_unmerged_header(struct wt_status *s)
|
||||
{
|
||||
int i;
|
||||
int del_mod_conflict = 0;
|
||||
@@ -191,7 +192,7 @@ static void wt_status_print_unmerged_header(struct wt_status *s)
|
||||
status_printf_ln(s, c, "%s", "");
|
||||
}
|
||||
|
||||
static void wt_status_print_cached_header(struct wt_status *s)
|
||||
static void wt_longstatus_print_cached_header(struct wt_status *s)
|
||||
{
|
||||
const char *c = color(WT_STATUS_HEADER, s);
|
||||
|
||||
@@ -207,9 +208,9 @@ static void wt_status_print_cached_header(struct wt_status *s)
|
||||
status_printf_ln(s, c, "%s", "");
|
||||
}
|
||||
|
||||
static void wt_status_print_dirty_header(struct wt_status *s,
|
||||
int has_deleted,
|
||||
int has_dirty_submodules)
|
||||
static void wt_longstatus_print_dirty_header(struct wt_status *s,
|
||||
int has_deleted,
|
||||
int has_dirty_submodules)
|
||||
{
|
||||
const char *c = color(WT_STATUS_HEADER, s);
|
||||
|
||||
@@ -226,9 +227,9 @@ static void wt_status_print_dirty_header(struct wt_status *s,
|
||||
status_printf_ln(s, c, "%s", "");
|
||||
}
|
||||
|
||||
static void wt_status_print_other_header(struct wt_status *s,
|
||||
const char *what,
|
||||
const char *how)
|
||||
static void wt_longstatus_print_other_header(struct wt_status *s,
|
||||
const char *what,
|
||||
const char *how)
|
||||
{
|
||||
const char *c = color(WT_STATUS_HEADER, s);
|
||||
status_printf_ln(s, c, "%s:", what);
|
||||
@@ -238,7 +239,7 @@ static void wt_status_print_other_header(struct wt_status *s,
|
||||
status_printf_ln(s, c, "%s", "");
|
||||
}
|
||||
|
||||
static void wt_status_print_trailer(struct wt_status *s)
|
||||
static void wt_longstatus_print_trailer(struct wt_status *s)
|
||||
{
|
||||
status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
|
||||
}
|
||||
@@ -304,8 +305,8 @@ static int maxwidth(const char *(*label)(int), int minval, int maxval)
|
||||
return result;
|
||||
}
|
||||
|
||||
static void wt_status_print_unmerged_data(struct wt_status *s,
|
||||
struct string_list_item *it)
|
||||
static void wt_longstatus_print_unmerged_data(struct wt_status *s,
|
||||
struct string_list_item *it)
|
||||
{
|
||||
const char *c = color(WT_STATUS_UNMERGED, s);
|
||||
struct wt_status_change_data *d = it->util;
|
||||
@@ -331,9 +332,9 @@ static void wt_status_print_unmerged_data(struct wt_status *s,
|
||||
strbuf_release(&onebuf);
|
||||
}
|
||||
|
||||
static void wt_status_print_change_data(struct wt_status *s,
|
||||
int change_type,
|
||||
struct string_list_item *it)
|
||||
static void wt_longstatus_print_change_data(struct wt_status *s,
|
||||
int change_type,
|
||||
struct string_list_item *it)
|
||||
{
|
||||
struct wt_status_change_data *d = it->util;
|
||||
const char *c = color(change_type, s);
|
||||
@@ -378,7 +379,7 @@ static void wt_status_print_change_data(struct wt_status *s,
|
||||
status = d->worktree_status;
|
||||
break;
|
||||
default:
|
||||
die("BUG: unhandled change_type %d in wt_status_print_change_data",
|
||||
die("BUG: unhandled change_type %d in wt_longstatus_print_change_data",
|
||||
change_type);
|
||||
}
|
||||
|
||||
@@ -434,6 +435,31 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q,
|
||||
if (S_ISGITLINK(p->two->mode))
|
||||
d->new_submodule_commits = !!oidcmp(&p->one->oid,
|
||||
&p->two->oid);
|
||||
|
||||
switch (p->status) {
|
||||
case DIFF_STATUS_ADDED:
|
||||
die("BUG: worktree status add???");
|
||||
break;
|
||||
|
||||
case DIFF_STATUS_DELETED:
|
||||
d->mode_index = p->one->mode;
|
||||
oidcpy(&d->oid_index, &p->one->oid);
|
||||
/* mode_worktree is zero for a delete. */
|
||||
break;
|
||||
|
||||
case DIFF_STATUS_MODIFIED:
|
||||
case DIFF_STATUS_TYPE_CHANGED:
|
||||
case DIFF_STATUS_UNMERGED:
|
||||
d->mode_index = p->one->mode;
|
||||
d->mode_worktree = p->two->mode;
|
||||
oidcpy(&d->oid_index, &p->one->oid);
|
||||
break;
|
||||
|
||||
case DIFF_STATUS_UNKNOWN:
|
||||
die("BUG: worktree status unknown???");
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,12 +505,36 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q,
|
||||
if (!d->index_status)
|
||||
d->index_status = p->status;
|
||||
switch (p->status) {
|
||||
case DIFF_STATUS_ADDED:
|
||||
/* Leave {mode,oid}_head zero for an add. */
|
||||
d->mode_index = p->two->mode;
|
||||
oidcpy(&d->oid_index, &p->two->oid);
|
||||
break;
|
||||
case DIFF_STATUS_DELETED:
|
||||
d->mode_head = p->one->mode;
|
||||
oidcpy(&d->oid_head, &p->one->oid);
|
||||
/* Leave {mode,oid}_index zero for a delete. */
|
||||
break;
|
||||
|
||||
case DIFF_STATUS_COPIED:
|
||||
case DIFF_STATUS_RENAMED:
|
||||
d->head_path = xstrdup(p->one->path);
|
||||
d->score = p->score * 100 / MAX_SCORE;
|
||||
/* fallthru */
|
||||
case DIFF_STATUS_MODIFIED:
|
||||
case DIFF_STATUS_TYPE_CHANGED:
|
||||
d->mode_head = p->one->mode;
|
||||
d->mode_index = p->two->mode;
|
||||
oidcpy(&d->oid_head, &p->one->oid);
|
||||
oidcpy(&d->oid_index, &p->two->oid);
|
||||
break;
|
||||
case DIFF_STATUS_UNMERGED:
|
||||
d->stagemask = unmerged_mask(p->two->path);
|
||||
/*
|
||||
* Don't bother setting {mode,oid}_{head,index} since the print
|
||||
* code will output the stage values directly and not use the
|
||||
* values in these fields.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -565,9 +615,17 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
|
||||
if (ce_stage(ce)) {
|
||||
d->index_status = DIFF_STATUS_UNMERGED;
|
||||
d->stagemask |= (1 << (ce_stage(ce) - 1));
|
||||
}
|
||||
else
|
||||
/*
|
||||
* Don't bother setting {mode,oid}_{head,index} since the print
|
||||
* code will output the stage values directly and not use the
|
||||
* values in these fields.
|
||||
*/
|
||||
} else {
|
||||
d->index_status = DIFF_STATUS_ADDED;
|
||||
/* Leave {mode,oid}_head zero for adds. */
|
||||
d->mode_index = ce->ce_mode;
|
||||
hashcpy(d->oid_index.hash, ce->sha1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +685,7 @@ void wt_status_collect(struct wt_status *s)
|
||||
wt_status_collect_untracked(s);
|
||||
}
|
||||
|
||||
static void wt_status_print_unmerged(struct wt_status *s)
|
||||
static void wt_longstatus_print_unmerged(struct wt_status *s)
|
||||
{
|
||||
int shown_header = 0;
|
||||
int i;
|
||||
@@ -640,17 +698,17 @@ static void wt_status_print_unmerged(struct wt_status *s)
|
||||
if (!d->stagemask)
|
||||
continue;
|
||||
if (!shown_header) {
|
||||
wt_status_print_unmerged_header(s);
|
||||
wt_longstatus_print_unmerged_header(s);
|
||||
shown_header = 1;
|
||||
}
|
||||
wt_status_print_unmerged_data(s, it);
|
||||
wt_longstatus_print_unmerged_data(s, it);
|
||||
}
|
||||
if (shown_header)
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
|
||||
}
|
||||
|
||||
static void wt_status_print_updated(struct wt_status *s)
|
||||
static void wt_longstatus_print_updated(struct wt_status *s)
|
||||
{
|
||||
int shown_header = 0;
|
||||
int i;
|
||||
@@ -664,14 +722,14 @@ static void wt_status_print_updated(struct wt_status *s)
|
||||
d->index_status == DIFF_STATUS_UNMERGED)
|
||||
continue;
|
||||
if (!shown_header) {
|
||||
wt_status_print_cached_header(s);
|
||||
wt_longstatus_print_cached_header(s);
|
||||
s->commitable = 1;
|
||||
shown_header = 1;
|
||||
}
|
||||
wt_status_print_change_data(s, WT_STATUS_UPDATED, it);
|
||||
wt_longstatus_print_change_data(s, WT_STATUS_UPDATED, it);
|
||||
}
|
||||
if (shown_header)
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -703,7 +761,7 @@ static int wt_status_check_worktree_changes(struct wt_status *s,
|
||||
return changes;
|
||||
}
|
||||
|
||||
static void wt_status_print_changed(struct wt_status *s)
|
||||
static void wt_longstatus_print_changed(struct wt_status *s)
|
||||
{
|
||||
int i, dirty_submodules;
|
||||
int worktree_changes = wt_status_check_worktree_changes(s, &dirty_submodules);
|
||||
@@ -711,7 +769,7 @@ static void wt_status_print_changed(struct wt_status *s)
|
||||
if (!worktree_changes)
|
||||
return;
|
||||
|
||||
wt_status_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
|
||||
wt_longstatus_print_dirty_header(s, worktree_changes < 0, dirty_submodules);
|
||||
|
||||
for (i = 0; i < s->change.nr; i++) {
|
||||
struct wt_status_change_data *d;
|
||||
@@ -721,12 +779,12 @@ static void wt_status_print_changed(struct wt_status *s)
|
||||
if (!d->worktree_status ||
|
||||
d->worktree_status == DIFF_STATUS_UNMERGED)
|
||||
continue;
|
||||
wt_status_print_change_data(s, WT_STATUS_CHANGED, it);
|
||||
wt_longstatus_print_change_data(s, WT_STATUS_CHANGED, it);
|
||||
}
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
}
|
||||
|
||||
static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitted)
|
||||
static void wt_longstatus_print_submodule_summary(struct wt_status *s, int uncommitted)
|
||||
{
|
||||
struct child_process sm_summary = CHILD_PROCESS_INIT;
|
||||
struct strbuf cmd_stdout = STRBUF_INIT;
|
||||
@@ -772,10 +830,10 @@ static void wt_status_print_submodule_summary(struct wt_status *s, int uncommitt
|
||||
strbuf_release(&summary);
|
||||
}
|
||||
|
||||
static void wt_status_print_other(struct wt_status *s,
|
||||
struct string_list *l,
|
||||
const char *what,
|
||||
const char *how)
|
||||
static void wt_longstatus_print_other(struct wt_status *s,
|
||||
struct string_list *l,
|
||||
const char *what,
|
||||
const char *how)
|
||||
{
|
||||
int i;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
@@ -785,7 +843,7 @@ static void wt_status_print_other(struct wt_status *s,
|
||||
if (!l->nr)
|
||||
return;
|
||||
|
||||
wt_status_print_other_header(s, what, how);
|
||||
wt_longstatus_print_other_header(s, what, how);
|
||||
|
||||
for (i = 0; i < l->nr; i++) {
|
||||
struct string_list_item *it;
|
||||
@@ -845,7 +903,7 @@ void wt_status_add_cut_line(FILE *fp)
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
static void wt_status_print_verbose(struct wt_status *s)
|
||||
static void wt_longstatus_print_verbose(struct wt_status *s)
|
||||
{
|
||||
struct rev_info rev;
|
||||
struct setup_revision_opt opt;
|
||||
@@ -878,7 +936,7 @@ static void wt_status_print_verbose(struct wt_status *s)
|
||||
if (s->verbose > 1 && s->commitable) {
|
||||
/* print_updated() printed a header, so do we */
|
||||
if (s->fp != stdout)
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
status_printf_ln(s, c, _("Changes to be committed:"));
|
||||
rev.diffopt.a_prefix = "c/";
|
||||
rev.diffopt.b_prefix = "i/";
|
||||
@@ -896,7 +954,7 @@ static void wt_status_print_verbose(struct wt_status *s)
|
||||
}
|
||||
}
|
||||
|
||||
static void wt_status_print_tracking(struct wt_status *s)
|
||||
static void wt_longstatus_print_tracking(struct wt_status *s)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
const char *cp, *ep, *branch_name;
|
||||
@@ -962,7 +1020,7 @@ static void show_merge_in_progress(struct wt_status *s,
|
||||
status_printf_ln(s, color,
|
||||
_(" (use \"git commit\" to conclude merge)"));
|
||||
}
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
}
|
||||
|
||||
static void show_am_in_progress(struct wt_status *s,
|
||||
@@ -983,7 +1041,7 @@ static void show_am_in_progress(struct wt_status *s,
|
||||
status_printf_ln(s, color,
|
||||
_(" (use \"git am --abort\" to restore the original branch)"));
|
||||
}
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
}
|
||||
|
||||
static char *read_line_from_git_path(const char *filename)
|
||||
@@ -1207,7 +1265,7 @@ static void show_rebase_in_progress(struct wt_status *s,
|
||||
_(" (use \"git rebase --continue\" once you are satisfied with your changes)"));
|
||||
}
|
||||
}
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
}
|
||||
|
||||
static void show_cherry_pick_in_progress(struct wt_status *s,
|
||||
@@ -1226,7 +1284,7 @@ static void show_cherry_pick_in_progress(struct wt_status *s,
|
||||
status_printf_ln(s, color,
|
||||
_(" (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"));
|
||||
}
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
}
|
||||
|
||||
static void show_revert_in_progress(struct wt_status *s,
|
||||
@@ -1245,7 +1303,7 @@ static void show_revert_in_progress(struct wt_status *s,
|
||||
status_printf_ln(s, color,
|
||||
_(" (use \"git revert --abort\" to cancel the revert operation)"));
|
||||
}
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
}
|
||||
|
||||
static void show_bisect_in_progress(struct wt_status *s,
|
||||
@@ -1262,7 +1320,7 @@ static void show_bisect_in_progress(struct wt_status *s,
|
||||
if (s->hints)
|
||||
status_printf_ln(s, color,
|
||||
_(" (use \"git bisect reset\" to get back to the original branch)"));
|
||||
wt_status_print_trailer(s);
|
||||
wt_longstatus_print_trailer(s);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1432,8 +1490,8 @@ void wt_status_get_state(struct wt_status_state *state,
|
||||
wt_status_get_detached_from(state);
|
||||
}
|
||||
|
||||
static void wt_status_print_state(struct wt_status *s,
|
||||
struct wt_status_state *state)
|
||||
static void wt_longstatus_print_state(struct wt_status *s,
|
||||
struct wt_status_state *state)
|
||||
{
|
||||
const char *state_color = color(WT_STATUS_HEADER, s);
|
||||
if (state->merge_in_progress)
|
||||
@@ -1450,7 +1508,7 @@ static void wt_status_print_state(struct wt_status *s,
|
||||
show_bisect_in_progress(s, state, state_color);
|
||||
}
|
||||
|
||||
void wt_status_print(struct wt_status *s)
|
||||
static void wt_longstatus_print(struct wt_status *s)
|
||||
{
|
||||
const char *branch_color = color(WT_STATUS_ONBRANCH, s);
|
||||
const char *branch_status_color = color(WT_STATUS_HEADER, s);
|
||||
@@ -1487,10 +1545,10 @@ void wt_status_print(struct wt_status *s)
|
||||
status_printf_more(s, branch_status_color, "%s", on_what);
|
||||
status_printf_more(s, branch_color, "%s\n", branch_name);
|
||||
if (!s->is_initial)
|
||||
wt_status_print_tracking(s);
|
||||
wt_longstatus_print_tracking(s);
|
||||
}
|
||||
|
||||
wt_status_print_state(s, &state);
|
||||
wt_longstatus_print_state(s, &state);
|
||||
free(state.branch);
|
||||
free(state.onto);
|
||||
free(state.detached_from);
|
||||
@@ -1501,19 +1559,19 @@ void wt_status_print(struct wt_status *s)
|
||||
status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", "");
|
||||
}
|
||||
|
||||
wt_status_print_updated(s);
|
||||
wt_status_print_unmerged(s);
|
||||
wt_status_print_changed(s);
|
||||
wt_longstatus_print_updated(s);
|
||||
wt_longstatus_print_unmerged(s);
|
||||
wt_longstatus_print_changed(s);
|
||||
if (s->submodule_summary &&
|
||||
(!s->ignore_submodule_arg ||
|
||||
strcmp(s->ignore_submodule_arg, "all"))) {
|
||||
wt_status_print_submodule_summary(s, 0); /* staged */
|
||||
wt_status_print_submodule_summary(s, 1); /* unstaged */
|
||||
wt_longstatus_print_submodule_summary(s, 0); /* staged */
|
||||
wt_longstatus_print_submodule_summary(s, 1); /* unstaged */
|
||||
}
|
||||
if (s->show_untracked_files) {
|
||||
wt_status_print_other(s, &s->untracked, _("Untracked files"), "add");
|
||||
wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
|
||||
if (s->show_ignored_files)
|
||||
wt_status_print_other(s, &s->ignored, _("Ignored files"), "add -f");
|
||||
wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
|
||||
if (advice_status_u_option && 2000 < s->untracked_in_ms) {
|
||||
status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
|
||||
status_printf_ln(s, GIT_COLOR_NORMAL,
|
||||
@@ -1528,7 +1586,7 @@ void wt_status_print(struct wt_status *s)
|
||||
? _(" (use -u option to show untracked files)") : "");
|
||||
|
||||
if (s->verbose)
|
||||
wt_status_print_verbose(s);
|
||||
wt_longstatus_print_verbose(s);
|
||||
if (!s->commitable) {
|
||||
if (s->amend)
|
||||
status_printf_ln(s, GIT_COLOR_NORMAL, _("No changes"));
|
||||
@@ -1717,7 +1775,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
|
||||
fputc(s->null_termination ? '\0' : '\n', s->fp);
|
||||
}
|
||||
|
||||
void wt_shortstatus_print(struct wt_status *s)
|
||||
static void wt_shortstatus_print(struct wt_status *s)
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -1749,7 +1807,7 @@ void wt_shortstatus_print(struct wt_status *s)
|
||||
}
|
||||
}
|
||||
|
||||
void wt_porcelain_print(struct wt_status *s)
|
||||
static void wt_porcelain_print(struct wt_status *s)
|
||||
{
|
||||
s->use_color = 0;
|
||||
s->relative_paths = 0;
|
||||
@@ -1757,3 +1815,475 @@ void wt_porcelain_print(struct wt_status *s)
|
||||
s->no_gettext = 1;
|
||||
wt_shortstatus_print(s);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print branch information for porcelain v2 output. These lines
|
||||
* are printed when the '--branch' parameter is given.
|
||||
*
|
||||
* # branch.oid <commit><eol>
|
||||
* # branch.head <head><eol>
|
||||
* [# branch.upstream <upstream><eol>
|
||||
* [# branch.ab +<ahead> -<behind><eol>]]
|
||||
*
|
||||
* <commit> ::= the current commit hash or the the literal
|
||||
* "(initial)" to indicate an initialized repo
|
||||
* with no commits.
|
||||
*
|
||||
* <head> ::= <branch_name> the current branch name or
|
||||
* "(detached)" literal when detached head or
|
||||
* "(unknown)" when something is wrong.
|
||||
*
|
||||
* <upstream> ::= the upstream branch name, when set.
|
||||
*
|
||||
* <ahead> ::= integer ahead value, when upstream set
|
||||
* and the commit is present (not gone).
|
||||
*
|
||||
* <behind> ::= integer behind value, when upstream set
|
||||
* and commit is present.
|
||||
*
|
||||
*
|
||||
* The end-of-line is defined by the -z flag.
|
||||
*
|
||||
* <eol> ::= NUL when -z,
|
||||
* LF when NOT -z.
|
||||
*
|
||||
*/
|
||||
static void wt_porcelain_v2_print_tracking(struct wt_status *s)
|
||||
{
|
||||
struct branch *branch;
|
||||
const char *base;
|
||||
const char *branch_name;
|
||||
struct wt_status_state state;
|
||||
int ab_info, nr_ahead, nr_behind;
|
||||
char eol = s->null_termination ? '\0' : '\n';
|
||||
|
||||
memset(&state, 0, sizeof(state));
|
||||
wt_status_get_state(&state, s->branch && !strcmp(s->branch, "HEAD"));
|
||||
|
||||
fprintf(s->fp, "# branch.oid %s%c",
|
||||
(s->is_initial ? "(initial)" : sha1_to_hex(s->sha1_commit)),
|
||||
eol);
|
||||
|
||||
if (!s->branch)
|
||||
fprintf(s->fp, "# branch.head %s%c", "(unknown)", eol);
|
||||
else {
|
||||
if (!strcmp(s->branch, "HEAD")) {
|
||||
fprintf(s->fp, "# branch.head %s%c", "(detached)", eol);
|
||||
|
||||
if (state.rebase_in_progress || state.rebase_interactive_in_progress)
|
||||
branch_name = state.onto;
|
||||
else if (state.detached_from)
|
||||
branch_name = state.detached_from;
|
||||
else
|
||||
branch_name = "";
|
||||
} else {
|
||||
branch_name = NULL;
|
||||
skip_prefix(s->branch, "refs/heads/", &branch_name);
|
||||
|
||||
fprintf(s->fp, "# branch.head %s%c", branch_name, eol);
|
||||
}
|
||||
|
||||
/* Lookup stats on the upstream tracking branch, if set. */
|
||||
branch = branch_get(branch_name);
|
||||
base = NULL;
|
||||
ab_info = (stat_tracking_info(branch, &nr_ahead, &nr_behind, &base) == 0);
|
||||
if (base) {
|
||||
base = shorten_unambiguous_ref(base, 0);
|
||||
fprintf(s->fp, "# branch.upstream %s%c", base, eol);
|
||||
free((char *)base);
|
||||
|
||||
if (ab_info)
|
||||
fprintf(s->fp, "# branch.ab +%d -%d%c", nr_ahead, nr_behind, eol);
|
||||
}
|
||||
}
|
||||
|
||||
free(state.branch);
|
||||
free(state.onto);
|
||||
free(state.detached_from);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert various submodule status values into a
|
||||
* fixed-length string of characters in the buffer provided.
|
||||
*/
|
||||
static void wt_porcelain_v2_submodule_state(
|
||||
struct wt_status_change_data *d,
|
||||
char sub[5])
|
||||
{
|
||||
if (S_ISGITLINK(d->mode_head) ||
|
||||
S_ISGITLINK(d->mode_index) ||
|
||||
S_ISGITLINK(d->mode_worktree)) {
|
||||
sub[0] = 'S';
|
||||
sub[1] = d->new_submodule_commits ? 'C' : '.';
|
||||
sub[2] = (d->dirty_submodule & DIRTY_SUBMODULE_MODIFIED) ? 'M' : '.';
|
||||
sub[3] = (d->dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) ? 'U' : '.';
|
||||
} else {
|
||||
sub[0] = 'N';
|
||||
sub[1] = '.';
|
||||
sub[2] = '.';
|
||||
sub[3] = '.';
|
||||
}
|
||||
sub[4] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fix-up changed entries before we print them.
|
||||
*/
|
||||
static void wt_porcelain_v2_fix_up_changed(
|
||||
struct string_list_item *it,
|
||||
struct wt_status *s)
|
||||
{
|
||||
struct wt_status_change_data *d = it->util;
|
||||
|
||||
if (!d->index_status) {
|
||||
/*
|
||||
* This entry is unchanged in the index (relative to the head).
|
||||
* Therefore, the collect_updated_cb was never called for this
|
||||
* entry (during the head-vs-index scan) and so the head column
|
||||
* fields were never set.
|
||||
*
|
||||
* We must have data for the index column (from the
|
||||
* index-vs-worktree scan (otherwise, this entry should not be
|
||||
* in the list of changes)).
|
||||
*
|
||||
* Copy index column fields to the head column, so that our
|
||||
* output looks complete.
|
||||
*/
|
||||
assert(d->mode_head == 0);
|
||||
d->mode_head = d->mode_index;
|
||||
oidcpy(&d->oid_head, &d->oid_index);
|
||||
}
|
||||
|
||||
if (!d->worktree_status) {
|
||||
/*
|
||||
* This entry is unchanged in the worktree (relative to the index).
|
||||
* Therefore, the collect_changed_cb was never called for this entry
|
||||
* (during the index-vs-worktree scan) and so the worktree column
|
||||
* fields were never set.
|
||||
*
|
||||
* We must have data for the index column (from the head-vs-index
|
||||
* scan).
|
||||
*
|
||||
* Copy the index column fields to the worktree column so that
|
||||
* our output looks complete.
|
||||
*
|
||||
* Note that we only have a mode field in the worktree column
|
||||
* because the scan code tries really hard to not have to compute it.
|
||||
*/
|
||||
assert(d->mode_worktree == 0);
|
||||
d->mode_worktree = d->mode_index;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Print porcelain v2 info for tracked entries with changes.
|
||||
*/
|
||||
static void wt_porcelain_v2_print_changed_entry(
|
||||
struct string_list_item *it,
|
||||
struct wt_status *s)
|
||||
{
|
||||
struct wt_status_change_data *d = it->util;
|
||||
struct strbuf buf_index = STRBUF_INIT;
|
||||
struct strbuf buf_head = STRBUF_INIT;
|
||||
const char *path_index = NULL;
|
||||
const char *path_head = NULL;
|
||||
char key[3];
|
||||
char submodule_token[5];
|
||||
char sep_char, eol_char;
|
||||
|
||||
wt_porcelain_v2_fix_up_changed(it, s);
|
||||
wt_porcelain_v2_submodule_state(d, submodule_token);
|
||||
|
||||
key[0] = d->index_status ? d->index_status : '.';
|
||||
key[1] = d->worktree_status ? d->worktree_status : '.';
|
||||
key[2] = 0;
|
||||
|
||||
if (s->null_termination) {
|
||||
/*
|
||||
* In -z mode, we DO NOT C-quote pathnames. Current path is ALWAYS first.
|
||||
* A single NUL character separates them.
|
||||
*/
|
||||
sep_char = '\0';
|
||||
eol_char = '\0';
|
||||
path_index = it->string;
|
||||
path_head = d->head_path;
|
||||
} else {
|
||||
/*
|
||||
* Path(s) are C-quoted if necessary. Current path is ALWAYS first.
|
||||
* The source path is only present when necessary.
|
||||
* A single TAB separates them (because paths can contain spaces
|
||||
* which are not escaped and C-quoting does escape TAB characters).
|
||||
*/
|
||||
sep_char = '\t';
|
||||
eol_char = '\n';
|
||||
path_index = quote_path(it->string, s->prefix, &buf_index);
|
||||
if (d->head_path)
|
||||
path_head = quote_path(d->head_path, s->prefix, &buf_head);
|
||||
}
|
||||
|
||||
if (path_head)
|
||||
fprintf(s->fp, "2 %s %s %06o %06o %06o %s %s %c%d %s%c%s%c",
|
||||
key, submodule_token,
|
||||
d->mode_head, d->mode_index, d->mode_worktree,
|
||||
oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
|
||||
key[0], d->score,
|
||||
path_index, sep_char, path_head, eol_char);
|
||||
else
|
||||
fprintf(s->fp, "1 %s %s %06o %06o %06o %s %s %s%c",
|
||||
key, submodule_token,
|
||||
d->mode_head, d->mode_index, d->mode_worktree,
|
||||
oid_to_hex(&d->oid_head), oid_to_hex(&d->oid_index),
|
||||
path_index, eol_char);
|
||||
|
||||
strbuf_release(&buf_index);
|
||||
strbuf_release(&buf_head);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print porcelain v2 status info for unmerged entries.
|
||||
*/
|
||||
static void wt_porcelain_v2_print_unmerged_entry(
|
||||
struct string_list_item *it,
|
||||
struct wt_status *s)
|
||||
{
|
||||
struct wt_status_change_data *d = it->util;
|
||||
const struct cache_entry *ce;
|
||||
struct strbuf buf_index = STRBUF_INIT;
|
||||
const char *path_index = NULL;
|
||||
int pos, stage, sum;
|
||||
struct {
|
||||
int mode;
|
||||
struct object_id oid;
|
||||
} stages[3];
|
||||
char *key;
|
||||
char submodule_token[5];
|
||||
char unmerged_prefix = 'u';
|
||||
char eol_char = s->null_termination ? '\0' : '\n';
|
||||
|
||||
wt_porcelain_v2_submodule_state(d, submodule_token);
|
||||
|
||||
switch (d->stagemask) {
|
||||
case 1: key = "DD"; break; /* both deleted */
|
||||
case 2: key = "AU"; break; /* added by us */
|
||||
case 3: key = "UD"; break; /* deleted by them */
|
||||
case 4: key = "UA"; break; /* added by them */
|
||||
case 5: key = "DU"; break; /* deleted by us */
|
||||
case 6: key = "AA"; break; /* both added */
|
||||
case 7: key = "UU"; break; /* both modified */
|
||||
default:
|
||||
die("BUG: unhandled unmerged status %x", d->stagemask);
|
||||
}
|
||||
|
||||
/*
|
||||
* Disregard d.aux.porcelain_v2 data that we accumulated
|
||||
* for the head and index columns during the scans and
|
||||
* replace with the actual stage data.
|
||||
*
|
||||
* Note that this is a last-one-wins for each the individual
|
||||
* stage [123] columns in the event of multiple cache entries
|
||||
* for same stage.
|
||||
*/
|
||||
memset(stages, 0, sizeof(stages));
|
||||
sum = 0;
|
||||
pos = cache_name_pos(it->string, strlen(it->string));
|
||||
assert(pos < 0);
|
||||
pos = -pos-1;
|
||||
while (pos < active_nr) {
|
||||
ce = active_cache[pos++];
|
||||
stage = ce_stage(ce);
|
||||
if (strcmp(ce->name, it->string) || !stage)
|
||||
break;
|
||||
stages[stage - 1].mode = ce->ce_mode;
|
||||
hashcpy(stages[stage - 1].oid.hash, ce->sha1);
|
||||
sum |= (1 << (stage - 1));
|
||||
}
|
||||
if (sum != d->stagemask)
|
||||
die("BUG: observed stagemask 0x%x != expected stagemask 0x%x", sum, d->stagemask);
|
||||
|
||||
if (s->null_termination)
|
||||
path_index = it->string;
|
||||
else
|
||||
path_index = quote_path(it->string, s->prefix, &buf_index);
|
||||
|
||||
fprintf(s->fp, "%c %s %s %06o %06o %06o %06o %s %s %s %s%c",
|
||||
unmerged_prefix, key, submodule_token,
|
||||
stages[0].mode, /* stage 1 */
|
||||
stages[1].mode, /* stage 2 */
|
||||
stages[2].mode, /* stage 3 */
|
||||
d->mode_worktree,
|
||||
oid_to_hex(&stages[0].oid), /* stage 1 */
|
||||
oid_to_hex(&stages[1].oid), /* stage 2 */
|
||||
oid_to_hex(&stages[2].oid), /* stage 3 */
|
||||
path_index,
|
||||
eol_char);
|
||||
|
||||
strbuf_release(&buf_index);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print porcelain V2 status info for untracked and ignored entries.
|
||||
*/
|
||||
static void wt_porcelain_v2_print_other(
|
||||
struct string_list_item *it,
|
||||
struct wt_status *s,
|
||||
char prefix)
|
||||
{
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
const char *path;
|
||||
char eol_char;
|
||||
|
||||
if (s->null_termination) {
|
||||
path = it->string;
|
||||
eol_char = '\0';
|
||||
} else {
|
||||
path = quote_path(it->string, s->prefix, &buf);
|
||||
eol_char = '\n';
|
||||
}
|
||||
|
||||
fprintf(s->fp, "%c %s%c", prefix, path, eol_char);
|
||||
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print porcelain V2 status.
|
||||
*
|
||||
* [<v2_branch>]
|
||||
* [<v2_changed_items>]*
|
||||
* [<v2_unmerged_items>]*
|
||||
* [<v2_untracked_items>]*
|
||||
* [<v2_ignored_items>]*
|
||||
*
|
||||
*/
|
||||
static void wt_porcelain_v2_print(struct wt_status *s)
|
||||
{
|
||||
struct wt_status_change_data *d;
|
||||
struct string_list_item *it;
|
||||
int i;
|
||||
|
||||
if (s->show_branch)
|
||||
wt_porcelain_v2_print_tracking(s);
|
||||
|
||||
for (i = 0; i < s->change.nr; i++) {
|
||||
it = &(s->change.items[i]);
|
||||
d = it->util;
|
||||
if (!d->stagemask)
|
||||
wt_porcelain_v2_print_changed_entry(it, s);
|
||||
}
|
||||
|
||||
for (i = 0; i < s->change.nr; i++) {
|
||||
it = &(s->change.items[i]);
|
||||
d = it->util;
|
||||
if (d->stagemask)
|
||||
wt_porcelain_v2_print_unmerged_entry(it, s);
|
||||
}
|
||||
|
||||
for (i = 0; i < s->untracked.nr; i++) {
|
||||
it = &(s->untracked.items[i]);
|
||||
wt_porcelain_v2_print_other(it, s, '?');
|
||||
}
|
||||
|
||||
for (i = 0; i < s->ignored.nr; i++) {
|
||||
it = &(s->ignored.items[i]);
|
||||
wt_porcelain_v2_print_other(it, s, '!');
|
||||
}
|
||||
}
|
||||
|
||||
void wt_status_print(struct wt_status *s)
|
||||
{
|
||||
switch (s->status_format) {
|
||||
case STATUS_FORMAT_SHORT:
|
||||
wt_shortstatus_print(s);
|
||||
break;
|
||||
case STATUS_FORMAT_PORCELAIN:
|
||||
wt_porcelain_print(s);
|
||||
break;
|
||||
case STATUS_FORMAT_PORCELAIN_V2:
|
||||
wt_porcelain_v2_print(s);
|
||||
break;
|
||||
case STATUS_FORMAT_UNSPECIFIED:
|
||||
die("BUG: finalize_deferred_config() should have been called");
|
||||
break;
|
||||
case STATUS_FORMAT_NONE:
|
||||
case STATUS_FORMAT_LONG:
|
||||
wt_longstatus_print(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 if there are unstaged changes, 0 otherwise.
|
||||
*/
|
||||
int has_unstaged_changes(int ignore_submodules)
|
||||
{
|
||||
struct rev_info rev_info;
|
||||
int result;
|
||||
|
||||
init_revisions(&rev_info, NULL);
|
||||
if (ignore_submodules)
|
||||
DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
|
||||
DIFF_OPT_SET(&rev_info.diffopt, QUICK);
|
||||
diff_setup_done(&rev_info.diffopt);
|
||||
result = run_diff_files(&rev_info, 0);
|
||||
return diff_result_code(&rev_info.diffopt, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 if there are uncommitted changes, 0 otherwise.
|
||||
*/
|
||||
int has_uncommitted_changes(int ignore_submodules)
|
||||
{
|
||||
struct rev_info rev_info;
|
||||
int result;
|
||||
|
||||
if (is_cache_unborn())
|
||||
return 0;
|
||||
|
||||
init_revisions(&rev_info, NULL);
|
||||
if (ignore_submodules)
|
||||
DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
|
||||
DIFF_OPT_SET(&rev_info.diffopt, QUICK);
|
||||
add_head_to_pending(&rev_info);
|
||||
diff_setup_done(&rev_info.diffopt);
|
||||
result = run_diff_index(&rev_info, 1);
|
||||
return diff_result_code(&rev_info.diffopt, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the work tree has unstaged or uncommitted changes, dies with the
|
||||
* appropriate message.
|
||||
*/
|
||||
int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently)
|
||||
{
|
||||
struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
|
||||
int err = 0;
|
||||
|
||||
hold_locked_index(lock_file, 0);
|
||||
refresh_cache(REFRESH_QUIET);
|
||||
update_index_if_able(&the_index, lock_file);
|
||||
rollback_lock_file(lock_file);
|
||||
|
||||
if (has_unstaged_changes(ignore_submodules)) {
|
||||
/* TRANSLATORS: the action is e.g. "pull with rebase" */
|
||||
error(_("cannot %s: You have unstaged changes."), _(action));
|
||||
err = 1;
|
||||
}
|
||||
|
||||
if (has_uncommitted_changes(ignore_submodules)) {
|
||||
if (err)
|
||||
error(_("additionally, your index contains uncommitted changes."));
|
||||
else
|
||||
error(_("cannot %s: Your index contains uncommitted changes."),
|
||||
_(action));
|
||||
err = 1;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
if (hint)
|
||||
error("%s", hint);
|
||||
if (!gently)
|
||||
exit(128);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
25
wt-status.h
25
wt-status.h
@@ -38,11 +38,24 @@ struct wt_status_change_data {
|
||||
int worktree_status;
|
||||
int index_status;
|
||||
int stagemask;
|
||||
int score;
|
||||
int mode_head, mode_index, mode_worktree;
|
||||
struct object_id oid_head, oid_index;
|
||||
char *head_path;
|
||||
unsigned dirty_submodule : 2;
|
||||
unsigned new_submodule_commits : 1;
|
||||
};
|
||||
|
||||
enum wt_status_format {
|
||||
STATUS_FORMAT_NONE = 0,
|
||||
STATUS_FORMAT_LONG,
|
||||
STATUS_FORMAT_SHORT,
|
||||
STATUS_FORMAT_PORCELAIN,
|
||||
STATUS_FORMAT_PORCELAIN_V2,
|
||||
|
||||
STATUS_FORMAT_UNSPECIFIED
|
||||
};
|
||||
|
||||
struct wt_status {
|
||||
int is_initial;
|
||||
char *branch;
|
||||
@@ -66,6 +79,9 @@ struct wt_status {
|
||||
int show_branch;
|
||||
int hints;
|
||||
|
||||
enum wt_status_format status_format;
|
||||
unsigned char sha1_commit[GIT_SHA1_RAWSZ]; /* when not Initial */
|
||||
|
||||
/* These are computed during processing of the individual sections */
|
||||
int commitable;
|
||||
int workdir_dirty;
|
||||
@@ -107,12 +123,15 @@ int wt_status_check_rebase(const struct worktree *wt,
|
||||
int wt_status_check_bisect(const struct worktree *wt,
|
||||
struct wt_status_state *state);
|
||||
|
||||
void wt_shortstatus_print(struct wt_status *s);
|
||||
void wt_porcelain_print(struct wt_status *s);
|
||||
|
||||
__attribute__((format (printf, 3, 4)))
|
||||
void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...);
|
||||
__attribute__((format (printf, 3, 4)))
|
||||
void status_printf(struct wt_status *s, const char *color, const char *fmt, ...);
|
||||
|
||||
/* The following functions expect that the caller took care of reading the index. */
|
||||
int has_unstaged_changes(int ignore_submodules);
|
||||
int has_uncommitted_changes(int ignore_submodules);
|
||||
int require_clean_work_tree(const char *action, const char *hint,
|
||||
int ignore_submodules, int gently);
|
||||
|
||||
#endif /* STATUS_H */
|
||||
|
||||
Reference in New Issue
Block a user