diff options
Diffstat (limited to 'builtin')
38 files changed, 747 insertions, 1295 deletions
diff --git a/builtin/blame.c b/builtin/blame.c index bf1cecdf3f..3c13634f27 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1061,6 +1061,14 @@ parse_done: string_list_clear(&ignore_revs_file_list, 0); string_list_clear(&ignore_rev_list, 0); setup_scoreboard(&sb, path, &o); + + /* + * Changed-path Bloom filters are disabled when looking + * for copies. + */ + if (!(opt & PICKAXE_BLAME_COPY)) + setup_blame_bloom_data(&sb, path); + lno = sb.num_lines; if (lno && !range_list.nr) @@ -1164,5 +1172,7 @@ parse_done: printf("num get patch: %d\n", sb.num_get_patch); printf("num commits: %d\n", sb.num_commits); } + + cleanup_scoreboard(&sb); return 0; } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 272f9fc6d7..0d03fdac6e 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -12,7 +12,7 @@ #include "userdiff.h" #include "streaming.h" #include "tree-walk.h" -#include "sha1-array.h" +#include "oid-array.h" #include "packfile.h" #include "object-store.h" #include "promisor-remote.h" @@ -42,7 +42,10 @@ static int filter_object(const char *path, unsigned mode, oid_to_hex(oid), path); if ((type == OBJ_BLOB) && S_ISREG(mode)) { struct strbuf strbuf = STRBUF_INIT; - if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf)) { + struct checkout_metadata meta; + + init_checkout_metadata(&meta, NULL, NULL, oid); + if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf, &meta)) { free(*buf); *size = strbuf.len; *buf = strbuf_detach(&strbuf, NULL); diff --git a/builtin/checkout.c b/builtin/checkout.c index d6773818b8..8bc94d392b 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -88,6 +88,19 @@ struct checkout_opts { struct tree *source_tree; }; +struct branch_info { + const char *name; /* The short name used */ + const char *path; /* The full name of a real branch */ + struct commit *commit; /* The named commit */ + char *refname; /* The full name of the ref being checked out. */ + struct object_id oid; /* The object ID of the commit being checked out. */ + /* + * if not null the branch is detached because it's already + * checked out in this checkout + */ + char *checkout; +}; + static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, int changed) { @@ -337,7 +350,8 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce, } } -static int checkout_worktree(const struct checkout_opts *opts) +static int checkout_worktree(const struct checkout_opts *opts, + const struct branch_info *info) { struct checkout state = CHECKOUT_INIT; int nr_checkouts = 0, nr_unmerged = 0; @@ -348,6 +362,10 @@ static int checkout_worktree(const struct checkout_opts *opts) state.refresh_cache = 1; state.istate = &the_index; + init_checkout_metadata(&state.meta, info->refname, + info->commit ? &info->commit->object.oid : &info->oid, + NULL); + enable_delayed_checkout(&state); for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; @@ -396,7 +414,7 @@ static int checkout_worktree(const struct checkout_opts *opts) } static int checkout_paths(const struct checkout_opts *opts, - const char *revision) + const struct branch_info *new_branch_info) { int pos; static char *ps_matched; @@ -462,7 +480,7 @@ static int checkout_paths(const struct checkout_opts *opts, else BUG("either flag must have been set, worktree=%d, index=%d", opts->checkout_worktree, opts->checkout_index); - return run_add_interactive(revision, patch_mode, &opts->pathspec); + return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec); } repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -523,7 +541,7 @@ static int checkout_paths(const struct checkout_opts *opts, /* Now we are committed to check them out */ if (opts->checkout_worktree) - errs |= checkout_worktree(opts); + errs |= checkout_worktree(opts, new_branch_info); else remove_marked_cache_entries(&the_index, 1); @@ -586,7 +604,8 @@ static void describe_detached_head(const char *msg, struct commit *commit) } static int reset_tree(struct tree *tree, const struct checkout_opts *o, - int worktree, int *writeout_error) + int worktree, int *writeout_error, + struct branch_info *info) { struct unpack_trees_options opts; struct tree_desc tree_desc; @@ -601,6 +620,11 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, opts.verbose_update = o->show_progress; opts.src_index = &the_index; opts.dst_index = &the_index; + init_checkout_metadata(&opts.meta, info->refname, + info->commit ? &info->commit->object.oid : + is_null_oid(&info->oid) ? &tree->object.oid : + &info->oid, + NULL); parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); switch (unpack_trees(1, &tree_desc, &opts)) { @@ -620,21 +644,17 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, } } -struct branch_info { - const char *name; /* The short name used */ - const char *path; /* The full name of a real branch */ - struct commit *commit; /* The named commit */ - /* - * if not null the branch is detached because it's already - * checked out in this checkout - */ - char *checkout; -}; - static void setup_branch_path(struct branch_info *branch) { struct strbuf buf = STRBUF_INIT; + /* + * If this is a ref, resolve it; otherwise, look up the OID for our + * expression. Failure here is okay. + */ + if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname)) + repo_get_oid_committish(the_repository, branch->name, &branch->oid); + strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL); if (strcmp(buf.buf, branch->name)) branch->name = xstrdup(buf.buf); @@ -663,7 +683,7 @@ static int merge_working_tree(const struct checkout_opts *opts, } else new_tree = get_commit_tree(new_branch_info->commit); if (opts->discard_changes) { - ret = reset_tree(new_tree, opts, 1, writeout_error); + ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info); if (ret) return ret; } else { @@ -692,6 +712,10 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.quiet = opts->merge && old_branch_info->commit; topts.verbose_update = opts->show_progress; topts.fn = twoway_merge; + init_checkout_metadata(&topts.meta, new_branch_info->refname, + new_branch_info->commit ? + &new_branch_info->commit->object.oid : + &new_branch_info->oid, NULL); if (opts->overwrite_ignore) { topts.dir = xcalloc(1, sizeof(*topts.dir)); topts.dir->flags |= DIR_SHOW_IGNORED; @@ -762,7 +786,7 @@ static int merge_working_tree(const struct checkout_opts *opts, ret = reset_tree(new_tree, opts, 1, - writeout_error); + writeout_error, new_branch_info); if (ret) return ret; o.ancestor = old_branch_info->name; @@ -782,7 +806,7 @@ static int merge_working_tree(const struct checkout_opts *opts, exit(128); ret = reset_tree(new_tree, opts, 0, - writeout_error); + writeout_error, new_branch_info); strbuf_release(&o.obuf); strbuf_release(&old_commit_shortname); if (ret) @@ -1710,7 +1734,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, UNLEAK(opts); if (opts->patch_mode || opts->pathspec.nr) - return checkout_paths(opts, new_branch_info.name); + return checkout_paths(opts, &new_branch_info); else return checkout_branch(opts, &new_branch_info); } diff --git a/builtin/clean.c b/builtin/clean.c index 5abf087e7c..f14c21b863 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -18,6 +18,7 @@ #include "color.h" #include "pathspec.h" #include "help.h" +#include "prompt.h" static int force = -1; /* unset */ static int interactive; @@ -420,7 +421,6 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff) return found; } - /* * Parse user input, and return choice(s) for menu (menu_stuff). * @@ -580,9 +580,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) clean_get_color(CLEAN_COLOR_RESET)); } - if (strbuf_getline_lf(&choice, stdin) != EOF) { - strbuf_trim(&choice); - } else { + if (git_read_line_interactively(&choice) == EOF) { eof = 1; break; } @@ -662,9 +660,7 @@ static int filter_by_patterns_cmd(void) clean_print_color(CLEAN_COLOR_PROMPT); printf(_("Input ignore patterns>> ")); clean_print_color(CLEAN_COLOR_RESET); - if (strbuf_getline_lf(&confirm, stdin) != EOF) - strbuf_trim(&confirm); - else + if (git_read_line_interactively(&confirm) == EOF) putchar('\n'); /* quit filter_by_pattern mode if press ENTER or Ctrl-D */ @@ -760,9 +756,7 @@ static int ask_each_cmd(void) qname = quote_path_relative(item->string, NULL, &buf); /* TRANSLATORS: Make sure to keep [y/N] as is */ printf(_("Remove %s [y/N]? "), qname); - if (strbuf_getline_lf(&confirm, stdin) != EOF) { - strbuf_trim(&confirm); - } else { + if (git_read_line_interactively(&confirm) == EOF) { putchar('\n'); eof = 1; } @@ -989,12 +983,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (!cache_name_is_other(ent->name, ent->len)) continue; - if (pathspec.nr) - matches = dir_path_match(&the_index, ent, &pathspec, 0, NULL); - - if (pathspec.nr && !matches) - continue; - if (lstat(ent->name, &st)) die_errno("Cannot lstat '%s'", ent->name); diff --git a/builtin/clone.c b/builtin/clone.c index 1ad26f4d8c..cb48a291ca 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -102,10 +102,10 @@ static struct option builtin_clone_options[] = { N_("don't use local hardlinks, always copy")), OPT_BOOL('s', "shared", &option_shared, N_("setup as shared repository")), - OPT_ALIAS(0, "recursive", "recurse-submodules"), { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules, N_("pathspec"), N_("initialize submodules in the clone"), PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." }, + OPT_ALIAS(0, "recursive", "recurse-submodules"), OPT_INTEGER('j', "jobs", &max_jobs, N_("number of submodules cloned in parallel")), OPT_STRING(0, "template", &option_template, N_("template-directory"), @@ -420,6 +420,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, struct dir_iterator *iter; int iter_status; unsigned int flags; + struct strbuf realpath = STRBUF_INIT; mkdir_if_missing(dest->buf, 0777); @@ -454,7 +455,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, if (unlink(dest->buf) && errno != ENOENT) die_errno(_("failed to unlink '%s'"), dest->buf); if (!option_no_hardlinks) { - if (!link(real_path(src->buf), dest->buf)) + strbuf_realpath(&realpath, src->buf, 1); + if (!link(realpath.buf, dest->buf)) continue; if (option_local > 0) die_errno(_("failed to create link '%s'"), dest->buf); @@ -468,6 +470,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, strbuf_setlen(src, src_len); die(_("failed to iterate over '%s'"), src->buf); } + + strbuf_release(&realpath); } static void clone_local(const char *src_repo, const char *dest_repo) @@ -639,7 +643,9 @@ static void write_followtags(const struct ref *refs, const char *msg) continue; if (ends_with(ref->name, "^{}")) continue; - if (!has_object_file(&ref->old_oid)) + if (!has_object_file_with_flags(&ref->old_oid, + OBJECT_INFO_QUICK | + OBJECT_INFO_SKIP_FETCH_OBJECT)) continue; update_ref(msg, ref->name, &ref->old_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); @@ -672,8 +678,7 @@ static void update_remote_refs(const struct ref *refs, const char *branch_top, const char *msg, struct transport *transport, - int check_connectivity, - int check_refs_are_promisor_objects_only) + int check_connectivity) { const struct ref *rm = mapped_refs; @@ -682,8 +687,6 @@ static void update_remote_refs(const struct ref *refs, opt.transport = transport; opt.progress = transport->progress; - opt.check_refs_are_promisor_objects_only = - !!check_refs_are_promisor_objects_only; if (check_connected(iterate_ref_map, &rm, &opt)) die(_("remote did not send all necessary objects")); @@ -780,11 +783,11 @@ static int checkout(int submodule_progress) if (!strcmp(head, "HEAD")) { if (advice_detached_head) detach_advice(oid_to_hex(&oid)); + FREE_AND_NULL(head); } else { if (!starts_with(head, "refs/heads/")) die(_("HEAD not found below refs/heads!")); } - free(head); /* We need to be in the new work tree for the checkout */ setup_work_tree(); @@ -799,6 +802,7 @@ static int checkout(int submodule_progress) opts.verbose_update = (option_verbosity >= 0); opts.src_index = &the_index; opts.dst_index = &the_index; + init_checkout_metadata(&opts.meta, head, &oid, NULL); tree = parse_tree_indirect(&oid); parse_tree(tree); @@ -806,6 +810,8 @@ static int checkout(int submodule_progress) if (unpack_trees(1, &t, &opts) < 0) die(_("unable to checkout working tree")); + free(head); + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); @@ -1102,7 +1108,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } } - init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET); + init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, INIT_DB_QUIET); if (real_git_dir) git_dir = real_git_dir; @@ -1275,7 +1281,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, filter_options.choice); + !is_local); update_head(our_head_points_at, remote_head, reflog_msg.buf); diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 4a70b33fb5..15fe60317c 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -9,7 +9,9 @@ static char const * const builtin_commit_graph_usage[] = { N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), - N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] <split options>"), + N_("git commit-graph write [--object-dir <objdir>] [--append] " + "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " + "[--changed-paths] [--[no-]progress] <split options>"), NULL }; @@ -19,7 +21,9 @@ static const char * const builtin_commit_graph_verify_usage[] = { }; static const char * const builtin_commit_graph_write_usage[] = { - N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] <split options>"), + N_("git commit-graph write [--object-dir <objdir>] [--append] " + "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " + "[--changed-paths] [--[no-]progress] <split options>"), NULL }; @@ -32,6 +36,7 @@ static struct opts_commit_graph { int split; int shallow; int progress; + int enable_changed_paths; } opts; static struct object_directory *find_odb(struct repository *r, @@ -39,14 +44,17 @@ static struct object_directory *find_odb(struct repository *r, { struct object_directory *odb; char *obj_dir_real = real_pathdup(obj_dir, 1); + struct strbuf odb_path_real = STRBUF_INIT; prepare_alt_odb(r); for (odb = r->objects->odb; odb; odb = odb->next) { - if (!strcmp(obj_dir_real, real_path(odb->path))) + strbuf_realpath(&odb_path_real, odb->path, 1); + if (!strcmp(obj_dir_real, odb_path_real.buf)) break; } free(obj_dir_real); + strbuf_release(&odb_path_real); if (!odb) die(_("could not find object directory matching %s"), obj_dir); @@ -111,10 +119,29 @@ static int graph_verify(int argc, const char **argv) extern int read_replace_refs; static struct split_commit_graph_opts split_opts; +static int write_option_parse_split(const struct option *opt, const char *arg, + int unset) +{ + enum commit_graph_split_flags *flags = opt->value; + + opts.split = 1; + if (!arg) + return 0; + + if (!strcmp(arg, "no-merge")) + *flags = COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED; + else if (!strcmp(arg, "replace")) + *flags = COMMIT_GRAPH_SPLIT_REPLACE; + else + die(_("unrecognized --split argument, %s"), arg); + + return 0; +} + static int graph_write(int argc, const char **argv) { struct string_list *pack_indexes = NULL; - struct string_list *commit_hex = NULL; + struct oidset commits = OIDSET_INIT; struct object_directory *odb = NULL; struct string_list lines; int result = 0; @@ -132,15 +159,19 @@ static int graph_write(int argc, const char **argv) N_("start walk at commits listed by stdin")), OPT_BOOL(0, "append", &opts.append, N_("include all commits already in the commit-graph file")), + OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths, + N_("enable computation for changed paths")), OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), - OPT_BOOL(0, "split", &opts.split, - N_("allow writing an incremental commit-graph file")), + OPT_CALLBACK_F(0, "split", &split_opts.flags, NULL, + N_("allow writing an incremental commit-graph file"), + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, + write_option_parse_split), OPT_INTEGER(0, "max-commits", &split_opts.max_commits, N_("maximum number of commits in a non-base split commit-graph")), OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple, N_("maximum ratio between two levels of a split commit-graph")), OPT_EXPIRY_DATE(0, "expire-time", &split_opts.expire_time, - N_("maximum number of commits in a non-base split commit-graph")), + N_("only expire files older than a given date-time")), OPT_END(), }; @@ -165,6 +196,9 @@ static int graph_write(int argc, const char **argv) flags |= COMMIT_GRAPH_WRITE_SPLIT; if (opts.progress) flags |= COMMIT_GRAPH_WRITE_PROGRESS; + if (opts.enable_changed_paths || + git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0)) + flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS; read_replace_refs = 0; odb = find_odb(the_repository, opts.obj_dir); @@ -185,7 +219,20 @@ static int graph_write(int argc, const char **argv) if (opts.stdin_packs) pack_indexes = &lines; if (opts.stdin_commits) { - commit_hex = &lines; + struct string_list_item *item; + oidset_init(&commits, lines.nr); + for_each_string_list_item(item, &lines) { + struct object_id oid; + const char *end; + + if (parse_oid_hex(item->string, &oid, &end)) { + error(_("unexpected non-hex object ID: " + "%s"), item->string); + return 1; + } + + oidset_insert(&commits, &oid); + } flags |= COMMIT_GRAPH_WRITE_CHECK_OIDS; } @@ -194,7 +241,7 @@ static int graph_write(int argc, const char **argv) if (write_commit_graph(odb, pack_indexes, - commit_hex, + opts.stdin_commits ? &commits : NULL, flags, &split_opts)) result = 1; diff --git a/builtin/commit.c b/builtin/commit.c index 7ba33a3bec..4743ea5a4c 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -59,6 +59,9 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\ " git commit --allow-empty\n" "\n"); +static const char empty_rebase_pick_advice[] = +N_("Otherwise, please use 'git rebase --skip'\n"); + static const char empty_cherry_pick_advice_single[] = N_("Otherwise, please use 'git cherry-pick --skip'\n"); @@ -122,7 +125,6 @@ static enum commit_msg_cleanup_mode cleanup_mode; static const char *cleanup_arg; static enum commit_whence whence; -static int sequencer_in_use; static int use_editor = 1, include_status = 1; static int have_option_m; static struct strbuf message = STRBUF_INIT; @@ -179,12 +181,7 @@ static void determine_whence(struct wt_status *s) { if (file_exists(git_path_merge_head(the_repository))) whence = FROM_MERGE; - else if (file_exists(git_path_cherry_pick_head(the_repository))) { - whence = FROM_CHERRY_PICK; - if (file_exists(git_path_seq_dir())) - sequencer_in_use = 1; - } - else + else if (!sequencer_determine_whence(the_repository, &whence)) whence = FROM_COMMIT; if (s) s->whence = whence; @@ -477,8 +474,10 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("cannot do a partial commit during a merge.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("cannot do a partial commit during a cherry-pick.")); + else if (is_from_rebase(whence)) + die(_("cannot do a partial commit during a rebase.")); } if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec)) @@ -795,7 +794,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ else if (whence == FROM_MERGE) hook_arg1 = "merge"; - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) { hook_arg1 = "commit"; hook_arg2 = "CHERRY_PICK_HEAD"; } @@ -973,12 +972,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix, run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || + whence == FROM_REBASE_PICK) { fputs(_(empty_cherry_pick_advice), stderr); - if (!sequencer_in_use) + if (whence == FROM_CHERRY_PICK_SINGLE) fputs(_(empty_cherry_pick_advice_single), stderr); - else + else if (whence == FROM_CHERRY_PICK_MULTI) fputs(_(empty_cherry_pick_advice_multi), stderr); + else + fputs(_(empty_rebase_pick_advice), stderr); } return 0; } @@ -1181,8 +1183,10 @@ static int parse_and_validate_options(int argc, const char *argv[], if (amend && whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("You are in the middle of a merge -- cannot amend.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("You are in the middle of a cherry-pick -- cannot amend.")); + else if (whence == FROM_REBASE_PICK) + die(_("You are in the middle of a rebase -- cannot amend.")); } if (fixup_message && squash_message) die(_("Options --squash and --fixup cannot be used together")); @@ -1204,7 +1208,8 @@ static int parse_and_validate_options(int argc, const char *argv[], use_message = edit_message; if (amend && !use_message && !fixup_message) use_message = "HEAD"; - if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship) + if (!use_message && !is_from_cherry_pick(whence) && + !is_from_rebase(whence) && renew_authorship) die(_("--reset-author can be used only with -C, -c or --amend.")); if (use_message) { use_message_buffer = read_commit_message(use_message); @@ -1213,7 +1218,8 @@ static int parse_and_validate_options(int argc, const char *argv[], author_message_buffer = use_message_buffer; } } - if (whence == FROM_CHERRY_PICK && !renew_authorship) { + if ((is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) && + !renew_authorship) { author_message = "CHERRY_PICK_HEAD"; author_message_buffer = read_commit_message(author_message); } @@ -1631,8 +1637,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) reduce_heads_replace(&parents); } else { if (!reflog_msg) - reflog_msg = (whence == FROM_CHERRY_PICK) + reflog_msg = is_from_cherry_pick(whence) ? "commit (cherry-pick)" + : is_from_rebase(whence) + ? "commit (rebase)" : "commit"; commit_list_insert(current_head, &parents); } @@ -1659,7 +1667,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } if (amend) { - const char *exclude_gpgsig[2] = { "gpgsig", NULL }; + const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL }; extra = read_commit_extra_headers(current_head, exclude_gpgsig); } else { struct commit_extra_header **tail = &extra; @@ -1692,9 +1700,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "new_index file. Check that disk is not full and quota is\n" "not exceeded, and then \"git restore --staged :/\" to recover.")); - if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) && - write_commit_graph_reachable(the_repository->objects->odb, 0, NULL)) - return 1; + git_test_write_commit_graph_or_die(); repo_rerere(the_repository, 0); run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); @@ -1713,6 +1719,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) &oid, flags); } + apply_autostash(git_path_merge_autostash(the_repository)); + UNLEAK(err); UNLEAK(sb); return 0; diff --git a/builtin/describe.c b/builtin/describe.c index 420f4c6401..21d2cb9e57 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -54,6 +54,7 @@ struct commit_name { struct tag *tag; unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ unsigned name_checked:1; + unsigned misnamed:1; struct object_id oid; char *path; }; @@ -132,6 +133,7 @@ static void add_to_known_names(const char *path, e->tag = tag; e->prio = prio; e->name_checked = 0; + e->misnamed = 0; oidcpy(&e->oid, oid); free(e->path); e->path = xstrdup(path); @@ -275,10 +277,11 @@ static void append_name(struct commit_name *n, struct strbuf *dst) die(_("annotated tag %s not available"), n->path); } if (n->tag && !n->name_checked) { - if (!n->tag->tag) - die(_("annotated tag %s has no embedded name"), n->path); - if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) - warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); + if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) { + warning(_("tag '%s' is externally known as '%s'"), + n->path, n->tag->tag); + n->misnamed = 1; + } n->name_checked = 1; } @@ -314,7 +317,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) * Exact match to an existing ref. */ append_name(n, dst); - if (longformat) + if (n->misnamed || longformat) append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst); if (suffix) strbuf_addstr(dst, suffix); @@ -463,7 +466,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) } append_name(all_matches[0].name, dst); - if (abbrev) + if (all_matches[0].name->misnamed || abbrev) append_suffix(all_matches[0].depth, &cmit->object.oid, dst); if (suffix) strbuf_addstr(dst, suffix); diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index cb9ea79367..802363d0a2 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -109,6 +109,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) struct object *tree1, *tree2; static struct rev_info *opt = &log_tree_opt; struct setup_revision_opt s_r_opt; + struct userformat_want w; int read_stdin = 0; if (argc == 2 && !strcmp(argv[1], "-h")) @@ -127,6 +128,14 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) precompose_argv(argc, argv); argc = setup_revisions(argc, argv, opt, &s_r_opt); + memset(&w, 0, sizeof(w)); + userformat_find_requirements(NULL, &w); + + if (!opt->show_notes_given && w.notes) + opt->show_notes = 1; + if (opt->show_notes) + load_display_notes(&opt->notes_opt); + while (--argc > 0) { const char *arg = *++argv; diff --git a/builtin/diff.c b/builtin/diff.c index 42ac803091..8537b17bd5 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -17,7 +17,7 @@ #include "log-tree.h" #include "builtin.h" #include "submodule.h" -#include "sha1-array.h" +#include "oid-array.h" #define DIFF_NO_INDEX_EXPLICIT 1 #define DIFF_NO_INDEX_IMPLICIT 2 diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index dc1485c8aa..4771100072 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -3,7 +3,7 @@ #include "fetch-pack.h" #include "remote.h" #include "connect.h" -#include "sha1-array.h" +#include "oid-array.h" #include "protocol.h" static const char fetch_pack_usage[] = diff --git a/builtin/fetch.c b/builtin/fetch.c index bf6bab80fa..1097e1e512 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -908,13 +908,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, if (!connectivity_checked) { struct check_connected_options opt = CHECK_CONNECTED_INIT; - if (filter_options.choice) - /* - * Since a filter is specified, objects indirectly - * referenced by refs are allowed to be absent. - */ - opt.check_refs_are_promisor_objects_only = 1; - rm = ref_map; if (check_connected(iterate_ref_map, &rm, &opt)) { rc = error(_("%s did not send all necessary objects\n"), url); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 736f666f64..48a8699de7 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -1,666 +1,13 @@ #include "builtin.h" -#include "cache.h" #include "config.h" -#include "refs.h" -#include "object-store.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "tag.h" -#include "string-list.h" -#include "branch.h" #include "fmt-merge-msg.h" -#include "gpg-interface.h" -#include "repository.h" -#include "commit-reach.h" +#include "parse-options.h" static const char * const fmt_merge_msg_usage[] = { N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"), NULL }; -static int use_branch_desc; - -int fmt_merge_msg_config(const char *key, const char *value, void *cb) -{ - if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) { - int is_bool; - merge_log_config = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool && merge_log_config < 0) - return error("%s: negative length %s", key, value); - if (is_bool && merge_log_config) - merge_log_config = DEFAULT_MERGE_LOG_LEN; - } else if (!strcmp(key, "merge.branchdesc")) { - use_branch_desc = git_config_bool(key, value); - } else { - return git_default_config(key, value, cb); - } - return 0; -} - -/* merge data per repository where the merged tips came from */ -struct src_data { - struct string_list branch, tag, r_branch, generic; - int head_status; -}; - -struct origin_data { - struct object_id oid; - unsigned is_local_branch:1; -}; - -static void init_src_data(struct src_data *data) -{ - data->branch.strdup_strings = 1; - data->tag.strdup_strings = 1; - data->r_branch.strdup_strings = 1; - data->generic.strdup_strings = 1; -} - -static struct string_list srcs = STRING_LIST_INIT_DUP; -static struct string_list origins = STRING_LIST_INIT_DUP; - -struct merge_parents { - int alloc, nr; - struct merge_parent { - struct object_id given; - struct object_id commit; - unsigned char used; - } *item; -}; - -/* - * I know, I know, this is inefficient, but you won't be pulling and merging - * hundreds of heads at a time anyway. - */ -static struct merge_parent *find_merge_parent(struct merge_parents *table, - struct object_id *given, - struct object_id *commit) -{ - int i; - for (i = 0; i < table->nr; i++) { - if (given && !oideq(&table->item[i].given, given)) - continue; - if (commit && !oideq(&table->item[i].commit, commit)) - continue; - return &table->item[i]; - } - return NULL; -} - -static void add_merge_parent(struct merge_parents *table, - struct object_id *given, - struct object_id *commit) -{ - if (table->nr && find_merge_parent(table, given, commit)) - return; - ALLOC_GROW(table->item, table->nr + 1, table->alloc); - oidcpy(&table->item[table->nr].given, given); - oidcpy(&table->item[table->nr].commit, commit); - table->item[table->nr].used = 0; - table->nr++; -} - -static int handle_line(char *line, struct merge_parents *merge_parents) -{ - int i, len = strlen(line); - struct origin_data *origin_data; - char *src; - const char *origin, *tag_name; - struct src_data *src_data; - struct string_list_item *item; - int pulling_head = 0; - struct object_id oid; - const unsigned hexsz = the_hash_algo->hexsz; - - if (len < hexsz + 3 || line[hexsz] != '\t') - return 1; - - if (starts_with(line + hexsz + 1, "not-for-merge")) - return 0; - - if (line[hexsz + 1] != '\t') - return 2; - - i = get_oid_hex(line, &oid); - if (i) - return 3; - - if (!find_merge_parent(merge_parents, &oid, NULL)) - return 0; /* subsumed by other parents */ - - origin_data = xcalloc(1, sizeof(struct origin_data)); - oidcpy(&origin_data->oid, &oid); - - if (line[len - 1] == '\n') - line[len - 1] = 0; - line += hexsz + 2; - - /* - * At this point, line points at the beginning of comment e.g. - * "branch 'frotz' of git://that/repository.git". - * Find the repository name and point it with src. - */ - src = strstr(line, " of "); - if (src) { - *src = 0; - src += 4; - pulling_head = 0; - } else { - src = line; - pulling_head = 1; - } - - item = unsorted_string_list_lookup(&srcs, src); - if (!item) { - item = string_list_append(&srcs, src); - item->util = xcalloc(1, sizeof(struct src_data)); - init_src_data(item->util); - } - src_data = item->util; - - if (pulling_head) { - origin = src; - src_data->head_status |= 1; - } else if (skip_prefix(line, "branch ", &origin)) { - origin_data->is_local_branch = 1; - string_list_append(&src_data->branch, origin); - src_data->head_status |= 2; - } else if (skip_prefix(line, "tag ", &tag_name)) { - origin = line; - string_list_append(&src_data->tag, tag_name); - src_data->head_status |= 2; - } else if (skip_prefix(line, "remote-tracking branch ", &origin)) { - string_list_append(&src_data->r_branch, origin); - src_data->head_status |= 2; - } else { - origin = src; - string_list_append(&src_data->generic, line); - src_data->head_status |= 2; - } - - if (!strcmp(".", src) || !strcmp(src, origin)) { - int len = strlen(origin); - if (origin[0] == '\'' && origin[len - 1] == '\'') - origin = xmemdupz(origin + 1, len - 2); - } else - origin = xstrfmt("%s of %s", origin, src); - if (strcmp(".", src)) - origin_data->is_local_branch = 0; - string_list_append(&origins, origin)->util = origin_data; - return 0; -} - -static void print_joined(const char *singular, const char *plural, - struct string_list *list, struct strbuf *out) -{ - if (list->nr == 0) - return; - if (list->nr == 1) { - strbuf_addf(out, "%s%s", singular, list->items[0].string); - } else { - int i; - strbuf_addstr(out, plural); - for (i = 0; i < list->nr - 1; i++) - strbuf_addf(out, "%s%s", i > 0 ? ", " : "", - list->items[i].string); - strbuf_addf(out, " and %s", list->items[list->nr - 1].string); - } -} - -static void add_branch_desc(struct strbuf *out, const char *name) -{ - struct strbuf desc = STRBUF_INIT; - - if (!read_branch_desc(&desc, name)) { - const char *bp = desc.buf; - while (*bp) { - const char *ep = strchrnul(bp, '\n'); - if (*ep) - ep++; - strbuf_addf(out, " : %.*s", (int)(ep - bp), bp); - bp = ep; - } - strbuf_complete_line(out); - } - strbuf_release(&desc); -} - -#define util_as_integral(elem) ((intptr_t)((elem)->util)) - -static void record_person_from_buf(int which, struct string_list *people, - const char *buffer) -{ - char *name_buf, *name, *name_end; - struct string_list_item *elem; - const char *field; - - field = (which == 'a') ? "\nauthor " : "\ncommitter "; - name = strstr(buffer, field); - if (!name) - return; - name += strlen(field); - name_end = strchrnul(name, '<'); - if (*name_end) - name_end--; - while (isspace(*name_end) && name <= name_end) - name_end--; - if (name_end < name) - return; - name_buf = xmemdupz(name, name_end - name + 1); - - elem = string_list_lookup(people, name_buf); - if (!elem) { - elem = string_list_insert(people, name_buf); - elem->util = (void *)0; - } - elem->util = (void*)(util_as_integral(elem) + 1); - free(name_buf); -} - - -static void record_person(int which, struct string_list *people, - struct commit *commit) -{ - const char *buffer = get_commit_buffer(commit, NULL); - record_person_from_buf(which, people, buffer); - unuse_commit_buffer(commit, buffer); -} - -static int cmp_string_list_util_as_integral(const void *a_, const void *b_) -{ - const struct string_list_item *a = a_, *b = b_; - return util_as_integral(b) - util_as_integral(a); -} - -static void add_people_count(struct strbuf *out, struct string_list *people) -{ - if (people->nr == 1) - strbuf_addstr(out, people->items[0].string); - else if (people->nr == 2) - strbuf_addf(out, "%s (%d) and %s (%d)", - people->items[0].string, - (int)util_as_integral(&people->items[0]), - people->items[1].string, - (int)util_as_integral(&people->items[1])); - else if (people->nr) - strbuf_addf(out, "%s (%d) and others", - people->items[0].string, - (int)util_as_integral(&people->items[0])); -} - -static void credit_people(struct strbuf *out, - struct string_list *them, - int kind) -{ - const char *label; - const char *me; - - if (kind == 'a') { - label = "By"; - me = git_author_info(IDENT_NO_DATE); - } else { - label = "Via"; - me = git_committer_info(IDENT_NO_DATE); - } - - if (!them->nr || - (them->nr == 1 && - me && - skip_prefix(me, them->items->string, &me) && - starts_with(me, " <"))) - return; - strbuf_addf(out, "\n%c %s ", comment_line_char, label); - add_people_count(out, them); -} - -static void add_people_info(struct strbuf *out, - struct string_list *authors, - struct string_list *committers) -{ - QSORT(authors->items, authors->nr, - cmp_string_list_util_as_integral); - QSORT(committers->items, committers->nr, - cmp_string_list_util_as_integral); - - credit_people(out, authors, 'a'); - credit_people(out, committers, 'c'); -} - -static void shortlog(const char *name, - struct origin_data *origin_data, - struct commit *head, - struct rev_info *rev, - struct fmt_merge_msg_opts *opts, - struct strbuf *out) -{ - int i, count = 0; - struct commit *commit; - struct object *branch; - struct string_list subjects = STRING_LIST_INIT_DUP; - struct string_list authors = STRING_LIST_INIT_DUP; - struct string_list committers = STRING_LIST_INIT_DUP; - int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; - struct strbuf sb = STRBUF_INIT; - const struct object_id *oid = &origin_data->oid; - int limit = opts->shortlog_len; - - branch = deref_tag(the_repository, parse_object(the_repository, oid), - oid_to_hex(oid), - the_hash_algo->hexsz); - if (!branch || branch->type != OBJ_COMMIT) - return; - - setup_revisions(0, NULL, rev, NULL); - add_pending_object(rev, branch, name); - add_pending_object(rev, &head->object, "^HEAD"); - head->object.flags |= UNINTERESTING; - if (prepare_revision_walk(rev)) - die("revision walk setup failed"); - while ((commit = get_revision(rev)) != NULL) { - struct pretty_print_context ctx = {0}; - - if (commit->parents && commit->parents->next) { - /* do not list a merge but count committer */ - if (opts->credit_people) - record_person('c', &committers, commit); - continue; - } - if (!count && opts->credit_people) - /* the 'tip' committer */ - record_person('c', &committers, commit); - if (opts->credit_people) - record_person('a', &authors, commit); - count++; - if (subjects.nr > limit) - continue; - - format_commit_message(commit, "%s", &sb, &ctx); - strbuf_ltrim(&sb); - - if (!sb.len) - string_list_append(&subjects, - oid_to_hex(&commit->object.oid)); - else - string_list_append_nodup(&subjects, - strbuf_detach(&sb, NULL)); - } - - if (opts->credit_people) - add_people_info(out, &authors, &committers); - if (count > limit) - strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); - else - strbuf_addf(out, "\n* %s:\n", name); - - if (origin_data->is_local_branch && use_branch_desc) - add_branch_desc(out, name); - - for (i = 0; i < subjects.nr; i++) - if (i >= limit) - strbuf_addstr(out, " ...\n"); - else - strbuf_addf(out, " %s\n", subjects.items[i].string); - - clear_commit_marks((struct commit *)branch, flags); - clear_commit_marks(head, flags); - free_commit_list(rev->commits); - rev->commits = NULL; - rev->pending.nr = 0; - - string_list_clear(&authors, 0); - string_list_clear(&committers, 0); - string_list_clear(&subjects, 0); -} - -static void fmt_merge_msg_title(struct strbuf *out, - const char *current_branch) -{ - int i = 0; - char *sep = ""; - - strbuf_addstr(out, "Merge "); - for (i = 0; i < srcs.nr; i++) { - struct src_data *src_data = srcs.items[i].util; - const char *subsep = ""; - - strbuf_addstr(out, sep); - sep = "; "; - - if (src_data->head_status == 1) { - strbuf_addstr(out, srcs.items[i].string); - continue; - } - if (src_data->head_status == 3) { - subsep = ", "; - strbuf_addstr(out, "HEAD"); - } - if (src_data->branch.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("branch ", "branches ", &src_data->branch, - out); - } - if (src_data->r_branch.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("remote-tracking branch ", "remote-tracking branches ", - &src_data->r_branch, out); - } - if (src_data->tag.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("tag ", "tags ", &src_data->tag, out); - } - if (src_data->generic.nr) { - strbuf_addstr(out, subsep); - print_joined("commit ", "commits ", &src_data->generic, - out); - } - if (strcmp(".", srcs.items[i].string)) - strbuf_addf(out, " of %s", srcs.items[i].string); - } - - if (!strcmp("master", current_branch)) - strbuf_addch(out, '\n'); - else - strbuf_addf(out, " into %s\n", current_branch); -} - -static void fmt_tag_signature(struct strbuf *tagbuf, - struct strbuf *sig, - const char *buf, - unsigned long len) -{ - const char *tag_body = strstr(buf, "\n\n"); - if (tag_body) { - tag_body += 2; - strbuf_add(tagbuf, tag_body, buf + len - tag_body); - } - strbuf_complete_line(tagbuf); - if (sig->len) { - strbuf_addch(tagbuf, '\n'); - strbuf_add_commented_lines(tagbuf, sig->buf, sig->len); - } -} - -static void fmt_merge_msg_sigs(struct strbuf *out) -{ - int i, tag_number = 0, first_tag = 0; - struct strbuf tagbuf = STRBUF_INIT; - - for (i = 0; i < origins.nr; i++) { - struct object_id *oid = origins.items[i].util; - enum object_type type; - unsigned long size, len; - char *buf = read_object_file(oid, &type, &size); - struct strbuf sig = STRBUF_INIT; - - if (!buf || type != OBJ_TAG) - goto next; - len = parse_signature(buf, size); - - if (size == len) - ; /* merely annotated */ - else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) { - if (!sig.len) - strbuf_addstr(&sig, "gpg verification failed.\n"); - } - - if (!tag_number++) { - fmt_tag_signature(&tagbuf, &sig, buf, len); - first_tag = i; - } else { - if (tag_number == 2) { - struct strbuf tagline = STRBUF_INIT; - strbuf_addch(&tagline, '\n'); - strbuf_add_commented_lines(&tagline, - origins.items[first_tag].string, - strlen(origins.items[first_tag].string)); - strbuf_insert(&tagbuf, 0, tagline.buf, - tagline.len); - strbuf_release(&tagline); - } - strbuf_addch(&tagbuf, '\n'); - strbuf_add_commented_lines(&tagbuf, - origins.items[i].string, - strlen(origins.items[i].string)); - fmt_tag_signature(&tagbuf, &sig, buf, len); - } - strbuf_release(&sig); - next: - free(buf); - } - if (tagbuf.len) { - strbuf_addch(out, '\n'); - strbuf_addbuf(out, &tagbuf); - } - strbuf_release(&tagbuf); -} - -static void find_merge_parents(struct merge_parents *result, - struct strbuf *in, struct object_id *head) -{ - struct commit_list *parents; - struct commit *head_commit; - int pos = 0, i, j; - - parents = NULL; - while (pos < in->len) { - int len; - char *p = in->buf + pos; - char *newline = strchr(p, '\n'); - const char *q; - struct object_id oid; - struct commit *parent; - struct object *obj; - - len = newline ? newline - p : strlen(p); - pos += len + !!newline; - - if (parse_oid_hex(p, &oid, &q) || - q[0] != '\t' || - q[1] != '\t') - continue; /* skip not-for-merge */ - /* - * Do not use get_merge_parent() here; we do not have - * "name" here and we do not want to contaminate its - * util field yet. - */ - obj = parse_object(the_repository, &oid); - parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT); - if (!parent) - continue; - commit_list_insert(parent, &parents); - add_merge_parent(result, &obj->oid, &parent->object.oid); - } - head_commit = lookup_commit(the_repository, head); - if (head_commit) - commit_list_insert(head_commit, &parents); - reduce_heads_replace(&parents); - - while (parents) { - struct commit *cmit = pop_commit(&parents); - for (i = 0; i < result->nr; i++) - if (oideq(&result->item[i].commit, &cmit->object.oid)) - result->item[i].used = 1; - } - - for (i = j = 0; i < result->nr; i++) { - if (result->item[i].used) { - if (i != j) - result->item[j] = result->item[i]; - j++; - } - } - result->nr = j; -} - -int fmt_merge_msg(struct strbuf *in, struct strbuf *out, - struct fmt_merge_msg_opts *opts) -{ - int i = 0, pos = 0; - struct object_id head_oid; - const char *current_branch; - void *current_branch_to_free; - struct merge_parents merge_parents; - - memset(&merge_parents, 0, sizeof(merge_parents)); - - /* get current branch */ - current_branch = current_branch_to_free = - resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL); - if (!current_branch) - die("No current branch"); - if (starts_with(current_branch, "refs/heads/")) - current_branch += 11; - - find_merge_parents(&merge_parents, in, &head_oid); - - /* get a line */ - while (pos < in->len) { - int len; - char *newline, *p = in->buf + pos; - - newline = strchr(p, '\n'); - len = newline ? newline - p : strlen(p); - pos += len + !!newline; - i++; - p[len] = 0; - if (handle_line(p, &merge_parents)) - die("error in line %d: %.*s", i, len, p); - } - - if (opts->add_title && srcs.nr) - fmt_merge_msg_title(out, current_branch); - - if (origins.nr) - fmt_merge_msg_sigs(out); - - if (opts->shortlog_len) { - struct commit *head; - struct rev_info rev; - - head = lookup_commit_or_die(&head_oid, "HEAD"); - repo_init_revisions(the_repository, &rev, NULL); - rev.commit_format = CMIT_FMT_ONELINE; - rev.ignore_merges = 1; - rev.limited = 1; - - strbuf_complete_line(out); - - for (i = 0; i < origins.nr; i++) - shortlog(origins.items[i].string, - origins.items[i].util, - head, &rev, opts, out); - } - - strbuf_complete_line(out); - free(current_branch_to_free); - free(merge_parents.item); - return 0; -} - int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; diff --git a/builtin/grep.c b/builtin/grep.c index 99e2685090..5e150f5825 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -295,6 +295,38 @@ static int grep_cmd_config(const char *var, const char *value, void *cb) return st; } +static void grep_source_name(struct grep_opt *opt, const char *filename, + int tree_name_len, struct strbuf *out) +{ + strbuf_reset(out); + + if (opt->null_following_name) { + if (opt->relative && opt->prefix_length) { + struct strbuf rel_buf = STRBUF_INIT; + const char *rel_name = + relative_path(filename + tree_name_len, + opt->prefix, &rel_buf); + + if (tree_name_len) + strbuf_add(out, filename, tree_name_len); + + strbuf_addstr(out, rel_name); + strbuf_release(&rel_buf); + } else { + strbuf_addstr(out, filename); + } + return; + } + + if (opt->relative && opt->prefix_length) + quote_path_relative(filename + tree_name_len, opt->prefix, out); + else + quote_c_style(filename + tree_name_len, out, NULL, 0); + + if (tree_name_len) + strbuf_insert(out, 0, filename, tree_name_len); +} + static int grep_oid(struct grep_opt *opt, const struct object_id *oid, const char *filename, int tree_name_len, const char *path) @@ -302,13 +334,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid, struct strbuf pathbuf = STRBUF_INIT; struct grep_source gs; - if (opt->relative && opt->prefix_length) { - quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf); - strbuf_insert(&pathbuf, 0, filename, tree_name_len); - } else { - strbuf_addstr(&pathbuf, filename); - } - + grep_source_name(opt, filename, tree_name_len, &pathbuf); grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid); strbuf_release(&pathbuf); @@ -334,11 +360,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) struct strbuf buf = STRBUF_INIT; struct grep_source gs; - if (opt->relative && opt->prefix_length) - quote_path_relative(filename, opt->prefix, &buf); - else - strbuf_addstr(&buf, filename); - + grep_source_name(opt, filename, 0, &buf); grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename); strbuf_release(&buf); @@ -679,8 +701,6 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, fill_directory(&dir, opt->repo->index, pathspec); for (i = 0; i < dir.nr; i++) { - if (!dir_path_match(opt->repo->index, dir.entries[i], pathspec, 0, NULL)) - continue; hit |= grep_file(opt, dir.entries[i]->name); if (hit && opt->status_only) break; diff --git a/builtin/help.c b/builtin/help.c index e5590d7787..c024110531 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -242,7 +242,7 @@ static int add_man_viewer_cmd(const char *name, static int add_man_viewer_info(const char *var, const char *value) { const char *name, *subkey; - int namelen; + size_t namelen; if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name) return 0; diff --git a/builtin/index-pack.c b/builtin/index-pack.c index d967d188a3..f176dd28c8 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1368,9 +1368,8 @@ static void fix_unresolved_deltas(struct hashfile *f) continue; oid_array_append(&to_fetch, &d->oid); } - if (to_fetch.nr) - promisor_remote_get_direct(the_repository, - to_fetch.oid, to_fetch.nr); + promisor_remote_get_direct(the_repository, + to_fetch.oid, to_fetch.nr); oid_array_clear(&to_fetch); } diff --git a/builtin/init-db.c b/builtin/init-db.c index 944ec77fe1..0b7222e718 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -20,6 +20,8 @@ #define TEST_FILEMODE 1 #endif +#define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH" + static int init_is_bare_repository = 0; static int init_shared_repository = -1; static const char *init_db_template_dir; @@ -176,13 +178,36 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) return 1; } +void initialize_repository_version(int hash_algo) +{ + char repo_version_string[10]; + int repo_version = GIT_REPO_VERSION; + +#ifndef ENABLE_SHA256 + if (hash_algo != GIT_HASH_SHA1) + die(_("The hash algorithm %s is not supported in this build."), hash_algos[hash_algo].name); +#endif + + if (hash_algo != GIT_HASH_SHA1) + repo_version = GIT_REPO_VERSION_READ; + + /* This forces creation of new config file */ + xsnprintf(repo_version_string, sizeof(repo_version_string), + "%d", repo_version); + git_config_set("core.repositoryformatversion", repo_version_string); + + if (hash_algo != GIT_HASH_SHA1) + git_config_set("extensions.objectformat", + hash_algos[hash_algo].name); +} + static int create_default_files(const char *template_path, - const char *original_git_dir) + const char *original_git_dir, + const struct repository_format *fmt) { struct stat st1; struct strbuf buf = STRBUF_INIT; char *path; - char repo_version_string[10]; char junk[2]; int reinit; int filemode; @@ -244,10 +269,7 @@ static int create_default_files(const char *template_path, exit(1); } - /* This forces creation of new config file */ - xsnprintf(repo_version_string, sizeof(repo_version_string), - "%d", GIT_REPO_VERSION); - git_config_set("core.repositoryformatversion", repo_version_string); + initialize_repository_version(fmt->hash_algo); /* Check filemode trustability */ path = git_path_buf(&buf, "config"); @@ -340,12 +362,33 @@ static void separate_git_dir(const char *git_dir, const char *git_link) write_file(git_link, "gitdir: %s", git_dir); } +static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash) +{ + const char *env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT); + /* + * If we already have an initialized repo, don't allow the user to + * specify a different algorithm, as that could cause corruption. + * Otherwise, if the user has specified one on the command line, use it. + */ + if (repo_fmt->version >= 0 && hash != GIT_HASH_UNKNOWN && hash != repo_fmt->hash_algo) + die(_("attempt to reinitialize repository with different hash")); + else if (hash != GIT_HASH_UNKNOWN) + repo_fmt->hash_algo = hash; + else if (env) { + int env_algo = hash_algo_by_name(env); + if (env_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), env); + repo_fmt->hash_algo = env_algo; + } +} + int init_db(const char *git_dir, const char *real_git_dir, - const char *template_dir, unsigned int flags) + const char *template_dir, int hash, unsigned int flags) { int reinit; int exist_ok = flags & INIT_DB_EXIST_OK; char *original_git_dir = real_pathdup(git_dir, 1); + struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; if (real_git_dir) { struct stat st; @@ -356,12 +399,12 @@ int init_db(const char *git_dir, const char *real_git_dir, if (!exist_ok && !stat(real_git_dir, &st)) die(_("%s already exists"), real_git_dir); - set_git_dir(real_path(real_git_dir)); + set_git_dir(real_git_dir, 1); git_dir = get_git_dir(); separate_git_dir(git_dir, original_git_dir); } else { - set_git_dir(real_path(git_dir)); + set_git_dir(git_dir, 1); git_dir = get_git_dir(); } startup_info->have_repository = 1; @@ -378,9 +421,11 @@ int init_db(const char *git_dir, const char *real_git_dir, * config file, so this will not fail. What we are catching * is an attempt to reinitialize new repository with an old tool. */ - check_repository_format(); + check_repository_format(&repo_fmt); - reinit = create_default_files(template_dir, original_git_dir); + validate_hash_algorithm(&repo_fmt, hash); + + reinit = create_default_files(template_dir, original_git_dir, &repo_fmt); create_object_directory(); @@ -482,6 +527,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *work_tree; const char *template_dir = NULL; unsigned int flags = 0; + const char *object_format = NULL; + int hash_algo = GIT_HASH_UNKNOWN; const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, N_("template-directory"), N_("directory from which templates will be used")), @@ -494,6 +541,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), + OPT_STRING(0, "object-format", &object_format, N_("hash"), + N_("specify the hash algorithm to use")), OPT_END() }; @@ -546,6 +595,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) free(cwd); } + if (object_format) { + hash_algo = hash_algo_by_name(object_format); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), object_format); + } + if (init_shared_repository != -1) set_shared_repository(init_shared_repository); @@ -597,5 +652,5 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) UNLEAK(work_tree); flags |= INIT_DB_EXIST_OK; - return init_db(git_dir, real_git_dir, template_dir, flags); + return init_db(git_dir, real_git_dir, template_dir, hash_algo, flags); } diff --git a/builtin/log.c b/builtin/log.c index 83a4a6188e..bef7403d5e 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -46,6 +46,7 @@ static int default_abbrev_commit; static int default_show_root = 1; static int default_follow; static int default_show_signature; +static int default_encode_email_headers = 1; static int decoration_style; static int decoration_given; static int use_mailmap_config = 1; @@ -151,6 +152,7 @@ static void cmd_log_init_defaults(struct rev_info *rev) rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; rev->show_signature = default_show_signature; + rev->encode_email_headers = default_encode_email_headers; rev->diffopt.flags.allow_textconv = 1; if (default_date_mode) @@ -164,15 +166,18 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, int quiet = 0, source = 0, mailmap; static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; + static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP; static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; struct decoration_filter decoration_filter = {&decorate_refs_include, - &decorate_refs_exclude}; + &decorate_refs_exclude, + &decorate_refs_exclude_config}; static struct revision_sources revision_sources; const struct option builtin_log_options[] = { OPT__QUIET(&quiet, N_("suppress diff output")), OPT_BOOL(0, "source", &source, N_("show source")), OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), + OPT_ALIAS(0, "mailmap", "use-mailmap"), OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, N_("pattern"), N_("only decorate refs that match <pattern>")), OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, @@ -236,7 +241,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, } if (decoration_style) { + const struct string_list *config_exclude = + repo_config_get_value_multi(the_repository, + "log.excludeDecoration"); + + if (config_exclude) { + struct string_list_item *item; + for_each_string_list_item(item, config_exclude) + string_list_append(&decorate_refs_exclude_config, + item->string); + } + rev->show_decorations = 1; + load_ref_decorations(&decoration_filter, decoration_style); } @@ -438,6 +455,10 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "format.encodeemailheaders")) { + default_encode_email_headers = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "log.abbrevcommit")) { default_abbrev_commit = git_config_bool(var, value); return 0; @@ -1719,6 +1740,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.show_notes = show_notes; memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt)); rev.commit_format = CMIT_FMT_EMAIL; + rev.encode_email_headers = default_encode_email_headers; rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; rev.diff = 1; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index f069a028ce..b87c22ac24 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -128,8 +128,9 @@ static void show_dir_entry(const struct index_state *istate, if (len > ent->len) die("git ls-files: internal error - directory entry not superset of prefix"); - if (!dir_path_match(istate, ent, &pathspec, len, ps_matched)) - return; + /* If ps_matches is non-NULL, figure out which pathspec(s) match. */ + if (ps_matched) + dir_path_match(istate, ent, &pathspec, len, ps_matched); fputs(tag, stdout); write_eolinfo(istate, NULL, ent->name); diff --git a/builtin/merge-base.c b/builtin/merge-base.c index e3f8da13b6..6719ac198d 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -114,26 +114,16 @@ static int handle_is_ancestor(int argc, const char **argv) static int handle_fork_point(int argc, const char **argv) { struct object_id oid; - char *refname; struct commit *derived, *fork_point; const char *commitname; - switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) { - case 0: - die("No such ref: '%s'", argv[0]); - case 1: - break; /* good */ - default: - die("Ambiguous refname: '%s'", argv[0]); - } - commitname = (argc == 2) ? argv[1] : "HEAD"; if (get_oid(commitname, &oid)) die("Not a valid object name: '%s'", commitname); derived = lookup_commit_reference(the_repository, &oid); - fork_point = get_fork_point(refname, derived); + fork_point = get_fork_point(argv[0], derived); if (!fork_point) return 1; diff --git a/builtin/merge.c b/builtin/merge.c index d127d2225f..97066a5632 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -40,6 +40,7 @@ #include "branch.h" #include "commit-reach.h" #include "wt-status.h" +#include "commit-graph.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -82,6 +83,7 @@ static int show_progress = -1; static int default_to_upstream = 1; static int signoff; static const char *sign_commit; +static int autostash; static int no_verify; static struct strategy all_strategy[] = { @@ -286,6 +288,7 @@ static struct option builtin_merge_options[] = { OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_AUTOSTASH(&autostash), OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")), OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")), @@ -475,6 +478,7 @@ static void finish(struct commit *head_commit, /* Run a post-merge hook */ run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL); + apply_autostash(git_path_merge_autostash(the_repository)); strbuf_release(&reflog_message); } @@ -597,10 +601,12 @@ static void parse_branch_merge_options(char *bmo) static int git_merge_config(const char *k, const char *v, void *cb) { int status; + const char *str; - if (branch && starts_with(k, "branch.") && - starts_with(k + 7, branch) && - !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { + if (branch && + skip_prefix(k, "branch.", &str) && + skip_prefix(str, branch, &str) && + !strcmp(str, ".mergeoptions")) { free(branch_mergeoptions); branch_mergeoptions = xstrdup(v); return 0; @@ -634,6 +640,9 @@ static int git_merge_config(const char *k, const char *v, void *cb) return 0; } else if (!strcmp(k, "gpg.mintrustlevel")) { check_trust_level = 0; + } else if (!strcmp(k, "merge.autostash")) { + autostash = git_config_bool(k, v); + return 0; } status = fmt_merge_msg_config(k, v, cb); @@ -1281,6 +1290,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (abort_current_merge) { int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; + struct strbuf stash_oid = STRBUF_INIT; if (orig_argc != 2) usage_msg_opt(_("--abort expects no arguments"), @@ -1289,8 +1299,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!file_exists(git_path_merge_head(the_repository))) die(_("There is no merge to abort (MERGE_HEAD missing).")); + if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository), + READ_ONELINER_SKIP_IF_EMPTY)) + unlink(git_path_merge_autostash(the_repository)); + /* Invoke 'git reset --merge' */ ret = cmd_reset(nargc, nargv, prefix); + + if (stash_oid.len) + apply_autostash_oid(stash_oid.buf); + + strbuf_release(&stash_oid); goto done; } @@ -1513,6 +1532,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (autostash) + create_autostash(the_repository, + git_path_merge_autostash(the_repository), + "merge"); if (checkout_fast_forward(the_repository, &head_commit->object.oid, &commit->object.oid, @@ -1579,6 +1602,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (fast_forward == FF_ONLY) die(_("Not possible to fast-forward, aborting.")); + if (autostash) + create_autostash(the_repository, + git_path_merge_autostash(the_repository), + "merge"); + /* We are going to make a new commit. */ git_committer_info(IDENT_STRICT); @@ -1673,9 +1701,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) head_commit); } - if (squash) + if (squash) { finish(head_commit, remoteheads, NULL, NULL); - else + + git_test_write_commit_graph_or_die(); + } else write_merge_state(remoteheads); if (merge_was_ok) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 02aa6ee480..fdd18c7ccb 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -26,7 +26,7 @@ #include "pack-bitmap.h" #include "delta-islands.h" #include "reachable.h" -#include "sha1-array.h" +#include "oid-array.h" #include "argv-array.h" #include "list.h" #include "packfile.h" @@ -880,7 +880,7 @@ static void write_reused_pack_one(size_t pos, struct hashfile *out, len = encode_in_pack_object_header(header, sizeof(header), OBJ_REF_DELTA, size); hashwrite(out, header, len); - hashwrite(out, base_oid.hash, 20); + hashwrite(out, base_oid.hash, the_hash_algo->rawsz); copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur); return; } @@ -3469,9 +3469,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) read_replace_refs = 0; - sparse = git_env_bool("GIT_TEST_PACK_SPARSE", 0); + sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1); prepare_repo_settings(the_repository); - if (!sparse && the_repository->settings.pack_use_sparse != -1) + if (sparse < 0) sparse = the_repository->settings.pack_use_sparse; reset_pack_idx_option(&pack_idx_opts); diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index 48c5e78e33..b7b9281a8c 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -1,54 +1,12 @@ #include "builtin.h" -#include "cache.h" -#include "progress.h" #include "parse-options.h" -#include "packfile.h" -#include "object-store.h" +#include "prune-packed.h" static const char * const prune_packed_usage[] = { N_("git prune-packed [-n | --dry-run] [-q | --quiet]"), NULL }; -static struct progress *progress; - -static int prune_subdir(unsigned int nr, const char *path, void *data) -{ - int *opts = data; - display_progress(progress, nr + 1); - if (!(*opts & PRUNE_PACKED_DRY_RUN)) - rmdir(path); - return 0; -} - -static int prune_object(const struct object_id *oid, const char *path, - void *data) -{ - int *opts = data; - - if (!has_object_pack(oid)) - return 0; - - if (*opts & PRUNE_PACKED_DRY_RUN) - printf("rm -f %s\n", path); - else - unlink_or_warn(path); - return 0; -} - -void prune_packed_objects(int opts) -{ - if (opts & PRUNE_PACKED_VERBOSE) - progress = start_delayed_progress(_("Removing duplicate objects"), 256); - - for_each_loose_file_in_objdir(get_object_directory(), - prune_object, NULL, prune_subdir, &opts); - - /* Ensure we show 100% before finishing progress */ - display_progress(progress, 256); - stop_progress(&progress); -} - int cmd_prune_packed(int argc, const char **argv, const char *prefix) { int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0; diff --git a/builtin/prune.c b/builtin/prune.c index 2b76872ad2..fd9acc7222 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -6,6 +6,7 @@ #include "reachable.h" #include "parse-options.h" #include "progress.h" +#include "prune-packed.h" #include "object-store.h" static const char * const prune_usage[] = { diff --git a/builtin/pull.c b/builtin/pull.c index 3e624d1e00..f1fa6db74e 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -12,7 +12,7 @@ #include "parse-options.h" #include "exec-cmd.h" #include "run-command.h" -#include "sha1-array.h" +#include "oid-array.h" #include "remote.h" #include "dir.h" #include "rebase.h" @@ -110,6 +110,7 @@ static char *opt_ipv4; static char *opt_ipv6; static int opt_show_forced_updates = -1; static char *set_upstream; +static struct argv_array opt_fetch = ARGV_ARRAY_INIT; static struct option pull_options[] = { /* Shared options */ @@ -163,7 +164,7 @@ static struct option pull_options[] = { N_("verify that the named commit has a valid GPG signature"), PARSE_OPT_NOARG), OPT_BOOL(0, "autostash", &opt_autostash, - N_("automatically stash/stash pop before and after rebase")), + N_("automatically stash/stash pop before and after")), OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), N_("merge strategy to use"), 0), @@ -207,6 +208,15 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), N_("deepen history of shallow clone"), 0), + OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"), + N_("deepen history of shallow repository based on time"), + 0), + OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("revision"), + N_("deepen history of shallow clone, excluding rev"), + 0), + OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"), + N_("deepen history of shallow clone"), + 0), OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, N_("convert to a complete repository"), PARSE_OPT_NONEG | PARSE_OPT_NOARG), @@ -216,12 +226,19 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), N_("specify fetch refmap"), PARSE_OPT_NONEG), + OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch, + N_("server-specific"), + N_("option to transmit"), + 0), OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, N_("use IPv4 addresses only"), PARSE_OPT_NOARG), OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, N_("use IPv6 addresses only"), PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), + N_("report that we have only objects reachable from this object"), + 0), OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, N_("check for forced-updates on all updated branches")), OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL, @@ -327,6 +344,22 @@ static enum rebase_type config_get_rebase(void) if (!git_config_get_value("pull.rebase", &value)) return parse_config_rebase("pull.rebase", value, 1); + if (opt_verbosity >= 0 && + (!opt_ff || strcmp(opt_ff, "--ff-only"))) { + warning(_("Pulling without specifying how to reconcile divergent branches is\n" + "discouraged. You can squelch this message by running one of the following\n" + "commands sometime before your next pull:\n" + "\n" + " git config pull.rebase false # merge (the default strategy)\n" + " git config pull.rebase true # rebase\n" + " git config pull.ff only # fast-forward only\n" + "\n" + "You can replace \"git config\" with \"git config --global\" to set a default\n" + "preference for all repositories. You can also pass --rebase, --no-rebase,\n" + "or --ff-only on the command line to override the configured default per\n" + "invocation.\n")); + } + return REBASE_FALSE; } @@ -551,6 +584,7 @@ static int run_fetch(const char *repo, const char **refspecs) argv_array_push(&args, "--no-show-forced-updates"); if (set_upstream) argv_array_push(&args, set_upstream); + argv_array_pushv(&args, opt_fetch.argv); if (repo) { argv_array_push(&args, repo); @@ -661,6 +695,10 @@ static int run_merge(void) argv_array_pushv(&args, opt_strategy_opts.argv); if (opt_gpg_sign) argv_array_push(&args, opt_gpg_sign); + if (opt_autostash == 0) + argv_array_push(&args, "--no-autostash"); + else if (opt_autostash == 1) + argv_array_push(&args, "--autostash"); if (opt_allow_unrelated_histories > 0) argv_array_push(&args, "--allow-unrelated-histories"); @@ -908,9 +946,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (get_oid("HEAD", &orig_head)) oidclr(&orig_head); - if (!opt_rebase && opt_autostash != -1) - die(_("--[no-]autostash option is only valid with --rebase.")); - autostash = config_autostash; if (opt_rebase) { if (opt_autostash != -1) @@ -976,6 +1011,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_rebase) { int ret = 0; + int ran_ff = 0; if ((recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) && submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head)) @@ -992,10 +1028,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (is_descendant_of(merge_head, list)) { /* we can fast-forward this without invoking rebase */ opt_ff = "--ff-only"; + ran_ff = 1; ret = run_merge(); } } - ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); + if (!ran_ff) + ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) diff --git a/builtin/rebase.c b/builtin/rebase.c index bff53d5d16..1c0a49c642 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,6 +27,9 @@ #include "branch.h" #include "sequencer.h" #include "rebase-interactive.h" +#include "reset.h" + +#define DEFAULT_REFLOG_ACTION "rebase" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec <cmd>] " @@ -85,6 +88,7 @@ struct rebase_options { const char *action; int signoff; int allow_rerere_autoupdate; + int keep_empty; int autosquash; char *gpg_sign_opt; int autostash; @@ -95,11 +99,13 @@ struct rebase_options { struct strbuf git_format_patch_opt; int reschedule_failed_exec; int use_legacy_rebase; + int reapply_cherry_picks; }; #define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ .empty = EMPTY_UNSPECIFIED, \ + .keep_empty = 1, \ .default_backend = "merge", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ @@ -379,11 +385,13 @@ static int run_sequencer_rebase(struct rebase_options *opts, 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 |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0; flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; + flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0; switch (command) { case ACTION_NONE: { @@ -442,6 +450,7 @@ static int run_sequencer_rebase(struct rebase_options *opts, return ret; } +static void imply_merge(struct rebase_options *opts, const char *option); static int parse_opt_keep_empty(const struct option *opt, const char *arg, int unset) { @@ -449,10 +458,8 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg, BUG_ON_OPT_ARG(arg); - /* - * If we ever want to remap --keep-empty to --empty=keep, insert: - * opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP; - */ + imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty"); + opts->keep_empty = !unset; opts->type = REBASE_MERGE; return 0; } @@ -471,7 +478,7 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"), REBASE_FORCE), { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL, - N_("(DEPRECATED) keep empty commits"), + N_("keep commits which start empty"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, parse_opt_keep_empty }, OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message, @@ -559,7 +566,7 @@ static void imply_merge(struct rebase_options *opts, const char *option) { switch (opts->type) { case REBASE_APPLY: - die(_("%s requires an interactive rebase"), option); + die(_("%s requires the merge backend"), option); break; case REBASE_MERGE: case REBASE_PRESERVE_MERGES: @@ -586,15 +593,6 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o return path.buf; } -/* Read one file, then strip line endings */ -static int read_one(const char *path, struct strbuf *buf) -{ - if (strbuf_read_file(buf, path, 0) < 0) - return error_errno(_("could not read '%s'"), path); - strbuf_trim_trailing_newline(buf); - return 0; -} - /* Initialize the rebase options from the state directory. */ static int read_basic_state(struct rebase_options *opts) { @@ -602,8 +600,10 @@ static int read_basic_state(struct rebase_options *opts) struct strbuf buf = STRBUF_INIT; struct object_id oid; - if (read_one(state_dir_path("head-name", opts), &head_name) || - read_one(state_dir_path("onto", opts), &buf)) + if (!read_oneliner(&head_name, state_dir_path("head-name", opts), + READ_ONELINER_WARN_MISSING) || + !read_oneliner(&buf, state_dir_path("onto", opts), + READ_ONELINER_WARN_MISSING)) return -1; opts->head_name = starts_with(head_name.buf, "refs/") ? xstrdup(head_name.buf) : NULL; @@ -619,9 +619,11 @@ static int read_basic_state(struct rebase_options *opts) */ strbuf_reset(&buf); if (file_exists(state_dir_path("orig-head", opts))) { - if (read_one(state_dir_path("orig-head", opts), &buf)) + if (!read_oneliner(&buf, state_dir_path("orig-head", opts), + READ_ONELINER_WARN_MISSING)) return -1; - } else if (read_one(state_dir_path("head", opts), &buf)) + } else if (!read_oneliner(&buf, state_dir_path("head", opts), + READ_ONELINER_WARN_MISSING)) return -1; if (get_oid(buf.buf, &opts->orig_head)) return error(_("invalid orig-head: '%s'"), buf.buf); @@ -641,8 +643,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("allow_rerere_autoupdate", opts), - &buf)) + if (!read_oneliner(&buf, state_dir_path("allow_rerere_autoupdate", opts), + READ_ONELINER_WARN_MISSING)) return -1; if (!strcmp(buf.buf, "--rerere-autoupdate")) opts->allow_rerere_autoupdate = RERERE_AUTOUPDATE; @@ -655,8 +657,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("gpg_sign_opt", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("gpg_sign_opt", opts), - &buf)) + if (!read_oneliner(&buf, state_dir_path("gpg_sign_opt", opts), + READ_ONELINER_WARN_MISSING)) return -1; free(opts->gpg_sign_opt); opts->gpg_sign_opt = xstrdup(buf.buf); @@ -664,7 +666,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("strategy", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("strategy", opts), &buf)) + if (!read_oneliner(&buf, state_dir_path("strategy", opts), + READ_ONELINER_WARN_MISSING)) return -1; free(opts->strategy); opts->strategy = xstrdup(buf.buf); @@ -672,7 +675,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("strategy_opts", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("strategy_opts", opts), &buf)) + if (!read_oneliner(&buf, state_dir_path("strategy_opts", opts), + READ_ONELINER_WARN_MISSING)) return -1; free(opts->strategy_opts); opts->strategy_opts = xstrdup(buf.buf); @@ -715,51 +719,6 @@ static int rebase_write_basic_state(struct rebase_options *opts) return 0; } -static int apply_autostash(struct rebase_options *opts) -{ - const char *path = state_dir_path("autostash", opts); - struct strbuf autostash = STRBUF_INIT; - struct child_process stash_apply = CHILD_PROCESS_INIT; - - if (!file_exists(path)) - return 0; - - if (read_one(path, &autostash)) - return error(_("Could not read '%s'"), path); - /* Ensure that the hash is not mistaken for a number */ - strbuf_addstr(&autostash, "^0"); - argv_array_pushl(&stash_apply.args, - "stash", "apply", autostash.buf, NULL); - stash_apply.git_cmd = 1; - stash_apply.no_stderr = stash_apply.no_stdout = - stash_apply.no_stdin = 1; - if (!run_command(&stash_apply)) - printf(_("Applied autostash.\n")); - else { - struct argv_array args = ARGV_ARRAY_INIT; - int res = 0; - - argv_array_pushl(&args, - "stash", "store", "-m", "autostash", "-q", - autostash.buf, NULL); - if (run_command_v_opt(args.argv, RUN_GIT_CMD)) - res = error(_("Cannot store %s"), autostash.buf); - argv_array_clear(&args); - strbuf_release(&autostash); - if (res) - return res; - - fprintf(stderr, - _("Applying autostash resulted in conflicts.\n" - "Your changes are safe in the stash.\n" - "You can run \"git stash pop\" or \"git stash drop\" " - "at any time.\n")); - } - - strbuf_release(&autostash); - return 0; -} - static int finish_rebase(struct rebase_options *opts) { struct strbuf dir = STRBUF_INIT; @@ -767,7 +726,7 @@ static int finish_rebase(struct rebase_options *opts) int ret = 0; delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); - apply_autostash(opts); + apply_autostash(state_dir_path("autostash", opts)); close_object_store(the_repository->objects); /* * We ignore errors in 'gc --auto', since the @@ -812,143 +771,6 @@ static void add_var(struct strbuf *buf, const char *name, const char *value) } } -#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" - -#define RESET_HEAD_DETACH (1<<0) -#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, - const char *reflog_orig_head, const char *reflog_head) -{ - unsigned detach_head = flags & RESET_HEAD_DETACH; - 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; - struct unpack_trees_options unpack_tree_opts; - struct tree *tree; - const char *reflog_action; - struct strbuf msg = STRBUF_INIT; - size_t prefix_len; - struct object_id *orig = NULL, oid_orig, - *old_orig = NULL, oid_old_orig; - int ret = 0, nr = 0; - - if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) - BUG("Not a fully qualified branch: '%s'", switch_to_branch); - - if (!refs_only && hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) { - ret = -1; - goto leave_reset_head; - } - - if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) { - ret = error(_("could not determine HEAD revision")); - goto leave_reset_head; - } - - if (!oid) - oid = &head_oid; - - if (refs_only) - goto reset_head_refs; - - memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); - setup_unpack_trees_porcelain(&unpack_tree_opts, action); - unpack_tree_opts.head_idx = 1; - unpack_tree_opts.src_index = the_repository->index; - unpack_tree_opts.dst_index = the_repository->index; - unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; - unpack_tree_opts.update = 1; - unpack_tree_opts.merge = 1; - if (!detach_head) - unpack_tree_opts.reset = 1; - - if (repo_read_index_unmerged(the_repository) < 0) { - ret = error(_("could not read index")); - goto leave_reset_head; - } - - if (!reset_hard && !fill_tree_descriptor(the_repository, &desc[nr++], &head_oid)) { - ret = error(_("failed to find tree of %s"), - oid_to_hex(&head_oid)); - goto leave_reset_head; - } - - if (!fill_tree_descriptor(the_repository, &desc[nr++], oid)) { - ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); - goto leave_reset_head; - } - - if (unpack_trees(nr, desc, &unpack_tree_opts)) { - ret = -1; - goto leave_reset_head; - } - - tree = parse_tree_indirect(oid); - prime_cache_tree(the_repository, the_repository->index, tree); - - if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) { - ret = error(_("could not write index")); - goto leave_reset_head; - } - -reset_head_refs: - reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); - strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); - prefix_len = msg.len; - - 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"); - reflog_head = msg.buf; - } - if (!switch_to_branch) - ret = update_ref(reflog_head, "HEAD", oid, orig, - detach_head ? REF_NO_DEREF : 0, - UPDATE_REFS_MSG_ON_ERR); - else { - 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, - reflog_head); - } - if (run_hook) - run_hook_le(NULL, "post-checkout", - oid_to_hex(orig ? orig : &null_oid), - oid_to_hex(oid), "1", NULL); - -leave_reset_head: - strbuf_release(&msg); - rollback_lock_file(&lock); - while (nr) - free((void *)desc[--nr].buffer); - return ret; -} - static int move_to_original_branch(struct rebase_options *opts) { struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; @@ -964,8 +786,10 @@ static int move_to_original_branch(struct rebase_options *opts) opts->head_name, oid_to_hex(&opts->onto->object.oid)); strbuf_addf(&head_reflog, "rebase finished: returning to %s", opts->head_name); - ret = reset_head(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY, - orig_head_reflog.buf, head_reflog.buf); + ret = reset_head(the_repository, NULL, "", opts->head_name, + RESET_HEAD_REFS_ONLY, + orig_head_reflog.buf, head_reflog.buf, + DEFAULT_REFLOG_ACTION); strbuf_release(&orig_head_reflog); strbuf_release(&head_reflog); @@ -1053,8 +877,9 @@ static int run_am(struct rebase_options *opts) free(rebased_patches); argv_array_clear(&am.args); - reset_head(&opts->orig_head, "checkout", opts->head_name, 0, - "HEAD", NULL); + reset_head(the_repository, &opts->orig_head, "checkout", + opts->head_name, 0, + "HEAD", NULL, DEFAULT_REFLOG_ACTION); error(_("\ngit encountered an error while preparing the " "patches to replay\n" "these revisions:\n" @@ -1162,6 +987,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) opts->allow_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); add_var(&script_snippet, "cmd", opts->cmd); @@ -1212,7 +1038,7 @@ finished_rebase: } else if (status == 2) { struct strbuf dir = STRBUF_INIT; - apply_autostash(opts); + apply_autostash(state_dir_path("autostash", opts)); strbuf_addstr(&dir, opts->state_dir); remove_dir_recursively(&dir, 0); strbuf_release(&dir); @@ -1453,7 +1279,6 @@ static int check_exec_cmd(const char *cmd) return 0; } - int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = REBASE_OPTIONS_INIT; @@ -1547,7 +1372,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("how to handle commits that become empty"), PARSE_OPT_NONEG, parse_opt_empty), { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL, - N_("(DEPRECATED) keep empty commits"), + N_("keep commits which start empty"), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, parse_opt_keep_empty }, OPT_BOOL(0, "autosquash", &options.autosquash, @@ -1556,8 +1381,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), N_("GPG-sign commits"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, - OPT_BOOL(0, "autostash", &options.autostash, - N_("automatically stash/stash pop before and after")), + OPT_AUTOSTASH(&options.autostash), OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), @@ -1582,6 +1406,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "reschedule-failed-exec", &reschedule_failed_exec, N_("automatically re-schedule any `exec` that fails")), + OPT_BOOL(0, "reapply-cherry-picks", &options.reapply_cherry_picks, + N_("apply all changes, even those already present upstream")), OPT_END(), }; int i; @@ -1592,6 +1418,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.allow_empty_message = 1; git_config(rebase_config, &options); + /* options.gpg_sign_opt will be either "-S" or NULL */ + gpg_sign = options.gpg_sign_opt ? "" : NULL; + FREE_AND_NULL(options.gpg_sign_opt); if (options.use_legacy_rebase || !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1)) @@ -1709,8 +1538,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) rerere_clear(the_repository, &merge_rr); string_list_clear(&merge_rr, 1); - if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD, - NULL, NULL) < 0) + if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD, + NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) die(_("could not discard worktree changes")); remove_branch_state(the_repository, 0); if (read_basic_state(&options)) @@ -1727,9 +1556,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state(&options)) exit(1); - if (reset_head(&options.orig_head, "reset", + if (reset_head(the_repository, &options.orig_head, "reset", options.head_name, RESET_HEAD_HARD, - NULL, NULL) < 0) + NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); remove_branch_state(the_repository, 0); @@ -1737,6 +1566,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) goto cleanup; } case ACTION_QUIT: { + save_autostash(state_dir_path("autostash", &options)); if (options.type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; @@ -1822,10 +1652,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.empty != EMPTY_UNSPECIFIED) imply_merge(&options, "--empty"); - if (gpg_sign) { - free(options.gpg_sign_opt); + if (options.reapply_cherry_picks) + imply_merge(&options, "--reapply-cherry-picks"); + + if (gpg_sign) options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); - } if (exec.nr) { int i; @@ -2086,49 +1917,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("could not read index")); if (options.autostash) { - struct lock_file lock_file = LOCK_INIT; - int fd; - - fd = hold_locked_index(&lock_file, 0); - refresh_cache(REFRESH_QUIET); - if (0 <= fd) - repo_update_index_if_able(the_repository, &lock_file); - rollback_lock_file(&lock_file); - - if (has_unstaged_changes(the_repository, 1) || - has_uncommitted_changes(the_repository, 1)) { - const char *autostash = - state_dir_path("autostash", &options); - struct child_process stash = CHILD_PROCESS_INIT; - struct object_id oid; - - argv_array_pushl(&stash.args, - "stash", "create", "autostash", NULL); - stash.git_cmd = 1; - stash.no_stdin = 1; - strbuf_reset(&buf); - if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) - die(_("Cannot autostash")); - strbuf_trim_trailing_newline(&buf); - if (get_oid(buf.buf, &oid)) - die(_("Unexpected stash response: '%s'"), - buf.buf); - strbuf_reset(&buf); - strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); - - if (safe_create_leading_directories_const(autostash)) - die(_("Could not create directory for '%s'"), - options.state_dir); - write_file(autostash, "%s", oid_to_hex(&oid)); - printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(NULL, "reset --hard", - NULL, RESET_HEAD_HARD, NULL, NULL) < 0) - die(_("could not reset --hard")); - - if (discard_index(the_repository->index) < 0 || - repo_read_index(the_repository) < 0) - die(_("could not read index")); - } + create_autostash(the_repository, state_dir_path("autostash", &options), + DEFAULT_REFLOG_ACTION); } if (require_clean_work_tree(the_repository, "rebase", @@ -2162,10 +1952,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&buf, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.switch_to); - if (reset_head(&options.orig_head, "checkout", + if (reset_head(the_repository, + &options.orig_head, "checkout", options.head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, buf.buf) < 0) { + NULL, buf.buf, + DEFAULT_REFLOG_ACTION) < 0) { ret = !!error(_("could not switch to " "%s"), options.switch_to); @@ -2237,10 +2029,10 @@ 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, + if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL, RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, msg.buf)) + NULL, msg.buf, DEFAULT_REFLOG_ACTION)) die(_("Could not detach HEAD")); strbuf_release(&msg); @@ -2255,8 +2047,9 @@ 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, - RESET_HEAD_REFS_ONLY, "HEAD", msg.buf); + reset_head(the_repository, NULL, "Fast-forwarded", options.head_name, + RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, + DEFAULT_REFLOG_ACTION); strbuf_release(&msg); ret = !!finish_rebase(&options); goto cleanup; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 2cc18bbffd..a00f91c1a0 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -13,7 +13,7 @@ #include "remote.h" #include "connect.h" #include "string-list.h" -#include "sha1-array.h" +#include "oid-array.h" #include "connected.h" #include "argv-array.h" #include "version.h" @@ -499,12 +499,27 @@ static char *find_header(const char *msg, size_t len, const char *key, return NULL; } +/* + * Return zero if a and b are equal up to n bytes and nonzero if they are not. + * This operation is guaranteed to run in constant time to avoid leaking data. + */ +static int constant_memequal(const char *a, const char *b, size_t n) +{ + int res = 0; + size_t i; + + for (i = 0; i < n; i++) + res |= a[i] ^ b[i]; + return res; +} + static const char *check_nonce(const char *buf, size_t len) { char *nonce = find_header(buf, len, "nonce", NULL); timestamp_t stamp, ostamp; char *bohmac, *expect = NULL; const char *retval = NONCE_BAD; + size_t noncelen; if (!nonce) { retval = NONCE_MISSING; @@ -546,8 +561,14 @@ static const char *check_nonce(const char *buf, size_t len) goto leave; } + noncelen = strlen(nonce); expect = prepare_push_cert_nonce(service_dir, stamp); - if (strcmp(expect, nonce)) { + if (noncelen != strlen(expect)) { + /* This is not even the right size. */ + retval = NONCE_BAD; + goto leave; + } + if (constant_memequal(expect, nonce, noncelen)) { /* Not what we would have signed earlier */ retval = NONCE_BAD; goto leave; @@ -872,12 +893,12 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) opt.env = tmp_objdir_env(tmp_objdir); setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra); if (check_connected(command_singleton_iterator, cmd, &opt)) { - rollback_lock_file(&shallow_lock); + rollback_shallow_file(the_repository, &shallow_lock); oid_array_clear(&extra); return -1; } - commit_lock_file(&shallow_lock); + commit_shallow_file(the_repository, &shallow_lock); /* * Make sure setup_alternate_shallow() for the next ref does diff --git a/builtin/reflog.c b/builtin/reflog.c index 81dfd563c0..52ecf6d43c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -459,7 +459,7 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len) static int reflog_expire_config(const char *var, const char *value, void *cb) { const char *pattern, *key; - int pattern_len; + size_t pattern_len; timestamp_t expire; int slot; struct reflog_expire_cfg *ent; diff --git a/builtin/repack.c b/builtin/repack.c index 0781763b06..1b686ee9ce 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -10,6 +10,7 @@ #include "argv-array.h" #include "midx.h" #include "packfile.h" +#include "prune-packed.h" #include "object-store.h" #include "promisor-remote.h" diff --git a/builtin/reset.c b/builtin/reset.c index 18228c312e..4c634111bd 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -46,7 +46,7 @@ static inline int is_merge(void) return !access(git_path_merge_head(the_repository), F_OK); } -static int reset_index(const struct object_id *oid, int reset_type, int quiet) +static int reset_index(const char *ref, const struct object_id *oid, int reset_type, int quiet) { int i, nr = 0; struct tree_desc desc[2]; @@ -60,6 +60,7 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet) opts.dst_index = &the_index; opts.fn = oneway_merge; opts.merge = 1; + init_checkout_metadata(&opts.meta, ref, oid, NULL); if (!quiet) opts.verbose_update = 1; switch (reset_type) { @@ -418,11 +419,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix) } } } else { - int err = reset_index(&oid, reset_type, quiet); + struct object_id dummy; + char *ref = NULL; + int err; + + dwim_ref(rev, strlen(rev), &dummy, &ref); + if (ref && !starts_with(ref, "refs/")) + ref = NULL; + + err = reset_index(ref, &oid, reset_type, quiet); if (reset_type == KEEP && !err) - err = reset_index(&oid, MIXED, quiet); + err = reset_index(ref, &oid, MIXED, quiet); if (err) die(_("Could not reset index file to revision '%s'."), rev); + free(ref); } if (write_locked_index(&the_index, &lock, COMMIT_LOCK)) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 7a00da8203..06056434ed 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -808,9 +808,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--show-superproject-working-tree")) { - const char *superproject = get_superproject_working_tree(); - if (superproject) - puts(superproject); + struct strbuf superproject = STRBUF_INIT; + if (get_superproject_working_tree(&superproject)) + puts(superproject.buf); + strbuf_release(&superproject); continue; } if (!strcmp(arg, "--show-prefix")) { @@ -857,7 +858,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!gitdir && !prefix) gitdir = ".git"; if (gitdir) { - puts(real_path(gitdir)); + struct strbuf realpath = STRBUF_INIT; + strbuf_realpath(&realpath, gitdir, 1); + puts(realpath.buf); + strbuf_release(&realpath); continue; } } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 098ebf22d0..f2c5a34402 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -11,7 +11,7 @@ #include "quote.h" #include "transport.h" #include "version.h" -#include "sha1-array.h" +#include "oid-array.h" #include "gpg-interface.h" #include "gettext.h" #include "protocol.h" diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 740da4b6d5..95d0882417 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -18,7 +18,7 @@ static const char *empty_base = ""; static char const * const builtin_sparse_checkout_usage[] = { - N_("git sparse-checkout (init|list|set|add|disable) <options>"), + N_("git sparse-checkout (init|list|set|add|reapply|disable) <options>"), NULL }; @@ -94,50 +94,37 @@ static int sparse_checkout_list(int argc, const char **argv) static int update_working_directory(struct pattern_list *pl) { - int result = 0; + enum update_sparsity_result result; struct unpack_trees_options o; struct lock_file lock_file = LOCK_INIT; - struct object_id oid; - struct tree *tree; - struct tree_desc t; struct repository *r = the_repository; - if (repo_read_index_unmerged(r)) - die(_("you need to resolve your current index first")); - - if (get_oid("HEAD", &oid)) - return 0; - - tree = parse_tree_indirect(&oid); - parse_tree(tree); - init_tree_desc(&t, tree->buffer, tree->size); - memset(&o, 0, sizeof(o)); o.verbose_update = isatty(2); - o.merge = 1; o.update = 1; - o.fn = oneway_merge; o.head_idx = -1; o.src_index = r->index; o.dst_index = r->index; o.skip_sparse_checkout = 0; o.pl = pl; - o.keep_pattern_list = !!pl; - resolve_undo_clear_index(r->index); setup_work_tree(); - cache_tree_free(&r->index->cache_tree); - repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR); - core_apply_sparse_checkout = 1; - result = unpack_trees(1, &t, &o); - - if (!result) { - prime_cache_tree(r, r->index, tree); + setup_unpack_trees_porcelain(&o, "sparse-checkout"); + result = update_sparsity(&o); + clear_unpack_trees_porcelain(&o); + + if (result == UPDATE_SPARSITY_WARNINGS) + /* + * We don't do any special handling of warnings from untracked + * files in the way or dirty entries that can't be removed. + */ + result = UPDATE_SPARSITY_SUCCESS; + if (result == UPDATE_SPARSITY_SUCCESS) write_locked_index(r->index, &lock_file, COMMIT_LOCK); - } else + else rollback_lock_file(&lock_file); return result; @@ -304,8 +291,6 @@ static int sparse_checkout_init(int argc, const char **argv) }; repo_read_index(the_repository); - require_clean_work_tree(the_repository, - N_("initialize sparse-checkout"), NULL, 1, 0); argc = parse_options(argc, argv, NULL, builtin_sparse_checkout_init_options, @@ -560,8 +545,6 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix, }; repo_read_index(the_repository); - require_clean_work_tree(the_repository, - N_("set sparse-checkout patterns"), NULL, 1, 0); argc = parse_options(argc, argv, prefix, builtin_sparse_checkout_set_options, @@ -571,14 +554,18 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix, return modify_pattern_list(argc, argv, m); } +static int sparse_checkout_reapply(int argc, const char **argv) +{ + repo_read_index(the_repository); + return update_working_directory(NULL); +} + static int sparse_checkout_disable(int argc, const char **argv) { struct pattern_list pl; struct strbuf match_all = STRBUF_INIT; repo_read_index(the_repository); - require_clean_work_tree(the_repository, - N_("disable sparse-checkout"), NULL, 1, 0); memset(&pl, 0, sizeof(pl)); hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0); @@ -622,6 +609,8 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix) return sparse_checkout_set(argc, argv, prefix, REPLACE); if (!strcmp(argv[0], "add")) return sparse_checkout_set(argc, argv, prefix, ADD); + if (!strcmp(argv[0], "reapply")) + return sparse_checkout_reapply(argc, argv); if (!strcmp(argv[0], "disable")) return sparse_checkout_disable(argc, argv); } diff --git a/builtin/stash.c b/builtin/stash.c index 78af6ce564..0c52a3b849 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -702,6 +702,7 @@ static int list_stash(int argc, const char **argv, const char *prefix) static int show_stat = 1; static int show_patch; +static int use_legacy_stash; static int git_stash_config(const char *var, const char *value, void *cb) { @@ -713,7 +714,11 @@ static int git_stash_config(const char *var, const char *value, void *cb) show_patch = git_config_bool(var, value); return 0; } - return git_default_config(var, value, cb); + if (!strcmp(var, "stash.usebuiltin")) { + use_legacy_stash = !git_config_bool(var, value); + return 0; + } + return git_diff_basic_config(var, value, cb); } static int show_stash(int argc, const char **argv, const char *prefix) @@ -750,7 +755,6 @@ static int show_stash(int argc, const char **argv, const char *prefix) * any options. */ if (revision_args.argc == 1) { - git_config(git_stash_config, NULL); if (show_stat) rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT; @@ -857,30 +861,23 @@ 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); + 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'); - } + 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); @@ -1037,7 +1034,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, } cp_diff_tree.git_cmd = 1; - argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD", + argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD", oid_to_hex(&info->w_tree), "--", NULL); if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { ret = -1; @@ -1559,29 +1556,6 @@ static int save_stash(int argc, const char **argv, const char *prefix) 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) { pid_t pid = getpid(); @@ -1592,21 +1566,12 @@ int cmd_stash(int argc, const char **argv, const char *prefix) 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_stash_config, NULL); - git_config(git_diff_basic_config, NULL); + if (use_legacy_stash || + !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1)) + warning(_("the stash.useBuiltin support has been removed!\n" + "See its entry in 'git help config' for details.")); argc = parse_options(argc, argv, prefix, options, git_stash_usage, PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 86a608eec1..1a4b391c88 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -444,19 +444,19 @@ static void for_each_listed_submodule(const struct module_list *list, fn(list->entries[i], cb_data); } -struct cb_foreach { +struct foreach_cb { int argc; const char **argv; const char *prefix; int quiet; int recursive; }; -#define CB_FOREACH_INIT { 0 } +#define FOREACH_CB_INIT { 0 } static void runcommand_in_submodule_cb(const struct cache_entry *list_item, void *cb_data) { - struct cb_foreach *info = cb_data; + struct foreach_cb *info = cb_data; const char *path = list_item->name; const struct object_id *ce_oid = &list_item->oid; @@ -557,7 +557,7 @@ cleanup: static int module_foreach(int argc, const char **argv, const char *prefix) { - struct cb_foreach info = CB_FOREACH_INIT; + struct foreach_cb info = FOREACH_CB_INIT; struct pathspec pathspec; struct module_list list = MODULE_LIST_INIT; diff --git a/builtin/tag.c b/builtin/tag.c index e0a4c25382..dd160b49c7 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -17,7 +17,7 @@ #include "diff.h" #include "revision.h" #include "gpg-interface.h" -#include "sha1-array.h" +#include "oid-array.h" #include "column.h" #include "ref-filter.h" @@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref, if (type <= OBJ_NONE) die(_("bad object type.")); - if (type == OBJ_TAG && advice_nested_tag) - advise(_(message_advice_nested_tag), tag, object_ref); + if (type == OBJ_TAG) + advise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag), + tag, object_ref); strbuf_addf(&header, "object %s\n" diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 2d8f7f0578..b74dd9a69d 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -50,7 +50,7 @@ static const char *parse_arg(const char *next, struct strbuf *arg) * the argument. Die if C-quoting is malformed or the reference name * is invalid. */ -static char *parse_refname(struct strbuf *input, const char **next) +static char *parse_refname(const char **next) { struct strbuf ref = STRBUF_INIT; @@ -95,7 +95,7 @@ static char *parse_refname(struct strbuf *input, const char **next) * provided but cannot be converted to a SHA-1, die. flags can * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY. */ -static int parse_next_oid(struct strbuf *input, const char **next, +static int parse_next_oid(const char **next, const char *end, struct object_id *oid, const char *command, const char *refname, int flags) @@ -103,7 +103,7 @@ static int parse_next_oid(struct strbuf *input, const char **next, struct strbuf arg = STRBUF_INIT; int ret = 0; - if (*next == input->buf + input->len) + if (*next == end) goto eof; if (line_termination) { @@ -128,7 +128,7 @@ static int parse_next_oid(struct strbuf *input, const char **next, die("%s %s: expected NUL but got: %s", command, refname, *next); (*next)++; - if (*next == input->buf + input->len) + if (*next == end) goto eof; strbuf_addstr(&arg, *next); *next += arg.len; @@ -178,23 +178,23 @@ static int parse_next_oid(struct strbuf *input, const char **next, * depending on how line_termination is set. */ -static const char *parse_cmd_update(struct ref_transaction *transaction, - struct strbuf *input, const char *next) +static void parse_cmd_update(struct ref_transaction *transaction, + const char *next, const char *end) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id new_oid, old_oid; int have_old; - refname = parse_refname(input, &next); + refname = parse_refname(&next); if (!refname) die("update: missing <ref>"); - if (parse_next_oid(input, &next, &new_oid, "update", refname, + if (parse_next_oid(&next, end, &new_oid, "update", refname, PARSE_SHA1_ALLOW_EMPTY)) die("update %s: missing <newvalue>", refname); - have_old = !parse_next_oid(input, &next, &old_oid, "update", refname, + have_old = !parse_next_oid(&next, end, &old_oid, "update", refname, PARSE_SHA1_OLD); if (*next != line_termination) @@ -209,22 +209,20 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, update_flags = default_flags; free(refname); strbuf_release(&err); - - return next; } -static const char *parse_cmd_create(struct ref_transaction *transaction, - struct strbuf *input, const char *next) +static void parse_cmd_create(struct ref_transaction *transaction, + const char *next, const char *end) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id new_oid; - refname = parse_refname(input, &next); + refname = parse_refname(&next); if (!refname) die("create: missing <ref>"); - if (parse_next_oid(input, &next, &new_oid, "create", refname, 0)) + if (parse_next_oid(&next, end, &new_oid, "create", refname, 0)) die("create %s: missing <newvalue>", refname); if (is_null_oid(&new_oid)) @@ -241,23 +239,21 @@ static const char *parse_cmd_create(struct ref_transaction *transaction, update_flags = default_flags; free(refname); strbuf_release(&err); - - return next; } -static const char *parse_cmd_delete(struct ref_transaction *transaction, - struct strbuf *input, const char *next) +static void parse_cmd_delete(struct ref_transaction *transaction, + const char *next, const char *end) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id old_oid; int have_old; - refname = parse_refname(input, &next); + refname = parse_refname(&next); if (!refname) die("delete: missing <ref>"); - if (parse_next_oid(input, &next, &old_oid, "delete", refname, + if (parse_next_oid(&next, end, &old_oid, "delete", refname, PARSE_SHA1_OLD)) { have_old = 0; } else { @@ -277,22 +273,20 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction, update_flags = default_flags; free(refname); strbuf_release(&err); - - return next; } -static const char *parse_cmd_verify(struct ref_transaction *transaction, - struct strbuf *input, const char *next) +static void parse_cmd_verify(struct ref_transaction *transaction, + const char *next, const char *end) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id old_oid; - refname = parse_refname(input, &next); + refname = parse_refname(&next); if (!refname) die("verify: missing <ref>"); - if (parse_next_oid(input, &next, &old_oid, "verify", refname, + if (parse_next_oid(&next, end, &old_oid, "verify", refname, PARSE_SHA1_OLD)) oidclr(&old_oid); @@ -306,50 +300,179 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, update_flags = default_flags; free(refname); strbuf_release(&err); - - return next; } -static const char *parse_cmd_option(struct strbuf *input, const char *next) +static void parse_cmd_option(struct ref_transaction *transaction, + const char *next, const char *end) { const char *rest; if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination) update_flags |= REF_NO_DEREF; else die("option unknown: %s", next); - return rest; } -static void update_refs_stdin(struct ref_transaction *transaction) +static void parse_cmd_start(struct ref_transaction *transaction, + const char *next, const char *end) +{ + if (*next != line_termination) + die("start: extra input: %s", next); + puts("start: ok"); +} + +static void parse_cmd_prepare(struct ref_transaction *transaction, + const char *next, const char *end) +{ + struct strbuf error = STRBUF_INIT; + if (*next != line_termination) + die("prepare: extra input: %s", next); + if (ref_transaction_prepare(transaction, &error)) + die("prepare: %s", error.buf); + puts("prepare: ok"); +} + +static void parse_cmd_abort(struct ref_transaction *transaction, + const char *next, const char *end) +{ + struct strbuf error = STRBUF_INIT; + if (*next != line_termination) + die("abort: extra input: %s", next); + if (ref_transaction_abort(transaction, &error)) + die("abort: %s", error.buf); + puts("abort: ok"); +} + +static void parse_cmd_commit(struct ref_transaction *transaction, + const char *next, const char *end) +{ + struct strbuf error = STRBUF_INIT; + if (*next != line_termination) + die("commit: extra input: %s", next); + if (ref_transaction_commit(transaction, &error)) + die("commit: %s", error.buf); + puts("commit: ok"); + ref_transaction_free(transaction); +} + +enum update_refs_state { + /* Non-transactional state open for updates. */ + UPDATE_REFS_OPEN, + /* A transaction has been started. */ + UPDATE_REFS_STARTED, + /* References are locked and ready for commit */ + UPDATE_REFS_PREPARED, + /* Transaction has been committed or closed. */ + UPDATE_REFS_CLOSED, +}; + +static const struct parse_cmd { + const char *prefix; + void (*fn)(struct ref_transaction *, const char *, const char *); + unsigned args; + enum update_refs_state state; +} command[] = { + { "update", parse_cmd_update, 3, UPDATE_REFS_OPEN }, + { "create", parse_cmd_create, 2, UPDATE_REFS_OPEN }, + { "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN }, + { "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN }, + { "option", parse_cmd_option, 1, UPDATE_REFS_OPEN }, + { "start", parse_cmd_start, 0, UPDATE_REFS_STARTED }, + { "prepare", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED }, + { "abort", parse_cmd_abort, 0, UPDATE_REFS_CLOSED }, + { "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED }, +}; + +static void update_refs_stdin(void) { - struct strbuf input = STRBUF_INIT; - const char *next; + struct strbuf input = STRBUF_INIT, err = STRBUF_INIT; + enum update_refs_state state = UPDATE_REFS_OPEN; + struct ref_transaction *transaction; + int i, j; + + transaction = ref_transaction_begin(&err); + if (!transaction) + die("%s", err.buf); - if (strbuf_read(&input, 0, 1000) < 0) - die_errno("could not read from stdin"); - next = input.buf; /* Read each line dispatch its command */ - while (next < input.buf + input.len) { - if (*next == line_termination) + while (!strbuf_getwholeline(&input, stdin, line_termination)) { + const struct parse_cmd *cmd = NULL; + + if (*input.buf == line_termination) die("empty command in input"); - else if (isspace(*next)) - die("whitespace before command: %s", next); - else if (skip_prefix(next, "update ", &next)) - next = parse_cmd_update(transaction, &input, next); - else if (skip_prefix(next, "create ", &next)) - next = parse_cmd_create(transaction, &input, next); - else if (skip_prefix(next, "delete ", &next)) - next = parse_cmd_delete(transaction, &input, next); - else if (skip_prefix(next, "verify ", &next)) - next = parse_cmd_verify(transaction, &input, next); - else if (skip_prefix(next, "option ", &next)) - next = parse_cmd_option(&input, next); - else - die("unknown command: %s", next); - - next++; + else if (isspace(*input.buf)) + die("whitespace before command: %s", input.buf); + + for (i = 0; i < ARRAY_SIZE(command); i++) { + const char *prefix = command[i].prefix; + char c; + + if (!starts_with(input.buf, prefix)) + continue; + + /* + * If the command has arguments, verify that it's + * followed by a space. Otherwise, it shall be followed + * by a line terminator. + */ + c = command[i].args ? ' ' : line_termination; + if (input.buf[strlen(prefix)] != c) + continue; + + cmd = &command[i]; + break; + } + if (!cmd) + die("unknown command: %s", input.buf); + + /* + * Read additional arguments if NUL-terminated. Do not raise an + * error in case there is an early EOF to let the command + * handle missing arguments with a proper error message. + */ + for (j = 1; line_termination == '\0' && j < cmd->args; j++) + if (strbuf_appendwholeline(&input, stdin, line_termination)) + break; + + switch (state) { + case UPDATE_REFS_OPEN: + case UPDATE_REFS_STARTED: + /* Do not downgrade a transaction to a non-transaction. */ + if (cmd->state >= state) + state = cmd->state; + break; + case UPDATE_REFS_PREPARED: + if (cmd->state != UPDATE_REFS_CLOSED) + die("prepared transactions can only be closed"); + state = cmd->state; + break; + case UPDATE_REFS_CLOSED: + die("transaction is closed"); + break; + } + + cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args, + input.buf + input.len); + } + + switch (state) { + case UPDATE_REFS_OPEN: + /* Commit by default if no transaction was requested. */ + if (ref_transaction_commit(transaction, &err)) + die("%s", err.buf); + ref_transaction_free(transaction); + break; + case UPDATE_REFS_STARTED: + case UPDATE_REFS_PREPARED: + /* If using a transaction, we want to abort it. */ + if (ref_transaction_abort(transaction, &err)) + die("%s", err.buf); + break; + case UPDATE_REFS_CLOSED: + /* Otherwise no need to do anything, the transaction was closed already. */ + break; } + strbuf_release(&err); strbuf_release(&input); } @@ -384,21 +507,11 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) } if (read_stdin) { - struct strbuf err = STRBUF_INIT; - struct ref_transaction *transaction; - - transaction = ref_transaction_begin(&err); - if (!transaction) - die("%s", err.buf); if (delete || argc > 0) usage_with_options(git_update_ref_usage, options); if (end_null) line_termination = '\0'; - update_refs_stdin(transaction); - if (ref_transaction_commit(transaction, &err)) - die("%s", err.buf); - ref_transaction_free(transaction); - strbuf_release(&err); + update_refs_stdin(); return 0; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 24f22800f3..d99db35668 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -258,7 +258,7 @@ static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; - struct strbuf sb = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT; const char *name; struct child_process cp = CHILD_PROCESS_INIT; struct argv_array child_env = ARGV_ARRAY_INIT; @@ -330,9 +330,11 @@ static int add_worktree(const char *path, const char *refname, strbuf_reset(&sb); strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - write_file(sb.buf, "%s", real_path(sb_git.buf)); + strbuf_realpath(&realpath, sb_git.buf, 1); + write_file(sb.buf, "%s", realpath.buf); + strbuf_realpath(&realpath, get_git_common_dir(), 1); write_file(sb_git.buf, "gitdir: %s/worktrees/%s", - real_path(get_git_common_dir()), name); + realpath.buf, name); /* * This is to keep resolve_ref() happy. We need a valid HEAD * or is_git_directory() will reject the directory. Any value which @@ -418,6 +420,7 @@ done: strbuf_release(&sb_repo); strbuf_release(&sb_git); strbuf_release(&sb_name); + strbuf_release(&realpath); return ret; } |