diff options
Diffstat (limited to 'builtin')
-rw-r--r-- | builtin/am.c | 4 | ||||
-rw-r--r-- | builtin/branch.c | 3 | ||||
-rw-r--r-- | builtin/checkout.c | 11 | ||||
-rw-r--r-- | builtin/commit-tree.c | 158 | ||||
-rw-r--r-- | builtin/commit.c | 9 | ||||
-rw-r--r-- | builtin/diff.c | 22 | ||||
-rw-r--r-- | builtin/fetch-pack.c | 2 | ||||
-rw-r--r-- | builtin/fetch.c | 2 | ||||
-rw-r--r-- | builtin/for-each-ref.c | 3 | ||||
-rw-r--r-- | builtin/fsck.c | 62 | ||||
-rw-r--r-- | builtin/init-db.c | 10 | ||||
-rw-r--r-- | builtin/log.c | 60 | ||||
-rw-r--r-- | builtin/ls-files.c | 2 | ||||
-rw-r--r-- | builtin/ls-remote.c | 3 | ||||
-rw-r--r-- | builtin/multi-pack-index.c | 3 | ||||
-rw-r--r-- | builtin/range-diff.c | 26 | ||||
-rw-r--r-- | builtin/rebase--interactive.c | 144 | ||||
-rw-r--r-- | builtin/rebase.c | 96 | ||||
-rw-r--r-- | builtin/reset.c | 1 | ||||
-rw-r--r-- | builtin/rev-list.c | 2 | ||||
-rw-r--r-- | builtin/stash.c | 1648 | ||||
-rw-r--r-- | builtin/submodule--helper.c | 2 | ||||
-rw-r--r-- | builtin/tag.c | 3 | ||||
-rw-r--r-- | builtin/update-index.c | 7 | ||||
-rw-r--r-- | builtin/worktree.c | 12 |
25 files changed, 2048 insertions, 247 deletions
diff --git a/builtin/am.c b/builtin/am.c index 4fb107a9d1..3ba5723c2c 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1501,11 +1501,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/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..2e72a5e5a9 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; } @@ -726,6 +726,8 @@ static int merge_working_tree(const struct checkout_opts *opts, struct tree *result; struct tree *work; struct merge_options o; + struct strbuf sb = STRBUF_INIT; + if (!opts->merge) return 1; @@ -736,6 +738,13 @@ static int merge_working_tree(const struct checkout_opts *opts, if (!old_branch_info->commit) return 1; + 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 */ /* diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 12cc403bd7..b866d83951 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -12,8 +12,13 @@ #include "builtin.h" #include "utf8.h" #include "gpg-interface.h" +#include "parse-options.h" -static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1>"; +static const char * const commit_tree_usage[] = { + N_("git commit-tree [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...] " + "[(-F <file>)...] <tree>"), + NULL +}; static const char *sign_commit; @@ -23,7 +28,7 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p) struct commit_list *parents; for (parents = *parents_p; parents; parents = parents->next) { if (parents->item == parent) { - error("duplicate parent %s ignored", oid_to_hex(oid)); + error(_("duplicate parent %s ignored"), oid_to_hex(oid)); return; } parents_p = &parents->next; @@ -39,91 +44,100 @@ static int commit_tree_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int parse_parent_arg_callback(const struct option *opt, + const char *arg, int unset) +{ + struct object_id oid; + struct commit_list **parents = opt->value; + + BUG_ON_OPT_NEG_NOARG(unset, arg); + + if (get_oid_commit(arg, &oid)) + die(_("not a valid object name %s"), arg); + + assert_oid_type(&oid, OBJ_COMMIT); + new_parent(lookup_commit(the_repository, &oid), parents); + return 0; +} + +static int parse_message_arg_callback(const struct option *opt, + const char *arg, int unset) +{ + struct strbuf *buf = opt->value; + + BUG_ON_OPT_NEG_NOARG(unset, arg); + + if (buf->len) + strbuf_addch(buf, '\n'); + strbuf_addstr(buf, arg); + strbuf_complete_line(buf); + + return 0; +} + +static int parse_file_arg_callback(const struct option *opt, + const char *arg, int unset) +{ + int fd; + struct strbuf *buf = opt->value; + + BUG_ON_OPT_NEG_NOARG(unset, arg); + + if (buf->len) + strbuf_addch(buf, '\n'); + if (!strcmp(arg, "-")) + fd = 0; + else { + fd = open(arg, O_RDONLY); + if (fd < 0) + die_errno(_("git commit-tree: failed to open '%s'"), arg); + } + if (strbuf_read(buf, fd, 0) < 0) + die_errno(_("git commit-tree: failed to read '%s'"), arg); + if (fd && close(fd)) + die_errno(_("git commit-tree: failed to close '%s'"), arg); + + return 0; +} + int cmd_commit_tree(int argc, const char **argv, const char *prefix) { - int i, got_tree = 0; + static struct strbuf buffer = STRBUF_INIT; struct commit_list *parents = NULL; struct object_id tree_oid; struct object_id commit_oid; - struct strbuf buffer = STRBUF_INIT; + + struct option options[] = { + { OPTION_CALLBACK, 'p', NULL, &parents, N_("parent"), + N_("id of a parent commit object"), PARSE_OPT_NONEG, + parse_parent_arg_callback }, + { OPTION_CALLBACK, 'm', NULL, &buffer, N_("message"), + N_("commit message"), PARSE_OPT_NONEG, + parse_message_arg_callback }, + { OPTION_CALLBACK, 'F', NULL, &buffer, N_("file"), + N_("read commit log message from file"), PARSE_OPT_NONEG, + parse_file_arg_callback }, + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), + N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_END() + }; git_config(commit_tree_config, NULL); if (argc < 2 || !strcmp(argv[1], "-h")) - usage(commit_tree_usage); - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "-p")) { - struct object_id oid; - if (argc <= ++i) - usage(commit_tree_usage); - if (get_oid_commit(argv[i], &oid)) - die("Not a valid object name %s", argv[i]); - assert_oid_type(&oid, OBJ_COMMIT); - new_parent(lookup_commit(the_repository, &oid), - &parents); - continue; - } + usage_with_options(commit_tree_usage, options); - if (!strcmp(arg, "--gpg-sign")) { - sign_commit = ""; - continue; - } + argc = parse_options(argc, argv, prefix, options, commit_tree_usage, 0); - if (skip_prefix(arg, "-S", &sign_commit) || - skip_prefix(arg, "--gpg-sign=", &sign_commit)) - continue; + if (argc != 1) + die(_("must give exactly one tree")); - if (!strcmp(arg, "--no-gpg-sign")) { - sign_commit = NULL; - continue; - } - - if (!strcmp(arg, "-m")) { - if (argc <= ++i) - usage(commit_tree_usage); - if (buffer.len) - strbuf_addch(&buffer, '\n'); - strbuf_addstr(&buffer, argv[i]); - strbuf_complete_line(&buffer); - continue; - } - - if (!strcmp(arg, "-F")) { - int fd; - - if (argc <= ++i) - usage(commit_tree_usage); - if (buffer.len) - strbuf_addch(&buffer, '\n'); - if (!strcmp(argv[i], "-")) - fd = 0; - else { - fd = open(argv[i], O_RDONLY); - if (fd < 0) - die_errno("git commit-tree: failed to open '%s'", - argv[i]); - } - if (strbuf_read(&buffer, fd, 0) < 0) - die_errno("git commit-tree: failed to read '%s'", - argv[i]); - if (fd && close(fd)) - die_errno("git commit-tree: failed to close '%s'", - argv[i]); - continue; - } - - if (get_oid_tree(arg, &tree_oid)) - die("Not a valid object name %s", arg); - if (got_tree) - die("Cannot give more than one trees"); - got_tree = 1; - } + if (get_oid_tree(argv[0], &tree_oid)) + die(_("not a valid object name %s"), argv[0]); if (!buffer.len) { if (strbuf_read(&buffer, 0, 0) < 0) - die_errno("git commit-tree: failed to read"); + die_errno(_("git commit-tree: failed to read")); } if (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid, diff --git a/builtin/commit.c b/builtin/commit.c index f17537474a..833ecb316a 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(); @@ -1194,7 +1194,8 @@ static int parse_and_validate_options(int argc, const char *argv[], 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; 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/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/fetch.c b/builtin/fetch.c index b620fd54b4..4ba63d5ac6 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1556,7 +1556,9 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, int pru sigchain_push_common(unlock_pack_on_signal); atexit(unlock_pack); + sigchain_push(SIGPIPE, SIG_IGN); exit_code = do_fetch(gtransport, &rs); + sigchain_pop(SIGPIPE); refspec_clear(&rs); transport_disconnect(gtransport); gtransport = NULL; 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/fsck.c b/builtin/fsck.c index bb4227bebc..d26fb0a044 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -235,6 +235,48 @@ static int mark_used(struct object *obj, int type, void *data, struct fsck_optio return 0; } +static void mark_unreachable_referents(const struct object_id *oid) +{ + struct fsck_options options = FSCK_OPTIONS_DEFAULT; + struct object *obj = lookup_object(the_repository, oid->hash); + + if (!obj || !(obj->flags & HAS_OBJ)) + return; /* not part of our original set */ + if (obj->flags & REACHABLE) + return; /* reachable objects already traversed */ + + /* + * Avoid passing OBJ_NONE to fsck_walk, which will parse the object + * (and we want to avoid parsing blobs). + */ + if (obj->type == OBJ_NONE) { + enum object_type type = oid_object_info(the_repository, + &obj->oid, NULL); + if (type > 0) + object_as_type(the_repository, obj, type, 0); + } + + options.walk = mark_used; + fsck_walk(obj, NULL, &options); +} + +static int mark_loose_unreachable_referents(const struct object_id *oid, + const char *path, + void *data) +{ + mark_unreachable_referents(oid); + return 0; +} + +static int mark_packed_unreachable_referents(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + mark_unreachable_referents(oid); + return 0; +} + /* * Check a single reachable object */ @@ -347,6 +389,26 @@ static void check_connectivity(void) /* Traverse the pending reachable objects */ traverse_reachable(); + /* + * With --connectivity-only, we won't have actually opened and marked + * unreachable objects with USED. Do that now to make --dangling, etc + * accurate. + */ + if (connectivity_only && (show_dangling || write_lost_and_found)) { + /* + * Even though we already have a "struct object" for each of + * these in memory, we must not iterate over the internal + * object hash as we do below. Our loop would potentially + * resize the hash, making our iteration invalid. + * + * Instead, we'll just go back to the source list of objects, + * and ignore any that weren't present in our earlier + * traversal. + */ + for_each_loose_object(mark_loose_unreachable_referents, NULL, 0); + for_each_packed_object(mark_packed_unreachable_referents, NULL, 0); + } + /* Look up all the requirements, warn about missing objects.. */ max = get_max_object_index(); if (verbose) diff --git a/builtin/init-db.c b/builtin/init-db.c index 93eff7618c..6ca002893f 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -96,7 +96,7 @@ static void copy_templates(const char *template_dir) struct strbuf path = STRBUF_INIT; struct strbuf template_path = STRBUF_INIT; size_t template_len; - struct repository_format template_format; + struct repository_format template_format = REPOSITORY_FORMAT_INIT; struct strbuf err = STRBUF_INIT; DIR *dir; char *to_free = NULL; @@ -148,6 +148,7 @@ free_return: free(to_free); strbuf_release(&path); strbuf_release(&template_path); + clear_repository_format(&template_format); } static int git_init_db_config(const char *k, const char *v, void *cb) @@ -155,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; } @@ -185,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); /* @@ -361,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..37a08c714b 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -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/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/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/rebase--interactive.c b/builtin/rebase--interactive.c index 888390f911..4535523bf5 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -14,6 +14,103 @@ 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 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(const char *upstream, const char *onto, const char **head_hash, char **revisions, char **shortrevisions) @@ -66,13 +163,13 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, 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) + struct string_list *commands, 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; + struct todo_list todo_list = TODO_LIST_INIT; if (prepare_branch_to_be_rebased(opts, switch_to)) return -1; @@ -94,34 +191,29 @@ static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, 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, + ret = sequencer_make_script(the_repository, &todo_list.buf, 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); + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) + BUG("unusable todo list"); + + ret = complete_action(the_repository, opts, flags, shortrevisions, onto_name, + onto, head_hash, commands, autosquash, &todo_list); } free(revisions); free(shortrevisions); + todo_list_release(&todo_list); argv_array_clear(&make_script_args); return ret; @@ -140,6 +232,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) const char *onto = NULL, *onto_name = NULL, *restrict_revision = NULL, *squash_onto = NULL, *upstream = NULL, *head_name = NULL, *switch_to = NULL, *cmd = NULL; + struct string_list commands = STRING_LIST_INIT_DUP; char *raw_strategies = NULL; enum { NONE = 0, CONTINUE, SKIP, EDIT_TODO, SHOW_CURRENT_PATCH, @@ -224,6 +317,14 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) warning(_("--[no-]rebase-cousins has no effect without " "--rebase-merges")); + 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); + } + switch (command) { case NONE: if (!onto && !upstream) @@ -231,7 +332,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) ret = do_interactive_rebase(&opts, flags, switch_to, upstream, onto, onto_name, squash_onto, head_name, restrict_revision, - raw_strategies, cmd, autosquash); + raw_strategies, &commands, autosquash); break; case SKIP: { struct string_list merge_rr = STRING_LIST_INIT_DUP; @@ -243,7 +344,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) break; } case EDIT_TODO: - ret = edit_todo_list(the_repository, flags); + ret = edit_todo_file(flags); break; case SHOW_CURRENT_PATCH: { struct child_process cmd = CHILD_PROCESS_INIT; @@ -256,20 +357,21 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) } case SHORTEN_OIDS: case EXPAND_OIDS: - ret = transform_todos(the_repository, flags); + ret = transform_todo_file(flags); break; case CHECK_TODO_LIST: - ret = check_todo_list(the_repository); + ret = check_todo_list_from_file(the_repository); break; case REARRANGE_SQUASH: - ret = rearrange_squash(the_repository); + ret = rearrange_squash_in_todo_file(); break; case ADD_EXEC: - ret = sequencer_add_exec_commands(the_repository, cmd); + ret = add_exec_commands(&commands); break; default: BUG("invalid command '%d'", command); } + string_list_clear(&commands, 0); return !!ret; } diff --git a/builtin/rebase.c b/builtin/rebase.c index 52114cbf0d..2e41ad5644 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -46,29 +46,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,6 +83,7 @@ struct rebase_options { char *strategy, *strategy_opts; struct strbuf git_format_patch_opt; int reschedule_failed_exec; + int use_legacy_rebase; }; static int is_interactive(struct rebase_options *opts) @@ -369,6 +347,7 @@ static void add_var(struct strbuf *buf, const char *name, const char *value) #define RESET_HEAD_HARD (1<<1) #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2) #define RESET_HEAD_REFS_ONLY (1<<3) +#define RESET_ORIG_HEAD (1<<4) static int reset_head(struct object_id *oid, const char *action, const char *switch_to_branch, unsigned flags, @@ -378,6 +357,7 @@ static int reset_head(struct object_id *oid, const char *action, unsigned reset_hard = flags & RESET_HEAD_HARD; unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; unsigned refs_only = flags & RESET_HEAD_REFS_ONLY; + unsigned update_orig_head = flags & RESET_ORIG_HEAD; struct object_id head_oid; struct tree_desc desc[2] = { { NULL }, { NULL } }; struct lock_file lock = LOCK_INIT; @@ -454,18 +434,21 @@ reset_head_refs: strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); prefix_len = msg.len; - if (!get_oid("ORIG_HEAD", &oid_old_orig)) - old_orig = &oid_old_orig; - if (!get_oid("HEAD", &oid_orig)) { - orig = &oid_orig; - if (!reflog_orig_head) { - strbuf_addstr(&msg, "updating ORIG_HEAD"); - reflog_orig_head = msg.buf; - } - update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0, - UPDATE_REFS_MSG_ON_ERR); - } else if (old_orig) - delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + if (update_orig_head) { + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (!get_oid("HEAD", &oid_orig)) { + orig = &oid_orig; + if (!reflog_orig_head) { + strbuf_addstr(&msg, "updating ORIG_HEAD"); + reflog_orig_head = msg.buf; + } + update_ref(reflog_orig_head, "ORIG_HEAD", orig, + old_orig, 0, UPDATE_REFS_MSG_ON_ERR); + } else if (old_orig) + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + } + if (!reflog_head) { strbuf_setlen(&msg, prefix_len); strbuf_addstr(&msg, "updating HEAD"); @@ -476,7 +459,7 @@ reset_head_refs: detach_head ? REF_NO_DEREF : 0, UPDATE_REFS_MSG_ON_ERR); else { - ret = update_ref(reflog_orig_head, switch_to_branch, oid, + ret = update_ref(reflog_head, switch_to_branch, oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR); if (!ret) ret = create_symref("HEAD", switch_to_branch, @@ -869,6 +852,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); } @@ -1100,8 +1088,8 @@ 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), + N_("(DEPRECATED) 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 " @@ -1143,22 +1131,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); @@ -1169,6 +1141,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) 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)) @@ -1212,6 +1189,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (options.type == REBASE_PRESERVE_MERGES) + warning(_("git rebase --preserve-merges is deprecated. " + "Use --rebase-merges instead.")); + if (action != NO_ACTION && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1777,7 +1758,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, + RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, msg.buf)) die(_("Could not detach HEAD")); strbuf_release(&msg); @@ -1793,8 +1775,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "rebase finished: %s onto %s", options.head_name ? options.head_name : "detached HEAD", oid_to_hex(&options.onto->object.oid)); - reset_head(NULL, "Fast-forwarded", options.head_name, 0, - "HEAD", msg.buf); + reset_head(NULL, "Fast-forwarded", options.head_name, + RESET_HEAD_REFS_ONLY, "HEAD", msg.buf); strbuf_release(&msg); ret = !!finish_rebase(&options); goto cleanup; 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 5b5b6dbb1c..425a5774db 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -238,7 +238,7 @@ static inline void finish_object__ma(struct object *obj) static int finish_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; - if (!has_object_file(&obj->oid)) { + if (oid_object_info_extended(the_repository, &obj->oid, NULL, 0) < 0) { finish_object__ma(obj); return 1; } 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..8342b78add 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); diff --git a/builtin/tag.c b/builtin/tag.c index 02f6bd1279..ad97595fbf 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -412,8 +412,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..890c251d66 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -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; |