summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/add.c3
-rw-r--r--builtin/am.c13
-rw-r--r--builtin/blame.c13
-rw-r--r--builtin/branch.c3
-rw-r--r--builtin/checkout.c22
-rw-r--r--builtin/clone.c12
-rw-r--r--builtin/commit-graph.c23
-rw-r--r--builtin/commit.c61
-rw-r--r--builtin/diff.c22
-rw-r--r--builtin/difftool.c24
-rw-r--r--builtin/fetch-pack.c2
-rw-r--r--builtin/for-each-ref.c3
-rw-r--r--builtin/gc.c37
-rw-r--r--builtin/get-tar-commit-id.c14
-rw-r--r--builtin/index-pack.c10
-rw-r--r--builtin/init-db.c7
-rw-r--r--builtin/log.c60
-rw-r--r--builtin/ls-files.c12
-rw-r--r--builtin/ls-remote.c3
-rw-r--r--builtin/merge.c51
-rw-r--r--builtin/multi-pack-index.c3
-rw-r--r--builtin/name-rev.c14
-rw-r--r--builtin/pack-objects.c10
-rw-r--r--builtin/pack-redundant.c2
-rw-r--r--builtin/pull.c23
-rw-r--r--builtin/range-diff.c26
-rw-r--r--builtin/read-tree.c1
-rw-r--r--builtin/rebase--interactive.c275
-rw-r--r--builtin/rebase.c641
-rw-r--r--builtin/repack.c5
-rw-r--r--builtin/replace.c20
-rw-r--r--builtin/reset.c1
-rw-r--r--builtin/rev-list.c4
-rw-r--r--builtin/revert.c7
-rw-r--r--builtin/rm.c2
-rw-r--r--builtin/serve.c30
-rw-r--r--builtin/show-branch.c6
-rw-r--r--builtin/stash.c1648
-rw-r--r--builtin/submodule--helper.c27
-rw-r--r--builtin/tag.c6
-rw-r--r--builtin/update-index.c9
-rw-r--r--builtin/worktree.c12
42 files changed, 2489 insertions, 678 deletions
diff --git a/builtin/add.c b/builtin/add.c
index db2dfa4350..dd18e5c9b6 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -374,11 +374,12 @@ static int add_files(struct dir_struct *dir, int flags)
}
for (i = 0; i < dir->nr; i++) {
- check_embedded_repo(dir->entries[i]->name);
if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) {
if (!ignore_add_errors)
die(_("adding files failed"));
exit_status = 1;
+ } else {
+ check_embedded_repo(dir->entries[i]->name);
}
}
return exit_status;
diff --git a/builtin/am.c b/builtin/am.c
index 4fb107a9d1..912d9821b1 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -486,23 +486,24 @@ static int copy_notes_for_rebase(const struct am_state *state)
while (!strbuf_getline_lf(&sb, fp)) {
struct object_id from_obj, to_obj;
+ const char *p;
- if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) {
+ if (sb.len != the_hash_algo->hexsz * 2 + 1) {
ret = error(invalid_line, sb.buf);
goto finish;
}
- if (get_oid_hex(sb.buf, &from_obj)) {
+ if (parse_oid_hex(sb.buf, &from_obj, &p)) {
ret = error(invalid_line, sb.buf);
goto finish;
}
- if (sb.buf[GIT_SHA1_HEXSZ] != ' ') {
+ if (*p != ' ') {
ret = error(invalid_line, sb.buf);
goto finish;
}
- if (get_oid_hex(sb.buf + GIT_SHA1_HEXSZ + 1, &to_obj)) {
+ if (get_oid_hex(p + 1, &to_obj)) {
ret = error(invalid_line, sb.buf);
goto finish;
}
@@ -1501,11 +1502,11 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
* review them with extra care to spot mismerges.
*/
struct rev_info rev_info;
- const char *diff_filter_str = "--diff-filter=AM";
repo_init_revisions(the_repository, &rev_info, NULL);
rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
- diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix);
+ rev_info.diffopt.filter |= diff_filter_bit('A');
+ rev_info.diffopt.filter |= diff_filter_bit('M');
add_pending_oid(&rev_info, "HEAD", &our_tree, 0);
diff_setup_done(&rev_info.diffopt);
run_diff_index(&rev_info, 1);
diff --git a/builtin/blame.c b/builtin/blame.c
index 177c1022a0..21cde57e71 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -27,6 +27,7 @@
#include "object-store.h"
#include "blame.h"
#include "string-list.h"
+#include "refs.h"
static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>");
@@ -993,6 +994,18 @@ parse_done:
revs.disable_stdin = 1;
setup_revisions(argc, argv, &revs, NULL);
+ if (!revs.pending.nr && is_bare_repository()) {
+ struct commit *head_commit;
+ struct object_id head_oid;
+
+ if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+ &head_oid, NULL) ||
+ !(head_commit = lookup_commit_reference_gently(revs.repo,
+ &head_oid, 1)))
+ die("no such ref: HEAD");
+
+ add_pending_object(&revs, &head_commit->object, "HEAD");
+ }
init_scoreboard(&sb);
sb.revs = &revs;
diff --git a/builtin/branch.c b/builtin/branch.c
index 4c83055730..d4359b33ac 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -644,8 +644,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_MERGED(&filter, N_("print only branches that are merged")),
OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")),
- OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
- N_("field name to sort on"), &parse_opt_ref_sorting),
+ OPT_REF_SORT(sorting_tail),
{
OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
N_("print only branches of the object"), 0, parse_opt_object_name
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 0e6037b296..ffa776c6e1 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -376,7 +376,7 @@ static int checkout_paths(const struct checkout_opts *opts,
ps_matched,
opts);
- if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
+ if (report_path_error(ps_matched, &opts->pathspec)) {
free(ps_matched);
return 1;
}
@@ -700,7 +700,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
topts.initial_checkout = is_cache_unborn();
topts.update = 1;
topts.merge = 1;
- topts.gently = opts->merge && old_branch_info->commit;
+ topts.quiet = opts->merge && old_branch_info->commit;
topts.verbose_update = opts->show_progress;
topts.fn = twoway_merge;
if (opts->overwrite_ignore) {
@@ -725,7 +725,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
*/
struct tree *result;
struct tree *work;
+ struct tree *old_tree;
struct merge_options o;
+ struct strbuf sb = STRBUF_INIT;
+
if (!opts->merge)
return 1;
@@ -735,6 +738,19 @@ static int merge_working_tree(const struct checkout_opts *opts,
*/
if (!old_branch_info->commit)
return 1;
+ old_tree = get_commit_tree(old_branch_info->commit);
+
+ if (repo_index_has_changes(the_repository, old_tree, &sb))
+ die(_("cannot continue with staged changes in "
+ "the following files:\n%s"), sb.buf);
+ strbuf_release(&sb);
+
+ if (repo_index_has_changes(the_repository,
+ get_commit_tree(old_branch_info->commit),
+ &sb))
+ warning(_("staged changes in the following files may be lost: %s"),
+ sb.buf);
+ strbuf_release(&sb);
/* Do more real merge */
@@ -772,7 +788,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
ret = merge_trees(&o,
get_commit_tree(new_branch_info->commit),
work,
- get_commit_tree(old_branch_info->commit),
+ old_tree,
&result);
if (ret < 0)
exit(128);
diff --git a/builtin/clone.c b/builtin/clone.c
index 50bde99618..ffdd94e8f6 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -66,6 +66,7 @@ static int option_dissociate;
static int max_jobs = -1;
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
static struct list_objects_filter_options filter_options;
+static struct string_list server_options = STRING_LIST_INIT_NODUP;
static int recurse_submodules_cb(const struct option *opt,
const char *arg, int unset)
@@ -137,6 +138,8 @@ static struct option builtin_clone_options[] = {
N_("separate git dir from working tree")),
OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
N_("set config inside the new repository")),
+ OPT_STRING_LIST(0, "server-option", &server_options,
+ N_("server-specific"), N_("option to transmit")),
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@ -657,7 +660,8 @@ static void update_remote_refs(const struct ref *refs,
const char *branch_top,
const char *msg,
struct transport *transport,
- int check_connectivity)
+ int check_connectivity,
+ int check_refs_only)
{
const struct ref *rm = mapped_refs;
@@ -666,6 +670,7 @@ static void update_remote_refs(const struct ref *refs,
opt.transport = transport;
opt.progress = transport->progress;
+ opt.check_refs_only = !!check_refs_only;
if (check_connected(iterate_ref_map, &rm, &opt))
die(_("remote did not send all necessary objects"));
@@ -1136,6 +1141,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
transport_set_option(transport, TRANS_OPT_UPLOADPACK,
option_upload_pack);
+ if (server_options.nr)
+ transport->server_options = &server_options;
+
if (filter_options.choice) {
struct strbuf expanded_filter_spec = STRBUF_INIT;
expand_list_objects_filter_spec(&filter_options,
@@ -1224,7 +1232,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
update_remote_refs(refs, mapped_refs, remote_head_points_at,
branch_top.buf, reflog_msg.buf, transport,
- !is_local);
+ !is_local, filter_options.choice);
update_head(our_head_points_at, remote_head, reflog_msg.buf);
diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c
index 4ae502754c..537fdfd0f0 100644
--- a/builtin/commit-graph.c
+++ b/builtin/commit-graph.c
@@ -42,6 +42,9 @@ static int graph_verify(int argc, const char **argv)
{
struct commit_graph *graph = NULL;
char *graph_name;
+ int open_ok;
+ int fd;
+ struct stat st;
static struct option builtin_commit_graph_verify_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
@@ -58,11 +61,16 @@ static int graph_verify(int argc, const char **argv)
opts.obj_dir = get_object_directory();
graph_name = get_commit_graph_filename(opts.obj_dir);
- graph = load_commit_graph_one(graph_name);
+ open_ok = open_commit_graph(graph_name, &fd, &st);
+ if (!open_ok && errno == ENOENT)
+ return 0;
+ if (!open_ok)
+ die_errno(_("Could not open commit-graph '%s'"), graph_name);
+ graph = load_commit_graph_one_fd_st(fd, &st);
FREE_AND_NULL(graph_name);
if (!graph)
- return 0;
+ return 1;
UNLEAK(graph);
return verify_commit_graph(the_repository, graph);
@@ -72,6 +80,9 @@ static int graph_read(int argc, const char **argv)
{
struct commit_graph *graph = NULL;
char *graph_name;
+ int open_ok;
+ int fd;
+ struct stat st;
static struct option builtin_commit_graph_read_options[] = {
OPT_STRING(0, "object-dir", &opts.obj_dir,
@@ -88,10 +99,14 @@ static int graph_read(int argc, const char **argv)
opts.obj_dir = get_object_directory();
graph_name = get_commit_graph_filename(opts.obj_dir);
- graph = load_commit_graph_one(graph_name);
+ open_ok = open_commit_graph(graph_name, &fd, &st);
+ if (!open_ok)
+ die_errno(_("Could not open commit-graph '%s'"), graph_name);
+
+ graph = load_commit_graph_one_fd_st(fd, &st);
if (!graph)
- die("graph file %s does not exist", graph_name);
+ return 1;
FREE_AND_NULL(graph_name);
diff --git a/builtin/commit.c b/builtin/commit.c
index f17537474a..1c9e8e2228 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -235,7 +235,7 @@ static int commit_index_files(void)
* and return the paths that match the given pattern in list.
*/
static int list_paths(struct string_list *list, const char *with_tree,
- const char *prefix, const struct pathspec *pattern)
+ const struct pathspec *pattern)
{
int i, ret;
char *m;
@@ -264,7 +264,7 @@ static int list_paths(struct string_list *list, const char *with_tree,
item->util = item; /* better a valid pointer than a fake one */
}
- ret = report_path_error(m, pattern, prefix);
+ ret = report_path_error(m, pattern);
free(m);
return ret;
}
@@ -454,7 +454,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
die(_("cannot do a partial commit during a cherry-pick."));
}
- if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
+ if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec))
exit(1);
discard_cache();
@@ -668,6 +668,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
const char *hook_arg2 = NULL;
int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE);
int old_display_comment_prefix;
+ int merge_contains_scissors = 0;
/* This checks and barfs if author is badly specified */
determine_author_info(author_ident);
@@ -728,6 +729,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {
+ size_t merge_msg_start;
+
/*
* prepend SQUASH_MSG here if it exists and a
* "merge --squash" was originally performed
@@ -738,8 +741,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
hook_arg1 = "squash";
} else
hook_arg1 = "merge";
+
+ merge_msg_start = sb.len;
if (strbuf_read_file(&sb, git_path_merge_msg(the_repository), 0) < 0)
die_errno(_("could not read MERGE_MSG"));
+
+ if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
+ wt_status_locate_end(sb.buf + merge_msg_start,
+ sb.len - merge_msg_start) <
+ sb.len - merge_msg_start)
+ merge_contains_scissors = 1;
} else if (!stat(git_path_squash_msg(the_repository), &statbuf)) {
if (strbuf_read_file(&sb, git_path_squash_msg(the_repository), 0) < 0)
die_errno(_("could not read SQUASH_MSG"));
@@ -807,7 +818,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
struct ident_split ci, ai;
if (whence != FROM_COMMIT) {
- if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
+ if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
+ !merge_contains_scissors)
wt_status_add_cut_line(s->fp);
status_printf_ln(s, GIT_COLOR_NORMAL,
whence == FROM_MERGE
@@ -832,10 +844,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
_("Please enter the commit message for your changes."
" Lines starting\nwith '%c' will be ignored, and an empty"
" message aborts the commit.\n"), comment_line_char);
- else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
- whence == FROM_COMMIT)
- wt_status_add_cut_line(s->fp);
- else /* COMMIT_MSG_CLEANUP_SPACE, that is. */
+ else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
+ if (whence == FROM_COMMIT && !merge_contains_scissors)
+ wt_status_add_cut_line(s->fp);
+ } else /* COMMIT_MSG_CLEANUP_SPACE, that is. */
status_printf(s, GIT_COLOR_NORMAL,
_("Please enter the commit message for your changes."
" Lines starting\n"
@@ -1172,29 +1184,13 @@ static int parse_and_validate_options(int argc, const char *argv[],
die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
if (argc == 0 && (also || (only && !amend && !allow_empty)))
die(_("No paths with --include/--only does not make sense."));
- if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
- cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_ALL :
- COMMIT_MSG_CLEANUP_SPACE;
- else if (!strcmp(cleanup_arg, "verbatim"))
- cleanup_mode = COMMIT_MSG_CLEANUP_NONE;
- else if (!strcmp(cleanup_arg, "whitespace"))
- cleanup_mode = COMMIT_MSG_CLEANUP_SPACE;
- else if (!strcmp(cleanup_arg, "strip"))
- cleanup_mode = COMMIT_MSG_CLEANUP_ALL;
- else if (!strcmp(cleanup_arg, "scissors"))
- cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_SCISSORS :
- COMMIT_MSG_CLEANUP_SPACE;
- /*
- * Please update _git_commit() in git-completion.bash when you
- * add new options.
- */
- else
- die(_("Invalid cleanup mode %s"), cleanup_arg);
+ cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor);
handle_untracked_files_arg(s);
if (all && argc > 0)
- die(_("Paths with -a does not make sense."));
+ die(_("paths '%s ...' with -a does not make sense"),
+ argv[0]);
if (status_format != STATUS_FORMAT_NONE)
dry_run = 1;
@@ -1490,7 +1486,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")),
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
- OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")),
+ OPT_CLEANUP(&cleanup_arg),
OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")),
{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"),
N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
@@ -1626,11 +1622,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
die(_("could not read commit message: %s"), strerror(saved_errno));
}
- if (verbose || /* Truncate the message just before the diff, if any. */
- cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
- strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len));
- if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
- strbuf_stripspace(&sb, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
+ cleanup_message(&sb, cleanup_mode, verbose);
if (message_is_empty(&sb, cleanup_mode) && !allow_empty_message) {
rollback_index_files();
@@ -1666,8 +1658,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
die("%s", err.buf);
}
- unlink(git_path_cherry_pick_head(the_repository));
- unlink(git_path_revert_head(the_repository));
+ sequencer_post_commit_cleanup(the_repository);
unlink(git_path_merge_head(the_repository));
unlink(git_path_merge_msg(the_repository));
unlink(git_path_merge_mode(the_repository));
diff --git a/builtin/diff.c b/builtin/diff.c
index 53d4234ff4..42ac803091 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -321,24 +321,6 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
repo_init_revisions(the_repository, &rev, prefix);
- if (no_index && argc != i + 2) {
- if (no_index == DIFF_NO_INDEX_IMPLICIT) {
- /*
- * There was no --no-index and there were not two
- * paths. It is possible that the user intended
- * to do an inside-repository operation.
- */
- fprintf(stderr, "Not a git repository\n");
- fprintf(stderr,
- "To compare two paths outside a working tree:\n");
- }
- /* Give the usage message for non-repository usage and exit. */
- usagef("git diff %s <path> <path>",
- no_index == DIFF_NO_INDEX_EXPLICIT ?
- "--no-index" : "[--no-index]");
-
- }
-
/* Set up defaults that will apply to both no-index and regular diffs. */
rev.diffopt.stat_width = -1;
rev.diffopt.stat_graph_width = -1;
@@ -347,7 +329,9 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
/* If this is a no-index diff, just run it and exit there. */
if (no_index)
- diff_no_index(&rev, argc, argv);
+ exit(diff_no_index(&rev, no_index == DIFF_NO_INDEX_IMPLICIT,
+ argc, argv));
+
/*
* Otherwise, we are doing the usual "git" diff; set up any
diff --git a/builtin/difftool.c b/builtin/difftool.c
index a3ea60ea71..04ffa1d943 100644
--- a/builtin/difftool.c
+++ b/builtin/difftool.c
@@ -65,14 +65,12 @@ static int parse_index_info(char *p, int *mode1, int *mode2,
*mode2 = (int)strtol(p + 1, &p, 8);
if (*p != ' ')
return error("expected ' ', got '%c'", *p);
- if (get_oid_hex(++p, oid1))
- return error("expected object ID, got '%s'", p + 1);
- p += GIT_SHA1_HEXSZ;
+ if (parse_oid_hex(++p, oid1, (const char **)&p))
+ return error("expected object ID, got '%s'", p);
if (*p != ' ')
return error("expected ' ', got '%c'", *p);
- if (get_oid_hex(++p, oid2))
- return error("expected object ID, got '%s'", p + 1);
- p += GIT_SHA1_HEXSZ;
+ if (parse_oid_hex(++p, oid2, (const char **)&p))
+ return error("expected object ID, got '%s'", p);
if (*p != ' ')
return error("expected ' ', got '%c'", *p);
*status = *++p;
@@ -690,7 +688,7 @@ static int run_file_diff(int prompt, const char *prefix,
int cmd_difftool(int argc, const char **argv, const char *prefix)
{
int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0,
- tool_help = 0;
+ tool_help = 0, no_index = 0;
static char *difftool_cmd = NULL, *extcmd = NULL;
struct option builtin_difftool_options[] = {
OPT_BOOL('g', "gui", &use_gui_tool,
@@ -714,6 +712,7 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
"tool returns a non - zero exit code")),
OPT_STRING('x', "extcmd", &extcmd, N_("command"),
N_("specify a custom command for viewing diffs")),
+ OPT_ARGUMENT("no-index", &no_index, N_("passed to `diff`")),
OPT_END()
};
@@ -727,9 +726,14 @@ int cmd_difftool(int argc, const char **argv, const char *prefix)
if (tool_help)
return print_tool_help();
- /* NEEDSWORK: once we no longer spawn anything, remove this */
- setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
- setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
+ if (!no_index && !startup_info->have_repository)
+ die(_("difftool requires worktree or --no-index"));
+
+ if (!no_index){
+ setup_work_tree();
+ setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1);
+ setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1);
+ }
if (use_gui_tool && diff_gui_tool && *diff_gui_tool)
setenv("GIT_DIFF_TOOL", diff_gui_tool, 1);
diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c
index 153a2bd282..dc1485c8aa 100644
--- a/builtin/fetch-pack.c
+++ b/builtin/fetch-pack.c
@@ -234,7 +234,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
BUG("unknown protocol version");
}
- ref = fetch_pack(&args, fd, conn, ref, dest, sought, nr_sought,
+ ref = fetch_pack(&args, fd, ref, sought, nr_sought,
&shallow, pack_lockfile_ptr, version);
if (pack_lockfile) {
printf("lock %s\n", pack_lockfile);
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index e931be9ce4..465153e853 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -37,8 +37,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT__COLOR(&format.use_color, N_("respect format colors")),
- OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
- N_("field name to sort on"), &parse_opt_ref_sorting),
+ OPT_REF_SORT(sorting_tail),
OPT_CALLBACK(0, "points-at", &filter.points_at,
N_("object"), N_("print only refs which points at the given object"),
parse_opt_object_name),
diff --git a/builtin/gc.c b/builtin/gc.c
index 020f725acc..8943bcc300 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -116,6 +116,19 @@ static void process_log_file_on_signal(int signo)
raise(signo);
}
+static int gc_config_is_timestamp_never(const char *var)
+{
+ const char *value;
+ timestamp_t expire;
+
+ if (!git_config_get_value(var, &value) && value) {
+ if (parse_expiry_date(value, &expire))
+ die(_("failed to parse '%s' value '%s'"), var, value);
+ return expire == 0;
+ }
+ return 0;
+}
+
static void gc_config(void)
{
const char *value;
@@ -127,6 +140,10 @@ static void gc_config(void)
pack_refs = git_config_bool("gc.packrefs", value);
}
+ if (gc_config_is_timestamp_never("gc.reflogexpire") &&
+ gc_config_is_timestamp_never("gc.reflogexpireunreachable"))
+ prune_reflogs = 0;
+
git_config_get_int("gc.aggressivewindow", &aggressive_window);
git_config_get_int("gc.aggressivedepth", &aggressive_depth);
git_config_get_int("gc.auto", &gc_auto_threshold);
@@ -156,9 +173,7 @@ static int too_many_loose_objects(void)
int auto_threshold;
int num_loose = 0;
int needed = 0;
-
- if (gc_auto_threshold <= 0)
- return 0;
+ const unsigned hexsz_loose = the_hash_algo->hexsz - 2;
dir = opendir(git_path("objects/17"));
if (!dir)
@@ -166,8 +181,8 @@ static int too_many_loose_objects(void)
auto_threshold = DIV_ROUND_UP(gc_auto_threshold, 256);
while ((ent = readdir(dir)) != NULL) {
- if (strspn(ent->d_name, "0123456789abcdef") != 38 ||
- ent->d_name[38] != '\0')
+ if (strspn(ent->d_name, "0123456789abcdef") != hexsz_loose ||
+ ent->d_name[hexsz_loose] != '\0')
continue;
if (++num_loose > auto_threshold) {
needed = 1;
@@ -491,14 +506,20 @@ done:
static void gc_before_repack(void)
{
+ /*
+ * We may be called twice, as both the pre- and
+ * post-daemonized phases will call us, but running these
+ * commands more than once is pointless and wasteful.
+ */
+ static int done = 0;
+ if (done++)
+ return;
+
if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD))
die(FAILED_RUN, pack_refs_cmd.argv[0]);
if (prune_reflogs && run_command_v_opt(reflog.argv, RUN_GIT_CMD))
die(FAILED_RUN, reflog.argv[0]);
-
- pack_refs = 0;
- prune_reflogs = 0;
}
int cmd_gc(int argc, const char **argv, const char *prefix)
diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c
index 2706fcfaf2..491af9202d 100644
--- a/builtin/get-tar-commit-id.c
+++ b/builtin/get-tar-commit-id.c
@@ -21,6 +21,8 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
char *content = buffer + RECORDSIZE;
const char *comment;
ssize_t n;
+ long len;
+ char *end;
if (argc != 1)
usage(builtin_get_tar_commit_id_usage);
@@ -32,10 +34,18 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
die_errno("git get-tar-commit-id: EOF before reading tar header");
if (header->typeflag[0] != 'g')
return 1;
- if (!skip_prefix(content, "52 comment=", &comment))
+
+ len = strtol(content, &end, 10);
+ if (errno == ERANGE || end == content || len < 0)
+ return 1;
+ if (!skip_prefix(end, " comment=", &comment))
+ return 1;
+ len -= comment - content;
+ if (len < 1 || !(len % 2) ||
+ hash_algo_by_length((len - 1) / 2) == GIT_HASH_UNKNOWN)
return 1;
- if (write_in_full(1, comment, 41) < 0)
+ if (write_in_full(1, comment, len) < 0)
die_errno("git get-tar-commit-id: write error");
return 0;
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 31046c7a0a..ccf4eb7e9b 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -219,8 +219,16 @@ static unsigned check_objects(void)
unsigned i, max, foreign_nr = 0;
max = get_max_object_index();
- for (i = 0; i < max; i++)
+
+ if (verbose)
+ progress = start_delayed_progress(_("Checking objects"), max);
+
+ for (i = 0; i < max; i++) {
foreign_nr += check_object(get_indexed_object(i));
+ display_progress(progress, i + 1);
+ }
+
+ stop_progress(&progress);
return foreign_nr;
}
diff --git a/builtin/init-db.c b/builtin/init-db.c
index 6090217025..6ca002893f 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -156,6 +156,9 @@ static int git_init_db_config(const char *k, const char *v, void *cb)
if (!strcmp(k, "init.templatedir"))
return git_config_pathname(&init_db_template_dir, k, v);
+ if (starts_with(k, "core."))
+ return platform_core_config(k, v, cb);
+
return 0;
}
@@ -186,6 +189,7 @@ static int create_default_files(const char *template_path,
struct strbuf err = STRBUF_INIT;
/* Just look for `init.templatedir` */
+ init_db_template_dir = NULL; /* re-set in case it was set before */
git_config(git_init_db_config, NULL);
/*
@@ -362,6 +366,9 @@ int init_db(const char *git_dir, const char *real_git_dir,
}
startup_info->have_repository = 1;
+ /* Just look for `core.hidedotfiles` */
+ git_config(git_init_db_config, NULL);
+
safe_create_dir(git_dir, 0);
init_is_bare_repository = is_bare_repository();
diff --git a/builtin/log.c b/builtin/log.c
index ab859f5904..e43ee12fb1 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -251,7 +251,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
* This gives a rough estimate for how many commits we
* will print out in the list.
*/
-static int estimate_commit_count(struct rev_info *rev, struct commit_list *list)
+static int estimate_commit_count(struct commit_list *list)
{
int n = 0;
@@ -289,7 +289,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
switch (simplify_commit(revs, commit)) {
case commit_show:
if (show_header) {
- int n = estimate_commit_count(revs, list);
+ int n = estimate_commit_count(list);
show_early_header(revs, "incomplete", n);
show_header = 0;
}
@@ -333,7 +333,7 @@ static void early_output(int signal)
show_early_output = log_show_early;
}
-static void setup_early_output(struct rev_info *rev)
+static void setup_early_output(void)
{
struct sigaction sa;
@@ -364,7 +364,7 @@ static void setup_early_output(struct rev_info *rev)
static void finish_early_output(struct rev_info *rev)
{
- int n = estimate_commit_count(rev, rev->commits);
+ int n = estimate_commit_count(rev->commits);
signal(SIGALRM, SIG_IGN);
show_early_header(rev, "done", n);
}
@@ -376,7 +376,7 @@ static int cmd_log_walk(struct rev_info *rev)
int saved_dcctc = 0, close_file = rev->diffopt.close_file;
if (rev->early_output)
- setup_early_output(rev);
+ setup_early_output();
if (prepare_revision_walk(rev))
die(_("revision walk setup failed"));
@@ -490,7 +490,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
return cmd_log_walk(&rev);
}
-static void show_tagger(char *buf, int len, struct rev_info *rev)
+static void show_tagger(const char *buf, struct rev_info *rev)
{
struct strbuf out = STRBUF_INIT;
struct pretty_print_context pp = {0};
@@ -517,7 +517,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c
if (get_oid_with_context(the_repository, obj_name,
GET_OID_RECORD_PATH,
&oidc, &obj_context))
- die(_("Not a valid object name %s"), obj_name);
+ die(_("not a valid object name %s"), obj_name);
if (!obj_context.path ||
!textconv_object(the_repository, obj_context.path,
obj_context.mode, &oidc, 1, &buf, &size)) {
@@ -541,16 +541,16 @@ static int show_tag_object(const struct object_id *oid, struct rev_info *rev)
int offset = 0;
if (!buf)
- return error(_("Could not read object %s"), oid_to_hex(oid));
+ return error(_("could not read object %s"), oid_to_hex(oid));
assert(type == OBJ_TAG);
while (offset < size && buf[offset] != '\n') {
int new_offset = offset + 1;
+ const char *ident;
while (new_offset < size && buf[new_offset++] != '\n')
; /* do nothing */
- if (starts_with(buf + offset, "tagger "))
- show_tagger(buf + offset + 7,
- new_offset - offset - 7, rev);
+ if (skip_prefix(buf + offset, "tagger ", &ident))
+ show_tagger(ident, rev);
offset = new_offset;
}
@@ -635,7 +635,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
break;
o = parse_object(the_repository, &t->tagged->oid);
if (!o)
- ret = error(_("Could not read object %s"),
+ ret = error(_("could not read object %s"),
oid_to_hex(&t->tagged->oid));
objects[i].item = o;
i--;
@@ -660,7 +660,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
ret = cmd_log_walk(&rev);
break;
default:
- ret = error(_("Unknown type: %d"), o->type);
+ ret = error(_("unknown type: %d"), o->type);
}
}
free(objects);
@@ -898,7 +898,7 @@ static int open_next_file(struct commit *commit, const char *subject,
printf("%s\n", filename.buf + outdir_offset);
if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) {
- error_errno(_("Cannot open patch file %s"), filename.buf);
+ error_errno(_("cannot open patch file %s"), filename.buf);
strbuf_release(&filename);
return -1;
}
@@ -915,7 +915,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
unsigned flags1, flags2;
if (rev->pending.nr != 2)
- die(_("Need exactly one range."));
+ die(_("need exactly one range"));
o1 = rev->pending.objects[0].item;
o2 = rev->pending.objects[1].item;
@@ -925,7 +925,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
c2 = lookup_commit_reference(the_repository, &o2->oid);
if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
- die(_("Not a range."));
+ die(_("not a range"));
init_patch_ids(the_repository, ids);
@@ -1048,13 +1048,13 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
struct commit *head = list[0];
if (!cmit_fmt_is_mail(rev->commit_format))
- die(_("Cover letter needs email format"));
+ die(_("cover letter needs email format"));
committer = git_committer_info(0);
if (!use_stdout &&
open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
- return;
+ die(_("failed to create cover-letter file"));
log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte, 0);
@@ -1218,7 +1218,7 @@ static int output_directory_callback(const struct option *opt, const char *arg,
const char **dir = (const char **)opt->value;
BUG_ON_OPT_NEG(unset);
if (*dir)
- die(_("Two output directories?"));
+ die(_("two output directories?"));
*dir = arg;
return 0;
}
@@ -1329,7 +1329,7 @@ static struct commit *get_base_commit(const char *base_commit,
if (base_commit && strcmp(base_commit, "auto")) {
base = lookup_commit_reference_by_name(base_commit);
if (!base)
- die(_("Unknown commit %s"), base_commit);
+ die(_("unknown commit %s"), base_commit);
} else if ((base_commit && !strcmp(base_commit, "auto")) || base_auto) {
struct branch *curr_branch = branch_get(NULL);
const char *upstream = branch_get_upstream(curr_branch, NULL);
@@ -1339,18 +1339,18 @@ static struct commit *get_base_commit(const char *base_commit,
struct object_id oid;
if (get_oid(upstream, &oid))
- die(_("Failed to resolve '%s' as a valid ref."), upstream);
+ die(_("failed to resolve '%s' as a valid ref"), upstream);
commit = lookup_commit_or_die(&oid, "upstream base");
base_list = get_merge_bases_many(commit, total, list);
/* There should be one and only one merge base. */
if (!base_list || base_list->next)
- die(_("Could not find exact merge base."));
+ die(_("could not find exact merge base"));
base = base_list->item;
free_commit_list(base_list);
} else {
- die(_("Failed to get upstream, if you want to record base commit automatically,\n"
+ die(_("failed to get upstream, if you want to record base commit automatically,\n"
"please use git branch --set-upstream-to to track a remote branch.\n"
- "Or you could specify base commit by --base=<base-commit-id> manually."));
+ "Or you could specify base commit by --base=<base-commit-id> manually"));
}
}
@@ -1368,7 +1368,7 @@ static struct commit *get_base_commit(const char *base_commit,
struct commit_list *merge_base;
merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]);
if (!merge_base || merge_base->next)
- die(_("Failed to find exact merge base"));
+ die(_("failed to find exact merge base"));
rev[i] = merge_base->item;
}
@@ -1747,7 +1747,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (use_stdout)
die(_("standard output, or directory, which one?"));
if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
- die_errno(_("Could not create directory '%s'"),
+ die_errno(_("could not create directory '%s'"),
output_directory);
}
@@ -1949,7 +1949,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (!use_stdout &&
open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet))
- die(_("Failed to create output files"));
+ die(_("failed to create output files"));
shown = log_tree_commit(&rev, commit);
free_commit_buffer(the_repository->parsed_objects,
commit);
@@ -2073,9 +2073,9 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
revs.max_parents = 1;
if (add_pending_commit(head, &revs, 0))
- die(_("Unknown commit %s"), head);
+ die(_("unknown commit %s"), head);
if (add_pending_commit(upstream, &revs, UNINTERESTING))
- die(_("Unknown commit %s"), upstream);
+ die(_("unknown commit %s"), upstream);
/* Don't say anything if head and upstream are the same. */
if (revs.pending.nr == 2) {
@@ -2087,7 +2087,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
get_patch_ids(&revs, &ids);
if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
- die(_("Unknown commit %s"), limit);
+ die(_("unknown commit %s"), limit);
/* reverse the list of commits */
if (prepare_revision_walk(&revs))
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 29a8762d46..7f83c9a6f2 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -112,11 +112,11 @@ static void print_debug(const struct cache_entry *ce)
if (debug_mode) {
const struct stat_data *sd = &ce->ce_stat_data;
- printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
- printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
- printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
- printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
- printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
+ printf(" ctime: %u:%u\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
+ printf(" mtime: %u:%u\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
+ printf(" dev: %u\tino: %u\n", sd->sd_dev, sd->sd_ino);
+ printf(" uid: %u\tgid: %u\n", sd->sd_uid, sd->sd_gid);
+ printf(" size: %u\tflags: %x\n", sd->sd_size, ce->ce_flags);
}
}
@@ -680,7 +680,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
if (ps_matched) {
int bad;
- bad = report_path_error(ps_matched, &pathspec, prefix);
+ bad = report_path_error(ps_matched, &pathspec);
if (bad)
fprintf(stderr, "Did you forget to 'git add'?\n");
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 1d7f1f5ce2..6ef519514b 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -67,8 +67,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
OPT_BIT(0, "refs", &flags, N_("do not show peeled tags"), REF_NORMAL),
OPT_BOOL(0, "get-url", &get_url,
N_("take url.<base>.insteadOf into account")),
- OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
- N_("field name to sort on"), &parse_opt_ref_sorting),
+ OPT_REF_SORT(sorting_tail),
OPT_SET_INT_F(0, "exit-code", &status,
N_("exit with exit code 2 if no matching refs are found"),
2, PARSE_OPT_NOCOMPLETE),
diff --git a/builtin/merge.c b/builtin/merge.c
index 5ce8946d39..e96f72af80 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -38,6 +38,7 @@
#include "tag.h"
#include "alias.h"
#include "commit-reach.h"
+#include "wt-status.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
@@ -98,6 +99,9 @@ enum ff_type {
static enum ff_type fast_forward = FF_ALLOW;
+static const char *cleanup_arg;
+static enum commit_msg_cleanup_mode cleanup_mode;
+
static int option_parse_message(const struct option *opt,
const char *arg, int unset)
{
@@ -249,6 +253,7 @@ static struct option builtin_merge_options[] = {
N_("perform a commit if the merge succeeds (default)")),
OPT_BOOL('e', "edit", &option_edit,
N_("edit message before committing")),
+ OPT_CLEANUP(&cleanup_arg),
OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW),
OPT_SET_INT_F(0, "ff-only", &fast_forward,
N_("abort if fast-forward is not possible"),
@@ -612,6 +617,8 @@ static int git_merge_config(const char *k, const char *v, void *cb)
return git_config_string(&pull_twohead, k, v);
else if (!strcmp(k, "pull.octopus"))
return git_config_string(&pull_octopus, k, v);
+ else if (!strcmp(k, "commit.cleanup"))
+ return git_config_string(&cleanup_arg, k, v);
else if (!strcmp(k, "merge.renormalize"))
option_renormalize = git_config_bool(k, v);
else if (!strcmp(k, "merge.ff")) {
@@ -800,8 +807,13 @@ static void abort_commit(struct commit_list *remoteheads, const char *err_msg)
static const char merge_editor_comment[] =
N_("Please enter a commit message to explain why this merge is necessary,\n"
"especially if it merges an updated upstream into a topic branch.\n"
- "\n"
- "Lines starting with '%c' will be ignored, and an empty message aborts\n"
+ "\n");
+
+static const char scissors_editor_comment[] =
+N_("An empty message aborts the commit.\n");
+
+static const char no_scissors_editor_comment[] =
+N_("Lines starting with '%c' will be ignored, and an empty message aborts\n"
"the commit.\n");
static void write_merge_heads(struct commit_list *);
@@ -809,11 +821,19 @@ static void prepare_to_commit(struct commit_list *remoteheads)
{
struct strbuf msg = STRBUF_INIT;
strbuf_addbuf(&msg, &merge_msg);
- strbuf_addch(&msg, '\n');
if (squash)
BUG("the control must not reach here under --squash");
- if (0 < option_edit)
- strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char);
+ if (0 < option_edit) {
+ strbuf_addch(&msg, '\n');
+ if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
+ wt_status_append_cut_line(&msg);
+ strbuf_commented_addf(&msg, "\n");
+ }
+ strbuf_commented_addf(&msg, _(merge_editor_comment));
+ strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ?
+ scissors_editor_comment :
+ no_scissors_editor_comment), comment_line_char);
+ }
if (signoff)
append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0);
write_merge_heads(remoteheads);
@@ -832,7 +852,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)
abort_commit(remoteheads, NULL);
read_merge_msg(&msg);
- strbuf_stripspace(&msg, 0 < option_edit);
+ cleanup_message(&msg, cleanup_mode, 0);
if (!msg.len)
abort_commit(remoteheads, _("Empty commit message."));
strbuf_release(&merge_msg);
@@ -880,7 +900,6 @@ static int finish_automerge(struct commit *head,
parents = remoteheads;
if (!head_subsumed || fast_forward == FF_NO)
commit_list_insert(head, &parents);
- strbuf_addch(&merge_msg, '\n');
prepare_to_commit(remoteheads);
if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
&result_commit, NULL, sign_commit))
@@ -901,7 +920,15 @@ static int suggest_conflicts(void)
filename = git_path_merge_msg(the_repository);
fp = xfopen(filename, "a");
- append_conflicts_hint(&the_index, &msgbuf);
+ /*
+ * We can't use cleanup_mode because if we're not using the editor,
+ * get_cleanup_mode will return COMMIT_MSG_CLEANUP_SPACE instead, even
+ * though the message is meant to be processed later by git-commit.
+ * Thus, we will get the cleanup mode which is returned when we _are_
+ * using an editor.
+ */
+ append_conflicts_hint(&the_index, &msgbuf,
+ get_cleanup_mode(cleanup_arg, 1));
fputs(msgbuf.buf, fp);
strbuf_release(&msgbuf);
fclose(fp);
@@ -1301,6 +1328,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
resolve_undo_clear();
+ if (option_edit < 0)
+ option_edit = default_edit_option();
+
+ cleanup_mode = get_cleanup_mode(cleanup_arg, 0 < option_edit);
+
if (verbosity < 0)
show_diffstat = 0;
@@ -1386,9 +1418,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
fast_forward = FF_NO;
}
- if (option_edit < 0)
- option_edit = default_edit_option();
-
if (!use_strategies) {
if (!remoteheads)
; /* already up-to-date */
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c
index fca70f8e4f..ae6e476ad5 100644
--- a/builtin/multi-pack-index.c
+++ b/builtin/multi-pack-index.c
@@ -3,6 +3,7 @@
#include "config.h"
#include "parse-options.h"
#include "midx.h"
+#include "trace2.h"
static char const * const builtin_multi_pack_index_usage[] = {
N_("git multi-pack-index [--object-dir=<dir>] (write|verify)"),
@@ -40,6 +41,8 @@ int cmd_multi_pack_index(int argc, const char **argv,
return 1;
}
+ trace2_cmd_mode(argv[0]);
+
if (!strcmp(argv[0], "write"))
return write_midx_file(opts.object_dir);
if (!strcmp(argv[0], "verify"))
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index f1cb45c227..05ccf53e00 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -361,23 +361,25 @@ static char const * const name_rev_usage[] = {
static void name_rev_line(char *p, struct name_ref_data *data)
{
struct strbuf buf = STRBUF_INIT;
- int forty = 0;
+ int counter = 0;
char *p_start;
+ const unsigned hexsz = the_hash_algo->hexsz;
+
for (p_start = p; *p; p++) {
#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
if (!ishex(*p))
- forty = 0;
- else if (++forty == GIT_SHA1_HEXSZ &&
+ counter = 0;
+ else if (++counter == hexsz &&
!ishex(*(p+1))) {
struct object_id oid;
const char *name = NULL;
char c = *(p+1);
int p_len = p - p_start + 1;
- forty = 0;
+ counter = 0;
*(p+1) = 0;
- if (!get_oid(p - (GIT_SHA1_HEXSZ - 1), &oid)) {
+ if (!get_oid(p - (hexsz - 1), &oid)) {
struct object *o =
lookup_object(the_repository,
oid.hash);
@@ -390,7 +392,7 @@ static void name_rev_line(char *p, struct name_ref_data *data)
continue;
if (data->name_only)
- printf("%.*s%s", p_len - GIT_SHA1_HEXSZ, p_start, name);
+ printf("%.*s%s", p_len - hexsz, p_start, name);
else
printf("%.*s (%s)", p_len, p_start, name);
p_start = p + 1;
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index a154fc29f6..9f424aabab 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -97,7 +97,7 @@ static off_t reuse_packfile_offset;
static int use_bitmap_index_default = 1;
static int use_bitmap_index = -1;
static int write_bitmap_index;
-static uint16_t write_bitmap_options;
+static uint16_t write_bitmap_options = BITMAP_OPT_HASH_CACHE;
static int exclude_promisor_objects;
@@ -964,6 +964,8 @@ static void write_pack_file(void)
if (written != nr_result)
die(_("wrote %"PRIu32" objects while expecting %"PRIu32),
written, nr_result);
+ trace2_data_intmax("pack-objects", the_repository,
+ "write_pack_file/wrote", nr_result);
}
static int no_try_delta(const char *path)
@@ -1487,6 +1489,7 @@ static int can_reuse_delta(const unsigned char *base_sha1,
struct object_entry **base_out)
{
struct object_entry *base;
+ struct object_id base_oid;
if (!base_sha1)
return 0;
@@ -1508,10 +1511,9 @@ static int can_reuse_delta(const unsigned char *base_sha1,
* even if it was buried too deep in history to make it into the
* packing list.
*/
- if (thin && bitmap_has_sha1_in_uninteresting(bitmap_git, base_sha1)) {
+ oidread(&base_oid, base_sha1);
+ if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, &base_oid)) {
if (use_delta_islands) {
- struct object_id base_oid;
- hashcpy(base_oid.hash, base_sha1);
if (!in_same_island(&delta->idx.oid, &base_oid))
return 0;
}
diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c
index 68c1e547c2..178e3409b7 100644
--- a/builtin/pack-redundant.c
+++ b/builtin/pack-redundant.c
@@ -641,7 +641,7 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix)
pl = red = pack_list_difference(local_packs, min);
while (pl) {
printf("%s\n%s\n",
- sha1_pack_index_name(pl->pack->sha1),
+ sha1_pack_index_name(pl->pack->hash),
pl->pack->pack_name);
pl = pl->next;
}
diff --git a/builtin/pull.c b/builtin/pull.c
index 33db889955..9dd32a115b 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -24,6 +24,7 @@
#include "lockfile.h"
#include "wt-status.h"
#include "commit-reach.h"
+#include "sequencer.h"
enum rebase_type {
REBASE_INVALID = -1,
@@ -101,6 +102,7 @@ static char *opt_signoff;
static char *opt_squash;
static char *opt_commit;
static char *opt_edit;
+static char *cleanup_arg;
static char *opt_ff;
static char *opt_verify_signatures;
static int opt_autostash = -1;
@@ -168,6 +170,7 @@ static struct option pull_options[] = {
OPT_PASSTHRU(0, "edit", &opt_edit, NULL,
N_("edit message before committing"),
PARSE_OPT_NOARG),
+ OPT_CLEANUP(&cleanup_arg),
OPT_PASSTHRU(0, "ff", &opt_ff, NULL,
N_("allow fast-forward"),
PARSE_OPT_NOARG),
@@ -369,9 +372,10 @@ static void get_merge_heads(struct oid_array *merge_heads)
fp = xfopen(filename, "r");
while (strbuf_getline_lf(&sb, fp) != EOF) {
- if (get_oid_hex(sb.buf, &oid))
- continue; /* invalid line: does not start with SHA1 */
- if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t"))
+ const char *p;
+ if (parse_oid_hex(sb.buf, &oid, &p))
+ continue; /* invalid line: does not start with object ID */
+ if (starts_with(p, "\tnot-for-merge\t"))
continue; /* ref is not-for-merge */
oid_array_append(merge_heads, &oid);
}
@@ -644,6 +648,8 @@ static int run_merge(void)
argv_array_push(&args, opt_commit);
if (opt_edit)
argv_array_push(&args, opt_edit);
+ if (cleanup_arg)
+ argv_array_pushf(&args, "--cleanup=%s", cleanup_arg);
if (opt_ff)
argv_array_push(&args, opt_ff);
if (opt_verify_signatures)
@@ -760,7 +766,7 @@ static int get_rebase_fork_point(struct object_id *fork_point, const char *repo,
cp.no_stderr = 1;
cp.git_cmd = 1;
- ret = capture_command(&cp, &sb, GIT_SHA1_HEXSZ);
+ ret = capture_command(&cp, &sb, GIT_MAX_HEXSZ);
if (ret)
goto cleanup;
@@ -805,7 +811,7 @@ static int get_octopus_merge_base(struct object_id *merge_base,
}
/**
- * Given the current HEAD SHA1, the merge head returned from git-fetch and the
+ * Given the current HEAD oid, the merge head returned from git-fetch and the
* fork point calculated by get_rebase_fork_point(), runs git-rebase with the
* appropriate arguments and returns its exit status.
*/
@@ -875,6 +881,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
+ if (cleanup_arg)
+ /*
+ * this only checks the validity of cleanup_arg; we don't need
+ * a valid value for use_editor
+ */
+ get_cleanup_mode(cleanup_arg, 0);
+
parse_repo_refspecs(argc, argv, &repo, &refspecs);
if (!opt_ff)
diff --git a/builtin/range-diff.c b/builtin/range-diff.c
index f01a0be851..784bd19321 100644
--- a/builtin/range-diff.c
+++ b/builtin/range-diff.c
@@ -16,42 +16,27 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT;
struct diff_options diffopt = { NULL };
int simple_color = -1;
- struct option options[] = {
+ struct option range_diff_options[] = {
OPT_INTEGER(0, "creation-factor", &creation_factor,
N_("Percentage by which creation is weighted")),
OPT_BOOL(0, "no-dual-color", &simple_color,
N_("use simple diff colors")),
OPT_END()
};
- int i, j, res = 0;
+ struct option *options;
+ int res = 0;
struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT;
git_config(git_diff_ui_config, NULL);
repo_diff_setup(the_repository, &diffopt);
+ options = parse_options_concat(range_diff_options, diffopt.parseopts);
argc = parse_options(argc, argv, NULL, options,
- builtin_range_diff_usage, PARSE_OPT_KEEP_UNKNOWN |
- PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0);
-
- for (i = j = 1; i < argc && strcmp("--", argv[i]); ) {
- int c = diff_opt_parse(&diffopt, argv + i, argc - i, prefix);
+ builtin_range_diff_usage, 0);
- if (!c)
- argv[j++] = argv[i++];
- else
- i += c;
- }
- while (i < argc)
- argv[j++] = argv[i++];
- argc = j;
diff_setup_done(&diffopt);
- /* Make sure that there are no unparsed options */
- argc = parse_options(argc, argv, NULL,
- options + ARRAY_SIZE(options) - 1, /* OPT_END */
- builtin_range_diff_usage, 0);
-
/* force color when --dual-color was used */
if (!simple_color)
diffopt.use_color = 1;
@@ -90,6 +75,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix)
error(_("need two commit ranges"));
usage_with_options(builtin_range_diff_usage, options);
}
+ FREE_AND_NULL(options);
res = show_range_diff(range1.buf, range2.buf, creation_factor,
simple_color < 1, &diffopt);
diff --git a/builtin/read-tree.c b/builtin/read-tree.c
index 9083dcfa28..5c9c082595 100644
--- a/builtin/read-tree.c
+++ b/builtin/read-tree.c
@@ -154,6 +154,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
{ OPTION_CALLBACK, 0, "recurse-submodules", NULL,
"checkout", "control recursive updating of submodules",
PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
+ OPT__QUIET(&opts.quiet, N_("suppress feedback messages")),
OPT_END()
};
diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c
deleted file mode 100644
index 888390f911..0000000000
--- a/builtin/rebase--interactive.c
+++ /dev/null
@@ -1,275 +0,0 @@
-#define USE_THE_INDEX_COMPATIBILITY_MACROS
-#include "builtin.h"
-#include "cache.h"
-#include "config.h"
-#include "parse-options.h"
-#include "sequencer.h"
-#include "rebase-interactive.h"
-#include "argv-array.h"
-#include "refs.h"
-#include "rerere.h"
-#include "run-command.h"
-
-static GIT_PATH_FUNC(path_state_dir, "rebase-merge/")
-static GIT_PATH_FUNC(path_squash_onto, "rebase-merge/squash-onto")
-static GIT_PATH_FUNC(path_interactive, "rebase-merge/interactive")
-
-static int get_revision_ranges(const char *upstream, const char *onto,
- const char **head_hash,
- char **revisions, char **shortrevisions)
-{
- const char *base_rev = upstream ? upstream : onto, *shorthead;
- struct object_id orig_head;
-
- if (get_oid("HEAD", &orig_head))
- return error(_("no HEAD?"));
-
- *head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ);
- *revisions = xstrfmt("%s...%s", base_rev, *head_hash);
-
- shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV);
-
- if (upstream) {
- const char *shortrev;
- struct object_id rev_oid;
-
- get_oid(base_rev, &rev_oid);
- shortrev = find_unique_abbrev(&rev_oid, DEFAULT_ABBREV);
-
- *shortrevisions = xstrfmt("%s..%s", shortrev, shorthead);
- } else
- *shortrevisions = xstrdup(shorthead);
-
- return 0;
-}
-
-static int init_basic_state(struct replay_opts *opts, const char *head_name,
- const char *onto, const char *orig_head)
-{
- FILE *interactive;
-
- if (!is_directory(path_state_dir()) && mkdir_in_gitdir(path_state_dir()))
- return error_errno(_("could not create temporary %s"), path_state_dir());
-
- delete_reflog("REBASE_HEAD");
-
- interactive = fopen(path_interactive(), "w");
- if (!interactive)
- return error_errno(_("could not mark as interactive"));
- fclose(interactive);
-
- return write_basic_state(opts, head_name, onto, orig_head);
-}
-
-static int do_interactive_rebase(struct replay_opts *opts, unsigned flags,
- const char *switch_to, const char *upstream,
- const char *onto, const char *onto_name,
- const char *squash_onto, const char *head_name,
- const char *restrict_revision, char *raw_strategies,
- const char *cmd, unsigned autosquash)
-{
- int ret;
- const char *head_hash = NULL;
- char *revisions = NULL, *shortrevisions = NULL;
- struct argv_array make_script_args = ARGV_ARRAY_INIT;
- FILE *todo_list;
-
- if (prepare_branch_to_be_rebased(opts, switch_to))
- return -1;
-
- if (get_revision_ranges(upstream, onto, &head_hash,
- &revisions, &shortrevisions))
- return -1;
-
- if (raw_strategies)
- parse_strategy_opts(opts, raw_strategies);
-
- if (init_basic_state(opts, head_name, onto, head_hash)) {
- free(revisions);
- free(shortrevisions);
-
- return -1;
- }
-
- if (!upstream && squash_onto)
- write_file(path_squash_onto(), "%s\n", squash_onto);
-
- todo_list = fopen(rebase_path_todo(), "w");
- if (!todo_list) {
- free(revisions);
- free(shortrevisions);
-
- return error_errno(_("could not open %s"), rebase_path_todo());
- }
-
- argv_array_pushl(&make_script_args, "", revisions, NULL);
- if (restrict_revision)
- argv_array_push(&make_script_args, restrict_revision);
-
- ret = sequencer_make_script(the_repository, todo_list,
- make_script_args.argc, make_script_args.argv,
- flags);
- fclose(todo_list);
-
- if (ret)
- error(_("could not generate todo list"));
- else {
- discard_cache();
- ret = complete_action(the_repository, opts, flags,
- shortrevisions, onto_name, onto,
- head_hash, cmd, autosquash);
- }
-
- free(revisions);
- free(shortrevisions);
- argv_array_clear(&make_script_args);
-
- return ret;
-}
-
-static const char * const builtin_rebase_interactive_usage[] = {
- N_("git rebase--interactive [<options>]"),
- NULL
-};
-
-int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
-{
- struct replay_opts opts = REPLAY_OPTS_INIT;
- unsigned flags = 0, keep_empty = 0, rebase_merges = 0, autosquash = 0;
- int abbreviate_commands = 0, rebase_cousins = -1, ret = 0;
- const char *onto = NULL, *onto_name = NULL, *restrict_revision = NULL,
- *squash_onto = NULL, *upstream = NULL, *head_name = NULL,
- *switch_to = NULL, *cmd = NULL;
- char *raw_strategies = NULL;
- enum {
- NONE = 0, CONTINUE, SKIP, EDIT_TODO, SHOW_CURRENT_PATCH,
- SHORTEN_OIDS, EXPAND_OIDS, CHECK_TODO_LIST, REARRANGE_SQUASH, ADD_EXEC
- } 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_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
- N_("allow commits with empty messages")),
- OPT_BOOL(0, "rebase-merges", &rebase_merges, N_("rebase merge commits")),
- OPT_BOOL(0, "rebase-cousins", &rebase_cousins,
- N_("keep original branch points of cousins")),
- OPT_BOOL(0, "autosquash", &autosquash,
- N_("move commits that begin with squash!/fixup!")),
- OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")),
- OPT__VERBOSE(&opts.verbose, N_("be verbose")),
- OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
- CONTINUE),
- OPT_CMDMODE(0, "skip", &command, N_("skip commit"), SKIP),
- OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"),
- EDIT_TODO),
- OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"),
- SHOW_CURRENT_PATCH),
- OPT_CMDMODE(0, "shorten-ids", &command,
- N_("shorten commit ids in the todo list"), SHORTEN_OIDS),
- OPT_CMDMODE(0, "expand-ids", &command,
- N_("expand commit ids in the todo list"), EXPAND_OIDS),
- OPT_CMDMODE(0, "check-todo-list", &command,
- N_("check the todo list"), CHECK_TODO_LIST),
- OPT_CMDMODE(0, "rearrange-squash", &command,
- N_("rearrange fixup/squash lines"), REARRANGE_SQUASH),
- OPT_CMDMODE(0, "add-exec-commands", &command,
- N_("insert exec commands in todo list"), ADD_EXEC),
- OPT_STRING(0, "onto", &onto, N_("onto"), N_("onto")),
- OPT_STRING(0, "restrict-revision", &restrict_revision,
- N_("restrict-revision"), N_("restrict revision")),
- OPT_STRING(0, "squash-onto", &squash_onto, N_("squash-onto"),
- N_("squash onto")),
- OPT_STRING(0, "upstream", &upstream, N_("upstream"),
- N_("the upstream commit")),
- OPT_STRING(0, "head-name", &head_name, N_("head-name"), N_("head name")),
- { OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign, N_("key-id"),
- N_("GPG-sign commits"),
- PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
- OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"),
- N_("rebase strategy")),
- OPT_STRING(0, "strategy-opts", &raw_strategies, N_("strategy-opts"),
- N_("strategy options")),
- OPT_STRING(0, "switch-to", &switch_to, N_("switch-to"),
- N_("the branch or commit to checkout")),
- OPT_STRING(0, "onto-name", &onto_name, N_("onto-name"), N_("onto name")),
- OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")),
- OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto),
- OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec,
- N_("automatically re-schedule any `exec` that fails")),
- OPT_END()
- };
-
- sequencer_init_config(&opts);
- git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
-
- opts.action = REPLAY_INTERACTIVE_REBASE;
- opts.allow_ff = 1;
- opts.allow_empty = 1;
-
- if (argc == 1)
- usage_with_options(builtin_rebase_interactive_usage, options);
-
- argc = parse_options(argc, argv, NULL, options,
- builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
-
- opts.gpg_sign = xstrdup_or_null(opts.gpg_sign);
-
- flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
- flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
- flags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
- flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
- flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
-
- if (rebase_cousins >= 0 && !rebase_merges)
- warning(_("--[no-]rebase-cousins has no effect without "
- "--rebase-merges"));
-
- switch (command) {
- case NONE:
- if (!onto && !upstream)
- die(_("a base commit must be provided with --upstream or --onto"));
-
- ret = do_interactive_rebase(&opts, flags, switch_to, upstream, onto,
- onto_name, squash_onto, head_name, restrict_revision,
- raw_strategies, cmd, autosquash);
- break;
- case SKIP: {
- struct string_list merge_rr = STRING_LIST_INIT_DUP;
-
- rerere_clear(the_repository, &merge_rr);
- /* fallthrough */
- case CONTINUE:
- ret = sequencer_continue(the_repository, &opts);
- break;
- }
- case EDIT_TODO:
- ret = edit_todo_list(the_repository, flags);
- break;
- case SHOW_CURRENT_PATCH: {
- struct child_process cmd = CHILD_PROCESS_INIT;
-
- cmd.git_cmd = 1;
- argv_array_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL);
- ret = run_command(&cmd);
-
- break;
- }
- case SHORTEN_OIDS:
- case EXPAND_OIDS:
- ret = transform_todos(the_repository, flags);
- break;
- case CHECK_TODO_LIST:
- ret = check_todo_list(the_repository);
- break;
- case REARRANGE_SQUASH:
- ret = rearrange_squash(the_repository);
- break;
- case ADD_EXEC:
- ret = sequencer_add_exec_commands(the_repository, cmd);
- break;
- default:
- BUG("invalid command '%d'", command);
- }
-
- return !!ret;
-}
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 77deebc65c..ba3a574e40 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -25,6 +25,8 @@
#include "commit-reach.h"
#include "rerere.h"
#include "branch.h"
+#include "sequencer.h"
+#include "rebase-interactive.h"
static char const * const builtin_rebase_usage[] = {
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
@@ -35,6 +37,8 @@ static char const * const builtin_rebase_usage[] = {
NULL
};
+static GIT_PATH_FUNC(path_squash_onto, "rebase-merge/squash-onto")
+static GIT_PATH_FUNC(path_interactive, "rebase-merge/interactive")
static GIT_PATH_FUNC(apply_dir, "rebase-apply")
static GIT_PATH_FUNC(merge_dir, "rebase-merge")
@@ -46,29 +50,6 @@ enum rebase_type {
REBASE_PRESERVE_MERGES
};
-static int use_builtin_rebase(void)
-{
- struct child_process cp = CHILD_PROCESS_INIT;
- struct strbuf out = STRBUF_INIT;
- int ret, env = git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1);
-
- if (env != -1)
- return env;
-
- argv_array_pushl(&cp.args,
- "config", "--bool", "rebase.usebuiltin", NULL);
- cp.git_cmd = 1;
- if (capture_command(&cp, &out, 6)) {
- strbuf_release(&out);
- return 1;
- }
-
- strbuf_trim(&out);
- ret = !strcmp("true", out.buf);
- strbuf_release(&out);
- return ret;
-}
-
struct rebase_options {
enum rebase_type type;
const char *state_dir;
@@ -106,8 +87,440 @@ struct rebase_options {
char *strategy, *strategy_opts;
struct strbuf git_format_patch_opt;
int reschedule_failed_exec;
+ int use_legacy_rebase;
+};
+
+#define REBASE_OPTIONS_INIT { \
+ .type = REBASE_UNSPECIFIED, \
+ .flags = REBASE_NO_QUIET, \
+ .git_am_opts = ARGV_ARRAY_INIT, \
+ .git_format_patch_opt = STRBUF_INIT \
+ }
+
+static struct replay_opts get_replay_opts(const struct rebase_options *opts)
+{
+ struct replay_opts replay = REPLAY_OPTS_INIT;
+
+ replay.action = REPLAY_INTERACTIVE_REBASE;
+ sequencer_init_config(&replay);
+
+ replay.signoff = opts->signoff;
+ replay.allow_ff = !(opts->flags & REBASE_FORCE);
+ if (opts->allow_rerere_autoupdate)
+ replay.allow_rerere_auto = opts->allow_rerere_autoupdate;
+ replay.allow_empty = 1;
+ replay.allow_empty_message = opts->allow_empty_message;
+ replay.verbose = opts->flags & REBASE_VERBOSE;
+ replay.reschedule_failed_exec = opts->reschedule_failed_exec;
+ replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
+ replay.strategy = opts->strategy;
+ if (opts->strategy_opts)
+ parse_strategy_opts(&replay, opts->strategy_opts);
+
+ return replay;
+}
+
+enum action {
+ ACTION_NONE = 0,
+ ACTION_CONTINUE,
+ ACTION_SKIP,
+ ACTION_ABORT,
+ ACTION_QUIT,
+ ACTION_EDIT_TODO,
+ ACTION_SHOW_CURRENT_PATCH,
+ ACTION_SHORTEN_OIDS,
+ ACTION_EXPAND_OIDS,
+ ACTION_CHECK_TODO_LIST,
+ ACTION_REARRANGE_SQUASH,
+ ACTION_ADD_EXEC
+};
+
+static const char *action_names[] = { "undefined",
+ "continue",
+ "skip",
+ "abort",
+ "quit",
+ "edit_todo",
+ "show_current_patch" };
+
+static int add_exec_commands(struct string_list *commands)
+{
+ const char *todo_file = rebase_path_todo();
+ struct todo_list todo_list = TODO_LIST_INIT;
+ int res;
+
+ if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+ return error_errno(_("could not read '%s'."), todo_file);
+
+ if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
+ &todo_list)) {
+ todo_list_release(&todo_list);
+ return error(_("unusable todo list: '%s'"), todo_file);
+ }
+
+ todo_list_add_exec_commands(&todo_list, commands);
+ res = todo_list_write_to_file(the_repository, &todo_list,
+ todo_file, NULL, NULL, -1, 0);
+ todo_list_release(&todo_list);
+
+ if (res)
+ return error_errno(_("could not write '%s'."), todo_file);
+ return 0;
+}
+
+static int rearrange_squash_in_todo_file(void)
+{
+ const char *todo_file = rebase_path_todo();
+ struct todo_list todo_list = TODO_LIST_INIT;
+ int res = 0;
+
+ if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+ return error_errno(_("could not read '%s'."), todo_file);
+ if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
+ &todo_list)) {
+ todo_list_release(&todo_list);
+ return error(_("unusable todo list: '%s'"), todo_file);
+ }
+
+ res = todo_list_rearrange_squash(&todo_list);
+ if (!res)
+ res = todo_list_write_to_file(the_repository, &todo_list,
+ todo_file, NULL, NULL, -1, 0);
+
+ todo_list_release(&todo_list);
+
+ if (res)
+ return error_errno(_("could not write '%s'."), todo_file);
+ return 0;
+}
+
+static int transform_todo_file(unsigned flags)
+{
+ const char *todo_file = rebase_path_todo();
+ struct todo_list todo_list = TODO_LIST_INIT;
+ int res;
+
+ if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+ return error_errno(_("could not read '%s'."), todo_file);
+
+ if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
+ &todo_list)) {
+ todo_list_release(&todo_list);
+ return error(_("unusable todo list: '%s'"), todo_file);
+ }
+
+ res = todo_list_write_to_file(the_repository, &todo_list, todo_file,
+ NULL, NULL, -1, flags);
+ todo_list_release(&todo_list);
+
+ if (res)
+ return error_errno(_("could not write '%s'."), todo_file);
+ return 0;
+}
+
+static int edit_todo_file(unsigned flags)
+{
+ const char *todo_file = rebase_path_todo();
+ struct todo_list todo_list = TODO_LIST_INIT,
+ new_todo = TODO_LIST_INIT;
+ int res = 0;
+
+ if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
+ return error_errno(_("could not read '%s'."), todo_file);
+
+ strbuf_stripspace(&todo_list.buf, 1);
+ res = edit_todo_list(the_repository, &todo_list, &new_todo, NULL, NULL, flags);
+ if (!res && todo_list_write_to_file(the_repository, &new_todo, todo_file,
+ NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS)))
+ res = error_errno(_("could not write '%s'"), todo_file);
+
+ todo_list_release(&todo_list);
+ todo_list_release(&new_todo);
+
+ return res;
+}
+
+static int get_revision_ranges(struct commit *upstream, struct commit *onto,
+ const char **head_hash,
+ char **revisions, char **shortrevisions)
+{
+ struct commit *base_rev = upstream ? upstream : onto;
+ const char *shorthead;
+ struct object_id orig_head;
+
+ if (get_oid("HEAD", &orig_head))
+ return error(_("no HEAD?"));
+
+ *head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ);
+ *revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid),
+ *head_hash);
+
+ shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV);
+
+ if (upstream) {
+ const char *shortrev;
+
+ shortrev = find_unique_abbrev(&base_rev->object.oid,
+ DEFAULT_ABBREV);
+
+ *shortrevisions = xstrfmt("%s..%s", shortrev, shorthead);
+ } else
+ *shortrevisions = xstrdup(shorthead);
+
+ return 0;
+}
+
+static int init_basic_state(struct replay_opts *opts, const char *head_name,
+ struct commit *onto, const char *orig_head)
+{
+ FILE *interactive;
+
+ if (!is_directory(merge_dir()) && mkdir_in_gitdir(merge_dir()))
+ return error_errno(_("could not create temporary %s"), merge_dir());
+
+ delete_reflog("REBASE_HEAD");
+
+ interactive = fopen(path_interactive(), "w");
+ if (!interactive)
+ return error_errno(_("could not mark as interactive"));
+ fclose(interactive);
+
+ return write_basic_state(opts, head_name, onto, orig_head);
+}
+
+static void split_exec_commands(const char *cmd, struct string_list *commands)
+{
+ if (cmd && *cmd) {
+ string_list_split(commands, cmd, '\n', -1);
+
+ /* rebase.c adds a new line to cmd after every command,
+ * so here the last command is always empty */
+ string_list_remove_empty_items(commands, 0);
+ }
+}
+
+static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)
+{
+ int ret;
+ const char *head_hash = NULL;
+ char *revisions = NULL, *shortrevisions = NULL;
+ struct argv_array make_script_args = ARGV_ARRAY_INIT;
+ struct todo_list todo_list = TODO_LIST_INIT;
+ struct replay_opts replay = get_replay_opts(opts);
+ struct string_list commands = STRING_LIST_INIT_DUP;
+
+ if (prepare_branch_to_be_rebased(the_repository, &replay,
+ opts->switch_to))
+ return -1;
+
+ if (get_revision_ranges(opts->upstream, opts->onto, &head_hash,
+ &revisions, &shortrevisions))
+ return -1;
+
+ if (init_basic_state(&replay,
+ opts->head_name ? opts->head_name : "detached HEAD",
+ opts->onto, head_hash)) {
+ free(revisions);
+ free(shortrevisions);
+
+ return -1;
+ }
+
+ if (!opts->upstream && opts->squash_onto)
+ write_file(path_squash_onto(), "%s\n",
+ oid_to_hex(opts->squash_onto));
+
+ argv_array_pushl(&make_script_args, "", revisions, NULL);
+ if (opts->restrict_revision)
+ argv_array_push(&make_script_args,
+ oid_to_hex(&opts->restrict_revision->object.oid));
+
+ ret = sequencer_make_script(the_repository, &todo_list.buf,
+ make_script_args.argc, make_script_args.argv,
+ flags);
+
+ if (ret)
+ error(_("could not generate todo list"));
+ else {
+ discard_cache();
+ if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf,
+ &todo_list))
+ BUG("unusable todo list");
+
+ split_exec_commands(opts->cmd, &commands);
+ ret = complete_action(the_repository, &replay, flags,
+ shortrevisions, opts->onto_name, opts->onto, head_hash,
+ &commands, opts->autosquash, &todo_list);
+ }
+
+ string_list_clear(&commands, 0);
+ free(revisions);
+ free(shortrevisions);
+ todo_list_release(&todo_list);
+ argv_array_clear(&make_script_args);
+
+ return ret;
+}
+
+static int run_rebase_interactive(struct rebase_options *opts,
+ enum action command)
+{
+ unsigned flags = 0;
+ int abbreviate_commands = 0, ret = 0;
+
+ git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
+
+ flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0;
+ flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0;
+ flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0;
+ flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0;
+ flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0;
+
+ switch (command) {
+ case ACTION_NONE: {
+ if (!opts->onto && !opts->upstream)
+ die(_("a base commit must be provided with --upstream or --onto"));
+
+ ret = do_interactive_rebase(opts, flags);
+ break;
+ }
+ case ACTION_SKIP: {
+ struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+ rerere_clear(the_repository, &merge_rr);
+ }
+ /* fallthrough */
+ case ACTION_CONTINUE: {
+ struct replay_opts replay_opts = get_replay_opts(opts);
+
+ ret = sequencer_continue(the_repository, &replay_opts);
+ break;
+ }
+ case ACTION_EDIT_TODO:
+ ret = edit_todo_file(flags);
+ break;
+ case ACTION_SHOW_CURRENT_PATCH: {
+ struct child_process cmd = CHILD_PROCESS_INIT;
+
+ cmd.git_cmd = 1;
+ argv_array_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL);
+ ret = run_command(&cmd);
+
+ break;
+ }
+ case ACTION_SHORTEN_OIDS:
+ case ACTION_EXPAND_OIDS:
+ ret = transform_todo_file(flags);
+ break;
+ case ACTION_CHECK_TODO_LIST:
+ ret = check_todo_list_from_file(the_repository);
+ break;
+ case ACTION_REARRANGE_SQUASH:
+ ret = rearrange_squash_in_todo_file();
+ break;
+ case ACTION_ADD_EXEC: {
+ struct string_list commands = STRING_LIST_INIT_DUP;
+
+ split_exec_commands(opts->cmd, &commands);
+ ret = add_exec_commands(&commands);
+ string_list_clear(&commands, 0);
+ break;
+ }
+ default:
+ BUG("invalid command '%d'", command);
+ }
+
+ return ret;
+}
+
+static const char * const builtin_rebase_interactive_usage[] = {
+ N_("git rebase--interactive [<options>]"),
+ NULL
};
+int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)
+{
+ struct rebase_options opts = REBASE_OPTIONS_INIT;
+ struct object_id squash_onto = null_oid;
+ enum action command = ACTION_NONE;
+ struct option options[] = {
+ OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"),
+ REBASE_FORCE),
+ OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")),
+ OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message,
+ N_("allow commits with empty messages")),
+ OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")),
+ OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins,
+ N_("keep original branch points of cousins")),
+ OPT_BOOL(0, "autosquash", &opts.autosquash,
+ N_("move commits that begin with squash!/fixup!")),
+ OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")),
+ OPT_BIT('v', "verbose", &opts.flags,
+ N_("display a diffstat of what changed upstream"),
+ REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
+ OPT_CMDMODE(0, "continue", &command, N_("continue rebase"),
+ ACTION_CONTINUE),
+ OPT_CMDMODE(0, "skip", &command, N_("skip commit"), ACTION_SKIP),
+ OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"),
+ ACTION_EDIT_TODO),
+ OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"),
+ ACTION_SHOW_CURRENT_PATCH),
+ OPT_CMDMODE(0, "shorten-ids", &command,
+ N_("shorten commit ids in the todo list"), ACTION_SHORTEN_OIDS),
+ OPT_CMDMODE(0, "expand-ids", &command,
+ N_("expand commit ids in the todo list"), ACTION_EXPAND_OIDS),
+ OPT_CMDMODE(0, "check-todo-list", &command,
+ N_("check the todo list"), ACTION_CHECK_TODO_LIST),
+ OPT_CMDMODE(0, "rearrange-squash", &command,
+ N_("rearrange fixup/squash lines"), ACTION_REARRANGE_SQUASH),
+ OPT_CMDMODE(0, "add-exec-commands", &command,
+ N_("insert exec commands in todo list"), ACTION_ADD_EXEC),
+ { OPTION_CALLBACK, 0, "onto", &opts.onto, N_("onto"), N_("onto"),
+ PARSE_OPT_NONEG, parse_opt_commit, 0 },
+ { OPTION_CALLBACK, 0, "restrict-revision", &opts.restrict_revision,
+ N_("restrict-revision"), N_("restrict revision"),
+ PARSE_OPT_NONEG, parse_opt_commit, 0 },
+ { OPTION_CALLBACK, 0, "squash-onto", &squash_onto, N_("squash-onto"),
+ N_("squash onto"), PARSE_OPT_NONEG, parse_opt_object_id, 0 },
+ { OPTION_CALLBACK, 0, "upstream", &opts.upstream, N_("upstream"),
+ N_("the upstream commit"), PARSE_OPT_NONEG, parse_opt_commit,
+ 0 },
+ OPT_STRING(0, "head-name", &opts.head_name, N_("head-name"), N_("head name")),
+ { OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign_opt, N_("key-id"),
+ N_("GPG-sign commits"),
+ PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+ OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"),
+ N_("rebase strategy")),
+ OPT_STRING(0, "strategy-opts", &opts.strategy_opts, N_("strategy-opts"),
+ N_("strategy options")),
+ OPT_STRING(0, "switch-to", &opts.switch_to, N_("switch-to"),
+ N_("the branch or commit to checkout")),
+ OPT_STRING(0, "onto-name", &opts.onto_name, N_("onto-name"), N_("onto name")),
+ OPT_STRING(0, "cmd", &opts.cmd, N_("cmd"), N_("the command to run")),
+ OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_autoupdate),
+ OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec,
+ N_("automatically re-schedule any `exec` that fails")),
+ OPT_END()
+ };
+
+ opts.rebase_cousins = -1;
+
+ if (argc == 1)
+ usage_with_options(builtin_rebase_interactive_usage, options);
+
+ argc = parse_options(argc, argv, NULL, options,
+ builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0);
+
+ if (!is_null_oid(&squash_onto))
+ opts.squash_onto = &squash_onto;
+
+ if (opts.rebase_cousins >= 0 && !opts.rebase_merges)
+ warning(_("--[no-]rebase-cousins has no effect without "
+ "--rebase-merges"));
+
+ return !!run_rebase_interactive(&opts, command);
+}
+
static int is_interactive(struct rebase_options *opts)
{
return opts->type == REBASE_INTERACTIVE ||
@@ -206,14 +619,13 @@ static int read_basic_state(struct rebase_options *opts)
&buf))
return -1;
if (!strcmp(buf.buf, "--rerere-autoupdate"))
- opts->allow_rerere_autoupdate = 1;
+ opts->allow_rerere_autoupdate = RERERE_AUTOUPDATE;
else if (!strcmp(buf.buf, "--no-rerere-autoupdate"))
- opts->allow_rerere_autoupdate = 0;
+ opts->allow_rerere_autoupdate = RERERE_NOAUTOUPDATE;
else
warning(_("ignoring invalid allow_rerere_autoupdate: "
"'%s'"), buf.buf);
- } else
- opts->allow_rerere_autoupdate = -1;
+ }
if (file_exists(state_dir_path("gpg_sign_opt", opts))) {
strbuf_reset(&buf);
@@ -245,7 +657,7 @@ static int read_basic_state(struct rebase_options *opts)
return 0;
}
-static int write_basic_state(struct rebase_options *opts)
+static int rebase_write_basic_state(struct rebase_options *opts)
{
write_file(state_dir_path("head-name", opts), "%s",
opts->head_name ? opts->head_name : "detached HEAD");
@@ -263,10 +675,11 @@ static int write_basic_state(struct rebase_options *opts)
if (opts->strategy_opts)
write_file(state_dir_path("strategy_opts", opts), "%s",
opts->strategy_opts);
- if (opts->allow_rerere_autoupdate >= 0)
+ if (opts->allow_rerere_autoupdate > 0)
write_file(state_dir_path("allow_rerere_autoupdate", opts),
"-%s-rerere-autoupdate",
- opts->allow_rerere_autoupdate ? "" : "-no");
+ opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
+ "" : "-no");
if (opts->gpg_sign_opt)
write_file(state_dir_path("gpg_sign_opt", opts), "%s",
opts->gpg_sign_opt);
@@ -630,9 +1043,9 @@ static int run_am(struct rebase_options *opts)
argv_array_push(&am.args, "--rebasing");
argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg);
argv_array_push(&am.args, "--patch-format=mboxrd");
- if (opts->allow_rerere_autoupdate > 0)
+ if (opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE)
argv_array_push(&am.args, "--rerere-autoupdate");
- else if (opts->allow_rerere_autoupdate == 0)
+ else if (opts->allow_rerere_autoupdate == RERERE_NOAUTOUPDATE)
argv_array_push(&am.args, "--no-rerere-autoupdate");
if (opts->gpg_sign_opt)
argv_array_push(&am.args, opts->gpg_sign_opt);
@@ -645,12 +1058,12 @@ static int run_am(struct rebase_options *opts)
}
if (is_directory(opts->state_dir))
- write_basic_state(opts);
+ rebase_write_basic_state(opts);
return status;
}
-static int run_specific_rebase(struct rebase_options *opts)
+static int run_specific_rebase(struct rebase_options *opts, enum action action)
{
const char *argv[] = { NULL, NULL };
struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT;
@@ -659,77 +1072,19 @@ static int run_specific_rebase(struct rebase_options *opts)
if (opts->type == REBASE_INTERACTIVE) {
/* Run builtin interactive rebase */
- struct child_process child = CHILD_PROCESS_INIT;
-
- argv_array_pushf(&child.env_array, "GIT_CHERRY_PICK_HELP=%s",
- resolvemsg);
+ setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1);
if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {
- argv_array_push(&child.env_array,
- "GIT_SEQUENCE_EDITOR=:");
+ setenv("GIT_SEQUENCE_EDITOR", ":", 1);
opts->autosquash = 0;
}
+ if (opts->gpg_sign_opt) {
+ /* remove the leading "-S" */
+ char *tmp = xstrdup(opts->gpg_sign_opt + 2);
+ free(opts->gpg_sign_opt);
+ opts->gpg_sign_opt = tmp;
+ }
- child.git_cmd = 1;
- argv_array_push(&child.args, "rebase--interactive");
-
- if (opts->action)
- argv_array_pushf(&child.args, "--%s", opts->action);
- if (opts->keep_empty)
- argv_array_push(&child.args, "--keep-empty");
- if (opts->rebase_merges)
- argv_array_push(&child.args, "--rebase-merges");
- if (opts->rebase_cousins)
- argv_array_push(&child.args, "--rebase-cousins");
- if (opts->autosquash)
- argv_array_push(&child.args, "--autosquash");
- if (opts->flags & REBASE_VERBOSE)
- argv_array_push(&child.args, "--verbose");
- if (opts->flags & REBASE_FORCE)
- argv_array_push(&child.args, "--no-ff");
- if (opts->restrict_revision)
- argv_array_pushf(&child.args,
- "--restrict-revision=^%s",
- oid_to_hex(&opts->restrict_revision->object.oid));
- if (opts->upstream)
- argv_array_pushf(&child.args, "--upstream=%s",
- oid_to_hex(&opts->upstream->object.oid));
- if (opts->onto)
- argv_array_pushf(&child.args, "--onto=%s",
- oid_to_hex(&opts->onto->object.oid));
- if (opts->squash_onto)
- argv_array_pushf(&child.args, "--squash-onto=%s",
- oid_to_hex(opts->squash_onto));
- if (opts->onto_name)
- argv_array_pushf(&child.args, "--onto-name=%s",
- opts->onto_name);
- argv_array_pushf(&child.args, "--head-name=%s",
- opts->head_name ?
- opts->head_name : "detached HEAD");
- if (opts->strategy)
- argv_array_pushf(&child.args, "--strategy=%s",
- opts->strategy);
- if (opts->strategy_opts)
- argv_array_pushf(&child.args, "--strategy-opts=%s",
- opts->strategy_opts);
- if (opts->switch_to)
- argv_array_pushf(&child.args, "--switch-to=%s",
- opts->switch_to);
- if (opts->cmd)
- argv_array_pushf(&child.args, "--cmd=%s", opts->cmd);
- if (opts->allow_empty_message)
- argv_array_push(&child.args, "--allow-empty-message");
- if (opts->allow_rerere_autoupdate > 0)
- argv_array_push(&child.args, "--rerere-autoupdate");
- else if (opts->allow_rerere_autoupdate == 0)
- argv_array_push(&child.args, "--no-rerere-autoupdate");
- if (opts->gpg_sign_opt)
- argv_array_push(&child.args, opts->gpg_sign_opt);
- if (opts->signoff)
- argv_array_push(&child.args, "--signoff");
- if (opts->reschedule_failed_exec)
- argv_array_push(&child.args, "--reschedule-failed-exec");
-
- status = run_command(&child);
+ status = run_rebase_interactive(opts, action);
goto finished_rebase;
}
@@ -769,9 +1124,9 @@ static int run_specific_rebase(struct rebase_options *opts)
add_var(&script_snippet, "action", opts->action ? opts->action : "");
add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : "");
add_var(&script_snippet, "allow_rerere_autoupdate",
- opts->allow_rerere_autoupdate < 0 ? "" :
opts->allow_rerere_autoupdate ?
- "--rerere-autoupdate" : "--no-rerere-autoupdate");
+ opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ?
+ "--rerere-autoupdate" : "--no-rerere-autoupdate" : "");
add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : "");
add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : "");
add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt);
@@ -874,6 +1229,11 @@ static int rebase_config(const char *var, const char *value, void *data)
return 0;
}
+ if (!strcmp(var, "rebase.usebuiltin")) {
+ opts->use_legacy_rebase = !git_config_bool(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, data);
}
@@ -1008,14 +1368,7 @@ static int check_exec_cmd(const char *cmd)
int cmd_rebase(int argc, const char **argv, const char *prefix)
{
- struct rebase_options options = {
- .type = REBASE_UNSPECIFIED,
- .flags = REBASE_NO_QUIET,
- .git_am_opts = ARGV_ARRAY_INIT,
- .allow_rerere_autoupdate = -1,
- .allow_empty_message = 1,
- .git_format_patch_opt = STRBUF_INIT,
- };
+ struct rebase_options options = REBASE_OPTIONS_INIT;
const char *branch_name;
int ret, flags, total_argc, in_progress = 0;
int ok_to_skip_pre_rebase = 0;
@@ -1023,23 +1376,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
struct strbuf revisions = STRBUF_INIT;
struct strbuf buf = STRBUF_INIT;
struct object_id merge_base;
- enum {
- NO_ACTION,
- ACTION_CONTINUE,
- ACTION_SKIP,
- ACTION_ABORT,
- ACTION_QUIT,
- ACTION_EDIT_TODO,
- ACTION_SHOW_CURRENT_PATCH,
- } action = NO_ACTION;
- static const char *action_names[] = { N_("undefined"),
- N_("continue"),
- N_("skip"),
- N_("abort"),
- N_("quit"),
- N_("edit_todo"),
- N_("show_current_patch"),
- NULL };
+ enum action action = ACTION_NONE;
const char *gpg_sign = NULL;
struct string_list exec = STRING_LIST_INIT_NODUP;
const char *rebase_merges = NULL;
@@ -1105,12 +1442,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
parse_opt_interactive },
OPT_SET_INT('p', "preserve-merges", &options.type,
- N_("try to recreate merges instead of ignoring "
- "them"), REBASE_PRESERVE_MERGES),
- OPT_BOOL(0, "rerere-autoupdate",
- &options.allow_rerere_autoupdate,
- N_("allow rerere to update index with resolved "
- "conflict")),
+ N_("(DEPRECATED) try to recreate merges instead of "
+ "ignoring them"), REBASE_PRESERVE_MERGES),
+ OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
OPT_BOOL('k', "keep-empty", &options.keep_empty,
N_("preserve empty commits during rebase")),
OPT_BOOL(0, "autosquash", &options.autosquash,
@@ -1148,22 +1482,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
};
int i;
- /*
- * NEEDSWORK: Once the builtin rebase has been tested enough
- * and git-legacy-rebase.sh is retired to contrib/, this preamble
- * can be removed.
- */
-
- if (!use_builtin_rebase()) {
- const char *path = mkpath("%s/git-legacy-rebase",
- git_exec_path());
-
- if (sane_execvp(path, (char **)argv) < 0)
- die_errno(_("could not exec %s"), path);
- else
- BUG("sane_execvp() returned???");
- }
-
if (argc == 2 && !strcmp(argv[1], "-h"))
usage_with_options(builtin_rebase_usage,
builtin_rebase_options);
@@ -1172,8 +1490,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
trace_repo_setup(prefix);
setup_work_tree();
+ options.allow_empty_message = 1;
git_config(rebase_config, &options);
+ if (options.use_legacy_rebase ||
+ !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1))
+ warning(_("the rebase.useBuiltin support has been removed!\n"
+ "See its entry in 'git help config' for details."));
+
strbuf_reset(&buf);
strbuf_addf(&buf, "%s/applying", apply_dir());
if(file_exists(buf.buf))
@@ -1208,7 +1532,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
builtin_rebase_options,
builtin_rebase_usage, 0);
- if (action != NO_ACTION && total_argc != 2) {
+ if (action != ACTION_NONE && total_argc != 2) {
usage_with_options(builtin_rebase_usage,
builtin_rebase_options);
}
@@ -1217,7 +1541,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
usage_with_options(builtin_rebase_usage,
builtin_rebase_options);
- if (action != NO_ACTION && !in_progress)
+ if (options.type == REBASE_PRESERVE_MERGES)
+ warning(_("git rebase --preserve-merges is deprecated. "
+ "Use --rebase-merges instead."));
+
+ if (action != ACTION_NONE && !in_progress)
die(_("No rebase in progress?"));
setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0);
@@ -1317,7 +1645,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.action = "show-current-patch";
options.dont_finish_rebase = 1;
goto run_rebase;
- case NO_ACTION:
+ case ACTION_NONE:
break;
default:
BUG("action: %d", action);
@@ -1592,8 +1920,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
branch_name = options.head_name;
} else {
- free(options.head_name);
- options.head_name = NULL;
+ FREE_AND_NULL(options.head_name);
branch_name = "HEAD";
}
if (get_oid("HEAD", &options.orig_head))
@@ -1793,7 +2120,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
* we just fast-forwarded.
*/
strbuf_reset(&msg);
- if (!oidcmp(&merge_base, &options.orig_head)) {
+ if (oideq(&merge_base, &options.orig_head)) {
printf(_("Fast-forwarded %s to %s.\n"),
branch_name, options.onto_name);
strbuf_addf(&msg, "rebase finished: %s onto %s",
@@ -1816,7 +2143,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.revisions = revisions.buf;
run_rebase:
- ret = !!run_specific_rebase(&options);
+ ret = !!run_specific_rebase(&options, action);
cleanup:
strbuf_release(&revisions);
diff --git a/builtin/repack.c b/builtin/repack.c
index 67f8978043..caca113927 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -14,7 +14,7 @@
static int delta_base_offset = 1;
static int pack_kept_objects = -1;
-static int write_bitmaps;
+static int write_bitmaps = -1;
static int use_delta_islands;
static char *packdir, *packtmp;
@@ -343,6 +343,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
(unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE)))
die(_("--keep-unreachable and -A are incompatible"));
+ if (write_bitmaps < 0)
+ write_bitmaps = (pack_everything & ALL_INTO_ONE) &&
+ is_bare_repository();
if (pack_kept_objects < 0)
pack_kept_objects = write_bitmaps;
diff --git a/builtin/replace.c b/builtin/replace.c
index f5701629a8..644b21ca8d 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -370,16 +370,19 @@ static int replace_parents(struct strbuf *buf, int argc, const char **argv)
/* prepare new parents */
for (i = 0; i < argc; i++) {
struct object_id oid;
+ struct commit *commit;
+
if (get_oid(argv[i], &oid) < 0) {
strbuf_release(&new_parents);
return error(_("not a valid object name: '%s'"),
argv[i]);
}
- if (!lookup_commit_reference(the_repository, &oid)) {
+ commit = lookup_commit_reference(the_repository, &oid);
+ if (!commit) {
strbuf_release(&new_parents);
- return error(_("could not parse %s"), argv[i]);
+ return error(_("could not parse %s as a commit"), argv[i]);
}
- strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&oid));
+ strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&commit->object.oid));
}
/* replace existing parents with new ones */
@@ -478,15 +481,18 @@ static int create_graft(int argc, const char **argv, int force, int gentle)
strbuf_release(&buf);
- if (oideq(&old_oid, &new_oid)) {
+ if (oideq(&commit->object.oid, &new_oid)) {
if (gentle) {
- warning(_("graft for '%s' unnecessary"), oid_to_hex(&old_oid));
+ warning(_("graft for '%s' unnecessary"),
+ oid_to_hex(&commit->object.oid));
return 0;
}
- return error(_("new commit is the same as the old one: '%s'"), oid_to_hex(&old_oid));
+ return error(_("new commit is the same as the old one: '%s'"),
+ oid_to_hex(&commit->object.oid));
}
- return replace_object_oid(old_ref, &old_oid, "replacement", &new_oid, force);
+ return replace_object_oid(old_ref, &commit->object.oid,
+ "replacement", &new_oid, force);
}
static int convert_graft_file(int force)
diff --git a/builtin/reset.c b/builtin/reset.c
index 7882829a95..26ef9a7bd0 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -386,6 +386,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN;
if (read_from_tree(&pathspec, &oid, intent_to_add))
return 1;
+ the_index.updated_skipworktree = 1;
if (!quiet && get_git_work_tree()) {
uint64_t t_begin, t_delta_in_ms;
diff --git a/builtin/rev-list.c b/builtin/rev-list.c
index 425a5774db..9f31837d30 100644
--- a/builtin/rev-list.c
+++ b/builtin/rev-list.c
@@ -379,7 +379,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
repo_init_revisions(the_repository, &revs, prefix);
revs.abbrev = DEFAULT_ABBREV;
revs.commit_format = CMIT_FMT_UNSPECIFIED;
- revs.do_not_die_on_missing_tree = 1;
/*
* Scan the argument list before invoking setup_revisions(), so that we
@@ -409,6 +408,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
}
}
+ if (arg_missing_action)
+ revs.do_not_die_on_missing_tree = 1;
+
argc = setup_revisions(argc, argv, &revs, &s_r_opt);
memset(&info, 0, sizeof(info));
diff --git a/builtin/revert.c b/builtin/revert.c
index a47b53ceaf..d4dcedbdc6 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -96,11 +96,13 @@ 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);
+ const char *cleanup_arg = NULL;
int cmd = 0;
struct option base_options[] = {
OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'),
OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'),
OPT_CMDMODE(0, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'),
+ OPT_CLEANUP(&cleanup_arg),
OPT_BOOL('n', "no-commit", &opts->no_commit, N_("don't automatically commit")),
OPT_BOOL('e', "edit", &opts->edit, N_("edit the commit message")),
OPT_NOOP_NOARG('r', NULL),
@@ -137,6 +139,11 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
if (opts->keep_redundant_commits)
opts->allow_empty = 1;
+ if (cleanup_arg) {
+ opts->default_msg_cleanup = get_cleanup_mode(cleanup_arg, 1);
+ opts->explicit_cleanup = 1;
+ }
+
/* Check for incompatible command line arguments */
if (cmd) {
char *this_operation;
diff --git a/builtin/rm.c b/builtin/rm.c
index db85b33982..90cbe896c9 100644
--- a/builtin/rm.c
+++ b/builtin/rm.c
@@ -110,7 +110,7 @@ static int check_local_mod(struct object_id *head, int index_only)
const struct cache_entry *ce;
const char *name = list.entry[i].name;
struct object_id oid;
- unsigned mode;
+ unsigned short mode;
int local_changes = 0;
int staged_changes = 0;
diff --git a/builtin/serve.c b/builtin/serve.c
deleted file mode 100644
index d3fd240bb3..0000000000
--- a/builtin/serve.c
+++ /dev/null
@@ -1,30 +0,0 @@
-#include "cache.h"
-#include "builtin.h"
-#include "parse-options.h"
-#include "serve.h"
-
-static char const * const serve_usage[] = {
- N_("git serve [<options>]"),
- NULL
-};
-
-int cmd_serve(int argc, const char **argv, const char *prefix)
-{
- struct serve_options opts = SERVE_OPTIONS_INIT;
-
- struct option options[] = {
- OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc,
- N_("quit after a single request/response exchange")),
- OPT_BOOL(0, "advertise-capabilities", &opts.advertise_capabilities,
- N_("exit immediately after advertising capabilities")),
- OPT_END()
- };
-
- /* ignore all unknown cmdline switches for now */
- argc = parse_options(argc, argv, prefix, options, serve_usage,
- PARSE_OPT_KEEP_DASHDASH |
- PARSE_OPT_KEEP_UNKNOWN);
- serve(&opts);
-
- return 0;
-}
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index 934e514944..082daeac32 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -753,7 +753,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
/* Ah, that is a date spec... */
timestamp_t at;
at = approxidate(reflog_base);
- read_ref_at(ref, flags, at, -1, &oid, NULL,
+ read_ref_at(get_main_ref_store(the_repository),
+ ref, flags, at, -1, &oid, NULL,
NULL, NULL, &base);
}
}
@@ -765,7 +766,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
timestamp_t timestamp;
int tz;
- if (read_ref_at(ref, flags, 0, base + i, &oid, &logmsg,
+ if (read_ref_at(get_main_ref_store(the_repository),
+ ref, flags, 0, base + i, &oid, &logmsg,
&timestamp, &tz, NULL)) {
reflog = i;
break;
diff --git a/builtin/stash.c b/builtin/stash.c
new file mode 100644
index 0000000000..2a8e6d09b4
--- /dev/null
+++ b/builtin/stash.c
@@ -0,0 +1,1648 @@
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
+#include "builtin.h"
+#include "config.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "lockfile.h"
+#include "cache-tree.h"
+#include "unpack-trees.h"
+#include "merge-recursive.h"
+#include "argv-array.h"
+#include "run-command.h"
+#include "dir.h"
+#include "rerere.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "diffcore.h"
+#include "exec-cmd.h"
+
+#define INCLUDE_ALL_FILES 2
+
+static const char * const git_stash_usage[] = {
+ N_("git stash list [<options>]"),
+ N_("git stash show [<options>] [<stash>]"),
+ N_("git stash drop [-q|--quiet] [<stash>]"),
+ N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"),
+ N_("git stash branch <branchname> [<stash>]"),
+ N_("git stash clear"),
+ N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+ " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
+ " [--] [<pathspec>...]]"),
+ N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+ " [-u|--include-untracked] [-a|--all] [<message>]"),
+ NULL
+};
+
+static const char * const git_stash_list_usage[] = {
+ N_("git stash list [<options>]"),
+ NULL
+};
+
+static const char * const git_stash_show_usage[] = {
+ N_("git stash show [<options>] [<stash>]"),
+ NULL
+};
+
+static const char * const git_stash_drop_usage[] = {
+ N_("git stash drop [-q|--quiet] [<stash>]"),
+ NULL
+};
+
+static const char * const git_stash_pop_usage[] = {
+ N_("git stash pop [--index] [-q|--quiet] [<stash>]"),
+ NULL
+};
+
+static const char * const git_stash_apply_usage[] = {
+ N_("git stash apply [--index] [-q|--quiet] [<stash>]"),
+ NULL
+};
+
+static const char * const git_stash_branch_usage[] = {
+ N_("git stash branch <branchname> [<stash>]"),
+ NULL
+};
+
+static const char * const git_stash_clear_usage[] = {
+ N_("git stash clear"),
+ NULL
+};
+
+static const char * const git_stash_store_usage[] = {
+ N_("git stash store [-m|--message <message>] [-q|--quiet] <commit>"),
+ NULL
+};
+
+static const char * const git_stash_push_usage[] = {
+ N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+ " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n"
+ " [--] [<pathspec>...]]"),
+ NULL
+};
+
+static const char * const git_stash_save_usage[] = {
+ N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n"
+ " [-u|--include-untracked] [-a|--all] [<message>]"),
+ NULL
+};
+
+static const char *ref_stash = "refs/stash";
+static struct strbuf stash_index_path = STRBUF_INIT;
+
+/*
+ * w_commit is set to the commit containing the working tree
+ * b_commit is set to the base commit
+ * i_commit is set to the commit containing the index tree
+ * u_commit is set to the commit containing the untracked files tree
+ * w_tree is set to the working tree
+ * b_tree is set to the base tree
+ * i_tree is set to the index tree
+ * u_tree is set to the untracked files tree
+ */
+struct stash_info {
+ struct object_id w_commit;
+ struct object_id b_commit;
+ struct object_id i_commit;
+ struct object_id u_commit;
+ struct object_id w_tree;
+ struct object_id b_tree;
+ struct object_id i_tree;
+ struct object_id u_tree;
+ struct strbuf revision;
+ int is_stash_ref;
+ int has_u;
+};
+
+static void free_stash_info(struct stash_info *info)
+{
+ strbuf_release(&info->revision);
+}
+
+static void assert_stash_like(struct stash_info *info, const char *revision)
+{
+ if (get_oidf(&info->b_commit, "%s^1", revision) ||
+ get_oidf(&info->w_tree, "%s:", revision) ||
+ get_oidf(&info->b_tree, "%s^1:", revision) ||
+ get_oidf(&info->i_tree, "%s^2:", revision))
+ die(_("'%s' is not a stash-like commit"), revision);
+}
+
+static int get_stash_info(struct stash_info *info, int argc, const char **argv)
+{
+ int ret;
+ char *end_of_rev;
+ char *expanded_ref;
+ const char *revision;
+ const char *commit = NULL;
+ struct object_id dummy;
+ struct strbuf symbolic = STRBUF_INIT;
+
+ if (argc > 1) {
+ int i;
+ struct strbuf refs_msg = STRBUF_INIT;
+
+ for (i = 0; i < argc; i++)
+ strbuf_addf(&refs_msg, " '%s'", argv[i]);
+
+ fprintf_ln(stderr, _("Too many revisions specified:%s"),
+ refs_msg.buf);
+ strbuf_release(&refs_msg);
+
+ return -1;
+ }
+
+ if (argc == 1)
+ commit = argv[0];
+
+ strbuf_init(&info->revision, 0);
+ if (!commit) {
+ if (!ref_exists(ref_stash)) {
+ free_stash_info(info);
+ fprintf_ln(stderr, _("No stash entries found."));
+ return -1;
+ }
+
+ strbuf_addf(&info->revision, "%s@{0}", ref_stash);
+ } else if (strspn(commit, "0123456789") == strlen(commit)) {
+ strbuf_addf(&info->revision, "%s@{%s}", ref_stash, commit);
+ } else {
+ strbuf_addstr(&info->revision, commit);
+ }
+
+ revision = info->revision.buf;
+
+ if (get_oid(revision, &info->w_commit)) {
+ error(_("%s is not a valid reference"), revision);
+ free_stash_info(info);
+ return -1;
+ }
+
+ assert_stash_like(info, revision);
+
+ info->has_u = !get_oidf(&info->u_tree, "%s^3:", revision);
+
+ end_of_rev = strchrnul(revision, '@');
+ strbuf_add(&symbolic, revision, end_of_rev - revision);
+
+ ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref);
+ strbuf_release(&symbolic);
+ switch (ret) {
+ case 0: /* Not found, but valid ref */
+ info->is_stash_ref = 0;
+ break;
+ case 1:
+ info->is_stash_ref = !strcmp(expanded_ref, ref_stash);
+ break;
+ default: /* Invalid or ambiguous */
+ free_stash_info(info);
+ }
+
+ free(expanded_ref);
+ return !(ret == 0 || ret == 1);
+}
+
+static int do_clear_stash(void)
+{
+ struct object_id obj;
+ if (get_oid(ref_stash, &obj))
+ return 0;
+
+ return delete_ref(NULL, ref_stash, &obj, 0);
+}
+
+static int clear_stash(int argc, const char **argv, const char *prefix)
+{
+ struct option options[] = {
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_stash_clear_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (argc)
+ return error(_("git stash clear with parameters is "
+ "unimplemented"));
+
+ return do_clear_stash();
+}
+
+static int reset_tree(struct object_id *i_tree, int update, int reset)
+{
+ int nr_trees = 1;
+ struct unpack_trees_options opts;
+ struct tree_desc t[MAX_UNPACK_TREES];
+ struct tree *tree;
+ struct lock_file lock_file = LOCK_INIT;
+
+ read_cache_preload(NULL);
+ if (refresh_cache(REFRESH_QUIET))
+ return -1;
+
+ hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR);
+
+ memset(&opts, 0, sizeof(opts));
+
+ tree = parse_tree_indirect(i_tree);
+ if (parse_tree(tree))
+ return -1;
+
+ init_tree_desc(t, tree->buffer, tree->size);
+
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.merge = 1;
+ opts.reset = reset;
+ opts.update = update;
+ opts.fn = oneway_merge;
+
+ if (unpack_trees(nr_trees, t, &opts))
+ return -1;
+
+ if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK))
+ return error(_("unable to write new index file"));
+
+ return 0;
+}
+
+static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ const char *w_commit_hex = oid_to_hex(w_commit);
+
+ /*
+ * Diff-tree would not be very hard to replace with a native function,
+ * however it should be done together with apply_cached.
+ */
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL);
+ argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex);
+
+ return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
+}
+
+static int apply_cached(struct strbuf *out)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ /*
+ * Apply currently only reads either from stdin or a file, thus
+ * apply_all_patches would have to be updated to optionally take a
+ * buffer.
+ */
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "apply", "--cached", NULL);
+ return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
+}
+
+static int reset_head(void)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ /*
+ * Reset is overall quite simple, however there is no current public
+ * API for resetting.
+ */
+ cp.git_cmd = 1;
+ argv_array_push(&cp.args, "reset");
+
+ return run_command(&cp);
+}
+
+static void add_diff_to_buf(struct diff_queue_struct *q,
+ struct diff_options *options,
+ void *data)
+{
+ int i;
+
+ for (i = 0; i < q->nr; i++) {
+ strbuf_addstr(data, q->queue[i]->one->path);
+
+ /* NUL-terminate: will be fed to update-index -z */
+ strbuf_addch(data, '\0');
+ }
+}
+
+static int get_newly_staged(struct strbuf *out, struct object_id *c_tree)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ const char *c_tree_hex = oid_to_hex(c_tree);
+
+ /*
+ * diff-index is very similar to diff-tree above, and should be
+ * converted together with update_index.
+ */
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only",
+ "--diff-filter=A", NULL);
+ argv_array_push(&cp.args, c_tree_hex);
+ return pipe_command(&cp, NULL, 0, out, 0, NULL, 0);
+}
+
+static int update_index(struct strbuf *out)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ /*
+ * Update-index is very complicated and may need to have a public
+ * function exposed in order to remove this forking.
+ */
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL);
+ return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0);
+}
+
+static int restore_untracked(struct object_id *u_tree)
+{
+ int res;
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ /*
+ * We need to run restore files from a given index, but without
+ * affecting the current index, so we use GIT_INDEX_FILE with
+ * run_command to fork processes that will not interfere.
+ */
+ cp.git_cmd = 1;
+ argv_array_push(&cp.args, "read-tree");
+ argv_array_push(&cp.args, oid_to_hex(u_tree));
+ argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
+ if (run_command(&cp)) {
+ remove_path(stash_index_path.buf);
+ return -1;
+ }
+
+ child_process_init(&cp);
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "checkout-index", "--all", NULL);
+ argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
+
+ res = run_command(&cp);
+ remove_path(stash_index_path.buf);
+ return res;
+}
+
+static int do_apply_stash(const char *prefix, struct stash_info *info,
+ int index, int quiet)
+{
+ int ret;
+ int has_index = index;
+ struct merge_options o;
+ struct object_id c_tree;
+ struct object_id index_tree;
+ struct commit *result;
+ const struct object_id *bases[1];
+
+ read_cache_preload(NULL);
+ if (refresh_cache(REFRESH_QUIET))
+ return -1;
+
+ if (write_cache_as_tree(&c_tree, 0, NULL))
+ return error(_("cannot apply a stash in the middle of a merge"));
+
+ if (index) {
+ if (oideq(&info->b_tree, &info->i_tree) ||
+ oideq(&c_tree, &info->i_tree)) {
+ has_index = 0;
+ } else {
+ struct strbuf out = STRBUF_INIT;
+
+ if (diff_tree_binary(&out, &info->w_commit)) {
+ strbuf_release(&out);
+ return error(_("could not generate diff %s^!."),
+ oid_to_hex(&info->w_commit));
+ }
+
+ ret = apply_cached(&out);
+ strbuf_release(&out);
+ if (ret)
+ return error(_("conflicts in index."
+ "Try without --index."));
+
+ discard_cache();
+ read_cache();
+ if (write_cache_as_tree(&index_tree, 0, NULL))
+ return error(_("could not save index tree"));
+
+ reset_head();
+ }
+ }
+
+ if (info->has_u && restore_untracked(&info->u_tree))
+ return error(_("could not restore untracked files from stash"));
+
+ init_merge_options(&o, the_repository);
+
+ o.branch1 = "Updated upstream";
+ o.branch2 = "Stashed changes";
+
+ if (oideq(&info->b_tree, &c_tree))
+ o.branch1 = "Version stash was based on";
+
+ if (quiet)
+ o.verbosity = 0;
+
+ if (o.verbosity >= 3)
+ printf_ln(_("Merging %s with %s"), o.branch1, o.branch2);
+
+ bases[0] = &info->b_tree;
+
+ ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases,
+ &result);
+ if (ret) {
+ rerere(0);
+
+ if (index)
+ fprintf_ln(stderr, _("Index was not unstashed."));
+
+ return ret;
+ }
+
+ if (has_index) {
+ if (reset_tree(&index_tree, 0, 0))
+ return -1;
+ } else {
+ struct strbuf out = STRBUF_INIT;
+
+ if (get_newly_staged(&out, &c_tree)) {
+ strbuf_release(&out);
+ return -1;
+ }
+
+ if (reset_tree(&c_tree, 0, 1)) {
+ strbuf_release(&out);
+ return -1;
+ }
+
+ ret = update_index(&out);
+ strbuf_release(&out);
+ if (ret)
+ return -1;
+
+ discard_cache();
+ }
+
+ if (quiet) {
+ if (refresh_cache(REFRESH_QUIET))
+ warning("could not refresh index");
+ } else {
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ /*
+ * Status is quite simple and could be replaced with calls to
+ * wt_status in the future, but it adds complexities which may
+ * require more tests.
+ */
+ cp.git_cmd = 1;
+ cp.dir = prefix;
+ argv_array_push(&cp.args, "status");
+ run_command(&cp);
+ }
+
+ return 0;
+}
+
+static int apply_stash(int argc, const char **argv, const char *prefix)
+{
+ int ret;
+ int quiet = 0;
+ int index = 0;
+ struct stash_info info;
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+ OPT_BOOL(0, "index", &index,
+ N_("attempt to recreate the index")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_stash_apply_usage, 0);
+
+ if (get_stash_info(&info, argc, argv))
+ return -1;
+
+ ret = do_apply_stash(prefix, &info, index, quiet);
+ free_stash_info(&info);
+ return ret;
+}
+
+static int do_drop_stash(struct stash_info *info, int quiet)
+{
+ int ret;
+ struct child_process cp_reflog = CHILD_PROCESS_INIT;
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ /*
+ * reflog does not provide a simple function for deleting refs. One will
+ * need to be added to avoid implementing too much reflog code here
+ */
+
+ cp_reflog.git_cmd = 1;
+ argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref",
+ "--rewrite", NULL);
+ argv_array_push(&cp_reflog.args, info->revision.buf);
+ ret = run_command(&cp_reflog);
+ if (!ret) {
+ if (!quiet)
+ printf_ln(_("Dropped %s (%s)"), info->revision.buf,
+ oid_to_hex(&info->w_commit));
+ } else {
+ return error(_("%s: Could not drop stash entry"),
+ info->revision.buf);
+ }
+
+ /*
+ * This could easily be replaced by get_oid, but currently it will throw
+ * a fatal error when a reflog is empty, which we can not recover from.
+ */
+ cp.git_cmd = 1;
+ /* Even though --quiet is specified, rev-parse still outputs the hash */
+ cp.no_stdout = 1;
+ argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL);
+ argv_array_pushf(&cp.args, "%s@{0}", ref_stash);
+ ret = run_command(&cp);
+
+ /* do_clear_stash if we just dropped the last stash entry */
+ if (ret)
+ do_clear_stash();
+
+ return 0;
+}
+
+static void assert_stash_ref(struct stash_info *info)
+{
+ if (!info->is_stash_ref) {
+ error(_("'%s' is not a stash reference"), info->revision.buf);
+ free_stash_info(info);
+ exit(1);
+ }
+}
+
+static int drop_stash(int argc, const char **argv, const char *prefix)
+{
+ int ret;
+ int quiet = 0;
+ struct stash_info info;
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_stash_drop_usage, 0);
+
+ if (get_stash_info(&info, argc, argv))
+ return -1;
+
+ assert_stash_ref(&info);
+
+ ret = do_drop_stash(&info, quiet);
+ free_stash_info(&info);
+ return ret;
+}
+
+static int pop_stash(int argc, const char **argv, const char *prefix)
+{
+ int ret;
+ int index = 0;
+ int quiet = 0;
+ struct stash_info info;
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("be quiet, only report errors")),
+ OPT_BOOL(0, "index", &index,
+ N_("attempt to recreate the index")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_stash_pop_usage, 0);
+
+ if (get_stash_info(&info, argc, argv))
+ return -1;
+
+ assert_stash_ref(&info);
+ if ((ret = do_apply_stash(prefix, &info, index, quiet)))
+ printf_ln(_("The stash entry is kept in case "
+ "you need it again."));
+ else
+ ret = do_drop_stash(&info, quiet);
+
+ free_stash_info(&info);
+ return ret;
+}
+
+static int branch_stash(int argc, const char **argv, const char *prefix)
+{
+ int ret;
+ const char *branch = NULL;
+ struct stash_info info;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct option options[] = {
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_stash_branch_usage, 0);
+
+ if (!argc) {
+ fprintf_ln(stderr, _("No branch name specified"));
+ return -1;
+ }
+
+ branch = argv[0];
+
+ if (get_stash_info(&info, argc - 1, argv + 1))
+ return -1;
+
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "checkout", "-b", NULL);
+ argv_array_push(&cp.args, branch);
+ argv_array_push(&cp.args, oid_to_hex(&info.b_commit));
+ ret = run_command(&cp);
+ if (!ret)
+ ret = do_apply_stash(prefix, &info, 1, 0);
+ if (!ret && info.is_stash_ref)
+ ret = do_drop_stash(&info, 0);
+
+ free_stash_info(&info);
+
+ return ret;
+}
+
+static int list_stash(int argc, const char **argv, const char *prefix)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct option options[] = {
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_stash_list_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+
+ if (!ref_exists(ref_stash))
+ return 0;
+
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "log", "--format=%gd: %gs", "-g",
+ "--first-parent", "-m", NULL);
+ argv_array_pushv(&cp.args, argv);
+ argv_array_push(&cp.args, ref_stash);
+ argv_array_push(&cp.args, "--");
+ return run_command(&cp);
+}
+
+static int show_stat = 1;
+static int show_patch;
+
+static int git_stash_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "stash.showstat")) {
+ show_stat = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "stash.showpatch")) {
+ show_patch = git_config_bool(var, value);
+ return 0;
+ }
+ return git_default_config(var, value, cb);
+}
+
+static int show_stash(int argc, const char **argv, const char *prefix)
+{
+ int i;
+ int opts = 0;
+ int ret = 0;
+ struct stash_info info;
+ struct rev_info rev;
+ struct argv_array stash_args = ARGV_ARRAY_INIT;
+ struct option options[] = {
+ OPT_END()
+ };
+
+ init_diff_ui_defaults();
+ git_config(git_diff_ui_config, NULL);
+ init_revisions(&rev, prefix);
+
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] != '-')
+ argv_array_push(&stash_args, argv[i]);
+ else
+ opts++;
+ }
+
+ ret = get_stash_info(&info, stash_args.argc, stash_args.argv);
+ argv_array_clear(&stash_args);
+ if (ret)
+ return -1;
+
+ /*
+ * The config settings are applied only if there are not passed
+ * any options.
+ */
+ if (!opts) {
+ git_config(git_stash_config, NULL);
+ if (show_stat)
+ rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT;
+
+ if (show_patch)
+ rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
+
+ if (!show_stat && !show_patch) {
+ free_stash_info(&info);
+ return 0;
+ }
+ }
+
+ argc = setup_revisions(argc, argv, &rev, NULL);
+ if (argc > 1) {
+ free_stash_info(&info);
+ usage_with_options(git_stash_show_usage, options);
+ }
+ if (!rev.diffopt.output_format) {
+ rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+ diff_setup_done(&rev.diffopt);
+ }
+
+ rev.diffopt.flags.recursive = 1;
+ setup_diff_pager(&rev.diffopt);
+ diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt);
+ log_tree_diff_flush(&rev);
+
+ free_stash_info(&info);
+ return diff_result_code(&rev.diffopt, 0);
+}
+
+static int do_store_stash(const struct object_id *w_commit, const char *stash_msg,
+ int quiet)
+{
+ if (!stash_msg)
+ stash_msg = "Created via \"git stash store\".";
+
+ if (update_ref(stash_msg, ref_stash, w_commit, NULL,
+ REF_FORCE_CREATE_REFLOG,
+ quiet ? UPDATE_REFS_QUIET_ON_ERR :
+ UPDATE_REFS_MSG_ON_ERR)) {
+ if (!quiet) {
+ fprintf_ln(stderr, _("Cannot update %s with %s"),
+ ref_stash, oid_to_hex(w_commit));
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+static int store_stash(int argc, const char **argv, const char *prefix)
+{
+ int quiet = 0;
+ const char *stash_msg = NULL;
+ struct object_id obj;
+ struct object_context dummy;
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("be quiet")),
+ OPT_STRING('m', "message", &stash_msg, "message",
+ N_("stash message")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_stash_store_usage,
+ PARSE_OPT_KEEP_UNKNOWN);
+
+ if (argc != 1) {
+ if (!quiet)
+ fprintf_ln(stderr, _("\"git stash store\" requires one "
+ "<commit> argument"));
+ return -1;
+ }
+
+ if (get_oid_with_context(the_repository,
+ argv[0], quiet ? GET_OID_QUIETLY : 0, &obj,
+ &dummy)) {
+ if (!quiet)
+ fprintf_ln(stderr, _("Cannot update %s with %s"),
+ ref_stash, argv[0]);
+ return -1;
+ }
+
+ return do_store_stash(&obj, stash_msg, quiet);
+}
+
+static void add_pathspecs(struct argv_array *args,
+ const struct pathspec *ps) {
+ int i;
+
+ for (i = 0; i < ps->nr; i++)
+ argv_array_push(args, ps->items[i].original);
+}
+
+/*
+ * `untracked_files` will be filled with the names of untracked files.
+ * The return value is:
+ *
+ * = 0 if there are not any untracked files
+ * > 0 if there are untracked files
+ */
+static int get_untracked_files(const struct pathspec *ps, int include_untracked,
+ struct strbuf *untracked_files)
+{
+ int i;
+ int max_len;
+ int found = 0;
+ char *seen;
+ struct dir_struct dir;
+
+ memset(&dir, 0, sizeof(dir));
+ if (include_untracked != INCLUDE_ALL_FILES)
+ setup_standard_excludes(&dir);
+
+ seen = xcalloc(ps->nr, 1);
+
+ max_len = fill_directory(&dir, the_repository->index, ps);
+ for (i = 0; i < dir.nr; i++) {
+ struct dir_entry *ent = dir.entries[i];
+ if (dir_path_match(&the_index, ent, ps, max_len, seen)) {
+ found++;
+ strbuf_addstr(untracked_files, ent->name);
+ /* NUL-terminate: will be fed to update-index -z */
+ strbuf_addch(untracked_files, '\0');
+ }
+ free(ent);
+ }
+
+ free(seen);
+ free(dir.entries);
+ free(dir.ignored);
+ clear_directory(&dir);
+ return found;
+}
+
+/*
+ * The return value of `check_changes_tracked_files()` can be:
+ *
+ * < 0 if there was an error
+ * = 0 if there are no changes.
+ * > 0 if there are changes.
+ */
+static int check_changes_tracked_files(const struct pathspec *ps)
+{
+ int result;
+ struct rev_info rev;
+ struct object_id dummy;
+ int ret = 0;
+
+ /* No initial commit. */
+ if (get_oid("HEAD", &dummy))
+ return -1;
+
+ if (read_cache() < 0)
+ return -1;
+
+ init_revisions(&rev, NULL);
+ copy_pathspec(&rev.prune_data, ps);
+
+ rev.diffopt.flags.quick = 1;
+ rev.diffopt.flags.ignore_submodules = 1;
+ rev.abbrev = 0;
+
+ add_head_to_pending(&rev);
+ diff_setup_done(&rev.diffopt);
+
+ result = run_diff_index(&rev, 1);
+ if (diff_result_code(&rev.diffopt, result)) {
+ ret = 1;
+ goto done;
+ }
+
+ object_array_clear(&rev.pending);
+ result = run_diff_files(&rev, 0);
+ if (diff_result_code(&rev.diffopt, result)) {
+ ret = 1;
+ goto done;
+ }
+
+done:
+ clear_pathspec(&rev.prune_data);
+ return ret;
+}
+
+/*
+ * The function will fill `untracked_files` with the names of untracked files
+ * It will return 1 if there were any changes and 0 if there were not.
+ */
+static int check_changes(const struct pathspec *ps, int include_untracked,
+ struct strbuf *untracked_files)
+{
+ int ret = 0;
+ if (check_changes_tracked_files(ps))
+ ret = 1;
+
+ if (include_untracked && get_untracked_files(ps, include_untracked,
+ untracked_files))
+ ret = 1;
+
+ return ret;
+}
+
+static int save_untracked_files(struct stash_info *info, struct strbuf *msg,
+ struct strbuf files)
+{
+ int ret = 0;
+ struct strbuf untracked_msg = STRBUF_INIT;
+ struct child_process cp_upd_index = CHILD_PROCESS_INIT;
+ struct index_state istate = { NULL };
+
+ cp_upd_index.git_cmd = 1;
+ argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
+ "--remove", "--stdin", NULL);
+ argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
+
+ strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf);
+ if (pipe_command(&cp_upd_index, files.buf, files.len, NULL, 0,
+ NULL, 0)) {
+ ret = -1;
+ goto done;
+ }
+
+ if (write_index_as_tree(&info->u_tree, &istate, stash_index_path.buf, 0,
+ NULL)) {
+ ret = -1;
+ goto done;
+ }
+
+ if (commit_tree(untracked_msg.buf, untracked_msg.len,
+ &info->u_tree, NULL, &info->u_commit, NULL, NULL)) {
+ ret = -1;
+ goto done;
+ }
+
+done:
+ discard_index(&istate);
+ strbuf_release(&untracked_msg);
+ remove_path(stash_index_path.buf);
+ return ret;
+}
+
+static int stash_patch(struct stash_info *info, const struct pathspec *ps,
+ struct strbuf *out_patch, int quiet)
+{
+ int ret = 0;
+ struct child_process cp_read_tree = CHILD_PROCESS_INIT;
+ struct child_process cp_add_i = CHILD_PROCESS_INIT;
+ struct child_process cp_diff_tree = CHILD_PROCESS_INIT;
+ struct index_state istate = { NULL };
+
+ remove_path(stash_index_path.buf);
+
+ cp_read_tree.git_cmd = 1;
+ argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL);
+ argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
+ if (run_command(&cp_read_tree)) {
+ ret = -1;
+ goto done;
+ }
+
+ /* Find out what the user wants. */
+ cp_add_i.git_cmd = 1;
+ argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash",
+ "--", NULL);
+ add_pathspecs(&cp_add_i.args, ps);
+ argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
+ if (run_command(&cp_add_i)) {
+ ret = -1;
+ goto done;
+ }
+
+ /* State of the working tree. */
+ if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,
+ NULL)) {
+ ret = -1;
+ goto done;
+ }
+
+ cp_diff_tree.git_cmd = 1;
+ argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD",
+ oid_to_hex(&info->w_tree), "--", NULL);
+ if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) {
+ ret = -1;
+ goto done;
+ }
+
+ if (!out_patch->len) {
+ if (!quiet)
+ fprintf_ln(stderr, _("No changes selected"));
+ ret = 1;
+ }
+
+done:
+ discard_index(&istate);
+ remove_path(stash_index_path.buf);
+ return ret;
+}
+
+static int stash_working_tree(struct stash_info *info, const struct pathspec *ps)
+{
+ int ret = 0;
+ struct rev_info rev;
+ struct child_process cp_upd_index = CHILD_PROCESS_INIT;
+ struct strbuf diff_output = STRBUF_INIT;
+ struct index_state istate = { NULL };
+
+ init_revisions(&rev, NULL);
+ copy_pathspec(&rev.prune_data, ps);
+
+ set_alternate_index_output(stash_index_path.buf);
+ if (reset_tree(&info->i_tree, 0, 0)) {
+ ret = -1;
+ goto done;
+ }
+ set_alternate_index_output(NULL);
+
+ rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
+ rev.diffopt.format_callback = add_diff_to_buf;
+ rev.diffopt.format_callback_data = &diff_output;
+
+ if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
+ ret = -1;
+ goto done;
+ }
+
+ add_pending_object(&rev, parse_object(the_repository, &info->b_commit),
+ "");
+ if (run_diff_index(&rev, 0)) {
+ ret = -1;
+ goto done;
+ }
+
+ cp_upd_index.git_cmd = 1;
+ argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add",
+ "--remove", "--stdin", NULL);
+ argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s",
+ stash_index_path.buf);
+
+ if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len,
+ NULL, 0, NULL, 0)) {
+ ret = -1;
+ goto done;
+ }
+
+ if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0,
+ NULL)) {
+ ret = -1;
+ goto done;
+ }
+
+done:
+ discard_index(&istate);
+ UNLEAK(rev);
+ object_array_clear(&rev.pending);
+ clear_pathspec(&rev.prune_data);
+ strbuf_release(&diff_output);
+ remove_path(stash_index_path.buf);
+ return ret;
+}
+
+static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf,
+ int include_untracked, int patch_mode,
+ struct stash_info *info, struct strbuf *patch,
+ int quiet)
+{
+ int ret = 0;
+ int flags = 0;
+ int untracked_commit_option = 0;
+ const char *head_short_sha1 = NULL;
+ const char *branch_ref = NULL;
+ const char *branch_name = "(no branch)";
+ struct commit *head_commit = NULL;
+ struct commit_list *parents = NULL;
+ struct strbuf msg = STRBUF_INIT;
+ struct strbuf commit_tree_label = STRBUF_INIT;
+ struct strbuf untracked_files = STRBUF_INIT;
+
+ prepare_fallback_ident("git stash", "git@stash");
+
+ read_cache_preload(NULL);
+ refresh_cache(REFRESH_QUIET);
+
+ if (get_oid("HEAD", &info->b_commit)) {
+ if (!quiet)
+ fprintf_ln(stderr, _("You do not have "
+ "the initial commit yet"));
+ ret = -1;
+ goto done;
+ } else {
+ head_commit = lookup_commit(the_repository, &info->b_commit);
+ }
+
+ if (!check_changes(ps, include_untracked, &untracked_files)) {
+ ret = 1;
+ goto done;
+ }
+
+ branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+ if (flags & REF_ISSYMREF)
+ branch_name = strrchr(branch_ref, '/') + 1;
+ head_short_sha1 = find_unique_abbrev(&head_commit->object.oid,
+ DEFAULT_ABBREV);
+ strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1);
+ pp_commit_easy(CMIT_FMT_ONELINE, head_commit, &msg);
+
+ strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf);
+ commit_list_insert(head_commit, &parents);
+ if (write_cache_as_tree(&info->i_tree, 0, NULL) ||
+ commit_tree(commit_tree_label.buf, commit_tree_label.len,
+ &info->i_tree, parents, &info->i_commit, NULL, NULL)) {
+ if (!quiet)
+ fprintf_ln(stderr, _("Cannot save the current "
+ "index state"));
+ ret = -1;
+ goto done;
+ }
+
+ if (include_untracked) {
+ if (save_untracked_files(info, &msg, untracked_files)) {
+ if (!quiet)
+ fprintf_ln(stderr, _("Cannot save "
+ "the untracked files"));
+ ret = -1;
+ goto done;
+ }
+ untracked_commit_option = 1;
+ }
+ if (patch_mode) {
+ ret = stash_patch(info, ps, patch, quiet);
+ if (ret < 0) {
+ if (!quiet)
+ fprintf_ln(stderr, _("Cannot save the current "
+ "worktree state"));
+ goto done;
+ } else if (ret > 0) {
+ goto done;
+ }
+ } else {
+ if (stash_working_tree(info, ps)) {
+ if (!quiet)
+ fprintf_ln(stderr, _("Cannot save the current "
+ "worktree state"));
+ ret = -1;
+ goto done;
+ }
+ }
+
+ if (!stash_msg_buf->len)
+ strbuf_addf(stash_msg_buf, "WIP on %s", msg.buf);
+ else
+ strbuf_insertf(stash_msg_buf, 0, "On %s: ", branch_name);
+
+ /*
+ * `parents` will be empty after calling `commit_tree()`, so there is
+ * no need to call `free_commit_list()`
+ */
+ parents = NULL;
+ if (untracked_commit_option)
+ commit_list_insert(lookup_commit(the_repository,
+ &info->u_commit),
+ &parents);
+ commit_list_insert(lookup_commit(the_repository, &info->i_commit),
+ &parents);
+ commit_list_insert(head_commit, &parents);
+
+ if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &info->w_tree,
+ parents, &info->w_commit, NULL, NULL)) {
+ if (!quiet)
+ fprintf_ln(stderr, _("Cannot record "
+ "working tree state"));
+ ret = -1;
+ goto done;
+ }
+
+done:
+ strbuf_release(&commit_tree_label);
+ strbuf_release(&msg);
+ strbuf_release(&untracked_files);
+ return ret;
+}
+
+static int create_stash(int argc, const char **argv, const char *prefix)
+{
+ int ret = 0;
+ struct strbuf stash_msg_buf = STRBUF_INIT;
+ struct stash_info info;
+ struct pathspec ps;
+
+ /* Starting with argv[1], since argv[0] is "create" */
+ strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' ');
+
+ memset(&ps, 0, sizeof(ps));
+ if (!check_changes_tracked_files(&ps))
+ return 0;
+
+ ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info,
+ NULL, 0);
+ if (!ret)
+ printf_ln("%s", oid_to_hex(&info.w_commit));
+
+ strbuf_release(&stash_msg_buf);
+ return ret;
+}
+
+static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet,
+ int keep_index, int patch_mode, int include_untracked)
+{
+ int ret = 0;
+ struct stash_info info;
+ struct strbuf patch = STRBUF_INIT;
+ struct strbuf stash_msg_buf = STRBUF_INIT;
+ struct strbuf untracked_files = STRBUF_INIT;
+
+ if (patch_mode && keep_index == -1)
+ keep_index = 1;
+
+ if (patch_mode && include_untracked) {
+ fprintf_ln(stderr, _("Can't use --patch and --include-untracked"
+ " or --all at the same time"));
+ ret = -1;
+ goto done;
+ }
+
+ read_cache_preload(NULL);
+ if (!include_untracked && ps->nr) {
+ int i;
+ char *ps_matched = xcalloc(ps->nr, 1);
+
+ for (i = 0; i < active_nr; i++)
+ ce_path_match(&the_index, active_cache[i], ps,
+ ps_matched);
+
+ if (report_path_error(ps_matched, ps)) {
+ fprintf_ln(stderr, _("Did you forget to 'git add'?"));
+ ret = -1;
+ free(ps_matched);
+ goto done;
+ }
+ free(ps_matched);
+ }
+
+ if (refresh_cache(REFRESH_QUIET)) {
+ ret = -1;
+ goto done;
+ }
+
+ if (!check_changes(ps, include_untracked, &untracked_files)) {
+ if (!quiet)
+ printf_ln(_("No local changes to save"));
+ goto done;
+ }
+
+ if (!reflog_exists(ref_stash) && do_clear_stash()) {
+ ret = -1;
+ if (!quiet)
+ fprintf_ln(stderr, _("Cannot initialize stash"));
+ goto done;
+ }
+
+ if (stash_msg)
+ strbuf_addstr(&stash_msg_buf, stash_msg);
+ if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode,
+ &info, &patch, quiet)) {
+ ret = -1;
+ goto done;
+ }
+
+ if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) {
+ ret = -1;
+ if (!quiet)
+ fprintf_ln(stderr, _("Cannot save the current status"));
+ goto done;
+ }
+
+ if (!quiet)
+ printf_ln(_("Saved working directory and index state %s"),
+ stash_msg_buf.buf);
+
+ if (!patch_mode) {
+ if (include_untracked && !ps->nr) {
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "clean", "--force",
+ "--quiet", "-d", NULL);
+ if (include_untracked == INCLUDE_ALL_FILES)
+ argv_array_push(&cp.args, "-x");
+ if (run_command(&cp)) {
+ ret = -1;
+ goto done;
+ }
+ }
+ discard_cache();
+ if (ps->nr) {
+ struct child_process cp_add = CHILD_PROCESS_INIT;
+ struct child_process cp_diff = CHILD_PROCESS_INIT;
+ struct child_process cp_apply = CHILD_PROCESS_INIT;
+ struct strbuf out = STRBUF_INIT;
+
+ cp_add.git_cmd = 1;
+ argv_array_push(&cp_add.args, "add");
+ if (!include_untracked)
+ argv_array_push(&cp_add.args, "-u");
+ if (include_untracked == INCLUDE_ALL_FILES)
+ argv_array_push(&cp_add.args, "--force");
+ argv_array_push(&cp_add.args, "--");
+ add_pathspecs(&cp_add.args, ps);
+ if (run_command(&cp_add)) {
+ ret = -1;
+ goto done;
+ }
+
+ cp_diff.git_cmd = 1;
+ argv_array_pushl(&cp_diff.args, "diff-index", "-p",
+ "--cached", "--binary", "HEAD", "--",
+ NULL);
+ add_pathspecs(&cp_diff.args, ps);
+ if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) {
+ ret = -1;
+ goto done;
+ }
+
+ cp_apply.git_cmd = 1;
+ argv_array_pushl(&cp_apply.args, "apply", "--index",
+ "-R", NULL);
+ if (pipe_command(&cp_apply, out.buf, out.len, NULL, 0,
+ NULL, 0)) {
+ ret = -1;
+ goto done;
+ }
+ } else {
+ struct child_process cp = CHILD_PROCESS_INIT;
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "reset", "--hard", "-q",
+ NULL);
+ if (run_command(&cp)) {
+ ret = -1;
+ goto done;
+ }
+ }
+
+ if (keep_index == 1 && !is_null_oid(&info.i_tree)) {
+ struct child_process cp_ls = CHILD_PROCESS_INIT;
+ struct child_process cp_checkout = CHILD_PROCESS_INIT;
+ struct strbuf out = STRBUF_INIT;
+
+ if (reset_tree(&info.i_tree, 0, 1)) {
+ ret = -1;
+ goto done;
+ }
+
+ cp_ls.git_cmd = 1;
+ argv_array_pushl(&cp_ls.args, "ls-files", "-z",
+ "--modified", "--", NULL);
+
+ add_pathspecs(&cp_ls.args, ps);
+ if (pipe_command(&cp_ls, NULL, 0, &out, 0, NULL, 0)) {
+ ret = -1;
+ goto done;
+ }
+
+ cp_checkout.git_cmd = 1;
+ argv_array_pushl(&cp_checkout.args, "checkout-index",
+ "-z", "--force", "--stdin", NULL);
+ if (pipe_command(&cp_checkout, out.buf, out.len, NULL,
+ 0, NULL, 0)) {
+ ret = -1;
+ goto done;
+ }
+ }
+ goto done;
+ } else {
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "apply", "-R", NULL);
+
+ if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) {
+ if (!quiet)
+ fprintf_ln(stderr, _("Cannot remove "
+ "worktree changes"));
+ ret = -1;
+ goto done;
+ }
+
+ if (keep_index < 1) {
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ cp.git_cmd = 1;
+ argv_array_pushl(&cp.args, "reset", "-q", "--", NULL);
+ add_pathspecs(&cp.args, ps);
+ if (run_command(&cp)) {
+ ret = -1;
+ goto done;
+ }
+ }
+ goto done;
+ }
+
+done:
+ strbuf_release(&stash_msg_buf);
+ return ret;
+}
+
+static int push_stash(int argc, const char **argv, const char *prefix)
+{
+ int keep_index = -1;
+ int patch_mode = 0;
+ int include_untracked = 0;
+ int quiet = 0;
+ const char *stash_msg = NULL;
+ struct pathspec ps;
+ struct option options[] = {
+ OPT_BOOL('k', "keep-index", &keep_index,
+ N_("keep index")),
+ OPT_BOOL('p', "patch", &patch_mode,
+ N_("stash in patch mode")),
+ OPT__QUIET(&quiet, N_("quiet mode")),
+ OPT_BOOL('u', "include-untracked", &include_untracked,
+ N_("include untracked files in stash")),
+ OPT_SET_INT('a', "all", &include_untracked,
+ N_("include ignore files"), 2),
+ OPT_STRING('m', "message", &stash_msg, N_("message"),
+ N_("stash message")),
+ OPT_END()
+ };
+
+ if (argc)
+ argc = parse_options(argc, argv, prefix, options,
+ git_stash_push_usage,
+ 0);
+
+ parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN,
+ prefix, argv);
+ return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode,
+ include_untracked);
+}
+
+static int save_stash(int argc, const char **argv, const char *prefix)
+{
+ int keep_index = -1;
+ int patch_mode = 0;
+ int include_untracked = 0;
+ int quiet = 0;
+ int ret = 0;
+ const char *stash_msg = NULL;
+ struct pathspec ps;
+ struct strbuf stash_msg_buf = STRBUF_INIT;
+ struct option options[] = {
+ OPT_BOOL('k', "keep-index", &keep_index,
+ N_("keep index")),
+ OPT_BOOL('p', "patch", &patch_mode,
+ N_("stash in patch mode")),
+ OPT__QUIET(&quiet, N_("quiet mode")),
+ OPT_BOOL('u', "include-untracked", &include_untracked,
+ N_("include untracked files in stash")),
+ OPT_SET_INT('a', "all", &include_untracked,
+ N_("include ignore files"), 2),
+ OPT_STRING('m', "message", &stash_msg, "message",
+ N_("stash message")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_stash_save_usage,
+ PARSE_OPT_KEEP_DASHDASH);
+
+ if (argc)
+ stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' ');
+
+ memset(&ps, 0, sizeof(ps));
+ ret = do_push_stash(&ps, stash_msg, quiet, keep_index,
+ patch_mode, include_untracked);
+
+ strbuf_release(&stash_msg_buf);
+ return ret;
+}
+
+static int use_builtin_stash(void)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf out = STRBUF_INIT;
+ int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1);
+
+ if (env != -1)
+ return env;
+
+ argv_array_pushl(&cp.args,
+ "config", "--bool", "stash.usebuiltin", NULL);
+ cp.git_cmd = 1;
+ if (capture_command(&cp, &out, 6)) {
+ strbuf_release(&out);
+ return 1;
+ }
+
+ strbuf_trim(&out);
+ ret = !strcmp("true", out.buf);
+ strbuf_release(&out);
+ return ret;
+}
+
+int cmd_stash(int argc, const char **argv, const char *prefix)
+{
+ int i = -1;
+ pid_t pid = getpid();
+ const char *index_file;
+ struct argv_array args = ARGV_ARRAY_INIT;
+
+ struct option options[] = {
+ OPT_END()
+ };
+
+ if (!use_builtin_stash()) {
+ const char *path = mkpath("%s/git-legacy-stash",
+ git_exec_path());
+
+ if (sane_execvp(path, (char **)argv) < 0)
+ die_errno(_("could not exec %s"), path);
+ else
+ BUG("sane_execvp() returned???");
+ }
+
+ prefix = setup_git_directory();
+ trace_repo_setup(prefix);
+ setup_work_tree();
+
+ git_config(git_diff_basic_config, NULL);
+
+ argc = parse_options(argc, argv, prefix, options, git_stash_usage,
+ PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
+
+ index_file = get_index_file();
+ strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file,
+ (uintmax_t)pid);
+
+ if (!argc)
+ return !!push_stash(0, NULL, prefix);
+ else if (!strcmp(argv[0], "apply"))
+ return !!apply_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "clear"))
+ return !!clear_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "drop"))
+ return !!drop_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "pop"))
+ return !!pop_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "branch"))
+ return !!branch_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "list"))
+ return !!list_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "show"))
+ return !!show_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "store"))
+ return !!store_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "create"))
+ return !!create_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "push"))
+ return !!push_stash(argc, argv, prefix);
+ else if (!strcmp(argv[0], "save"))
+ return !!save_stash(argc, argv, prefix);
+ else if (*argv[0] != '-')
+ usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]),
+ git_stash_usage, options);
+
+ if (strcmp(argv[0], "-p")) {
+ while (++i < argc && strcmp(argv[i], "--")) {
+ /*
+ * `akpqu` is a string which contains all short options,
+ * except `-m` which is verified separately.
+ */
+ if ((strlen(argv[i]) == 2) && *argv[i] == '-' &&
+ strchr("akpqu", argv[i][1]))
+ continue;
+
+ if (!strcmp(argv[i], "--all") ||
+ !strcmp(argv[i], "--keep-index") ||
+ !strcmp(argv[i], "--no-keep-index") ||
+ !strcmp(argv[i], "--patch") ||
+ !strcmp(argv[i], "--quiet") ||
+ !strcmp(argv[i], "--include-untracked"))
+ continue;
+
+ /*
+ * `-m` and `--message=` are verified separately because
+ * they need to be immediately followed by a string
+ * (i.e.`-m"foobar"` or `--message="foobar"`).
+ */
+ if (starts_with(argv[i], "-m") ||
+ starts_with(argv[i], "--message="))
+ continue;
+
+ usage_with_options(git_stash_usage, options);
+ }
+ }
+
+ argv_array_push(&args, "push");
+ argv_array_pushv(&args, argv);
+ return !!push_stash(args.argc, args.argv, prefix);
+}
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 6bcc4f1bd7..8c72ea864c 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -348,7 +348,7 @@ static int module_list_compute(int argc, const char **argv,
i++;
}
- if (ps_matched && report_path_error(ps_matched, pathspec, prefix))
+ if (ps_matched && report_path_error(ps_matched, pathspec))
result = -1;
free(ps_matched);
@@ -566,12 +566,12 @@ static int module_foreach(int argc, const char **argv, const char *prefix)
};
const char *const git_submodule_helper_usage[] = {
- N_("git submodule--helper foreach [--quiet] [--recursive] <command>"),
+ N_("git submodule--helper foreach [--quiet] [--recursive] [--] <command>"),
NULL
};
argc = parse_options(argc, argv, prefix, module_foreach_options,
- git_submodule_helper_usage, PARSE_OPT_KEEP_UNKNOWN);
+ git_submodule_helper_usage, 0);
if (module_list_compute(0, NULL, prefix, &pathspec, &list) < 0)
return 1;
@@ -709,7 +709,7 @@ static int module_init(int argc, const char **argv, const char *prefix)
};
const char *const git_submodule_helper_usage[] = {
- N_("git submodule--helper init [<path>]"),
+ N_("git submodule--helper init [<options>] [<path>]"),
NULL
};
@@ -2096,7 +2096,7 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
};
const char *const git_submodule_helper_usage[] = {
- N_("git submodule--helper embed-git-dir [<path>...]"),
+ N_("git submodule--helper absorb-git-dirs [<options>] [<path>...]"),
NULL
};
@@ -2147,17 +2147,22 @@ static int check_name(int argc, const char **argv, const char *prefix)
static int module_config(int argc, const char **argv, const char *prefix)
{
enum {
- CHECK_WRITEABLE = 1
+ CHECK_WRITEABLE = 1,
+ DO_UNSET = 2
} command = 0;
struct option module_config_options[] = {
OPT_CMDMODE(0, "check-writeable", &command,
N_("check if it is safe to write to the .gitmodules file"),
CHECK_WRITEABLE),
+ OPT_CMDMODE(0, "unset", &command,
+ N_("unset the config in the .gitmodules file"),
+ DO_UNSET),
OPT_END()
};
const char *const git_submodule_helper_usage[] = {
- N_("git submodule--helper config name [value]"),
+ N_("git submodule--helper config <name> [<value>]"),
+ N_("git submodule--helper config --unset <name>"),
N_("git submodule--helper config --check-writeable"),
NULL
};
@@ -2169,15 +2174,17 @@ static int module_config(int argc, const char **argv, const char *prefix)
return is_writing_gitmodules_ok() ? 0 : -1;
/* Equivalent to ACTION_GET in builtin/config.c */
- if (argc == 2)
+ if (argc == 2 && command != DO_UNSET)
return print_config_from_gitmodules(the_repository, argv[1]);
/* Equivalent to ACTION_SET in builtin/config.c */
- if (argc == 3) {
+ if (argc == 3 || (argc == 2 && command == DO_UNSET)) {
+ const char *value = (argc == 3) ? argv[2] : NULL;
+
if (!is_writing_gitmodules_ok())
die(_("please make sure that the .gitmodules file is in the working tree"));
- return config_set_in_gitmodules_file_gently(argv[1], argv[2]);
+ return config_set_in_gitmodules_file_gently(argv[1], value);
}
usage_with_options(git_submodule_helper_usage, module_config_options);
diff --git a/builtin/tag.c b/builtin/tag.c
index 16b59c45cf..ef37dccf86 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -408,8 +408,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")),
OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
- OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"),
- N_("how to strip spaces and #comments from message")),
+ OPT_CLEANUP(&cleanup_arg),
OPT_STRING('u', "local-user", &keyid, N_("key-id"),
N_("use another key to sign the tag")),
OPT__FORCE(&force, N_("replace the tag if exists"), 0),
@@ -423,8 +422,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")),
OPT_MERGED(&filter, N_("print only tags that are merged")),
OPT_NO_MERGED(&filter, N_("print only tags that are not merged")),
- OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"),
- N_("field name to sort on"), &parse_opt_ref_sorting),
+ OPT_REF_SORT(sorting_tail),
{
OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"),
N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT,
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 1b6c42f748..27db0928bf 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -597,7 +597,7 @@ static struct cache_entry *read_one_ent(const char *which,
struct object_id *ent, const char *path,
int namelen, int stage)
{
- unsigned mode;
+ unsigned short mode;
struct object_id oid;
struct cache_entry *ce;
@@ -724,7 +724,7 @@ static int do_unresolve(int ac, const char **av,
}
static int do_reupdate(int ac, const char **av,
- const char *prefix, int prefix_length)
+ const char *prefix)
{
/* Read HEAD and run update-index on paths that are
* merged and already different between index and HEAD.
@@ -940,8 +940,7 @@ static enum parse_opt_result reupdate_callback(
/* consume remaining arguments. */
setup_work_tree();
- *has_errors = do_reupdate(ctx->argc, ctx->argv,
- prefix, prefix ? strlen(prefix) : 0);
+ *has_errors = do_reupdate(ctx->argc, ctx->argv, prefix);
if (*has_errors)
active_cache_changed = 0;
@@ -1082,6 +1081,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
if (entries < 0)
die("cache corrupted");
+ the_index.updated_skipworktree = 1;
+
/*
* Custom copy of parse_options() because we want to handle
* filename arguments as they come.
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 6cc094a453..d2a7e2f3f1 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -268,10 +268,10 @@ static int add_worktree(const char *path, const char *refname,
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
struct strbuf sb = STRBUF_INIT;
const char *name;
- struct stat st;
struct child_process cp = CHILD_PROCESS_INIT;
struct argv_array child_env = ARGV_ARRAY_INIT;
- int counter = 0, len, ret;
+ unsigned int counter = 0;
+ int len, ret;
struct strbuf symref = STRBUF_INIT;
struct commit *commit = NULL;
int is_branch = 0;
@@ -295,8 +295,12 @@ static int add_worktree(const char *path, const char *refname,
if (safe_create_leading_directories_const(sb_repo.buf))
die_errno(_("could not create leading directories of '%s'"),
sb_repo.buf);
- while (!stat(sb_repo.buf, &st)) {
+
+ while (mkdir(sb_repo.buf, 0777)) {
counter++;
+ if ((errno != EEXIST) || !counter /* overflow */)
+ die_errno(_("could not create directory of '%s'"),
+ sb_repo.buf);
strbuf_setlen(&sb_repo, len);
strbuf_addf(&sb_repo, "%d", counter);
}
@@ -306,8 +310,6 @@ static int add_worktree(const char *path, const char *refname,
atexit(remove_junk);
sigchain_push_common(remove_junk_on_signal);
- if (mkdir(sb_repo.buf, 0777))
- die_errno(_("could not create directory of '%s'"), sb_repo.buf);
junk_git_dir = xstrdup(sb_repo.buf);
is_junk = 1;