diff options
Diffstat (limited to 'builtin')
102 files changed, 15396 insertions, 5318 deletions
diff --git a/builtin/add.c b/builtin/add.c index 9ef7fb02d5..298e0114f9 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -3,13 +3,14 @@ * * Copyright (C) 2006 Linus Torvalds */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "builtin.h" #include "lockfile.h" #include "dir.h" #include "pathspec.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "cache-tree.h" #include "run-command.h" #include "parse-options.h" @@ -19,6 +20,7 @@ #include "bulk-checkin.h" #include "argv-array.h" #include "submodule.h" +#include "add-interactive.h" static const char * const builtin_add_usage[] = { N_("git add [<options>] [--] <pathspec>..."), @@ -27,6 +29,9 @@ static const char * const builtin_add_usage[] = { static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; static int add_renormalize; +static int pathspec_file_nul; +static const char *pathspec_from_file; +static int legacy_stash_p; /* support for the scripted `git stash` */ struct update_callback_data { int flags; @@ -40,7 +45,7 @@ static void chmod_pathspec(struct pathspec *pathspec, char flip) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - if (pathspec && !ce_path_match(ce, pathspec, NULL)) + if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) continue; if (chmod_cache_entry(ce, flip) < 0) @@ -110,7 +115,7 @@ int add_files_to_cache(const char *prefix, memset(&data, 0, sizeof(data)); data.flags = flags; - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); setup_revisions(0, NULL, &rev, NULL); if (pathspec) copy_pathspec(&rev.prune_data, pathspec); @@ -135,9 +140,9 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags) continue; /* do not touch unmerged paths */ if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode)) continue; /* do not touch non blobs */ - if (pathspec && !ce_path_match(ce, pathspec, NULL)) + if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) continue; - retval |= add_file_to_cache(ce->name, flags | HASH_RENORMALIZE); + retval |= add_file_to_cache(ce->name, flags | ADD_CACHE_RENORMALIZE); } return retval; @@ -155,7 +160,7 @@ static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, i = dir->nr; while (--i >= 0) { struct dir_entry *entry = *src++; - if (dir_path_match(entry, pathspec, prefix, seen)) + if (dir_path_match(&the_index, entry, pathspec, prefix, seen)) *dst++ = entry; } dir->nr = dst - dir->entries; @@ -176,7 +181,7 @@ static void refresh(int verbose, const struct pathspec *pathspec) die(_("pathspec '%s' did not match any files"), pathspec->items[i].match); } - free(seen); + free(seen); } int run_add_interactive(const char *revision, const char *patch_mode, @@ -184,6 +189,34 @@ int run_add_interactive(const char *revision, const char *patch_mode, { int status, i; struct argv_array argv = ARGV_ARRAY_INIT; + int use_builtin_add_i = + git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); + + if (use_builtin_add_i < 0) + git_config_get_bool("add.interactive.usebuiltin", + &use_builtin_add_i); + + if (use_builtin_add_i == 1) { + enum add_p_mode mode; + + if (!patch_mode) + return !!run_add_i(the_repository, pathspec); + + if (!strcmp(patch_mode, "--patch")) + mode = ADD_P_ADD; + else if (!strcmp(patch_mode, "--patch=stash")) + mode = ADD_P_STASH; + else if (!strcmp(patch_mode, "--patch=reset")) + mode = ADD_P_RESET; + else if (!strcmp(patch_mode, "--patch=checkout")) + mode = ADD_P_CHECKOUT; + else if (!strcmp(patch_mode, "--patch=worktree")) + mode = ADD_P_WORKTREE; + else + die("'%s' not supported", patch_mode); + + return !!run_add_p(the_repository, mode, revision, pathspec); + } argv_array_push(&argv, "add--interactive"); if (patch_mode) @@ -232,14 +265,14 @@ static int edit_patch(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("Could not read the index")); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); rev.diffopt.context = 7; argc = setup_revisions(argc, argv, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_PATCH; rev.diffopt.use_color = 0; rev.diffopt.flags.ignore_dirty_submodules = 1; - out = open(file, O_CREAT | O_WRONLY, 0666); + out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (out < 0) die(_("Could not open '%s' for writing."), file); rev.diffopt.file = xfdopen(out, "w"); @@ -265,8 +298,6 @@ static int edit_patch(int argc, const char **argv, const char *prefix) return 0; } -static struct lock_file lock_file; - static const char ignore_error[] = N_("The following paths are ignored by one of your .gitignore files:\n"); @@ -299,16 +330,21 @@ static struct option builtin_add_options[] = { OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")), OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")), - { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit, + OPT_CALLBACK_F(0, "ignore-removal", &addremove_explicit, NULL /* takes no arguments */, N_("ignore paths removed in the working tree (same as --no-all)"), - PARSE_OPT_NOARG, ignore_removal_cb }, + PARSE_OPT_NOARG, ignore_removal_cb), OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")), OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), - OPT_STRING( 0 , "chmod", &chmod_arg, N_("(+/-)x"), N_("override the executable bit of the listed files")), + OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x", + N_("override the executable bit of the listed files")), OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo, N_("warn when adding an embedded repository")), + OPT_HIDDEN_BOOL(0, "legacy-stash-p", &legacy_stash_p, + N_("backend for `git stash -p`")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END(), }; @@ -319,6 +355,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors = git_config_bool(var, value); return 0; } + return git_default_config(var, value, cb); } @@ -369,16 +406,20 @@ static int add_files(struct dir_struct *dir, int flags) fprintf(stderr, _(ignore_error)); for (i = 0; i < dir->ignored_nr; i++) fprintf(stderr, "%s\n", dir->ignored[i]->name); - fprintf(stderr, _("Use -f if you really want to add them.\n")); + if (advice_add_ignored_file) + advise(_("Use -f if you really want to add them.\n" + "Turn this message off by running\n" + "\"git config advice.addIgnoredFile false\"")); exit_status = 1; } for (i = 0; i < dir->nr; i++) { - check_embedded_repo(dir->entries[i]->name); if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) { if (!ignore_add_errors) die(_("adding files failed")); exit_status = 1; + } else { + check_embedded_repo(dir->entries[i]->name); } } return exit_status; @@ -393,6 +434,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) int add_new_files; int require_pathspec; char *seen = NULL; + struct lock_file lock_file = LOCK_INIT; git_config(add_config, NULL); @@ -400,11 +442,28 @@ int cmd_add(int argc, const char **argv, const char *prefix) builtin_add_usage, PARSE_OPT_KEEP_ARGV0); if (patch_interactive) add_interactive = 1; - if (add_interactive) + if (add_interactive) { + if (pathspec_from_file) + die(_("--pathspec-from-file is incompatible with --interactive/--patch")); exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive)); + } + if (legacy_stash_p) { + struct pathspec pathspec; + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_SYMLINK_LEADING_PATH | + PATHSPEC_PREFIX_ORIGIN, + prefix, argv); - if (edit_interactive) + return run_add_interactive(NULL, "--patch=stash", &pathspec); + } + + if (edit_interactive) { + if (pathspec_from_file) + die(_("--pathspec-from-file is incompatible with --edit")); return(edit_patch(argc, argv, prefix)); + } argc--; argv++; @@ -416,10 +475,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (addremove && take_worktree_changes) die(_("-A and -u are mutually incompatible")); - if (!take_worktree_changes && addremove_explicit < 0 && argc) - /* Turn "git add pathspec..." to "git add -A pathspec..." */ - addremove = 1; - if (!show_only && ignore_missing) die(_("Option --ignore-missing can only be used together with --dry-run")); @@ -432,6 +487,40 @@ int cmd_add(int argc, const char **argv, const char *prefix) hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + /* + * Check the "pathspec '%s' did not match any files" block + * below before enabling new magic. + */ + parse_pathspec(&pathspec, PATHSPEC_ATTR, + PATHSPEC_PREFER_FULL | + PATHSPEC_SYMLINK_LEADING_PATH, + prefix, argv); + + if (pathspec_from_file) { + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, PATHSPEC_ATTR, + PATHSPEC_PREFER_FULL | + PATHSPEC_SYMLINK_LEADING_PATH, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (require_pathspec && pathspec.nr == 0) { + fprintf(stderr, _("Nothing specified, nothing added.\n")); + if (advice_add_empty_pathspec) + advise( _("Maybe you wanted to say 'git add .'?\n" + "Turn this message off by running\n" + "\"git config advice.addEmptyPathspec false\"")); + return 0; + } + + if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr) + /* Turn "git add pathspec..." to "git add -A pathspec..." */ + addremove = 1; + flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | (show_only ? ADD_CACHE_PRETEND : 0) | (intent_to_add ? ADD_CACHE_INTENT : 0) | @@ -439,26 +528,10 @@ int cmd_add(int argc, const char **argv, const char *prefix) (!(addremove || take_worktree_changes) ? ADD_CACHE_IGNORE_REMOVAL : 0)); - if (require_pathspec && argc == 0) { - fprintf(stderr, _("Nothing specified, nothing added.\n")); - fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n")); - return 0; - } - - if (read_cache() < 0) + if (read_cache_preload(&pathspec) < 0) die(_("index file corrupt")); die_in_unpopulated_submodule(&the_index, prefix); - - /* - * Check the "pathspec '%s' did not match any files" block - * below before enabling new magic. - */ - parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_FULL | - PATHSPEC_SYMLINK_LEADING_PATH, - prefix, argv); - die_path_inside_submodule(&the_index, &pathspec); if (add_new_files) { diff --git a/builtin/am.c b/builtin/am.c index 9c82603f70..69e50de018 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -3,10 +3,11 @@ * * Based on git-am.sh by Junio C Hamano. */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "builtin.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "parse-options.h" #include "dir.h" #include "run-command.h" @@ -23,7 +24,6 @@ #include "sequencer.h" #include "revision.h" #include "merge-recursive.h" -#include "revision.h" #include "log-tree.h" #include "notes-utils.h" #include "rerere.h" @@ -32,22 +32,7 @@ #include "apply.h" #include "string-list.h" #include "packfile.h" - -/** - * Returns 1 if the file is empty or does not exist, 0 otherwise. - */ -static int is_empty_file(const char *filename) -{ - struct stat st; - - if (stat(filename, &st) < 0) { - if (errno == ENOENT) - return 1; - die_errno(_("could not stat %s"), filename); - } - - return !st.st_size; -} +#include "repository.h" /** * Returns the length of the first line of msg. @@ -96,6 +81,11 @@ enum signoff_type { SIGNOFF_EXPLICIT /* --signoff was set on the command-line */ }; +enum show_patch_type { + SHOW_PATCH_RAW = 0, + SHOW_PATCH_DIFF = 1, +}; + struct am_state { /* state directory path */ char *dir; @@ -260,32 +250,6 @@ static int read_state_file(struct strbuf *sb, const struct am_state *state, } /** - * Take a series of KEY='VALUE' lines where VALUE part is - * sq-quoted, and append <KEY, VALUE> at the end of the string list - */ -static int parse_key_value_squoted(char *buf, struct string_list *list) -{ - while (*buf) { - struct string_list_item *item; - char *np; - char *cp = strchr(buf, '='); - if (!cp) - return -1; - np = strchrnul(cp, '\n'); - *cp++ = '\0'; - item = string_list_append(list, buf); - - buf = np + (*np == '\n'); - *np = '\0'; - cp = sq_dequote(cp); - if (!cp) - return -1; - item->util = xstrdup(cp); - } - return 0; -} - -/** * Reads and parses the state directory's "author-script" file, and sets * state->author_name, state->author_email and state->author_date accordingly. * Returns 0 on success, -1 if the file could not be parsed. @@ -301,42 +265,16 @@ static int parse_key_value_squoted(char *buf, struct string_list *list) * script, and thus if the file differs from what this function expects, it is * better to bail out than to do something that the user does not expect. */ -static int read_author_script(struct am_state *state) +static int read_am_author_script(struct am_state *state) { const char *filename = am_path(state, "author-script"); - struct strbuf buf = STRBUF_INIT; - struct string_list kv = STRING_LIST_INIT_DUP; - int retval = -1; /* assume failure */ - int fd; assert(!state->author_name); assert(!state->author_email); assert(!state->author_date); - fd = open(filename, O_RDONLY); - if (fd < 0) { - if (errno == ENOENT) - return 0; - die_errno(_("could not open '%s' for reading"), filename); - } - strbuf_read(&buf, fd, 0); - close(fd); - if (parse_key_value_squoted(buf.buf, &kv)) - goto finish; - - if (kv.nr != 3 || - strcmp(kv.items[0].string, "GIT_AUTHOR_NAME") || - strcmp(kv.items[1].string, "GIT_AUTHOR_EMAIL") || - strcmp(kv.items[2].string, "GIT_AUTHOR_DATE")) - goto finish; - state->author_name = kv.items[0].util; - state->author_email = kv.items[1].util; - state->author_date = kv.items[2].util; - retval = 0; -finish: - string_list_clear(&kv, !!retval); - strbuf_release(&buf); - return retval; + return read_author_script(filename, &state->author_name, + &state->author_email, &state->author_date, 1); } /** @@ -403,14 +341,14 @@ static void am_load(struct am_state *state) struct strbuf sb = STRBUF_INIT; if (read_state_file(&sb, state, "next", 1) < 0) - die("BUG: state file 'next' does not exist"); + BUG("state file 'next' does not exist"); state->cur = strtol(sb.buf, NULL, 10); if (read_state_file(&sb, state, "last", 1) < 0) - die("BUG: state file 'last' does not exist"); + BUG("state file 'last' does not exist"); state->last = strtol(sb.buf, NULL, 10); - if (read_author_script(state) < 0) + if (read_am_author_script(state) < 0) die(_("could not parse author script")); read_commit_msg(state); @@ -519,6 +457,7 @@ static int run_post_rewrite_hook(const struct am_state *state) cp.in = xopen(am_path(state, "rewritten"), O_RDONLY); cp.stdout_to_stderr = 1; + cp.trace2_hook_name = "post-rewrite"; ret = run_command(&cp); @@ -551,23 +490,24 @@ static int copy_notes_for_rebase(const struct am_state *state) while (!strbuf_getline_lf(&sb, fp)) { struct object_id from_obj, to_obj; + const char *p; - if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) { + if (sb.len != the_hash_algo->hexsz * 2 + 1) { ret = error(invalid_line, sb.buf); goto finish; } - if (get_oid_hex(sb.buf, &from_obj)) { + if (parse_oid_hex(sb.buf, &from_obj, &p)) { ret = error(invalid_line, sb.buf); goto finish; } - if (sb.buf[GIT_SHA1_HEXSZ] != ' ') { + if (*p != ' ') { ret = error(invalid_line, sb.buf); goto finish; } - if (get_oid_hex(sb.buf + GIT_SHA1_HEXSZ + 1, &to_obj)) { + if (get_oid_hex(p + 1, &to_obj)) { ret = error(invalid_line, sb.buf); goto finish; } @@ -578,7 +518,7 @@ static int copy_notes_for_rebase(const struct am_state *state) } finish: - finish_copy_notes_for_rewrite(c, msg); + finish_copy_notes_for_rewrite(the_repository, c, msg); fclose(fp); strbuf_release(&sb); return ret; @@ -986,7 +926,7 @@ static int split_mail(struct am_state *state, enum patch_format patch_format, case PATCH_FORMAT_MBOXRD: return split_mail_mbox(state, paths, keep_cr, 1); default: - die("BUG: invalid patch_format"); + BUG("invalid patch_format"); } return -1; } @@ -1041,7 +981,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, str = "b"; break; default: - die("BUG: invalid value for state->keep"); + BUG("invalid value for state->keep"); } write_state_text(state, "keep", str); @@ -1058,7 +998,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, str = "t"; break; default: - die("BUG: invalid value for state->scissors"); + BUG("invalid value for state->scissors"); } write_state_text(state, "scissors", str); @@ -1136,19 +1076,6 @@ static const char *msgnum(const struct am_state *state) } /** - * Refresh and write index. - */ -static void refresh_and_write_cache(void) -{ - struct lock_file lock_file = LOCK_INIT; - - hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); - refresh_cache(REFRESH_QUIET); - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) - die(_("unable to write index file")); -} - -/** * Dies with a user-friendly message on how to proceed after resolving the * problem. This message can be overridden with state->resolvemsg. */ @@ -1216,7 +1143,7 @@ static int parse_mail(struct am_state *state, const char *mail) mi.keep_non_patch_brackets_in_subject = 1; break; default: - die("BUG: invalid value for state->keep"); + BUG("invalid value for state->keep"); } if (state->message_id) @@ -1232,7 +1159,7 @@ static int parse_mail(struct am_state *state, const char *mail) mi.use_scissors = 1; break; default: - die("BUG: invalid value for state->scissors"); + BUG("invalid value for state->scissors"); } mi.input = xfopen(mail, "r"); @@ -1243,6 +1170,10 @@ static int parse_mail(struct am_state *state, const char *mail) fclose(mi.input); fclose(mi.output); + if (mi.format_flowed) + warning(_("Patch sent with format=flowed; " + "space at the end of lines might be lost.")); + /* Extract message and author information */ fp = xfopen(am_path(state, "info"), "r"); while (!strbuf_getline_lf(&sb, fp)) { @@ -1267,7 +1198,7 @@ static int parse_mail(struct am_state *state, const char *mail) goto finish; } - if (is_empty_file(am_path(state, "patch"))) { + if (is_empty_or_missing_file(am_path(state, "patch"))) { printf_ln(_("Patch is empty.")); die_user_resolve(state); } @@ -1332,7 +1263,9 @@ static void get_commit_info(struct am_state *state, struct commit *commit) buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding()); ident_line = find_commit_header(buffer, "author", &ident_len); - + if (!ident_line) + die(_("missing author line in commit %s"), + oid_to_hex(&commit->object.oid)); if (split_ident_line(&id, ident_line, ident_len) < 0) die(_("invalid ident line: %.*s"), (int)ident_len, ident_line); @@ -1371,7 +1304,7 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm FILE *fp; fp = xfopen(am_path(state, "patch"), "w"); - init_revisions(&rev_info, NULL); + repo_init_revisions(the_repository, &rev_info, NULL); rev_info.diff = 1; rev_info.abbrev = 0; rev_info.disable_stdin = 1; @@ -1399,13 +1332,15 @@ static void write_index_patch(const struct am_state *state) struct rev_info rev_info; FILE *fp; - if (!get_oid_tree("HEAD", &head)) - tree = lookup_tree(&head); - else - tree = lookup_tree(the_hash_algo->empty_tree); + if (!get_oid("HEAD", &head)) { + struct commit *commit = lookup_commit_or_die(&head, "HEAD"); + tree = get_commit_tree(commit); + } else + tree = lookup_tree(the_repository, + the_repository->hash_algo->empty_tree); fp = xfopen(am_path(state, "patch"), "w"); - init_revisions(&rev_info, NULL); + repo_init_revisions(the_repository, &rev_info, NULL); rev_info.diff = 1; rev_info.disable_stdin = 1; rev_info.no_commit_id = 1; @@ -1462,8 +1397,8 @@ static int run_apply(const struct am_state *state, const char *index_file) int force_apply = 0; int options = 0; - if (init_apply_state(&apply_state, NULL)) - die("BUG: init_apply_state() failed"); + if (init_apply_state(&apply_state, the_repository, NULL)) + BUG("init_apply_state() failed"); argv_array_push(&apply_opts, "apply"); argv_array_pushv(&apply_opts, state->git_apply_opts.argv); @@ -1489,7 +1424,7 @@ static int run_apply(const struct am_state *state, const char *index_file) apply_state.apply_verbosity = verbosity_silent; if (check_apply_state(&apply_state, force_apply)) - die("BUG: check_apply_state() failed"); + BUG("check_apply_state() failed"); argv_array_push(&apply_paths, am_path(state, "patch")); @@ -1542,7 +1477,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa char *their_tree_name; if (get_oid("HEAD", &our_tree) < 0) - hashcpy(our_tree.hash, EMPTY_TREE_SHA1_BIN); + oidcpy(&our_tree, the_hash_algo->empty_tree); if (build_fake_ancestor(state, index_path)) return error("could not build fake ancestor"); @@ -1561,11 +1496,11 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa * review them with extra care to spot mismerges. */ struct rev_info rev_info; - const char *diff_filter_str = "--diff-filter=AM"; - init_revisions(&rev_info, NULL); + repo_init_revisions(the_repository, &rev_info, NULL); rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS; - diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1, rev_info.prefix); + rev_info.diffopt.filter |= diff_filter_bit('A'); + rev_info.diffopt.filter |= diff_filter_bit('M'); add_pending_oid(&rev_info, "HEAD", &our_tree, 0); diff_setup_done(&rev_info.diffopt); run_diff_index(&rev_info, 1); @@ -1591,17 +1526,18 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa * changes. */ - init_merge_options(&o); + init_merge_options(&o, the_repository); o.branch1 = "HEAD"; their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg); o.branch2 = their_tree_name; + o.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE; if (state->quiet) o.verbosity = 0; if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) { - rerere(state->allow_rerere_autoupdate); + repo_rerere(the_repository, state->allow_rerere_autoupdate); free(their_tree_name); return error(_("Failed to merge in the changes.")); } @@ -1631,13 +1567,15 @@ static void do_commit(const struct am_state *state) if (!get_oid_commit("HEAD", &parent)) { old_oid = &parent; - commit_list_insert(lookup_commit(&parent), &parents); + commit_list_insert(lookup_commit(the_repository, &parent), + &parents); } else { old_oid = NULL; say(state, stderr, _("applying to an empty history")); } author = fmt_ident(state->author_name, state->author_email, + WANT_AUTHOR_IDENT, state->ignore_date ? NULL : state->author_date, IDENT_STRICT); @@ -1699,11 +1637,8 @@ static int do_interactive(struct am_state *state) { assert(state->msg); - if (!isatty(0)) - die(_("cannot be interactive without stdin connected to a terminal.")); - for (;;) { - const char *reply; + char reply[64]; puts(_("Commit Body is:")); puts("--------------------------"); @@ -1715,11 +1650,11 @@ static int do_interactive(struct am_state *state) * in your translation. The program will only accept English * input at this point. */ - reply = git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "), PROMPT_ECHO); + printf(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: ")); + if (!fgets(reply, sizeof(reply), stdin)) + die("unable to read from stdin; aborting"); - if (!reply) { - continue; - } else if (*reply == 'y' || *reply == 'Y') { + if (*reply == 'y' || *reply == 'Y') { return 0; } else if (*reply == 'a' || *reply == 'A') { state->interactive = 0; @@ -1756,14 +1691,14 @@ static int do_interactive(struct am_state *state) */ static void am_run(struct am_state *state, int resume) { - const char *argv_gc_auto[] = {"gc", "--auto", NULL}; struct strbuf sb = STRBUF_INIT; unlink(am_path(state, "dirtyindex")); - refresh_and_write_cache(); + if (refresh_and_write_cache(REFRESH_QUIET, 0, 0) < 0) + die(_("unable to write index file")); - if (index_has_changes(&sb)) { + if (repo_index_has_changes(the_repository, NULL, &sb)) { write_state_bool(state, "dirtyindex", 1); die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf); } @@ -1820,22 +1755,19 @@ static void am_run(struct am_state *state, int resume) * Applying the patch to an earlier tree and merging * the result may have produced the same tree as ours. */ - if (!apply_status && !index_has_changes(NULL)) { + if (!apply_status && + !repo_index_has_changes(the_repository, NULL, NULL)) { say(state, stdout, _("No changes -- Patch already applied.")); goto next; } } if (apply_status) { - int advice_amworkdir = 1; - printf_ln(_("Patch failed at %s %.*s"), msgnum(state), linelen(state->msg), state->msg); - git_config_get_bool("advice.amworkdir", &advice_amworkdir); - if (advice_amworkdir) - printf_ln(_("Use 'git am --show-current-patch' to see the failed patch")); + advise(_("Use 'git am --show-current-patch=diff' to see the failed patch")); die_user_resolve(state); } @@ -1850,7 +1782,7 @@ next: resume = 0; } - if (!is_empty_file(am_path(state, "rewritten"))) { + if (!is_empty_or_missing_file(am_path(state, "rewritten"))) { assert(state->rebasing); copy_notes_for_rebase(state); run_post_rewrite_hook(state); @@ -1862,8 +1794,8 @@ next: */ if (!state->rebasing) { am_destroy(state); - close_all_packs(the_repository->objects); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + close_object_store(the_repository->objects); + run_auto_gc(state->quiet); } } @@ -1878,7 +1810,7 @@ static void am_resolve(struct am_state *state) say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); - if (!index_has_changes(NULL)) { + if (!repo_index_has_changes(the_repository, NULL, NULL)) { printf_ln(_("No changes - did you forget to use 'git add'?\n" "If there is nothing left to stage, chances are that something else\n" "already introduced the same changes; you might want to skip this patch.")); @@ -1898,7 +1830,7 @@ static void am_resolve(struct am_state *state) goto next; } - rerere(0); + repo_rerere(the_repository, 0); do_commit(state); @@ -2017,7 +1949,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem if (merge_tree(remote_tree)) return -1; - remove_branch_state(); + remove_branch_state(the_repository, 0); return 0; } @@ -2028,7 +1960,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem static void am_rerere_clear(void) { struct string_list merge_rr = STRING_LIST_INIT_DUP; - rerere_clear(&merge_rr); + rerere_clear(the_repository, &merge_rr); string_list_clear(&merge_rr, 1); } @@ -2042,11 +1974,20 @@ static void am_skip(struct am_state *state) am_rerere_clear(); if (get_oid("HEAD", &head)) - hashcpy(head.hash, EMPTY_TREE_SHA1_BIN); + oidcpy(&head, the_hash_algo->empty_tree); if (clean_index(&head, &head)) die(_("failed to clean index")); + if (state->rebasing) { + FILE *fp = xfopen(am_path(state, "rewritten"), "a"); + + assert(!is_null_oid(&state->orig_commit)); + fprintf(fp, "%s ", oid_to_hex(&state->orig_commit)); + fprintf(fp, "%s\n", oid_to_hex(&head)); + fclose(fp); + } + am_next(state); am_load(state); am_run(state, 0); @@ -2077,7 +2018,7 @@ static int safe_to_abort(const struct am_state *state) if (get_oid("HEAD", &head)) oidclr(&head); - if (!oidcmp(&head, &abort_safety)) + if (oideq(&head, &abort_safety)) return 1; warning(_("You seem to have moved HEAD since the last 'am' failure.\n" @@ -2105,11 +2046,11 @@ static void am_abort(struct am_state *state) curr_branch = resolve_refdup("HEAD", 0, &curr_head, NULL); has_curr_head = curr_branch && !is_null_oid(&curr_head); if (!has_curr_head) - hashcpy(curr_head.hash, EMPTY_TREE_SHA1_BIN); + oidcpy(&curr_head, the_hash_algo->empty_tree); has_orig_head = !get_oid("ORIG_HEAD", &orig_head); if (!has_orig_head) - hashcpy(orig_head.hash, EMPTY_TREE_SHA1_BIN); + oidcpy(&orig_head, the_hash_algo->empty_tree); clean_index(&curr_head, &orig_head); @@ -2124,7 +2065,7 @@ static void am_abort(struct am_state *state) am_destroy(state); } -static int show_patch(struct am_state *state) +static int show_patch(struct am_state *state, enum show_patch_type sub_mode) { struct strbuf sb = STRBUF_INIT; const char *patch_path; @@ -2141,7 +2082,17 @@ static int show_patch(struct am_state *state) return ret; } - patch_path = am_path(state, msgnum(state)); + switch (sub_mode) { + case SHOW_PATCH_RAW: + patch_path = am_path(state, msgnum(state)); + break; + case SHOW_PATCH_DIFF: + patch_path = am_path(state, "patch"); + break; + default: + BUG("invalid mode for --show-current-patch"); + } + len = strbuf_read_file(&sb, patch_path, 0); if (len < 0) die_errno(_("failed to read '%s'"), patch_path); @@ -2160,7 +2111,9 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int { int *opt_value = opt->value; - if (!strcmp(arg, "mbox")) + if (unset) + *opt_value = PATCH_FORMAT_UNKNOWN; + else if (!strcmp(arg, "mbox")) *opt_value = PATCH_FORMAT_MBOX; else if (!strcmp(arg, "stgit")) *opt_value = PATCH_FORMAT_STGIT; @@ -2170,12 +2123,16 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int *opt_value = PATCH_FORMAT_HG; else if (!strcmp(arg, "mboxrd")) *opt_value = PATCH_FORMAT_MBOXRD; + /* + * Please update $__git_patchformat in git-completion.bash + * when you add new options + */ else return error(_("Invalid value for --patch-format: %s"), arg); return 0; } -enum resume_mode { +enum resume_type { RESUME_FALSE = 0, RESUME_APPLY, RESUME_RESOLVED, @@ -2185,6 +2142,45 @@ enum resume_mode { RESUME_SHOW_PATCH }; +struct resume_mode { + enum resume_type mode; + enum show_patch_type sub_mode; +}; + +static int parse_opt_show_current_patch(const struct option *opt, const char *arg, int unset) +{ + int *opt_value = opt->value; + struct resume_mode *resume = container_of(opt_value, struct resume_mode, mode); + + /* + * Please update $__git_showcurrentpatch in git-completion.bash + * when you add new options + */ + const char *valid_modes[] = { + [SHOW_PATCH_DIFF] = "diff", + [SHOW_PATCH_RAW] = "raw" + }; + int new_value = SHOW_PATCH_RAW; + + if (arg) { + for (new_value = 0; new_value < ARRAY_SIZE(valid_modes); new_value++) { + if (!strcmp(arg, valid_modes[new_value])) + break; + } + if (new_value >= ARRAY_SIZE(valid_modes)) + return error(_("Invalid value for --show-current-patch: %s"), arg); + } + + if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode) + return error(_("--show-current-patch=%s is incompatible with " + "--show-current-patch=%s"), + arg, valid_modes[resume->sub_mode]); + + resume->mode = RESUME_SHOW_PATCH; + resume->sub_mode = new_value; + return 0; +} + static int git_am_config(const char *k, const char *v, void *cb) { int status; @@ -2202,7 +2198,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) int binary = -1; int keep_cr = -1; int patch_format = PATCH_FORMAT_UNKNOWN; - enum resume_mode resume = RESUME_FALSE; + struct resume_mode resume = { .mode = RESUME_FALSE }; int in_progress; int ret = 0; @@ -2231,12 +2227,12 @@ int cmd_am(int argc, const char **argv, const char *prefix) N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH), OPT_BOOL('m', "message-id", &state.message_id, N_("pass -m flag to git-mailinfo")), - { OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL, - N_("pass --keep-cr flag to git-mailsplit for mbox format"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1}, - { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL, - N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, + OPT_SET_INT_F(0, "keep-cr", &keep_cr, + N_("pass --keep-cr flag to git-mailsplit for mbox format"), + 1, PARSE_OPT_NONEG), + OPT_SET_INT_F(0, "no-keep-cr", &keep_cr, + N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"), + 0, PARSE_OPT_NONEG), OPT_BOOL('c', "scissors", &state.scissors, N_("strip everything before a scissors line")), OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"), @@ -2271,24 +2267,26 @@ int cmd_am(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG), OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL, N_("override error message when patch failure occurs")), - OPT_CMDMODE(0, "continue", &resume, + OPT_CMDMODE(0, "continue", &resume.mode, N_("continue applying patches after resolving a conflict"), RESUME_RESOLVED), - OPT_CMDMODE('r', "resolved", &resume, + OPT_CMDMODE('r', "resolved", &resume.mode, N_("synonyms for --continue"), RESUME_RESOLVED), - OPT_CMDMODE(0, "skip", &resume, + OPT_CMDMODE(0, "skip", &resume.mode, N_("skip the current patch"), RESUME_SKIP), - OPT_CMDMODE(0, "abort", &resume, + OPT_CMDMODE(0, "abort", &resume.mode, N_("restore the original branch and abort the patching operation."), RESUME_ABORT), - OPT_CMDMODE(0, "quit", &resume, + OPT_CMDMODE(0, "quit", &resume.mode, N_("abort the patching operation but keep HEAD where it is."), RESUME_QUIT), - OPT_CMDMODE(0, "show-current-patch", &resume, - N_("show the patch being applied."), - RESUME_SHOW_PATCH), + { OPTION_CALLBACK, 0, "show-current-patch", &resume.mode, + "(diff|raw)", + N_("show the patch being applied"), + PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + parse_opt_show_current_patch, RESUME_SHOW_PATCH }, OPT_BOOL(0, "committer-date-is-author-date", &state.committer_date_is_author_date, N_("lie about committer date")), @@ -2323,7 +2321,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) /* Ensure a valid committer ident can be constructed */ git_committer_info(IDENT_STRICT); - if (read_index_preload(&the_index, NULL) < 0) + if (repo_read_index_preload(the_repository, NULL, 0) < 0) die(_("failed to read the index")); if (in_progress) { @@ -2338,12 +2336,12 @@ int cmd_am(int argc, const char **argv, const char *prefix) * intend to feed us a patch but wanted to continue * unattended. */ - if (argc || (resume == RESUME_FALSE && !isatty(0))) + if (argc || (resume.mode == RESUME_FALSE && !isatty(0))) die(_("previous rebase directory %s still exists but mbox given."), state.dir); - if (resume == RESUME_FALSE) - resume = RESUME_APPLY; + if (resume.mode == RESUME_FALSE) + resume.mode = RESUME_APPLY; if (state.signoff == SIGNOFF_EXPLICIT) am_append_signoff(&state); @@ -2357,7 +2355,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) * stray directories. */ if (file_exists(state.dir) && !state.rebasing) { - if (resume == RESUME_ABORT || resume == RESUME_QUIT) { + if (resume.mode == RESUME_ABORT || resume.mode == RESUME_QUIT) { am_destroy(&state); am_state_release(&state); return 0; @@ -2368,7 +2366,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) state.dir); } - if (resume) + if (resume.mode) die(_("Resolve operation not in progress, we are not resuming.")); for (i = 0; i < argc; i++) { @@ -2378,12 +2376,15 @@ int cmd_am(int argc, const char **argv, const char *prefix) argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i])); } + if (state.interactive && !paths.argc) + die(_("interactive mode requires patches on the command line")); + am_setup(&state, patch_format, paths.argv, keep_cr); argv_array_clear(&paths); } - switch (resume) { + switch (resume.mode) { case RESUME_FALSE: am_run(&state, 0); break; @@ -2404,10 +2405,10 @@ int cmd_am(int argc, const char **argv, const char *prefix) am_destroy(&state); break; case RESUME_SHOW_PATCH: - ret = show_patch(&state); + ret = show_patch(&state, resume.sub_mode); break; default: - die("BUG: invalid resume value"); + BUG("invalid resume value"); } am_state_release(&state); diff --git a/builtin/apply.c b/builtin/apply.c index 48d3989331..3f099b9605 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -16,7 +16,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int ret; struct apply_state state; - if (init_apply_state(&state, prefix)) + if (init_apply_state(&state, the_repository, prefix)) exit(128); argc = apply_parse_options(argc, argv, diff --git a/builtin/archive.c b/builtin/archive.c index 73971d0dd2..45d11669aa 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -27,10 +27,10 @@ static int run_remote_archiver(int argc, const char **argv, const char *remote, const char *exec, const char *name_hint) { - char *buf; int fd[2], i, rv; struct transport *transport; struct remote *_remote; + struct packet_reader reader; _remote = remote_get(remote); if (!_remote->url[0]) @@ -53,18 +53,19 @@ static int run_remote_archiver(int argc, const char **argv, packet_write_fmt(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); - buf = packet_read_line(fd[0], NULL); - if (!buf) + packet_reader_init(&reader, fd[0], NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_DIE_ON_ERR_PACKET); + + if (packet_reader_read(&reader) != PACKET_READ_NORMAL) die(_("git archive: expected ACK/NAK, got a flush packet")); - if (strcmp(buf, "ACK")) { - if (starts_with(buf, "NACK ")) - die(_("git archive: NACK %s"), buf + 5); - if (starts_with(buf, "ERR ")) - die(_("remote error: %s"), buf + 4); + if (strcmp(reader.line, "ACK")) { + if (starts_with(reader.line, "NACK ")) + die(_("git archive: NACK %s"), reader.line + 5); die(_("git archive: protocol error")); } - if (packet_read_line(fd[0], NULL)) + if (packet_reader_read(&reader) != PACKET_READ_FLUSH) die(_("git archive: expected a flush")); /* Now, start reading from fd[0] and spit it out to stdout */ @@ -97,6 +98,8 @@ int cmd_archive(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, local_opts, NULL, PARSE_OPT_KEEP_ALL); + init_archivers(); + if (output) create_output_file(output); @@ -105,5 +108,5 @@ int cmd_archive(int argc, const char **argv, const char *prefix) setvbuf(stderr, NULL, _IOLBF, BUFSIZ); - return write_archive(argc, argv, prefix, output, 0); + return write_archive(argc, argv, prefix, the_repository, output, 0); } diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 4b5fadcbe1..c1c40b516d 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -3,18 +3,58 @@ #include "parse-options.h" #include "bisect.h" #include "refs.h" +#include "dir.h" +#include "argv-array.h" +#include "run-command.h" +#include "prompt.h" +#include "quote.h" static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV") static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK") +static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START") +static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD") +static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") +static GIT_PATH_FUNC(git_path_head_name, "head-name") +static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --next-all [--no-checkout]"), N_("git bisect--helper --write-terms <bad_term> <good_term>"), N_("git bisect--helper --bisect-clean-state"), + N_("git bisect--helper --bisect-reset [<commit>]"), + N_("git bisect--helper --bisect-write [--no-log] <state> <revision> <good_term> <bad_term>"), + N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"), + N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"), + N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), + N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]" + "[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"), NULL }; +struct bisect_terms { + char *term_good; + char *term_bad; +}; + +static void free_terms(struct bisect_terms *terms) +{ + FREE_AND_NULL(terms->term_good); + FREE_AND_NULL(terms->term_bad); +} + +static void set_terms(struct bisect_terms *terms, const char *bad, + const char *good) +{ + free((void *)terms->term_good); + terms->term_good = xstrdup(good); + free((void *)terms->term_bad); + terms->term_bad = xstrdup(bad); +} + +static const char vocab_bad[] = "bad|new"; +static const char vocab_good[] = "good|old"; + /* * Check whether the string `term` belongs to the set of strings * included in the variable arguments. @@ -106,15 +146,488 @@ static void check_expected_revs(const char **revs, int rev_nr) } } +static int bisect_reset(const char *commit) +{ + struct strbuf branch = STRBUF_INIT; + + if (!commit) { + if (strbuf_read_file(&branch, git_path_bisect_start(), 0) < 1) { + printf(_("We are not bisecting.\n")); + return 0; + } + strbuf_rtrim(&branch); + } else { + struct object_id oid; + + if (get_oid_commit(commit, &oid)) + return error(_("'%s' is not a valid commit"), commit); + strbuf_addstr(&branch, commit); + } + + if (!file_exists(git_path_bisect_head())) { + struct argv_array argv = ARGV_ARRAY_INIT; + + argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL); + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + error(_("could not check out original" + " HEAD '%s'. Try 'git bisect" + " reset <commit>'."), branch.buf); + strbuf_release(&branch); + argv_array_clear(&argv); + return -1; + } + argv_array_clear(&argv); + } + + strbuf_release(&branch); + return bisect_clean_state(); +} + +static void log_commit(FILE *fp, char *fmt, const char *state, + struct commit *commit) +{ + struct pretty_print_context pp = {0}; + struct strbuf commit_msg = STRBUF_INIT; + char *label = xstrfmt(fmt, state); + + format_commit_message(commit, "%s", &commit_msg, &pp); + + fprintf(fp, "# %s: [%s] %s\n", label, oid_to_hex(&commit->object.oid), + commit_msg.buf); + + strbuf_release(&commit_msg); + free(label); +} + +static int bisect_write(const char *state, const char *rev, + const struct bisect_terms *terms, int nolog) +{ + struct strbuf tag = STRBUF_INIT; + struct object_id oid; + struct commit *commit; + FILE *fp = NULL; + int res = 0; + + if (!strcmp(state, terms->term_bad)) { + strbuf_addf(&tag, "refs/bisect/%s", state); + } else if (one_of(state, terms->term_good, "skip", NULL)) { + strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev); + } else { + res = error(_("Bad bisect_write argument: %s"), state); + goto finish; + } + + if (get_oid(rev, &oid)) { + res = error(_("couldn't get the oid of the rev '%s'"), rev); + goto finish; + } + + if (update_ref(NULL, tag.buf, &oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR)) { + res = -1; + goto finish; + } + + fp = fopen(git_path_bisect_log(), "a"); + if (!fp) { + res = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log()); + goto finish; + } + + commit = lookup_commit_reference(the_repository, &oid); + log_commit(fp, "%s", state, commit); + + if (!nolog) + fprintf(fp, "git bisect %s %s\n", state, rev); + +finish: + if (fp) + fclose(fp); + strbuf_release(&tag); + return res; +} + +static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) +{ + int has_term_file = !is_empty_or_missing_file(git_path_bisect_terms()); + + if (one_of(cmd, "skip", "start", "terms", NULL)) + return 0; + + if (has_term_file && strcmp(cmd, terms->term_bad) && + strcmp(cmd, terms->term_good)) + return error(_("Invalid command: you're currently in a " + "%s/%s bisect"), terms->term_bad, + terms->term_good); + + if (!has_term_file) { + if (one_of(cmd, "bad", "good", NULL)) { + set_terms(terms, "bad", "good"); + return write_terms(terms->term_bad, terms->term_good); + } + if (one_of(cmd, "new", "old", NULL)) { + set_terms(terms, "new", "old"); + return write_terms(terms->term_bad, terms->term_good); + } + } + + return 0; +} + +static int mark_good(const char *refname, const struct object_id *oid, + int flag, void *cb_data) +{ + int *m_good = (int *)cb_data; + *m_good = 0; + return 1; +} + +static const char need_bad_and_good_revision_warning[] = + N_("You need to give me at least one %s and %s revision.\n" + "You can use \"git bisect %s\" and \"git bisect %s\" for that."); + +static const char need_bisect_start_warning[] = + N_("You need to start by \"git bisect start\".\n" + "You then need to give me at least one %s and %s revision.\n" + "You can use \"git bisect %s\" and \"git bisect %s\" for that."); + +static int decide_next(const struct bisect_terms *terms, + const char *current_term, int missing_good, + int missing_bad) +{ + if (!missing_good && !missing_bad) + return 0; + if (!current_term) + return -1; + + if (missing_good && !missing_bad && + !strcmp(current_term, terms->term_good)) { + char *yesno; + /* + * have bad (or new) but not good (or old). We could bisect + * although this is less optimum. + */ + warning(_("bisecting only with a %s commit"), terms->term_bad); + if (!isatty(0)) + return 0; + /* + * TRANSLATORS: Make sure to include [Y] and [n] in your + * translation. The program will only accept English input + * at this point. + */ + yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO); + if (starts_with(yesno, "N") || starts_with(yesno, "n")) + return -1; + return 0; + } + + if (!is_empty_or_missing_file(git_path_bisect_start())) + return error(_(need_bad_and_good_revision_warning), + vocab_bad, vocab_good, vocab_bad, vocab_good); + else + return error(_(need_bisect_start_warning), + vocab_good, vocab_bad, vocab_good, vocab_bad); +} + +static int bisect_next_check(const struct bisect_terms *terms, + const char *current_term) +{ + int missing_good = 1, missing_bad = 1; + char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); + char *good_glob = xstrfmt("%s-*", terms->term_good); + + if (ref_exists(bad_ref)) + missing_bad = 0; + + for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/", + (void *) &missing_good); + + free(good_glob); + free(bad_ref); + + return decide_next(terms, current_term, missing_good, missing_bad); +} + +static int get_terms(struct bisect_terms *terms) +{ + struct strbuf str = STRBUF_INIT; + FILE *fp = NULL; + int res = 0; + + fp = fopen(git_path_bisect_terms(), "r"); + if (!fp) { + res = -1; + goto finish; + } + + free_terms(terms); + strbuf_getline_lf(&str, fp); + terms->term_bad = strbuf_detach(&str, NULL); + strbuf_getline_lf(&str, fp); + terms->term_good = strbuf_detach(&str, NULL); + +finish: + if (fp) + fclose(fp); + strbuf_release(&str); + return res; +} + +static int bisect_terms(struct bisect_terms *terms, const char *option) +{ + if (get_terms(terms)) + return error(_("no terms defined")); + + if (option == NULL) { + printf(_("Your current terms are %s for the old state\n" + "and %s for the new state.\n"), + terms->term_good, terms->term_bad); + return 0; + } + if (one_of(option, "--term-good", "--term-old", NULL)) + printf("%s\n", terms->term_good); + else if (one_of(option, "--term-bad", "--term-new", NULL)) + printf("%s\n", terms->term_bad); + else + return error(_("invalid argument %s for 'git bisect terms'.\n" + "Supported options are: " + "--term-good|--term-old and " + "--term-bad|--term-new."), option); + + return 0; +} + +static int bisect_append_log_quoted(const char **argv) +{ + int res = 0; + FILE *fp = fopen(git_path_bisect_log(), "a"); + struct strbuf orig_args = STRBUF_INIT; + + if (!fp) + return -1; + + if (fprintf(fp, "git bisect start") < 1) { + res = -1; + goto finish; + } + + sq_quote_argv(&orig_args, argv); + if (fprintf(fp, "%s\n", orig_args.buf) < 1) + res = -1; + +finish: + fclose(fp); + strbuf_release(&orig_args); + return res; +} + +static int bisect_start(struct bisect_terms *terms, int no_checkout, + const char **argv, int argc) +{ + int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0; + int flags, pathspec_pos, res = 0; + struct string_list revs = STRING_LIST_INIT_DUP; + struct string_list states = STRING_LIST_INIT_DUP; + struct strbuf start_head = STRBUF_INIT; + struct strbuf bisect_names = STRBUF_INIT; + struct object_id head_oid; + struct object_id oid; + const char *head; + + if (is_bare_repository()) + no_checkout = 1; + + /* + * Check for one bad and then some good revisions + */ + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--")) { + has_double_dash = 1; + break; + } + } + + for (i = 0; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(argv[i], "--")) { + break; + } else if (!strcmp(arg, "--no-checkout")) { + no_checkout = 1; + } else if (!strcmp(arg, "--term-good") || + !strcmp(arg, "--term-old")) { + must_write_terms = 1; + free((void *) terms->term_good); + terms->term_good = xstrdup(argv[++i]); + } else if (skip_prefix(arg, "--term-good=", &arg) || + skip_prefix(arg, "--term-old=", &arg)) { + must_write_terms = 1; + free((void *) terms->term_good); + terms->term_good = xstrdup(arg); + } else if (!strcmp(arg, "--term-bad") || + !strcmp(arg, "--term-new")) { + must_write_terms = 1; + free((void *) terms->term_bad); + terms->term_bad = xstrdup(argv[++i]); + } else if (skip_prefix(arg, "--term-bad=", &arg) || + skip_prefix(arg, "--term-new=", &arg)) { + must_write_terms = 1; + free((void *) terms->term_bad); + terms->term_bad = xstrdup(arg); + } else if (starts_with(arg, "--") && + !one_of(arg, "--term-good", "--term-bad", NULL)) { + return error(_("unrecognized option: '%s'"), arg); + } else { + char *commit_id = xstrfmt("%s^{commit}", arg); + if (get_oid(commit_id, &oid) && has_double_dash) + die(_("'%s' does not appear to be a valid " + "revision"), arg); + + string_list_append(&revs, oid_to_hex(&oid)); + free(commit_id); + } + } + pathspec_pos = i; + + /* + * The user ran "git bisect start <sha1> <sha1>", hence did not + * explicitly specify the terms, but we are already starting to + * set references named with the default terms, and won't be able + * to change afterwards. + */ + if (revs.nr) + must_write_terms = 1; + for (i = 0; i < revs.nr; i++) { + if (bad_seen) { + string_list_append(&states, terms->term_good); + } else { + bad_seen = 1; + string_list_append(&states, terms->term_bad); + } + } + + /* + * Verify HEAD + */ + head = resolve_ref_unsafe("HEAD", 0, &head_oid, &flags); + if (!head) + if (get_oid("HEAD", &head_oid)) + return error(_("bad HEAD - I need a HEAD")); + + /* + * Check if we are bisecting + */ + if (!is_empty_or_missing_file(git_path_bisect_start())) { + /* Reset to the rev from where we started */ + strbuf_read_file(&start_head, git_path_bisect_start(), 0); + strbuf_trim(&start_head); + if (!no_checkout) { + struct argv_array argv = ARGV_ARRAY_INIT; + + argv_array_pushl(&argv, "checkout", start_head.buf, + "--", NULL); + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + res = error(_("checking out '%s' failed." + " Try 'git bisect start " + "<valid-branch>'."), + start_head.buf); + goto finish; + } + } + } else { + /* Get the rev from where we start. */ + if (!get_oid(head, &head_oid) && + !starts_with(head, "refs/heads/")) { + strbuf_reset(&start_head); + strbuf_addstr(&start_head, oid_to_hex(&head_oid)); + } else if (!get_oid(head, &head_oid) && + skip_prefix(head, "refs/heads/", &head)) { + /* + * This error message should only be triggered by + * cogito usage, and cogito users should understand + * it relates to cg-seek. + */ + if (!is_empty_or_missing_file(git_path_head_name())) + return error(_("won't bisect on cg-seek'ed tree")); + strbuf_addstr(&start_head, head); + } else { + return error(_("bad HEAD - strange symbolic ref")); + } + } + + /* + * Get rid of any old bisect state. + */ + if (bisect_clean_state()) + return -1; + + /* + * In case of mistaken revs or checkout error, or signals received, + * "bisect_auto_next" below may exit or misbehave. + * We have to trap this to be able to clean up using + * "bisect_clean_state". + */ + + /* + * Write new start state + */ + write_file(git_path_bisect_start(), "%s\n", start_head.buf); + + if (no_checkout) { + if (get_oid(start_head.buf, &oid) < 0) { + res = error(_("invalid ref: '%s'"), start_head.buf); + goto finish; + } + if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR)) { + res = -1; + goto finish; + } + } + + if (pathspec_pos < argc - 1) + sq_quote_argv(&bisect_names, argv + pathspec_pos); + write_file(git_path_bisect_names(), "%s\n", bisect_names.buf); + + for (i = 0; i < states.nr; i++) + if (bisect_write(states.items[i].string, + revs.items[i].string, terms, 1)) { + res = -1; + goto finish; + } + + if (must_write_terms && write_terms(terms->term_bad, + terms->term_good)) { + res = -1; + goto finish; + } + + res = bisect_append_log_quoted(argv); + if (res) + res = -1; + +finish: + string_list_clear(&revs, 0); + string_list_clear(&states, 0); + strbuf_release(&start_head); + strbuf_release(&bisect_names); + return res; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { NEXT_ALL = 1, WRITE_TERMS, BISECT_CLEAN_STATE, - CHECK_EXPECTED_REVS + CHECK_EXPECTED_REVS, + BISECT_RESET, + BISECT_WRITE, + CHECK_AND_SET_TERMS, + BISECT_NEXT_CHECK, + BISECT_TERMS, + BISECT_START } cmdmode = 0; - int no_checkout = 0; + int no_checkout = 0, res = 0, nolog = 0; struct option options[] = { OPT_CMDMODE(0, "next-all", &cmdmode, N_("perform 'git bisect next'"), NEXT_ALL), @@ -124,20 +637,37 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("cleanup the bisection state"), BISECT_CLEAN_STATE), OPT_CMDMODE(0, "check-expected-revs", &cmdmode, N_("check for expected revs"), CHECK_EXPECTED_REVS), + OPT_CMDMODE(0, "bisect-reset", &cmdmode, + N_("reset the bisection state"), BISECT_RESET), + OPT_CMDMODE(0, "bisect-write", &cmdmode, + N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE), + OPT_CMDMODE(0, "check-and-set-terms", &cmdmode, + N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS), + OPT_CMDMODE(0, "bisect-next-check", &cmdmode, + N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK), + OPT_CMDMODE(0, "bisect-terms", &cmdmode, + N_("print out the bisect terms"), BISECT_TERMS), + OPT_CMDMODE(0, "bisect-start", &cmdmode, + N_("start the bisect session"), BISECT_START), OPT_BOOL(0, "no-checkout", &no_checkout, N_("update BISECT_HEAD instead of checking out the current commit")), + OPT_BOOL(0, "no-log", &nolog, + N_("no log for BISECT_WRITE")), OPT_END() }; + struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL }; argc = parse_options(argc, argv, prefix, options, - git_bisect_helper_usage, 0); + git_bisect_helper_usage, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN); if (!cmdmode) usage_with_options(git_bisect_helper_usage, options); switch (cmdmode) { case NEXT_ALL: - return bisect_next_all(prefix, no_checkout); + res = bisect_next_all(the_repository, prefix, no_checkout); + break; case WRITE_TERMS: if (argc != 2) return error(_("--write-terms requires two arguments")); @@ -149,8 +679,48 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) case CHECK_EXPECTED_REVS: check_expected_revs(argv, argc); return 0; + case BISECT_RESET: + if (argc > 1) + return error(_("--bisect-reset requires either no argument or a commit")); + return !!bisect_reset(argc ? argv[0] : NULL); + case BISECT_WRITE: + if (argc != 4 && argc != 5) + return error(_("--bisect-write requires either 4 or 5 arguments")); + set_terms(&terms, argv[3], argv[2]); + res = bisect_write(argv[0], argv[1], &terms, nolog); + break; + case CHECK_AND_SET_TERMS: + if (argc != 3) + return error(_("--check-and-set-terms requires 3 arguments")); + set_terms(&terms, argv[2], argv[1]); + res = check_and_set_terms(&terms, argv[0]); + break; + case BISECT_NEXT_CHECK: + if (argc != 2 && argc != 3) + return error(_("--bisect-next-check requires 2 or 3 arguments")); + set_terms(&terms, argv[1], argv[0]); + res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL); + break; + case BISECT_TERMS: + if (argc > 1) + return error(_("--bisect-terms requires 0 or 1 argument")); + res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL); + break; + case BISECT_START: + set_terms(&terms, "bad", "good"); + res = bisect_start(&terms, no_checkout, argv, argc); + break; default: return error("BUG: unknown subcommand '%d'", cmdmode); } - return 0; + free_terms(&terms); + + /* + * Handle early success + * From check_merge_bases > check_good_are_ancestors_of_bad > bisect_next_all + */ + if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) + res = BISECT_OK; + + return abs(res); } diff --git a/builtin/blame.c b/builtin/blame.c index db38c0b307..94ef57c1cc 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -7,7 +7,9 @@ #include "cache.h" #include "config.h" +#include "color.h" #include "builtin.h" +#include "repository.h" #include "commit.h" #include "diff.h" #include "revision.h" @@ -22,7 +24,9 @@ #include "line-log.h" #include "dir.h" #include "progress.h" +#include "object-store.h" #include "blame.h" +#include "refs.h" static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); @@ -46,14 +50,19 @@ static int xdl_opts; static int abbrev = -1; static int no_whole_file_rename; static int show_progress; +static char repeated_meta_color[COLOR_MAXLEN]; +static int coloring_mode; +static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP; +static int mark_unblamable_lines; +static int mark_ignored_lines; static struct date_mode blame_date_mode = { DATE_ISO8601 }; static size_t blame_date_width; static struct string_list mailmap = STRING_LIST_INIT_NODUP; -#ifndef DEBUG -#define DEBUG 0 +#ifndef DEBUG_BLAME +#define DEBUG_BLAME 0 #endif static unsigned blame_move_score; @@ -310,16 +319,18 @@ static const char *format_time(timestamp_t time, const char *tz_str, return time_buf.buf; } -#define OUTPUT_ANNOTATE_COMPAT 001 -#define OUTPUT_LONG_OBJECT_NAME 002 -#define OUTPUT_RAW_TIMESTAMP 004 -#define OUTPUT_PORCELAIN 010 -#define OUTPUT_SHOW_NAME 020 -#define OUTPUT_SHOW_NUMBER 040 -#define OUTPUT_SHOW_SCORE 0100 -#define OUTPUT_NO_AUTHOR 0200 -#define OUTPUT_SHOW_EMAIL 0400 -#define OUTPUT_LINE_PORCELAIN 01000 +#define OUTPUT_ANNOTATE_COMPAT (1U<<0) +#define OUTPUT_LONG_OBJECT_NAME (1U<<1) +#define OUTPUT_RAW_TIMESTAMP (1U<<2) +#define OUTPUT_PORCELAIN (1U<<3) +#define OUTPUT_SHOW_NAME (1U<<4) +#define OUTPUT_SHOW_NUMBER (1U<<5) +#define OUTPUT_SHOW_SCORE (1U<<6) +#define OUTPUT_NO_AUTHOR (1U<<7) +#define OUTPUT_SHOW_EMAIL (1U<<8) +#define OUTPUT_LINE_PORCELAIN (1U<<9) +#define OUTPUT_COLOR_LINE (1U<<10) +#define OUTPUT_SHOW_AGE_WITH_COLOR (1U<<11) static void emit_porcelain_details(struct blame_origin *suspect, int repeat) { @@ -367,6 +378,64 @@ static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent, putchar('\n'); } +static struct color_field { + timestamp_t hop; + char col[COLOR_MAXLEN]; +} *colorfield; +static int colorfield_nr, colorfield_alloc; + +static void parse_color_fields(const char *s) +{ + struct string_list l = STRING_LIST_INIT_DUP; + struct string_list_item *item; + enum { EXPECT_DATE, EXPECT_COLOR } next = EXPECT_COLOR; + + colorfield_nr = 0; + + /* Ideally this would be stripped and split at the same time? */ + string_list_split(&l, s, ',', -1); + ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc); + + for_each_string_list_item(item, &l) { + switch (next) { + case EXPECT_DATE: + colorfield[colorfield_nr].hop = approxidate(item->string); + next = EXPECT_COLOR; + colorfield_nr++; + ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc); + break; + case EXPECT_COLOR: + if (color_parse(item->string, colorfield[colorfield_nr].col)) + die(_("expecting a color: %s"), item->string); + next = EXPECT_DATE; + break; + } + } + + if (next == EXPECT_COLOR) + die(_("must end with a color")); + + colorfield[colorfield_nr].hop = TIME_MAX; + string_list_clear(&l, 0); +} + +static void setup_default_color_by_age(void) +{ + parse_color_fields("blue,12 month ago,white,1 month ago,red"); +} + +static void determine_line_heat(struct blame_entry *ent, const char **dest_color) +{ + int i = 0; + struct commit_info ci; + get_commit_info(ent->suspect->commit, &ci, 1); + + while (i < colorfield_nr && ci.author_time > colorfield[i].hop) + i++; + + *dest_color = colorfield[i].col; +} + static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt) { int cnt; @@ -375,14 +444,34 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int struct commit_info ci; char hex[GIT_MAX_HEXSZ + 1]; int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); + const char *default_color = NULL, *color = NULL, *reset = NULL; get_commit_info(suspect->commit, &ci, 1); oid_to_hex_r(hex, &suspect->commit->object.oid); cp = blame_nth_line(sb, ent->lno); + + if (opt & OUTPUT_SHOW_AGE_WITH_COLOR) { + determine_line_heat(ent, &default_color); + color = default_color; + reset = GIT_COLOR_RESET; + } + for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? the_hash_algo->hexsz : abbrev; + + if (opt & OUTPUT_COLOR_LINE) { + if (cnt > 0) { + color = repeated_meta_color; + reset = GIT_COLOR_RESET; + } else { + color = default_color ? default_color : NULL; + reset = default_color ? GIT_COLOR_RESET : NULL; + } + } + if (color) + fputs(color, stdout); if (suspect->commit->object.flags & UNINTERESTING) { if (blank_boundary) @@ -393,6 +482,14 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int } } + if (mark_unblamable_lines && ent->unblamable) { + length--; + putchar('*'); + } + if (mark_ignored_lines && ent->ignored) { + length--; + putchar('?'); + } printf("%.*s", length, hex); if (opt & OUTPUT_ANNOTATE_COMPAT) { const char *name; @@ -433,6 +530,8 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int printf(" %*d) ", max_digits, ent->lno + 1 + cnt); } + if (reset) + fputs(reset, stdout); do { ch = *cp++; putchar(ch); @@ -457,7 +556,7 @@ static void output(struct blame_scoreboard *sb, int option) struct commit *commit = ent->suspect->commit; if (commit->object.flags & MORE_THAN_ONE_PATH) continue; - for (suspect = commit->util; suspect; suspect = suspect->next) { + for (suspect = get_blame_suspects(commit); suspect; suspect = suspect->next) { if (suspect->guilty && count++) { commit->object.flags |= MORE_THAN_ONE_PATH; break; @@ -490,7 +589,7 @@ static int read_ancestry(const char *graft_file) /* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft = read_graft_line(&buf); if (graft) - register_commit_graft(graft, 0); + register_commit_graft(the_repository, graft, 0); } fclose(fp); strbuf_release(&buf); @@ -607,6 +706,48 @@ static int git_blame_config(const char *var, const char *value, void *cb) parse_date_format(value, &blame_date_mode); return 0; } + if (!strcmp(var, "blame.ignorerevsfile")) { + const char *str; + int ret; + + ret = git_config_pathname(&str, var, value); + if (ret) + return ret; + string_list_insert(&ignore_revs_file_list, str); + return 0; + } + if (!strcmp(var, "blame.markunblamablelines")) { + mark_unblamable_lines = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "blame.markignoredlines")) { + mark_ignored_lines = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "color.blame.repeatedlines")) { + if (color_parse_mem(value, strlen(value), repeated_meta_color)) + warning(_("invalid color '%s' in color.blame.repeatedLines"), + value); + return 0; + } + if (!strcmp(var, "color.blame.highlightrecent")) { + parse_color_fields(value); + return 0; + } + + if (!strcmp(var, "blame.coloring")) { + if (!strcmp(value, "repeatedLines")) { + coloring_mode |= OUTPUT_COLOR_LINE; + } else if (!strcmp(value, "highlightRecent")) { + coloring_mode |= OUTPUT_SHOW_AGE_WITH_COLOR; + } else if (!strcmp(value, "none")) { + coloring_mode &= ~(OUTPUT_COLOR_LINE | + OUTPUT_SHOW_AGE_WITH_COLOR); + } else { + warning(_("invalid value for blame.coloring")); + return 0; + } + } if (git_diff_heuristic_config(var, value, cb) < 0) return -1; @@ -620,6 +761,8 @@ static int blame_copy_callback(const struct option *option, const char *arg, int { int *opt = option->value; + BUG_ON_OPT_NEG(unset); + /* * -C enables copy from removed files; * -C -C enables copy from existing files, but only @@ -642,6 +785,8 @@ static int blame_move_callback(const struct option *option, const char *arg, int { int *opt = option->value; + BUG_ON_OPT_NEG(unset); + *opt |= PICKAXE_BLAME_MOVE; if (arg) @@ -655,7 +800,28 @@ static int is_a_rev(const char *name) if (get_oid(name, &oid)) return 0; - return OBJ_NONE < oid_object_info(&oid, NULL); + return OBJ_NONE < oid_object_info(the_repository, &oid, NULL); +} + +static void build_ignorelist(struct blame_scoreboard *sb, + struct string_list *ignore_revs_file_list, + struct string_list *ignore_rev_list) +{ + struct string_list_item *i; + struct object_id oid; + + oidset_init(&sb->ignore_list, 0); + for_each_string_list_item(i, ignore_revs_file_list) { + if (!strcmp(i->string, "")) + oidset_clear(&sb->ignore_list); + else + oidset_parse_file(&sb->ignore_list, i->string); + } + for_each_string_list_item(i, ignore_rev_list) { + if (get_oid_committish(i->string, &oid)) + die(_("cannot find revision %s to ignore"), i->string); + oidset_insert(&sb->ignore_list, &oid); + } } int cmd_blame(int argc, const char **argv, const char *prefix) @@ -669,6 +835,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) struct progress_info pi = { NULL, 0 }; struct string_list range_list = STRING_LIST_INIT_NODUP; + struct string_list ignore_rev_list = STRING_LIST_INIT_NODUP; int output_option = 0, opt = 0; int show_stats = 0; const char *revs_file = NULL; @@ -690,19 +857,15 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE), - - /* - * The following two options are parsed by parse_revision_opt() - * and are only included here to get included in the "-h" - * output: - */ - { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, parse_opt_unknown_cb }, - + OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("Ignore <rev> when blaming")), + OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")), + OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE), + OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR), OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), - { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback }, - { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback }, + OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback), + OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback), OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")), OPT__ABBREV(&abbrev), OPT_END() @@ -713,9 +876,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) struct range_set ranges; unsigned int range_i; long anchor; + const int hexsz = the_hash_algo->hexsz; + setup_default_color_by_age(); git_config(git_blame_config, &output_option); - init_revisions(&revs, NULL); + repo_init_revisions(the_repository, &revs, NULL); revs.date_mode = blame_date_mode; revs.diffopt.flags.allow_textconv = 1; revs.diffopt.flags.follow_renames = 1; @@ -731,6 +896,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) case PARSE_OPT_HELP: case PARSE_OPT_ERROR: exit(129); + case PARSE_OPT_COMPLETE: + exit(0); case PARSE_OPT_DONE: if (ctx.argv[0]) dashdash_pos = ctx.cpidx; @@ -756,11 +923,11 @@ parse_done: } else if (show_progress < 0) show_progress = isatty(2); - if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ) + if (0 < abbrev && abbrev < hexsz) /* one more abbrev length is needed for the boundary commit */ abbrev++; else if (!abbrev) - abbrev = GIT_SHA1_HEXSZ; + abbrev = hexsz; if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); @@ -804,6 +971,10 @@ parse_done: */ blame_date_width = utf8_strwidth(_("4 years, 11 months ago")) + 1; /* add the null */ break; + case DATE_HUMAN: + /* If the year is shown, no time is shown */ + blame_date_width = sizeof("Thu Oct 19 16:00"); + break; case DATE_NORMAL: blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700"); break; @@ -868,12 +1039,36 @@ parse_done: revs.disable_stdin = 1; setup_revisions(argc, argv, &revs, NULL); + if (!revs.pending.nr && is_bare_repository()) { + struct commit *head_commit; + struct object_id head_oid; + + if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + &head_oid, NULL) || + !(head_commit = lookup_commit_reference_gently(revs.repo, + &head_oid, 1))) + die("no such ref: HEAD"); + + add_pending_object(&revs, &head_commit->object, "HEAD"); + } init_scoreboard(&sb); sb.revs = &revs; sb.contents_from = contents_from; sb.reverse = reverse; + sb.repo = the_repository; + build_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list); + 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) @@ -885,15 +1080,16 @@ parse_done: long bottom, top; if (parse_range_arg(range_list.items[range_i].string, nth_line_cb, &sb, lno, anchor, - &bottom, &top, sb.path)) + &bottom, &top, sb.path, + the_repository->index)) usage(blame_usage); - if (lno < top || ((lno || bottom) && lno < bottom)) + if ((!lno && (top || bottom)) || lno < bottom) die(Q_("file %s has only %lu line", "file %s has only %lu lines", lno), path, lno); if (bottom < 1) bottom = 1; - if (top < 1) + if (top < 1 || lno < top) top = lno; bottom--; range_set_append_unsafe(&ranges, bottom, top); @@ -922,7 +1118,7 @@ parse_done: if (blame_copy_score) sb.copy_score = blame_copy_score; - sb.debug = DEBUG; + sb.debug = DEBUG_BLAME; sb.on_sanity_fail = &sanity_check_on_fail; sb.show_root = show_root; @@ -949,8 +1145,19 @@ parse_done: blame_coalesce(&sb); - if (!(output_option & OUTPUT_PORCELAIN)) + if (!(output_option & (OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR))) + output_option |= coloring_mode; + + if (!(output_option & OUTPUT_PORCELAIN)) { find_alignment(&sb, &output_option); + if (!*repeated_meta_color && + (output_option & OUTPUT_COLOR_LINE)) + xsnprintf(repeated_meta_color, + sizeof(repeated_meta_color), + "%s", GIT_COLOR_CYAN); + } + if (output_option & OUTPUT_ANNOTATE_COMPAT) + output_option &= ~(OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR); output(&sb, output_option); free((void *)sb.final_buf); @@ -965,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/branch.c b/builtin/branch.c index 5bd2a0dd48..accb61b1aa 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -22,6 +22,8 @@ #include "wt-status.h" #include "ref-filter.h" #include "worktree.h" +#include "help.h" +#include "commit-reach.h" static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), @@ -45,6 +47,7 @@ static char branch_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* LOCAL */ GIT_COLOR_GREEN, /* CURRENT */ GIT_COLOR_BLUE, /* UPSTREAM */ + GIT_COLOR_CYAN, /* WORKTREE */ }; enum color_branch { BRANCH_COLOR_RESET = 0, @@ -52,32 +55,36 @@ enum color_branch { BRANCH_COLOR_REMOTE = 2, BRANCH_COLOR_LOCAL = 3, BRANCH_COLOR_CURRENT = 4, - BRANCH_COLOR_UPSTREAM = 5 + BRANCH_COLOR_UPSTREAM = 5, + BRANCH_COLOR_WORKTREE = 6 +}; + +static const char *color_branch_slots[] = { + [BRANCH_COLOR_RESET] = "reset", + [BRANCH_COLOR_PLAIN] = "plain", + [BRANCH_COLOR_REMOTE] = "remote", + [BRANCH_COLOR_LOCAL] = "local", + [BRANCH_COLOR_CURRENT] = "current", + [BRANCH_COLOR_UPSTREAM] = "upstream", + [BRANCH_COLOR_WORKTREE] = "worktree", }; static struct string_list output = STRING_LIST_INIT_DUP; static unsigned int colopts; -static int parse_branch_color_slot(const char *slot) -{ - if (!strcasecmp(slot, "plain")) - return BRANCH_COLOR_PLAIN; - if (!strcasecmp(slot, "reset")) - return BRANCH_COLOR_RESET; - if (!strcasecmp(slot, "remote")) - return BRANCH_COLOR_REMOTE; - if (!strcasecmp(slot, "local")) - return BRANCH_COLOR_LOCAL; - if (!strcasecmp(slot, "current")) - return BRANCH_COLOR_CURRENT; - if (!strcasecmp(slot, "upstream")) - return BRANCH_COLOR_UPSTREAM; - return -1; -} +define_list_config_array(color_branch_slots); static int git_branch_config(const char *var, const char *value, void *cb) { const char *slot_name; + struct ref_sorting **sorting_tail = (struct ref_sorting **)cb; + + if (!strcmp(var, "branch.sort")) { + if (!value) + return config_error_nonbool(var); + parse_ref_sorting(sorting_tail, value); + return 0; + } if (starts_with(var, "column.")) return git_column_config(var, value, "branch", &colopts); @@ -86,7 +93,7 @@ static int git_branch_config(const char *var, const char *value, void *cb) return 0; } if (skip_prefix(var, "color.branch.", &slot_name)) { - int slot = parse_branch_color_slot(slot_name); + int slot = LOOKUP_CONFIG(color_branch_slots, slot_name); if (slot < 0) return 0; if (!value) @@ -126,7 +133,8 @@ static int branch_merged(int kind, const char *name, (reference_name = reference_name_to_free = resolve_refdup(upstream, RESOLVE_REF_READING, &oid, NULL)) != NULL) - reference_rev = lookup_commit_reference(&oid); + reference_rev = lookup_commit_reference(the_repository, + &oid); } if (!reference_rev) reference_rev = head_rev; @@ -159,7 +167,7 @@ static int check_branch_commit(const char *branchname, const char *refname, const struct object_id *oid, struct commit *head_rev, int kinds, int force) { - struct commit *rev = lookup_commit_reference(oid); + struct commit *rev = lookup_commit_reference(the_repository, oid); if (!rev) { error(_("Couldn't look up commit object for '%s'"), refname); return -1; @@ -213,7 +221,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, } if (!force) { - head_rev = lookup_commit_reference(&head_oid); + head_rev = lookup_commit_reference(the_repository, &head_oid); if (!head_rev) die(_("Couldn't look up commit object for HEAD")); } @@ -337,9 +345,10 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r struct strbuf local = STRBUF_INIT; struct strbuf remote = STRBUF_INIT; - strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else) %s%%(end)", - branch_get_color(BRANCH_COLOR_CURRENT), - branch_get_color(BRANCH_COLOR_LOCAL)); + strbuf_addf(&local, "%%(if)%%(HEAD)%%(then)* %s%%(else)%%(if)%%(worktreepath)%%(then)+ %s%%(else) %s%%(end)%%(end)", + branch_get_color(BRANCH_COLOR_CURRENT), + branch_get_color(BRANCH_COLOR_WORKTREE), + branch_get_color(BRANCH_COLOR_LOCAL)); strbuf_addf(&remote, " %s", branch_get_color(BRANCH_COLOR_REMOTE)); @@ -358,9 +367,13 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r strbuf_addf(&local, " %s ", obname.buf); if (filter->verbose > 1) + { + strbuf_addf(&local, "%%(if:notequals=*)%%(HEAD)%%(then)%%(if)%%(worktreepath)%%(then)(%s%%(worktreepath)%s) %%(end)%%(end)", + branch_get_color(BRANCH_COLOR_WORKTREE), branch_get_color(BRANCH_COLOR_RESET)); strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)" "%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)", branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET)); + } else strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)"); @@ -391,7 +404,6 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin struct ref_array array; int maxwidth = 0; const char *remote_prefix = ""; - struct strbuf out = STRBUF_INIT; char *to_free = NULL; /* @@ -419,7 +431,10 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) { - format_ref_array_item(array.items[i], format, &out); + struct strbuf out = STRBUF_INIT; + struct strbuf err = STRBUF_INIT; + if (format_ref_array_item(array.items[i], format, &out, &err)) + die("%s", err.buf); if (column_active(colopts)) { assert(!filter->verbose && "--column and --verbose are incompatible"); /* format to a string_list to let print_columns() do its job */ @@ -428,6 +443,7 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin fwrite(out.buf, 1, out.len, stdout); putchar('\n'); } + strbuf_release(&err); strbuf_release(&out); } @@ -435,6 +451,21 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin free(to_free); } +static void print_current_branch_name(void) +{ + int flags; + const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags); + const char *shortname; + if (!refname) + die(_("could not resolve HEAD")); + else if (!(flags & REF_ISSYMREF)) + return; + else if (skip_prefix(refname, "refs/heads/", &shortname)) + puts(shortname); + else + die(_("HEAD (%s) points outside of refs/heads/"), refname); +} + static void reject_rebase_or_bisect_branch(const char *target) { struct worktree **worktrees = get_worktrees(0); @@ -497,7 +528,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) || !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) { - die("BUG: expected prefix missing for refs"); + BUG("expected prefix missing for refs"); } if (copy) @@ -573,6 +604,7 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, copy = 0, force = 0, list = 0; + int show_current = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; const char *new_upstream = NULL; @@ -589,10 +621,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT__QUIET(&quiet, N_("suppress informational messages")), OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"), BRANCH_TRACK_EXPLICIT), - { OPTION_SET_INT, 0, "set-upstream", &track, NULL, N_("do not use"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, BRANCH_TRACK_OVERRIDE }, + OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"), + BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN), OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")), - OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")), + OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("unset the upstream info")), OPT__COLOR(&branch_use_color, N_("use colored output")), OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"), FILTER_REFS_REMOTES), @@ -611,20 +643,18 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2), OPT_BIT('c', "copy", ©, N_("copy a branch and its reflog"), 1), OPT_BIT('C', NULL, ©, N_("copy a branch, even if target exists"), 2), - OPT_BOOL(0, "list", &list, N_("list branch names")), - OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")), + OPT_BOOL('l', "list", &list, N_("list branch names")), + OPT_BOOL(0, "show-current", &show_current, N_("show current branch name")), + OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")), OPT_BOOL(0, "edit-description", &edit_description, N_("edit the description for the branch")), OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE), OPT_MERGED(&filter, N_("print only branches that are merged")), OPT_NO_MERGED(&filter, N_("print only branches that are not merged")), OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), - OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), - N_("field name to sort on"), &parse_opt_ref_sorting), - { - OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), - N_("print only branches of the object"), 0, parse_opt_object_name - }, + OPT_REF_SORT(sorting_tail), + OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"), + N_("print only branches of the object"), parse_opt_object_name), OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT_END(), @@ -639,7 +669,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_branch_usage, options); - git_config(git_branch_config, NULL); + git_config(git_branch_config, sorting_tail); track = git_branch_track; @@ -654,14 +684,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); - if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0) + if (!delete && !rename && !copy && !edit_description && !new_upstream && + !show_current && !unset_upstream && argc == 0) list = 1; if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr || filter.no_commit) list = 1; - if (!!delete + !!rename + !!copy + !!new_upstream + + if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); @@ -689,6 +720,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!argc) die(_("branch name required")); return delete_branches(argc, argv, delete > 1, filter.kind, quiet); + } else if (show_current) { + print_current_branch_name(); + return 0; } else if (list) { /* git branch --local also shows HEAD when it is detached */ if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) @@ -698,18 +732,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix) * If no sorting parameter is given then we default to sorting * by 'refname'. This would give us an alphabetically sorted * array with the 'HEAD' ref at the beginning followed by - * local branches 'refs/heads/...' and finally remote-tacking + * local branches 'refs/heads/...' and finally remote-tracking * branches 'refs/remotes/...'. */ if (!sorting) sorting = ref_default_sorting(); - sorting->ignore_case = icase; + ref_sorting_icase_all(sorting, icase); print_ref_list(&filter, sorting, &format); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); return 0; - } - else if (edit_description) { + } else if (edit_description) { const char *branch_name; struct strbuf branch_ref = STRBUF_INIT; @@ -776,7 +809,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) * create_branch takes care of setting up the tracking * info and making sure new_upstream is correct */ - create_branch(branch->name, new_upstream, 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE); + create_branch(the_repository, branch->name, new_upstream, + 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE); } else if (unset_upstream) { struct branch *branch = branch_get(argv[0]); struct strbuf buf = STRBUF_INIT; @@ -801,18 +835,15 @@ int cmd_branch(int argc, const char **argv, const char *prefix) git_config_set_multivar(buf.buf, NULL, NULL, 1); strbuf_release(&buf); } else if (argc > 0 && argc <= 2) { - struct branch *branch = branch_get(argv[0]); - - if (!branch) - die(_("no such branch '%s'"), argv[0]); - if (filter.kind != FILTER_REFS_BRANCHES) - die(_("-a and -r options to 'git branch' do not make sense with a branch name")); + die(_("The -a, and -r, options to 'git branch' do not take a branch name.\n" + "Did you mean to use: -a|-r --list <pattern>?")); if (track == BRANCH_TRACK_OVERRIDE) die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.")); - create_branch(argv[0], (argc == 2) ? argv[1] : head, + create_branch(the_repository, + argv[0], (argc == 2) ? argv[1] : head, force, 0, reflog, quiet, track); } else diff --git a/builtin/bundle.c b/builtin/bundle.c index d0de59b94f..f049d27a14 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -1,4 +1,6 @@ #include "builtin.h" +#include "argv-array.h" +#include "parse-options.h" #include "cache.h" #include "bundle.h" @@ -9,59 +11,184 @@ * bundle supporting "fetch", "pull", and "ls-remote". */ -static const char builtin_bundle_usage[] = - "git bundle create <file> <git-rev-list args>\n" - " or: git bundle verify <file>\n" - " or: git bundle list-heads <file> [<refname>...]\n" - " or: git bundle unbundle <file> [<refname>...]"; +static const char * const builtin_bundle_usage[] = { + N_("git bundle create [<options>] <file> <git-rev-list args>"), + N_("git bundle verify [<options>] <file>"), + N_("git bundle list-heads <file> [<refname>...]"), + N_("git bundle unbundle <file> [<refname>...]"), + NULL +}; -int cmd_bundle(int argc, const char **argv, const char *prefix) -{ +static const char * const builtin_bundle_create_usage[] = { + N_("git bundle create [<options>] <file> <git-rev-list args>"), + NULL +}; + +static const char * const builtin_bundle_verify_usage[] = { + N_("git bundle verify [<options>] <file>"), + NULL +}; + +static const char * const builtin_bundle_list_heads_usage[] = { + N_("git bundle list-heads <file> [<refname>...]"), + NULL +}; + +static const char * const builtin_bundle_unbundle_usage[] = { + N_("git bundle unbundle <file> [<refname>...]"), + NULL +}; + +static int verbose; + +static int parse_options_cmd_bundle(int argc, + const char **argv, + const char* prefix, + const char * const usagestr[], + const struct option options[], + const char **bundle_file) { + int newargc; + newargc = parse_options(argc, argv, NULL, options, usagestr, + PARSE_OPT_STOP_AT_NON_OPTION); + if (argc < 1) + usage_with_options(usagestr, options); + *bundle_file = prefix_filename(prefix, argv[0]); + return newargc; +} + +static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { + int all_progress_implied = 0; + int progress = isatty(STDERR_FILENO); + struct argv_array pack_opts; + + struct option options[] = { + OPT_SET_INT('q', "quiet", &progress, + N_("do not show progress meter"), 0), + OPT_SET_INT(0, "progress", &progress, + N_("show progress meter"), 1), + OPT_SET_INT(0, "all-progress", &progress, + N_("show progress meter during object writing phase"), 2), + OPT_BOOL(0, "all-progress-implied", + &all_progress_implied, + N_("similar to --all-progress when progress meter is shown")), + OPT_END() + }; + const char* bundle_file; + + argc = parse_options_cmd_bundle(argc, argv, prefix, + builtin_bundle_create_usage, options, &bundle_file); + /* bundle internals use argv[1] as further parameters */ + + argv_array_init(&pack_opts); + if (progress == 0) + argv_array_push(&pack_opts, "--quiet"); + else if (progress == 1) + argv_array_push(&pack_opts, "--progress"); + else if (progress == 2) + argv_array_push(&pack_opts, "--all-progress"); + if (progress && all_progress_implied) + argv_array_push(&pack_opts, "--all-progress-implied"); + + if (!startup_info->have_repository) + die(_("Need a repository to create a bundle.")); + return !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts); +} + +static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) { struct bundle_header header; - const char *cmd, *bundle_file; int bundle_fd = -1; + int quiet = 0; - if (argc < 3) - usage(builtin_bundle_usage); + struct option options[] = { + OPT_BOOL('q', "quiet", &quiet, + N_("do not show bundle details")), + OPT_END() + }; + const char* bundle_file; - cmd = argv[1]; - bundle_file = prefix_filename(prefix, argv[2]); - argc -= 2; - argv += 2; + argc = parse_options_cmd_bundle(argc, argv, prefix, + builtin_bundle_verify_usage, options, &bundle_file); + /* bundle internals use argv[1] as further parameters */ memset(&header, 0, sizeof(header)); - if (strcmp(cmd, "create") && (bundle_fd = - read_bundle_header(bundle_file, &header)) < 0) + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) + return 1; + close(bundle_fd); + if (verify_bundle(the_repository, &header, !quiet)) return 1; + fprintf(stderr, _("%s is okay\n"), bundle_file); + return 0; +} - if (!strcmp(cmd, "verify")) { - close(bundle_fd); - if (argc != 1) { - usage(builtin_bundle_usage); - return 1; - } - if (verify_bundle(&header, 1)) - return 1; - fprintf(stderr, _("%s is okay\n"), bundle_file); - return 0; - } - if (!strcmp(cmd, "list-heads")) { - close(bundle_fd); - return !!list_bundle_refs(&header, argc, argv); +static int cmd_bundle_list_heads(int argc, const char **argv, const char *prefix) { + struct bundle_header header; + int bundle_fd = -1; + + struct option options[] = { + OPT_END() + }; + const char* bundle_file; + + argc = parse_options_cmd_bundle(argc, argv, prefix, + builtin_bundle_list_heads_usage, options, &bundle_file); + /* bundle internals use argv[1] as further parameters */ + + memset(&header, 0, sizeof(header)); + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) + return 1; + close(bundle_fd); + return !!list_bundle_refs(&header, argc, argv); +} + +static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) { + struct bundle_header header; + int bundle_fd = -1; + + struct option options[] = { + OPT_END() + }; + const char* bundle_file; + + argc = parse_options_cmd_bundle(argc, argv, prefix, + builtin_bundle_unbundle_usage, options, &bundle_file); + /* bundle internals use argv[1] as further parameters */ + + memset(&header, 0, sizeof(header)); + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) + return 1; + if (!startup_info->have_repository) + die(_("Need a repository to unbundle.")); + return !!unbundle(the_repository, &header, bundle_fd, 0) || + list_bundle_refs(&header, argc, argv); +} + +int cmd_bundle(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { + OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")), + OPT_END() + }; + int result; + + argc = parse_options(argc, argv, prefix, options, builtin_bundle_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + packet_trace_identity("bundle"); + + if (argc < 2) + usage_with_options(builtin_bundle_usage, options); + + else if (!strcmp(argv[0], "create")) + result = cmd_bundle_create(argc, argv, prefix); + else if (!strcmp(argv[0], "verify")) + result = cmd_bundle_verify(argc, argv, prefix); + else if (!strcmp(argv[0], "list-heads")) + result = cmd_bundle_list_heads(argc, argv, prefix); + else if (!strcmp(argv[0], "unbundle")) + result = cmd_bundle_unbundle(argc, argv, prefix); + else { + error(_("Unknown subcommand: %s"), argv[0]); + usage_with_options(builtin_bundle_usage, options); } - if (!strcmp(cmd, "create")) { - if (argc < 2) { - usage(builtin_bundle_usage); - return 1; - } - if (!startup_info->have_repository) - die(_("Need a repository to create a bundle.")); - return !!create_bundle(&header, bundle_file, argc, argv); - } else if (!strcmp(cmd, "unbundle")) { - if (!startup_info->have_repository) - die(_("Need a repository to unbundle.")); - return !!unbundle(&header, bundle_fd, 0) || - list_bundle_refs(&header, argc, argv); - } else - usage(builtin_bundle_usage); + return result ? 1 : 0; } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 2c46d257cd..ae18e20a7c 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -3,6 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "builtin.h" @@ -11,8 +12,10 @@ #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" struct batch_options { int enabled; @@ -20,6 +23,7 @@ struct batch_options { int print_contents; int buffer_output; int all_objects; + int unordered; int cmdmode; /* may be 'w' or 'c' for --filters or --textconv */ const char *format; }; @@ -38,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(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); @@ -48,6 +55,13 @@ static int filter_object(const char *path, unsigned mode, return 0; } +static int stream_blob(const struct object_id *oid) +{ + if (stream_blob_to_fd(1, oid, NULL, 0)) + die("unable to stream %s to stdout", oid_to_hex(oid)); + return 0; +} + static int cat_one_file(int opt, const char *exp_type, const char *obj_name, int unknown_type) { @@ -64,7 +78,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, if (unknown_type) flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE; - if (get_oid_with_context(obj_name, GET_OID_RECORD_PATH, + if (get_oid_with_context(the_repository, obj_name, + GET_OID_RECORD_PATH, &oid, &obj_context)) die("Not a valid object name %s", obj_name); @@ -77,7 +92,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, switch (opt) { case 't': oi.type_name = &sb; - if (oid_object_info_extended(&oid, &oi, flags) < 0) + if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0) die("git cat-file: could not get object info"); if (sb.len) { printf("%s\n", sb.buf); @@ -88,9 +103,9 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, case 's': oi.sizep = &size; - if (oid_object_info_extended(&oid, &oi, flags) < 0) + if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0) die("git cat-file: could not get object info"); - printf("%lu\n", size); + printf("%"PRIuMAX"\n", (uintmax_t)size); return 0; case 'e': @@ -111,12 +126,13 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, die("git cat-file --textconv %s: <object> must be <sha1:path>", obj_name); - if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size)) + if (textconv_object(the_repository, path, obj_context.mode, + &oid, 1, &buf, &size)) break; /* else fallthrough */ case 'p': - type = oid_object_info(&oid, NULL); + type = oid_object_info(the_repository, &oid, NULL); if (type < 0) die("Not a valid object name %s", obj_name); @@ -129,7 +145,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } if (type == OBJ_BLOB) - return stream_blob_to_fd(1, &oid, NULL, 0); + return stream_blob(&oid); buf = read_object_file(&oid, &type, &size); if (!buf) die("Cannot read object %s", obj_name); @@ -140,7 +156,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, case 0: if (type_from_string(exp_type) == OBJ_BLOB) { struct object_id blob_oid; - if (oid_object_info(&oid, NULL) == OBJ_TAG) { + if (oid_object_info(the_repository, &oid, NULL) == OBJ_TAG) { char *buffer = read_object_file(&oid, &type, &size); const char *target; @@ -151,8 +167,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } else oidcpy(&blob_oid, &oid); - if (oid_object_info(&blob_oid, NULL) == OBJ_BLOB) - return stream_blob_to_fd(1, &blob_oid, NULL, 0); + if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB) + return stream_blob(&blob_oid); /* * we attempted to dereference a tag to a blob * and failed; there may be new dereference @@ -160,7 +176,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, * fall-back to the usual case. */ } - buf = read_object_with_reference(&oid, exp_type, &size, NULL); + buf = read_object_with_reference(the_repository, + &oid, exp_type, &size, NULL); break; default: @@ -199,14 +216,14 @@ struct expand_data { /* * After a mark_query run, this object_info is set up to be - * passed to sha1_object_info_extended. It will point to the data + * passed to oid_object_info_extended. It will point to the data * elements above, so you can retrieve the response from there. */ struct object_info info; /* * This flag will be true if the requested batch format and options - * don't require us to call sha1_object_info, which can then be + * don't require us to call oid_object_info, which can then be * optimized out. */ unsigned skip_object_info : 1; @@ -235,7 +252,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, if (data->mark_query) data->info.sizep = &data->size; else - strbuf_addf(sb, "%lu", data->size); + strbuf_addf(sb, "%"PRIuMAX , (uintmax_t)data->size); } else if (is_atom("objectsize:disk", atom, len)) { if (data->mark_query) data->info.disk_sizep = &data->disk_size; @@ -248,7 +265,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, strbuf_addstr(sb, data->rest); } else if (is_atom("deltabase", atom, len)) { if (data->mark_query) - data->info.delta_base_sha1 = data->delta_base_oid.hash; + data->info.delta_base_oid = &data->delta_base_oid; else strbuf_addstr(sb, oid_to_hex(&data->delta_base_oid)); @@ -303,7 +320,8 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d oid_to_hex(oid), data->rest); } else if (opt->cmdmode == 'c') { enum object_type type; - if (!textconv_object(data->rest, 0100644, oid, + if (!textconv_object(the_repository, + data->rest, 0100644, oid, 1, &contents, &size)) contents = read_object_file(oid, &type, @@ -312,11 +330,12 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d die("could not convert '%s' %s", oid_to_hex(oid), data->rest); } else - die("BUG: invalid cmdmode: %c", opt->cmdmode); + BUG("invalid cmdmode: %c", opt->cmdmode); batch_write(opt, contents, size); free(contents); - } else if (stream_blob_to_fd(1, oid, NULL, 0) < 0) - die("unable to stream %s to stdout", oid_to_hex(oid)); + } else { + stream_blob(oid); + } } else { enum object_type type; @@ -336,13 +355,13 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d } } -static void batch_object_write(const char *obj_name, struct batch_options *opt, +static void batch_object_write(const char *obj_name, + struct strbuf *scratch, + struct batch_options *opt, struct expand_data *data) { - struct strbuf buf = STRBUF_INIT; - if (!data->skip_object_info && - oid_object_info_extended(&data->oid, &data->info, + oid_object_info_extended(the_repository, &data->oid, &data->info, OBJECT_INFO_LOOKUP_REPLACE) < 0) { printf("%s missing\n", obj_name ? obj_name : oid_to_hex(&data->oid)); @@ -350,10 +369,10 @@ static void batch_object_write(const char *obj_name, struct batch_options *opt, return; } - strbuf_expand(&buf, opt->format, expand_format, data); - strbuf_addch(&buf, '\n'); - batch_write(opt, buf.buf, buf.len); - strbuf_release(&buf); + strbuf_reset(scratch); + strbuf_expand(scratch, opt->format, expand_format, data); + strbuf_addch(scratch, '\n'); + batch_write(opt, scratch->buf, scratch->len); if (opt->print_contents) { print_object_or_die(opt, data); @@ -361,19 +380,25 @@ static void batch_object_write(const char *obj_name, struct batch_options *opt, } } -static void batch_one_object(const char *obj_name, struct batch_options *opt, +static void batch_one_object(const char *obj_name, + struct strbuf *scratch, + struct batch_options *opt, struct expand_data *data) { struct object_context ctx; int flags = opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0; - enum follow_symlinks_result result; + enum get_oid_result result; - result = get_oid_with_context(obj_name, flags, &data->oid, &ctx); + result = get_oid_with_context(the_repository, obj_name, + flags, &data->oid, &ctx); if (result != FOUND) { switch (result) { case MISSING_OBJECT: printf("%s missing\n", obj_name); break; + case SHORT_NAME_AMBIGUOUS: + printf("%s ambiguous\n", obj_name); + break; case DANGLING_SYMLINK: printf("dangling %"PRIuMAX"\n%s\n", (uintmax_t)strlen(obj_name), obj_name); @@ -387,7 +412,7 @@ static void batch_one_object(const char *obj_name, struct batch_options *opt, (uintmax_t)strlen(obj_name), obj_name); break; default: - die("BUG: unknown get_sha1_with_context result %d\n", + BUG("unknown get_sha1_with_context result %d\n", result); break; } @@ -403,42 +428,70 @@ static void batch_one_object(const char *obj_name, struct batch_options *opt, return; } - batch_object_write(obj_name, opt, data); + batch_object_write(obj_name, scratch, opt, data); } struct object_cb_data { struct batch_options *opt; struct expand_data *expand; + struct oidset *seen; + struct strbuf *scratch; }; static int batch_object_cb(const struct object_id *oid, void *vdata) { struct object_cb_data *data = vdata; oidcpy(&data->expand->oid, oid); - batch_object_write(NULL, data->opt, data->expand); + batch_object_write(NULL, data->scratch, data->opt, data->expand); return 0; } -static int batch_loose_object(const struct object_id *oid, - const char *path, - void *data) +static int collect_loose_object(const struct object_id *oid, + const char *path, + void *data) { oid_array_append(data, oid); return 0; } -static int batch_packed_object(const struct object_id *oid, - struct packed_git *pack, - uint32_t pos, - void *data) +static int collect_packed_object(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *data) { oid_array_append(data, oid); return 0; } +static int batch_unordered_object(const struct object_id *oid, void *vdata) +{ + struct object_cb_data *data = vdata; + + if (oidset_insert(data->seen, oid)) + return 0; + + return batch_object_cb(oid, data); +} + +static int batch_unordered_loose(const struct object_id *oid, + const char *path, + void *data) +{ + return batch_unordered_object(oid, data); +} + +static int batch_unordered_packed(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + return batch_unordered_object(oid, data); +} + static int batch_objects(struct batch_options *opt) { - struct strbuf buf = STRBUF_INIT; + struct strbuf input = STRBUF_INIT; + struct strbuf output = STRBUF_INIT; struct expand_data data; int save_warning; int retval = 0; @@ -448,13 +501,14 @@ static int batch_objects(struct batch_options *opt) /* * Expand once with our special mark_query flag, which will prime the - * object_info to be handed to sha1_object_info_extended for each + * object_info to be handed to oid_object_info_extended for each * object. */ memset(&data, 0, sizeof(data)); data.mark_query = 1; - strbuf_expand(&buf, opt->format, expand_format, &data); + strbuf_expand(&output, opt->format, expand_format, &data); data.mark_query = 0; + strbuf_release(&output); if (opt->cmdmode) data.split_on_whitespace = 1; @@ -472,19 +526,37 @@ static int batch_objects(struct batch_options *opt) data.info.typep = &data.type; if (opt->all_objects) { - struct oid_array sa = OID_ARRAY_INIT; struct object_cb_data cb; - for_each_loose_object(batch_loose_object, &sa, 0); - for_each_packed_object(batch_packed_object, &sa, 0); - if (repository_format_partial_clone) - warning("This repository has extensions.partialClone set. Some objects may not be loaded."); + if (has_promisor_remote()) + warning("This repository uses promisor remotes. Some objects may not be loaded."); cb.opt = opt; cb.expand = &data; - oid_array_for_each_unique(&sa, batch_object_cb, &cb); + cb.scratch = &output; + + if (opt->unordered) { + struct oidset seen = OIDSET_INIT; + + cb.seen = &seen; - oid_array_clear(&sa); + for_each_loose_object(batch_unordered_loose, &cb, 0); + for_each_packed_object(batch_unordered_packed, &cb, + FOR_EACH_OBJECT_PACK_ORDER); + + oidset_clear(&seen); + } else { + struct oid_array sa = OID_ARRAY_INIT; + + for_each_loose_object(collect_loose_object, &sa, 0); + for_each_packed_object(collect_packed_object, &sa, 0); + + oid_array_for_each_unique(&sa, batch_object_cb, &cb); + + oid_array_clear(&sa); + } + + strbuf_release(&output); return 0; } @@ -498,14 +570,14 @@ static int batch_objects(struct batch_options *opt) save_warning = warn_on_object_refname_ambiguity; warn_on_object_refname_ambiguity = 0; - while (strbuf_getline(&buf, stdin) != EOF) { + while (strbuf_getline(&input, stdin) != EOF) { if (data.split_on_whitespace) { /* * Split at first whitespace, tying off the beginning * of the string and saving the remainder (or NULL) in * data.rest. */ - char *p = strpbrk(buf.buf, " \t"); + char *p = strpbrk(input.buf, " \t"); if (p) { while (*p && strchr(" \t", *p)) *p++ = '\0'; @@ -513,10 +585,11 @@ static int batch_objects(struct batch_options *opt) data.rest = p; } - batch_one_object(buf.buf, opt, &data); + batch_one_object(input.buf, &output, opt, &data); } - strbuf_release(&buf); + strbuf_release(&input); + strbuf_release(&output); warn_on_object_refname_ambiguity = save_warning; return retval; } @@ -541,8 +614,10 @@ static int batch_option_callback(const struct option *opt, { struct batch_options *bo = opt->value; + BUG_ON_OPT_NEG(unset); + if (bo->enabled) { - return 1; + return error(_("only one batch option may be specified")); } bo->enabled = 1; @@ -575,16 +650,20 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), - { OPTION_CALLBACK, 0, "batch", &batch, "format", + OPT_CALLBACK_F(0, "batch", &batch, "format", N_("show info and content of objects fed from the standard input"), - PARSE_OPT_OPTARG, batch_option_callback }, - { OPTION_CALLBACK, 0, "batch-check", &batch, "format", + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, + batch_option_callback), + OPT_CALLBACK_F(0, "batch-check", &batch, "format", N_("show info about objects fed from the standard input"), - PARSE_OPT_OPTARG, batch_option_callback }, + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, + batch_option_callback), OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, N_("follow in-tree symlinks (used with --batch or --batch-check)")), OPT_BOOL(0, "batch-all-objects", &batch.all_objects, N_("show all objects with --batch or --batch-check")), + OPT_BOOL(0, "unordered", &batch.unordered, + N_("do not order --batch-all-objects output")), OPT_END() }; diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 91444dc044..dd83397786 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -1,3 +1,4 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "cache.h" #include "config.h" @@ -63,10 +64,9 @@ static void check_attr(const char *prefix, prefix_path(prefix, prefix ? strlen(prefix) : 0, file); if (collect_all) { - git_all_attrs(full_path, check); + git_all_attrs(&the_index, full_path, check); } else { - if (git_check_attr(full_path, check)) - die("git_check_attr died"); + git_check_attr(&the_index, full_path, check); } output_attr(check, file); @@ -120,7 +120,7 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) } if (cached_attrs) - git_attr_set_direction(GIT_ATTR_INDEX, NULL); + git_attr_set_direction(GIT_ATTR_INDEX); doubledash = -1; for (i = 0; doubledash < 0 && i < argc; i++) { diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index ec9a959e08..ea5d0ae3a6 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -1,3 +1,4 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "cache.h" #include "config.h" @@ -31,19 +32,19 @@ static const struct option check_ignore_options[] = { OPT_END() }; -static void output_exclude(const char *path, struct exclude *exclude) +static void output_pattern(const char *path, struct path_pattern *pattern) { - char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : ""; - char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : ""; + char *bang = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE) ? "!" : ""; + char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : ""; if (!nul_term_line) { if (!verbose) { write_name_quoted(path, stdout, '\n'); } else { - if (exclude) { - quote_c_style(exclude->el->src, NULL, stdout, 0); + if (pattern) { + quote_c_style(pattern->pl->src, NULL, stdout, 0); printf(":%d:%s%s%s\t", - exclude->srcpos, - bang, exclude->pattern, slash); + pattern->srcpos, + bang, pattern->pattern, slash); } else { printf("::\t"); @@ -55,11 +56,11 @@ static void output_exclude(const char *path, struct exclude *exclude) if (!verbose) { printf("%s%c", path, '\0'); } else { - if (exclude) + if (pattern) printf("%s%c%d%c%s%s%s%c%s%c", - exclude->el->src, '\0', - exclude->srcpos, '\0', - bang, exclude->pattern, slash, '\0', + pattern->pl->src, '\0', + pattern->srcpos, '\0', + bang, pattern->pattern, slash, '\0', path, '\0'); else printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0'); @@ -73,7 +74,7 @@ static int check_ignore(struct dir_struct *dir, const char *full_path; char *seen; int num_ignored = 0, i; - struct exclude *exclude; + struct path_pattern *pattern; struct pathspec pathspec; if (!argc) { @@ -102,15 +103,18 @@ static int check_ignore(struct dir_struct *dir, seen = find_pathspecs_matching_against_index(&pathspec, &the_index); for (i = 0; i < pathspec.nr; i++) { full_path = pathspec.items[i].match; - exclude = NULL; + pattern = NULL; if (!seen[i]) { int dtype = DT_UNKNOWN; - exclude = last_exclude_matching(dir, &the_index, + pattern = last_matching_pattern(dir, &the_index, full_path, &dtype); + if (!verbose && pattern && + pattern->flags & PATTERN_FLAG_NEGATIVE) + pattern = NULL; } - if (!quiet && (exclude || show_non_matching)) - output_exclude(pathspec.items[i].original, exclude); - if (exclude) + if (!quiet && (pattern || show_non_matching)) + output_pattern(pathspec.items[i].original, pattern); + if (pattern) num_ignored++; } free(seen); diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index a730f6a1aa..a854fd16e7 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -4,6 +4,7 @@ * Copyright (C) 2005 Linus Torvalds * */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "config.h" #include "lockfile.h" @@ -67,7 +68,8 @@ static int checkout_file(const char *name, const char *prefix) continue; did_checkout = 1; if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) + to_tempfile ? topath[ce_stage(ce)] : NULL, + NULL) < 0) errs++; } @@ -111,7 +113,8 @@ static void checkout_all(const char *prefix, int prefix_length) write_tempfile_record(last_ce->name, prefix); } if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) + to_tempfile ? topath[ce_stage(ce)] : NULL, + NULL) < 0) errs++; last_ce = ce; } @@ -132,6 +135,8 @@ static const char * const builtin_checkout_index_usage[] = { static int option_parse_stage(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); + if (!strcmp(arg, "all")) { to_tempfile = 1; checkout_stage = CHECKOUT_ALL; @@ -172,9 +177,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) N_("write the content to temporary files")), OPT_STRING(0, "prefix", &state.base_dir, N_("string"), N_("when creating files, prepend <string>")), - { OPTION_CALLBACK, 0, "stage", NULL, "1-3|all", + OPT_CALLBACK_F(0, "stage", NULL, "(1|2|3|all)", N_("copy out the files from named stage"), - PARSE_OPT_NONEG, option_parse_stage }, + PARSE_OPT_NONEG, option_parse_stage), OPT_END() }; @@ -190,6 +195,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_checkout_index_options, builtin_checkout_index_usage, 0); + state.istate = &the_index; state.force = force; state.quiet = quiet; state.not_new = not_new; diff --git a/builtin/checkout.c b/builtin/checkout.c index b49b582071..24336e1017 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1,27 +1,31 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" -#include "config.h" +#include "advice.h" +#include "blob.h" +#include "branch.h" +#include "cache-tree.h" #include "checkout.h" +#include "commit.h" +#include "config.h" +#include "diff.h" +#include "dir.h" +#include "ll-merge.h" #include "lockfile.h" +#include "merge-recursive.h" +#include "object-store.h" #include "parse-options.h" #include "refs.h" -#include "commit.h" +#include "remote.h" +#include "resolve-undo.h" +#include "revision.h" +#include "run-command.h" +#include "submodule.h" +#include "submodule-config.h" #include "tree.h" #include "tree-walk.h" -#include "cache-tree.h" #include "unpack-trees.h" -#include "dir.h" -#include "run-command.h" -#include "merge-recursive.h" -#include "branch.h" -#include "diff.h" -#include "revision.h" -#include "remote.h" -#include "blob.h" +#include "wt-status.h" #include "xdiff-interface.h" -#include "ll-merge.h" -#include "resolve-undo.h" -#include "submodule-config.h" -#include "submodule.h" static const char * const checkout_usage[] = { N_("git checkout [<options>] <branch>"), @@ -29,17 +33,45 @@ static const char * const checkout_usage[] = { NULL, }; +static const char * const switch_branch_usage[] = { + N_("git switch [<options>] [<branch>]"), + NULL, +}; + +static const char * const restore_usage[] = { + N_("git restore [<options>] [--source=<branch>] <file>..."), + NULL, +}; + struct checkout_opts { int patch_mode; int quiet; int merge; int force; int force_detach; + int implicit_detach; int writeout_stage; int overwrite_ignore; int ignore_skipworktree; int ignore_other_worktrees; int show_progress; + int count_checkout_paths; + int overlay_mode; + int dwim_new_local_branch; + int discard_changes; + int accept_ref; + int accept_pathspec; + int switch_branch_doing_nothing_is_ok; + int only_merge_on_switching_branches; + int can_switch_when_in_progress; + int orphan_from_empty_tree; + int empty_pathspec_ok; + int checkout_index; + int checkout_worktree; + const char *ignore_unmerged_opt; + int ignore_unmerged; + int pathspec_file_nul; + const char *pathspec_from_file; const char *new_branch; const char *new_branch_force; @@ -47,13 +79,28 @@ struct checkout_opts { int new_branch_log; enum branch_track track; struct diff_options diff_options; + char *conflict_style; int branch_exists; const char *prefix; struct pathspec pathspec; + const char *from_treeish; 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) { @@ -77,7 +124,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base, return READ_TREE_RECURSIVE; len = base->len + strlen(pathname); - ce = xcalloc(1, cache_entry_size(len)); + ce = make_empty_cache_entry(&the_index, len); oidcpy(&ce->oid, oid); memcpy(ce->name, base->buf, base->len); memcpy(ce->name + base->len, pathname, len - base->len); @@ -94,9 +141,10 @@ static int update_some(const struct object_id *oid, struct strbuf *base, if (pos >= 0) { struct cache_entry *old = active_cache[pos]; if (ce->ce_mode == old->ce_mode && - !oidcmp(&ce->oid, &old->oid)) { + !ce_intent_to_add(old) && + oideq(&ce->oid, &old->oid)) { old->ce_flags |= CE_UPDATE; - free(ce); + discard_cache_entry(ce); return 0; } } @@ -107,7 +155,8 @@ static int update_some(const struct object_id *oid, struct strbuf *base, static int read_tree_some(struct tree *tree, const struct pathspec *pathspec) { - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); + read_tree_recursive(the_repository, tree, "", 0, 0, + pathspec, update_some, NULL); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -124,7 +173,8 @@ static int skip_same_name(const struct cache_entry *ce, int pos) return pos; } -static int check_stage(int stage, const struct cache_entry *ce, int pos) +static int check_stage(int stage, const struct cache_entry *ce, int pos, + int overlay_mode) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { @@ -132,6 +182,8 @@ static int check_stage(int stage, const struct cache_entry *ce, int pos) return 0; pos++; } + if (!overlay_mode) + return 0; if (stage == 2) return error(_("path '%s' does not have our version"), ce->name); else @@ -157,21 +209,27 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) } static int checkout_stage(int stage, const struct cache_entry *ce, int pos, - const struct checkout *state) + const struct checkout *state, int *nr_checkouts, + int overlay_mode) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { if (ce_stage(active_cache[pos]) == stage) - return checkout_entry(active_cache[pos], state, NULL); + return checkout_entry(active_cache[pos], state, + NULL, nr_checkouts); pos++; } + if (!overlay_mode) { + unlink_entry(ce); + return 0; + } if (stage == 2) return error(_("path '%s' does not have our version"), ce->name); else return error(_("path '%s' does not have their version"), ce->name); } -static int checkout_merged(int pos, const struct checkout *state) +static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts) { struct cache_entry *ce = active_cache[pos]; const char *path = ce->name; @@ -206,7 +264,8 @@ static int checkout_merged(int pos, const struct checkout *state) * merge.renormalize set, too */ status = ll_merge(&result_buf, path, &ancestor, "base", - &ours, "ours", &theirs, "theirs", NULL); + &ours, "ours", &theirs, "theirs", + state->istate, NULL); free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); @@ -230,24 +289,142 @@ static int checkout_merged(int pos, const struct checkout *state) if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid)) die(_("Unable to add merge result for '%s'"), path); free(result_buf.ptr); - ce = make_cache_entry(mode, oid.hash, path, 2, 0); + ce = make_transient_cache_entry(mode, &oid, path, 2); if (!ce) die(_("make_cache_entry failed for path '%s'"), path); - status = checkout_entry(ce, state, NULL); - free(ce); + status = checkout_entry(ce, state, NULL, nr_checkouts); + discard_cache_entry(ce); return status; } +static void mark_ce_for_checkout_overlay(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) +{ + ce->ce_flags &= ~CE_MATCHED; + if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) + return; + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * "git checkout tree-ish -- path", but this entry + * is in the original index but is not in tree-ish + * or does not match the pathspec; it will not be + * checked out to the working tree. We will not do + * anything to this entry at all. + */ + return; + /* + * Either this entry came from the tree-ish we are + * checking the paths out of, or we are checking out + * of the index. + * + * If it comes from the tree-ish, we already know it + * matches the pathspec and could just stamp + * CE_MATCHED to it from update_some(). But we still + * need ps_matched and read_tree_recursive (and + * eventually tree_entry_interesting) cannot fill + * ps_matched yet. Once it can, we can avoid calling + * match_pathspec() for _all_ entries when + * opts->source_tree != NULL. + */ + if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) + ce->ce_flags |= CE_MATCHED; +} + +static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) +{ + ce->ce_flags &= ~CE_MATCHED; + if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) + return; + if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) { + ce->ce_flags |= CE_MATCHED; + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * In overlay mode, but the path is not in + * tree-ish, which means we should remove it + * from the index and the working tree. + */ + ce->ce_flags |= CE_REMOVE | CE_WT_REMOVE; + } +} + +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; + int errs = 0; + int pos; + + state.force = 1; + 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]; + if (ce->ce_flags & CE_MATCHED) { + if (!ce_stage(ce)) { + errs |= checkout_entry(ce, &state, + NULL, &nr_checkouts); + continue; + } + if (opts->writeout_stage) + errs |= checkout_stage(opts->writeout_stage, + ce, pos, + &state, + &nr_checkouts, opts->overlay_mode); + else if (opts->merge) + errs |= checkout_merged(pos, &state, + &nr_unmerged); + pos = skip_same_name(ce, pos) - 1; + } + } + remove_marked_cache_entries(&the_index, 1); + remove_scheduled_dirs(); + errs |= finish_delayed_checkout(&state, &nr_checkouts); + + if (opts->count_checkout_paths) { + if (nr_unmerged) + fprintf_ln(stderr, Q_("Recreated %d merge conflict", + "Recreated %d merge conflicts", + nr_unmerged), + nr_unmerged); + if (opts->source_tree) + fprintf_ln(stderr, Q_("Updated %d path from %s", + "Updated %d paths from %s", + nr_checkouts), + nr_checkouts, + find_unique_abbrev(&opts->source_tree->object.oid, + DEFAULT_ABBREV)); + else if (!nr_unmerged || nr_checkouts) + fprintf_ln(stderr, Q_("Updated %d path from the index", + "Updated %d paths from the index", + nr_checkouts), + nr_checkouts); + } + + return errs; +} + static int checkout_paths(const struct checkout_opts *opts, - const char *revision) + const struct branch_info *new_branch_info) { int pos; - struct checkout state = CHECKOUT_INIT; static char *ps_matched; struct object_id rev; struct commit *head; int errs = 0; struct lock_file lock_file = LOCK_INIT; + int checkout_index; + + trace2_cmd_mode(opts->patch_mode ? "patch" : "path"); if (opts->track != BRANCH_TRACK_UNSPECIFIED) die(_("'%s' cannot be used with updating paths"), "--track"); @@ -255,8 +432,9 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->new_branch_log) die(_("'%s' cannot be used with updating paths"), "-l"); - if (opts->force && opts->patch_mode) - die(_("'%s' cannot be used with updating paths"), "-f"); + if (opts->ignore_unmerged && opts->patch_mode) + die(_("'%s' cannot be used with updating paths"), + opts->ignore_unmerged_opt); if (opts->force_detach) die(_("'%s' cannot be used with updating paths"), "--detach"); @@ -264,18 +442,48 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->merge && opts->patch_mode) die(_("'%s' cannot be used with %s"), "--merge", "--patch"); - if (opts->force && opts->merge) - die(_("'%s' cannot be used with %s"), "-f", "-m"); + if (opts->ignore_unmerged && opts->merge) + die(_("'%s' cannot be used with %s"), + opts->ignore_unmerged_opt, "-m"); if (opts->new_branch) die(_("Cannot update paths and switch to branch '%s' at the same time."), opts->new_branch); - if (opts->patch_mode) - return run_add_interactive(revision, "--patch=checkout", - &opts->pathspec); + if (!opts->checkout_worktree && !opts->checkout_index) + die(_("neither '%s' or '%s' is specified"), + "--staged", "--worktree"); + + if (!opts->checkout_worktree && !opts->from_treeish) + die(_("'%s' must be used when '%s' is not specified"), + "--worktree", "--source"); + + if (opts->checkout_index && !opts->checkout_worktree && + opts->writeout_stage) + die(_("'%s' or '%s' cannot be used with %s"), + "--ours", "--theirs", "--staged"); + + if (opts->checkout_index && !opts->checkout_worktree && + opts->merge) + die(_("'%s' or '%s' cannot be used with %s"), + "--merge", "--conflict", "--staged"); + + if (opts->patch_mode) { + const char *patch_mode; + + if (opts->checkout_index && opts->checkout_worktree) + patch_mode = "--patch=checkout"; + else if (opts->checkout_index && !opts->checkout_worktree) + patch_mode = "--patch=reset"; + else if (!opts->checkout_index && opts->checkout_worktree) + patch_mode = "--patch=worktree"; + else + BUG("either flag must have been set, worktree=%d, index=%d", + opts->checkout_worktree, opts->checkout_index); + return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec); + } - hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); if (read_cache_preload(&opts->pathspec) < 0) return error(_("index file corrupt")); @@ -288,39 +496,17 @@ static int checkout_paths(const struct checkout_opts *opts, * Make sure all pathspecs participated in locating the paths * to be checked out. */ - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - ce->ce_flags &= ~CE_MATCHED; - if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) - continue; - if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) - /* - * "git checkout tree-ish -- path", but this entry - * is in the original index; it will not be checked - * out to the working tree and it does not matter - * if pathspec matched this entry. We will not do - * anything to this entry at all. - */ - continue; - /* - * Either this entry came from the tree-ish we are - * checking the paths out of, or we are checking out - * of the index. - * - * If it comes from the tree-ish, we already know it - * matches the pathspec and could just stamp - * CE_MATCHED to it from update_some(). But we still - * need ps_matched and read_tree_recursive (and - * eventually tree_entry_interesting) cannot fill - * ps_matched yet. Once it can, we can avoid calling - * match_pathspec() for _all_ entries when - * opts->source_tree != NULL. - */ - if (ce_path_match(ce, &opts->pathspec, ps_matched)) - ce->ce_flags |= CE_MATCHED; - } + for (pos = 0; pos < active_nr; pos++) + if (opts->overlay_mode) + mark_ce_for_checkout_overlay(active_cache[pos], + ps_matched, + opts); + else + mark_ce_for_checkout_no_overlay(active_cache[pos], + ps_matched, + opts); - if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) { + if (report_path_error(ps_matched, &opts->pathspec)) { free(ps_matched); return 1; } @@ -336,10 +522,11 @@ static int checkout_paths(const struct checkout_opts *opts, if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) continue; - if (opts->force) { - warning(_("path '%s' is unmerged"), ce->name); + if (opts->ignore_unmerged) { + if (!opts->quiet) + warning(_("path '%s' is unmerged"), ce->name); } else if (opts->writeout_stage) { - errs |= check_stage(opts->writeout_stage, ce, pos); + errs |= check_stage(opts->writeout_stage, ce, pos, opts->overlay_mode); } else if (opts->merge) { errs |= check_stages((1<<2) | (1<<3), ce, pos); } else { @@ -353,32 +540,35 @@ static int checkout_paths(const struct checkout_opts *opts, return 1; /* Now we are committed to check them out */ - state.force = 1; - state.refresh_cache = 1; - state.istate = &the_index; + if (opts->checkout_worktree) + errs |= checkout_worktree(opts, new_branch_info); + else + remove_marked_cache_entries(&the_index, 1); - enable_delayed_checkout(&state); - for (pos = 0; pos < active_nr; pos++) { - struct cache_entry *ce = active_cache[pos]; - if (ce->ce_flags & CE_MATCHED) { - if (!ce_stage(ce)) { - errs |= checkout_entry(ce, &state, NULL); - continue; - } - if (opts->writeout_stage) - errs |= checkout_stage(opts->writeout_stage, ce, pos, &state); - else if (opts->merge) - errs |= checkout_merged(pos, &state); - pos = skip_same_name(ce, pos) - 1; - } - } - errs |= finish_delayed_checkout(&state); + /* + * Allow updating the index when checking out from the index. + * This is to save new stat info. + */ + if (opts->checkout_worktree && !opts->checkout_index && !opts->source_tree) + checkout_index = 1; + else + checkout_index = opts->checkout_index; - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) - die(_("unable to write new index file")); + if (checkout_index) { + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + die(_("unable to write new index file")); + } else { + /* + * NEEDSWORK: if --worktree is not specified, we + * should save stat info of checked out files in the + * index to avoid the next (potentially costly) + * refresh. But it's a bit tricker to do... + */ + rollback_lock_file(&lock_file); + } read_ref_full("HEAD", 0, &rev, NULL); - head = lookup_commit_reference_gently(&rev, 1); + head = lookup_commit_reference_gently(the_repository, &rev, 1); errs |= post_checkout_hook(head, head, 0); return errs; @@ -389,7 +579,7 @@ static void show_local_changes(struct object *head, { struct rev_info rev; /* I think we want full paths, even if we're in a subdirectory. */ - init_revisions(&rev, NULL); + repo_init_revisions(the_repository, &rev, NULL); rev.diffopt.flags = opts->flags; rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; diff_setup_done(&rev.diffopt); @@ -414,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; @@ -429,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)) { @@ -448,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); @@ -477,14 +669,21 @@ static int merge_working_tree(const struct checkout_opts *opts, { int ret; struct lock_file lock_file = LOCK_INIT; + struct tree *new_tree; hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); if (read_cache_preload(NULL) < 0) return error(_("index file corrupt")); resolve_undo_clear(); - if (opts->force) { - ret = reset_tree(new_branch_info->commit->tree, opts, 1, writeout_error); + if (opts->new_orphan_branch && opts->orphan_from_empty_tree) { + if (new_branch_info->commit) + BUG("'switch --orphan' should never accept a commit as starting point"); + new_tree = parse_tree_indirect(the_hash_algo->empty_tree); + } else + new_tree = get_commit_tree(new_branch_info->commit); + if (opts->discard_changes) { + ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info); if (ret) return ret; } else { @@ -510,9 +709,13 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.initial_checkout = is_cache_unborn(); topts.update = 1; topts.merge = 1; - topts.gently = opts->merge && old_branch_info->commit; + topts.quiet = opts->merge && old_branch_info->commit; topts.verbose_update = opts->show_progress; topts.fn = twoway_merge; + 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; @@ -522,19 +725,24 @@ static int merge_working_tree(const struct checkout_opts *opts, &old_branch_info->commit->object.oid : the_hash_algo->empty_tree); init_tree_desc(&trees[0], tree->buffer, tree->size); - tree = parse_tree_indirect(&new_branch_info->commit->object.oid); + parse_tree(new_tree); + tree = new_tree; init_tree_desc(&trees[1], tree->buffer, tree->size); ret = unpack_trees(2, trees, &topts); + clear_unpack_trees_porcelain(&topts); if (ret == -1) { /* * Unpack couldn't do a trivial merge; either * give up or do a real merge, depending on * whether the merge flag was used. */ - struct tree *result; struct tree *work; + struct tree *old_tree; struct merge_options o; + struct strbuf sb = STRBUF_INIT; + struct strbuf old_commit_shortname = STRBUF_INIT; + if (!opts->merge) return 1; @@ -544,6 +752,12 @@ static int merge_working_tree(const struct checkout_opts *opts, */ if (!old_branch_info->commit) return 1; + old_tree = get_commit_tree(old_branch_info->commit); + + if (repo_index_has_changes(the_repository, old_tree, &sb)) + die(_("cannot continue with staged changes in " + "the following files:\n%s"), sb.buf); + strbuf_release(&sb); /* Do more real merge */ @@ -566,24 +780,35 @@ static int merge_working_tree(const struct checkout_opts *opts, * a pain; plumb in an option to set * o.renormalize? */ - init_merge_options(&o); + init_merge_options(&o, the_repository); o.verbosity = 0; - work = write_tree_from_memory(&o); + work = write_in_core_index_as_tree(the_repository); - ret = reset_tree(new_branch_info->commit->tree, opts, 1, - writeout_error); + ret = reset_tree(new_tree, + opts, 1, + writeout_error, new_branch_info); if (ret) return ret; o.ancestor = old_branch_info->name; + if (old_branch_info->name == NULL) { + strbuf_add_unique_abbrev(&old_commit_shortname, + &old_branch_info->commit->object.oid, + DEFAULT_ABBREV); + o.ancestor = old_commit_shortname.buf; + } o.branch1 = new_branch_info->name; o.branch2 = "local"; - ret = merge_trees(&o, new_branch_info->commit->tree, work, - old_branch_info->commit->tree, &result); + ret = merge_trees(&o, + new_tree, + work, + old_tree); if (ret < 0) exit(128); - ret = reset_tree(new_branch_info->commit->tree, opts, 0, - writeout_error); + ret = reset_tree(new_tree, + opts, 0, + writeout_error, new_branch_info); strbuf_release(&o.obuf); + strbuf_release(&old_commit_shortname); if (ret) return ret; } @@ -598,7 +823,7 @@ static int merge_working_tree(const struct checkout_opts *opts, if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - if (!opts->force && !opts->quiet) + if (!opts->discard_changes && !opts->quiet && new_branch_info->commit) show_local_changes(&new_branch_info->commit->object, &opts->diff_options); return 0; @@ -644,7 +869,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts, free(refname); } else - create_branch(opts->new_branch, new_branch_info->name, + create_branch(the_repository, + opts->new_branch, new_branch_info->name, opts->new_branch_force ? 1 : 0, opts->new_branch_force ? 1 : 0, opts->new_branch_log, @@ -663,7 +889,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, strbuf_addf(&msg, "checkout: moving from %s to %s", old_desc ? old_desc : "(invalid)", new_branch_info->name); else - strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg)); + strbuf_insertstr(&msg, 0, reflog_msg); if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) { /* Nothing to do. */ @@ -702,7 +928,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, delete_reflog(old_branch_info->path); } } - remove_branch_state(); + remove_branch_state(the_repository, !opts->quiet); strbuf_release(&msg); if (!opts->quiet && (new_branch_info->path || (!opts->force_detach && !strcmp(new_branch_info->name, "HEAD")))) @@ -791,14 +1017,17 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne struct rev_info revs; struct object *object = &old_commit->object; - init_revisions(&revs, NULL); + repo_init_revisions(the_repository, &revs, NULL); setup_revisions(0, NULL, &revs, NULL); object->flags &= ~UNINTERESTING; add_pending_object(&revs, object, oid_to_hex(&object->oid)); for_each_ref(add_pending_uninteresting_ref, &revs); - add_pending_oid(&revs, "HEAD", &new_commit->object.oid, UNINTERESTING); + if (new_commit) + add_pending_oid(&revs, "HEAD", + &new_commit->object.oid, + UNINTERESTING); if (prepare_revision_walk(&revs)) die(_("internal error in revision walk")); @@ -819,28 +1048,45 @@ static int switch_branches(const struct checkout_opts *opts, void *path_to_free; struct object_id rev; int flag, writeout_error = 0; + int do_merge = 1; + + trace2_cmd_mode("branch"); + memset(&old_branch_info, 0, sizeof(old_branch_info)); old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag); if (old_branch_info.path) - old_branch_info.commit = lookup_commit_reference_gently(&rev, 1); + old_branch_info.commit = lookup_commit_reference_gently(the_repository, &rev, 1); if (!(flag & REF_ISSYMREF)) old_branch_info.path = NULL; if (old_branch_info.path) skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name); + if (opts->new_orphan_branch && opts->orphan_from_empty_tree) { + if (new_branch_info->name) + BUG("'switch --orphan' should never accept a commit as starting point"); + new_branch_info->commit = NULL; + new_branch_info->name = "(empty)"; + do_merge = 1; + } + if (!new_branch_info->name) { new_branch_info->name = "HEAD"; new_branch_info->commit = old_branch_info.commit; if (!new_branch_info->commit) die(_("You are on a branch yet to be born")); parse_commit_or_die(new_branch_info->commit); + + if (opts->only_merge_on_switching_branches) + do_merge = 0; } - ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); - if (ret) { - free(path_to_free); - return ret; + if (do_merge) { + ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); + if (ret) { + free(path_to_free); + return ret; + } } if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit) @@ -867,16 +1113,74 @@ static int git_checkout_config(const char *var, const char *value, void *cb) return git_xmerge_config(var, value, NULL); } +static void setup_new_branch_info_and_source_tree( + struct branch_info *new_branch_info, + struct checkout_opts *opts, + struct object_id *rev, + const char *arg) +{ + struct tree **source_tree = &opts->source_tree; + struct object_id branch_rev; + + new_branch_info->name = arg; + setup_branch_path(new_branch_info); + + if (!check_refname_format(new_branch_info->path, 0) && + !read_ref(new_branch_info->path, &branch_rev)) + oidcpy(rev, &branch_rev); + else + new_branch_info->path = NULL; /* not an existing branch */ + + new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1); + if (!new_branch_info->commit) { + /* not a commit */ + *source_tree = parse_tree_indirect(rev); + } else { + parse_commit_or_die(new_branch_info->commit); + *source_tree = get_commit_tree(new_branch_info->commit); + } +} + +static const char *parse_remote_branch(const char *arg, + struct object_id *rev, + int could_be_checkout_paths) +{ + int num_matches = 0; + const char *remote = unique_tracking_name(arg, rev, &num_matches); + + if (remote && could_be_checkout_paths) { + die(_("'%s' could be both a local file and a tracking branch.\n" + "Please use -- (and optionally --no-guess) to disambiguate"), + arg); + } + + if (!remote && num_matches > 1) { + if (advice_checkout_ambiguous_remote_branch_name) { + advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" + "you can do so by fully qualifying the name with the --track option:\n" + "\n" + " git checkout --track origin/<name>\n" + "\n" + "If you'd like to always have checkouts of an ambiguous <name> prefer\n" + "one remote, e.g. the 'origin' remote, consider setting\n" + "checkout.defaultRemote=origin in your config.")); + } + + die(_("'%s' matched multiple (%d) remote tracking branches"), + arg, num_matches); + } + + return remote; +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new_branch_info, struct checkout_opts *opts, struct object_id *rev) { - struct tree **source_tree = &opts->source_tree; const char **new_branch = &opts->new_branch; int argcount = 0; - struct object_id branch_rev; const char *arg; int dash_dash_pos; int has_dash_dash = 0; @@ -903,8 +1207,10 @@ static int parse_branchname_arg(int argc, const char **argv, * (b) If <something> is _not_ a commit, either "--" is present * or <something> is not a path, no -t or -b was given, and * and there is a tracking branch whose name is <something> - * in one and only one remote, then this is a short-hand to - * fork local <something> from that remote-tracking branch. + * in one and only one remote (or if the branch exists on the + * remote named in checkout.defaultRemote), then this is a + * short-hand to fork local <something> from that + * remote-tracking branch. * * (c) Otherwise, if "--" is present, treat it like case (1). * @@ -924,10 +1230,16 @@ static int parse_branchname_arg(int argc, const char **argv, if (!argc) return 0; + if (!opts->accept_pathspec) { + if (argc > 1) + die(_("only one reference expected")); + has_dash_dash = 1; /* helps disambiguate */ + } + arg = argv[0]; dash_dash_pos = -1; for (i = 0; i < argc; i++) { - if (!strcmp(argv[i], "--")) { + if (opts->accept_pathspec && !strcmp(argv[i], "--")) { dash_dash_pos = i; break; } @@ -938,6 +1250,7 @@ static int parse_branchname_arg(int argc, const char **argv, has_dash_dash = 1; /* case (3) or (1) */ else if (dash_dash_pos >= 2) die(_("only one reference expected, %d given."), dash_dash_pos); + opts->count_checkout_paths = !opts->quiet && !has_dash_dash; if (!strcmp(arg, "-")) arg = "@{-1}"; @@ -953,19 +1266,24 @@ static int parse_branchname_arg(int argc, const char **argv, */ int recover_with_dwim = dwim_new_local_branch_ok; - if (!has_dash_dash && - (check_filename(opts->prefix, arg) || !no_wildcard(arg))) + int could_be_checkout_paths = !has_dash_dash && + check_filename(opts->prefix, arg); + + if (!has_dash_dash && !no_wildcard(arg)) recover_with_dwim = 0; + /* - * Accept "git checkout foo" and "git checkout foo --" - * as candidates for dwim. + * Accept "git checkout foo", "git checkout foo --" + * and "git switch foo" as candidates for dwim. */ if (!(argc == 1 && !has_dash_dash) && - !(argc == 2 && has_dash_dash)) + !(argc == 2 && has_dash_dash) && + opts->accept_pathspec) recover_with_dwim = 0; if (recover_with_dwim) { - const char *remote = unique_tracking_name(arg, rev); + const char *remote = parse_remote_branch(arg, rev, + could_be_checkout_paths); if (remote) { *new_branch = arg; arg = remote; @@ -987,26 +1305,11 @@ static int parse_branchname_arg(int argc, const char **argv, argv++; argc--; - new_branch_info->name = arg; - setup_branch_path(new_branch_info); - - if (!check_refname_format(new_branch_info->path, 0) && - !read_ref(new_branch_info->path, &branch_rev)) - oidcpy(rev, &branch_rev); - else - new_branch_info->path = NULL; /* not an existing branch */ - - new_branch_info->commit = lookup_commit_reference_gently(rev, 1); - if (!new_branch_info->commit) { - /* not a commit */ - *source_tree = parse_tree_indirect(rev); - } else { - parse_commit_or_die(new_branch_info->commit); - *source_tree = new_branch_info->commit->tree; - } + setup_new_branch_info_and_source_tree(new_branch_info, opts, rev, arg); - if (!*source_tree) /* case (1): want a tree */ + if (!opts->source_tree) /* case (1): want a tree */ die(_("reference is not a tree: %s"), arg); + if (!has_dash_dash) { /* case (3).(d) -> (1) */ /* * Do not complain the most common case @@ -1016,7 +1319,7 @@ static int parse_branchname_arg(int argc, const char **argv, */ if (argc) verify_non_filename(opts->prefix, arg); - } else { + } else if (opts->accept_pathspec) { argcount++; argv++; argc--; @@ -1030,6 +1333,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts) int status; struct strbuf branch_ref = STRBUF_INIT; + trace2_cmd_mode("unborn"); + if (!opts->new_branch) die(_("You are on a branch yet to be born")); strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch); @@ -1041,6 +1346,60 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts) return status; } +static void die_expecting_a_branch(const struct branch_info *branch_info) +{ + struct object_id oid; + char *to_free; + + if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 1) { + const char *ref = to_free; + + if (skip_prefix(ref, "refs/tags/", &ref)) + die(_("a branch is expected, got tag '%s'"), ref); + if (skip_prefix(ref, "refs/remotes/", &ref)) + die(_("a branch is expected, got remote branch '%s'"), ref); + die(_("a branch is expected, got '%s'"), ref); + } + if (branch_info->commit) + die(_("a branch is expected, got commit '%s'"), branch_info->name); + /* + * This case should never happen because we already die() on + * non-commit, but just in case. + */ + die(_("a branch is expected, got '%s'"), branch_info->name); +} + +static void die_if_some_operation_in_progress(void) +{ + struct wt_status_state state; + + memset(&state, 0, sizeof(state)); + wt_status_get_state(the_repository, &state, 0); + + if (state.merge_in_progress) + die(_("cannot switch branch while merging\n" + "Consider \"git merge --quit\" " + "or \"git worktree add\".")); + if (state.am_in_progress) + die(_("cannot switch branch in the middle of an am session\n" + "Consider \"git am --quit\" " + "or \"git worktree add\".")); + if (state.rebase_interactive_in_progress || state.rebase_in_progress) + die(_("cannot switch branch while rebasing\n" + "Consider \"git rebase --quit\" " + "or \"git worktree add\".")); + if (state.cherry_pick_in_progress) + die(_("cannot switch branch while cherry-picking\n" + "Consider \"git cherry-pick --quit\" " + "or \"git worktree add\".")); + if (state.revert_in_progress) + die(_("cannot switch branch while reverting\n" + "Consider \"git revert --quit\" " + "or \"git worktree add\".")); + if (state.bisect_in_progress) + warning(_("you are switching branch while bisecting")); +} + static int checkout_branch(struct checkout_opts *opts, struct branch_info *new_branch_info) { @@ -1051,6 +1410,10 @@ static int checkout_branch(struct checkout_opts *opts, die(_("'%s' cannot be used with switching branches"), "--patch"); + if (opts->overlay_mode != -1) + die(_("'%s' cannot be used with switching branches"), + "--[no]-overlay"); + if (opts->writeout_stage) die(_("'%s' cannot be used with switching branches"), "--ours/--theirs"); @@ -1058,6 +1421,9 @@ static int checkout_branch(struct checkout_opts *opts, if (opts->force && opts->merge) die(_("'%s' cannot be used with '%s'"), "-f", "-m"); + if (opts->discard_changes && opts->merge) + die(_("'%s' cannot be used with '%s'"), "--discard-changes", "--merge"); + if (opts->force_detach && opts->new_branch) die(_("'%s' cannot be used with '%s'"), "--detach", "-b/-B/--orphan"); @@ -1065,6 +1431,8 @@ static int checkout_branch(struct checkout_opts *opts, if (opts->new_orphan_branch) { if (opts->track != BRANCH_TRACK_UNSPECIFIED) die(_("'%s' cannot be used with '%s'"), "--orphan", "-t"); + if (opts->orphan_from_empty_tree && new_branch_info->name) + die(_("'%s' cannot take <start-point>"), "--orphan"); } else if (opts->force_detach) { if (opts->track != BRANCH_TRACK_UNSPECIFIED) die(_("'%s' cannot be used with '%s'"), "--detach", "-t"); @@ -1075,6 +1443,23 @@ static int checkout_branch(struct checkout_opts *opts, die(_("Cannot switch branch to a non-commit '%s'"), new_branch_info->name); + if (!opts->switch_branch_doing_nothing_is_ok && + !new_branch_info->name && + !opts->new_branch && + !opts->force_detach) + die(_("missing branch or commit argument")); + + if (!opts->implicit_detach && + !opts->force_detach && + !opts->new_branch && + !opts->new_branch_force && + new_branch_info->name && + !new_branch_info->path) + die_expecting_a_branch(new_branch_info); + + if (!opts->can_switch_when_in_progress) + die_if_some_operation_in_progress(); + if (new_branch_info->path && !opts->force_detach && !opts->new_branch && !opts->ignore_other_worktrees) { int flag; @@ -1096,99 +1481,162 @@ static int checkout_branch(struct checkout_opts *opts, return switch_branches(opts, new_branch_info); } -int cmd_checkout(int argc, const char **argv, const char *prefix) +static struct option *add_common_options(struct checkout_opts *opts, + struct option *prevopts) { - struct checkout_opts opts; - struct branch_info new_branch_info; - char *conflict_style = NULL; - int dwim_new_local_branch = 1; struct option options[] = { - OPT__QUIET(&opts.quiet, N_("suppress progress reporting")), - OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), - N_("create and checkout a new branch")), - OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"), - N_("create/reset and checkout a branch")), - OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")), - OPT_BOOL(0, "detach", &opts.force_detach, N_("detach HEAD at named commit")), - OPT_SET_INT('t', "track", &opts.track, N_("set upstream info for new branch"), + OPT__QUIET(&opts->quiet, N_("suppress progress reporting")), + OPT_CALLBACK_F(0, "recurse-submodules", NULL, + "checkout", "control recursive updating of submodules", + PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater), + OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")), + OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")), + OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"), + N_("conflict style (merge or diff3)")), + OPT_END() + }; + struct option *newopts = parse_options_concat(prevopts, options); + free(prevopts); + return newopts; +} + +static struct option *add_common_switch_branch_options( + struct checkout_opts *opts, struct option *prevopts) +{ + struct option options[] = { + OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")), + OPT_SET_INT('t', "track", &opts->track, N_("set upstream info for new branch"), BRANCH_TRACK_EXPLICIT), - OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new-branch"), N_("new unparented branch")), - OPT_SET_INT('2', "ours", &opts.writeout_stage, N_("checkout our version for unmerged files"), - 2), - OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"), - 3), - OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)"), + OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"), PARSE_OPT_NOCOMPLETE), - OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")), - OPT_BOOL_F(0, "overwrite-ignore", &opts.overwrite_ignore, + OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")), + OPT_BOOL_F(0, "overwrite-ignore", &opts->overwrite_ignore, N_("update ignored files (default)"), PARSE_OPT_NOCOMPLETE), - OPT_STRING(0, "conflict", &conflict_style, N_("style"), - N_("conflict style (merge or diff3)")), - OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")), - OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree, - N_("do not limit pathspecs to sparse entries only")), - OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch, - N_("second guess 'git checkout <no-such-branch>'")), - OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, + OPT_BOOL(0, "ignore-other-worktrees", &opts->ignore_other_worktrees, N_("do not check if another worktree is holding the given ref")), - { OPTION_CALLBACK, 0, "recurse-submodules", NULL, - "checkout", "control recursive updating of submodules", - PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, - OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")), - OPT_END(), + OPT_END() }; + struct option *newopts = parse_options_concat(prevopts, options); + free(prevopts); + return newopts; +} + +static struct option *add_checkout_path_options(struct checkout_opts *opts, + struct option *prevopts) +{ + struct option options[] = { + OPT_SET_INT_F('2', "ours", &opts->writeout_stage, + N_("checkout our version for unmerged files"), + 2, PARSE_OPT_NONEG), + OPT_SET_INT_F('3', "theirs", &opts->writeout_stage, + N_("checkout their version for unmerged files"), + 3, PARSE_OPT_NONEG), + OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")), + OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree, + N_("do not limit pathspecs to sparse entries only")), + OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&opts->pathspec_file_nul), + OPT_END() + }; + struct option *newopts = parse_options_concat(prevopts, options); + free(prevopts); + return newopts; +} + +/* create-branch option (either b or c) */ +static char cb_option = 'b'; + +static int checkout_main(int argc, const char **argv, const char *prefix, + struct checkout_opts *opts, struct option *options, + const char * const usagestr[]) +{ + struct branch_info new_branch_info; + int parseopt_flags = 0; - memset(&opts, 0, sizeof(opts)); memset(&new_branch_info, 0, sizeof(new_branch_info)); - opts.overwrite_ignore = 1; - opts.prefix = prefix; - opts.show_progress = -1; + opts->overwrite_ignore = 1; + opts->prefix = prefix; + opts->show_progress = -1; - git_config(git_checkout_config, &opts); + git_config(git_checkout_config, opts); - opts.track = BRANCH_TRACK_UNSPECIFIED; + opts->track = BRANCH_TRACK_UNSPECIFIED; - argc = parse_options(argc, argv, prefix, options, checkout_usage, - PARSE_OPT_KEEP_DASHDASH); + if (!opts->accept_pathspec && !opts->accept_ref) + BUG("make up your mind, you need to take _something_"); + if (opts->accept_pathspec && opts->accept_ref) + parseopt_flags = PARSE_OPT_KEEP_DASHDASH; - if (opts.show_progress < 0) { - if (opts.quiet) - opts.show_progress = 0; + argc = parse_options(argc, argv, prefix, options, + usagestr, parseopt_flags); + + if (opts->show_progress < 0) { + if (opts->quiet) + opts->show_progress = 0; else - opts.show_progress = isatty(2); + opts->show_progress = isatty(2); } - if (conflict_style) { - opts.merge = 1; /* implied */ - git_xmerge_config("merge.conflictstyle", conflict_style, NULL); + if (opts->conflict_style) { + opts->merge = 1; /* implied */ + git_xmerge_config("merge.conflictstyle", opts->conflict_style, NULL); + } + if (opts->force) { + opts->discard_changes = 1; + opts->ignore_unmerged_opt = "--force"; + opts->ignore_unmerged = 1; } - if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1) - die(_("-b, -B and --orphan are mutually exclusive")); + if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1) + die(_("-%c, -%c and --orphan are mutually exclusive"), + cb_option, toupper(cb_option)); + + if (opts->overlay_mode == 1 && opts->patch_mode) + die(_("-p and --overlay are mutually exclusive")); + + if (opts->checkout_index >= 0 || opts->checkout_worktree >= 0) { + if (opts->checkout_index < 0) + opts->checkout_index = 0; + if (opts->checkout_worktree < 0) + opts->checkout_worktree = 0; + } else { + if (opts->checkout_index < 0) + opts->checkout_index = -opts->checkout_index - 1; + if (opts->checkout_worktree < 0) + opts->checkout_worktree = -opts->checkout_worktree - 1; + } + if (opts->checkout_index < 0 || opts->checkout_worktree < 0) + BUG("these flags should be non-negative by now"); + /* + * convenient shortcut: "git restore --staged [--worktree]" equals + * "git restore --staged [--worktree] --source HEAD" + */ + if (!opts->from_treeish && opts->checkout_index) + opts->from_treeish = "HEAD"; /* * From here on, new_branch will contain the branch to be checked out, * and new_branch_force and new_orphan_branch will tell us which one of - * -b/-B/--orphan is being used. + * -b/-B/-c/-C/--orphan is being used. */ - if (opts.new_branch_force) - opts.new_branch = opts.new_branch_force; + if (opts->new_branch_force) + opts->new_branch = opts->new_branch_force; - if (opts.new_orphan_branch) - opts.new_branch = opts.new_orphan_branch; + if (opts->new_orphan_branch) + opts->new_branch = opts->new_orphan_branch; - /* --track without -b/-B/--orphan should DWIM */ - if (opts.track != BRANCH_TRACK_UNSPECIFIED && !opts.new_branch) { + /* --track without -c/-C/-b/-B/--orphan should DWIM */ + if (opts->track != BRANCH_TRACK_UNSPECIFIED && !opts->new_branch) { const char *argv0 = argv[0]; if (!argc || !strcmp(argv0, "--")) - die (_("--track needs a branch name")); + die(_("--track needs a branch name")); skip_prefix(argv0, "refs/", &argv0); skip_prefix(argv0, "remotes/", &argv0); argv0 = strchr(argv0, '/'); if (!argv0 || !argv0[1]) - die (_("Missing branch name; try -b")); - opts.new_branch = argv0 + 1; + die(_("missing branch name; try -%c"), cb_option); + opts->new_branch = argv0 + 1; } /* @@ -1204,58 +1652,221 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) * including "last branch" syntax and DWIM-ery for names of * remote branches, erroring out for invalid or ambiguous cases. */ - if (argc) { + if (argc && opts->accept_ref) { struct object_id rev; int dwim_ok = - !opts.patch_mode && - dwim_new_local_branch && - opts.track == BRANCH_TRACK_UNSPECIFIED && - !opts.new_branch; + !opts->patch_mode && + opts->dwim_new_local_branch && + opts->track == BRANCH_TRACK_UNSPECIFIED && + !opts->new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, - &new_branch_info, &opts, &rev); + &new_branch_info, opts, &rev); argv += n; argc -= n; + } else if (!opts->accept_ref && opts->from_treeish) { + struct object_id rev; + + if (get_oid_mb(opts->from_treeish, &rev)) + die(_("could not resolve %s"), opts->from_treeish); + + setup_new_branch_info_and_source_tree(&new_branch_info, + opts, &rev, + opts->from_treeish); + + if (!opts->source_tree) + die(_("reference is not a tree: %s"), opts->from_treeish); } if (argc) { - parse_pathspec(&opts.pathspec, 0, - opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0, + parse_pathspec(&opts->pathspec, 0, + opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0, prefix, argv); - if (!opts.pathspec.nr) + if (!opts->pathspec.nr) die(_("invalid path specification")); /* * Try to give more helpful suggestion. * new_branch && argc > 1 will be caught later. */ - if (opts.new_branch && argc == 1) + if (opts->new_branch && argc == 1 && !new_branch_info.commit) die(_("'%s' is not a commit and a branch '%s' cannot be created from it"), - argv[0], opts.new_branch); + argv[0], opts->new_branch); - if (opts.force_detach) + if (opts->force_detach) die(_("git checkout: --detach does not take a path argument '%s'"), argv[0]); + } + + if (opts->pathspec_from_file) { + if (opts->pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + if (opts->force_detach) + die(_("--pathspec-from-file is incompatible with --detach")); - if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) + if (opts->patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + + parse_pathspec_file(&opts->pathspec, 0, + 0, + prefix, opts->pathspec_from_file, opts->pathspec_file_nul); + } else if (opts->pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (opts->pathspec.nr) { + if (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge) die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\n" "checking out of the index.")); + } else { + if (opts->accept_pathspec && !opts->empty_pathspec_ok && + !opts->patch_mode) /* patch mode is special */ + die(_("you must specify path(s) to restore")); } - if (opts.new_branch) { + if (opts->new_branch) { struct strbuf buf = STRBUF_INIT; - if (opts.new_branch_force) - opts.branch_exists = validate_branchname(opts.new_branch, &buf); + if (opts->new_branch_force) + opts->branch_exists = validate_branchname(opts->new_branch, &buf); else - opts.branch_exists = - validate_new_branchname(opts.new_branch, &buf, 0); + opts->branch_exists = + validate_new_branchname(opts->new_branch, &buf, 0); strbuf_release(&buf); } UNLEAK(opts); - if (opts.patch_mode || opts.pathspec.nr) - return checkout_paths(&opts, new_branch_info.name); + if (opts->patch_mode || opts->pathspec.nr) + return checkout_paths(opts, &new_branch_info); else - return checkout_branch(&opts, &new_branch_info); + return checkout_branch(opts, &new_branch_info); +} + +int cmd_checkout(int argc, const char **argv, const char *prefix) +{ + struct checkout_opts opts; + struct option *options; + struct option checkout_options[] = { + OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), + N_("create and checkout a new branch")), + OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"), + N_("create/reset and checkout a branch")), + OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")), + OPT_BOOL(0, "guess", &opts.dwim_new_local_branch, + N_("second guess 'git checkout <no-such-branch>' (default)")), + OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")), + OPT_END() + }; + int ret; + + memset(&opts, 0, sizeof(opts)); + opts.dwim_new_local_branch = 1; + opts.switch_branch_doing_nothing_is_ok = 1; + opts.only_merge_on_switching_branches = 0; + opts.accept_ref = 1; + opts.accept_pathspec = 1; + opts.implicit_detach = 1; + opts.can_switch_when_in_progress = 1; + opts.orphan_from_empty_tree = 0; + opts.empty_pathspec_ok = 1; + opts.overlay_mode = -1; + opts.checkout_index = -2; /* default on */ + opts.checkout_worktree = -2; /* default on */ + + if (argc == 3 && !strcmp(argv[1], "-b")) { + /* + * User ran 'git checkout -b <branch>' and expects + * the same behavior as 'git switch -c <branch>'. + */ + opts.switch_branch_doing_nothing_is_ok = 0; + opts.only_merge_on_switching_branches = 1; + } + + options = parse_options_dup(checkout_options); + options = add_common_options(&opts, options); + options = add_common_switch_branch_options(&opts, options); + options = add_checkout_path_options(&opts, options); + + ret = checkout_main(argc, argv, prefix, &opts, + options, checkout_usage); + FREE_AND_NULL(options); + return ret; +} + +int cmd_switch(int argc, const char **argv, const char *prefix) +{ + struct checkout_opts opts; + struct option *options = NULL; + struct option switch_options[] = { + OPT_STRING('c', "create", &opts.new_branch, N_("branch"), + N_("create and switch to a new branch")), + OPT_STRING('C', "force-create", &opts.new_branch_force, N_("branch"), + N_("create/reset and switch to a branch")), + OPT_BOOL(0, "guess", &opts.dwim_new_local_branch, + N_("second guess 'git switch <no-such-branch>'")), + OPT_BOOL(0, "discard-changes", &opts.discard_changes, + N_("throw away local modifications")), + OPT_END() + }; + int ret; + + memset(&opts, 0, sizeof(opts)); + opts.dwim_new_local_branch = 1; + opts.accept_ref = 1; + opts.accept_pathspec = 0; + opts.switch_branch_doing_nothing_is_ok = 0; + opts.only_merge_on_switching_branches = 1; + opts.implicit_detach = 0; + opts.can_switch_when_in_progress = 0; + opts.orphan_from_empty_tree = 1; + opts.overlay_mode = -1; + + options = parse_options_dup(switch_options); + options = add_common_options(&opts, options); + options = add_common_switch_branch_options(&opts, options); + + cb_option = 'c'; + + ret = checkout_main(argc, argv, prefix, &opts, + options, switch_branch_usage); + FREE_AND_NULL(options); + return ret; +} + +int cmd_restore(int argc, const char **argv, const char *prefix) +{ + struct checkout_opts opts; + struct option *options; + struct option restore_options[] = { + OPT_STRING('s', "source", &opts.from_treeish, "<tree-ish>", + N_("which tree-ish to checkout from")), + OPT_BOOL('S', "staged", &opts.checkout_index, + N_("restore the index")), + OPT_BOOL('W', "worktree", &opts.checkout_worktree, + N_("restore the working tree (default)")), + OPT_BOOL(0, "ignore-unmerged", &opts.ignore_unmerged, + N_("ignore unmerged entries")), + OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode")), + OPT_END() + }; + int ret; + + memset(&opts, 0, sizeof(opts)); + opts.accept_ref = 0; + opts.accept_pathspec = 1; + opts.empty_pathspec_ok = 0; + opts.overlay_mode = 0; + opts.checkout_index = -1; /* default off */ + opts.checkout_worktree = -2; /* default on */ + opts.ignore_unmerged_opt = "--ignore-unmerged"; + + options = parse_options_dup(restore_options); + options = add_common_options(&opts, options); + options = add_checkout_path_options(&opts, options); + + ret = checkout_main(argc, argv, prefix, &opts, + options, restore_usage); + FREE_AND_NULL(options); + return ret; } diff --git a/builtin/clean.c b/builtin/clean.c index fad533a0a7..4ca12bc0c0 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -6,6 +6,7 @@ * Based on git-clean.sh by Pavel Roskin */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "cache.h" #include "config.h" @@ -16,6 +17,8 @@ #include "column.h" #include "color.h" #include "pathspec.h" +#include "help.h" +#include "prompt.h" static int force = -1; /* unset */ static int interactive; @@ -32,6 +35,7 @@ static const char *msg_would_remove = N_("Would remove %s\n"); static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); static const char *msg_warn_remove_failed = N_("failed to remove %s"); +static const char *msg_warn_lstat_failed = N_("could not lstat %s\n"); enum color_clean { CLEAN_COLOR_RESET = 0, @@ -42,6 +46,15 @@ enum color_clean { CLEAN_COLOR_ERROR = 5 }; +static const char *color_interactive_slots[] = { + [CLEAN_COLOR_ERROR] = "error", + [CLEAN_COLOR_HEADER] = "header", + [CLEAN_COLOR_HELP] = "help", + [CLEAN_COLOR_PLAIN] = "plain", + [CLEAN_COLOR_PROMPT] = "prompt", + [CLEAN_COLOR_RESET] = "reset", +}; + static int clean_use_color = -1; static char clean_colors[][COLOR_MAXLEN] = { [CLEAN_COLOR_ERROR] = GIT_COLOR_BOLD_RED, @@ -82,22 +95,7 @@ struct menu_stuff { void *stuff; }; -static int parse_clean_color_slot(const char *var) -{ - if (!strcasecmp(var, "reset")) - return CLEAN_COLOR_RESET; - if (!strcasecmp(var, "plain")) - return CLEAN_COLOR_PLAIN; - if (!strcasecmp(var, "prompt")) - return CLEAN_COLOR_PROMPT; - if (!strcasecmp(var, "header")) - return CLEAN_COLOR_HEADER; - if (!strcasecmp(var, "help")) - return CLEAN_COLOR_HELP; - if (!strcasecmp(var, "error")) - return CLEAN_COLOR_ERROR; - return -1; -} +define_list_config_array(color_interactive_slots); static int git_clean_config(const char *var, const char *value, void *cb) { @@ -113,7 +111,7 @@ static int git_clean_config(const char *var, const char *value, void *cb) return 0; } if (skip_prefix(var, "color.interactive.", &slot_name)) { - int slot = parse_clean_color_slot(slot_name); + int slot = LOOKUP_CONFIG(color_interactive_slots, slot_name); if (slot < 0) return 0; if (!value) @@ -145,6 +143,7 @@ static void clean_print_color(enum color_clean ix) static int exclude_cb(const struct option *opt, const char *arg, int unset) { struct string_list *exclude_list = opt->value; + BUG_ON_OPT_NEG(unset); string_list_append(exclude_list, arg); return 0; } @@ -160,7 +159,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, *dir_gone = 1; - if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) { + if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && + is_nonbare_repository_dir(path)) { if (!quiet) { quote_path_relative(path->buf, prefix, "ed); printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), @@ -197,7 +197,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, strbuf_setlen(path, len); strbuf_addstr(path, e->d_name); if (lstat(path->buf, &st)) - ; /* fall thru */ + warning_errno(_(msg_warn_lstat_failed), path->buf); else if (S_ISDIR(st.st_mode)) { if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) ret = 1; @@ -421,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). * @@ -581,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; } @@ -650,7 +647,7 @@ static int filter_by_patterns_cmd(void) struct strbuf confirm = STRBUF_INIT; struct strbuf **ignore_list; struct string_list_item *item; - struct exclude_list *el; + struct pattern_list *pl; int changed = -1, i; for (;;) { @@ -663,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 */ @@ -673,7 +668,7 @@ static int filter_by_patterns_cmd(void) break; memset(&dir, 0, sizeof(dir)); - el = add_exclude_list(&dir, EXC_CMDL, "manual exclude"); + pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude"); ignore_list = strbuf_split_max(&confirm, ' ', 0); for (i = 0; ignore_list[i]; i++) { @@ -681,7 +676,7 @@ static int filter_by_patterns_cmd(void) if (!ignore_list[i]->len) continue; - add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1)); + add_pattern(ignore_list[i]->buf, "", 0, pl, -(i+1)); } changed = 0; @@ -761,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; } @@ -903,7 +896,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct pathspec pathspec; struct strbuf buf = STRBUF_INIT; struct string_list exclude_list = STRING_LIST_INIT_NODUP; - struct exclude_list *el; + struct pattern_list *pl; struct string_list_item *item; const char *qname; struct option options[] = { @@ -913,8 +906,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")), OPT_BOOL('d', NULL, &remove_directories, N_("remove whole directories")), - { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"), - N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb }, + OPT_CALLBACK_F('e', "exclude", &exclude_list, N_("pattern"), + N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb), OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")), OPT_BOOL('X', NULL, &ignored_only, N_("remove only ignored files")), @@ -948,9 +941,19 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (force > 1) rm_flags = 0; + else + dir.flags |= DIR_SKIP_NESTED_GIT; dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; + if (argc) { + /* + * Remaining args implies pathspecs specified, and we should + * recurse within those. + */ + remove_directories = 1; + } + if (remove_directories) dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS; @@ -960,9 +963,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (!ignored) setup_standard_excludes(&dir); - el = add_exclude_list(&dir, EXC_CMDL, "--exclude option"); + pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option"); for (i = 0; i < exclude_list.nr; i++) - add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1)); + add_pattern(exclude_list.items[i].string, "", 0, pl, -(i+1)); parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, @@ -980,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(ent, &pathspec, 0, NULL); - - if (pathspec.nr && !matches) - continue; - if (lstat(ent->name, &st)) die_errno("Cannot lstat '%s'", ent->name); @@ -1009,6 +1006,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) for_each_string_list_item(item, &del_list) { struct stat st; + strbuf_reset(&abs_path); if (prefix) strbuf_addstr(&abs_path, prefix); @@ -1042,7 +1040,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } - strbuf_reset(&abs_path); } strbuf_release(&abs_path); diff --git a/builtin/clone.c b/builtin/clone.c index 7df5932b85..cb48a291ca 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -8,18 +8,23 @@ * Clone a repository into a different directory that does not yet exist. */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "config.h" #include "lockfile.h" #include "parse-options.h" #include "fetch-pack.h" #include "refs.h" +#include "refspec.h" +#include "object-store.h" #include "tree.h" #include "tree-walk.h" #include "unpack-trees.h" #include "transport.h" #include "strbuf.h" #include "dir.h" +#include "dir-iterator.h" +#include "iterator.h" #include "sigchain.h" #include "branch.h" #include "remote.h" @@ -27,7 +32,6 @@ #include "connected.h" #include "packfile.h" #include "list-objects-filter-options.h" -#include "object-store.h" /* * Overall FIXMEs: @@ -55,6 +59,7 @@ static const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress = -1; +static int option_sparse_checkout; static enum transport_family family; static struct string_list option_config = STRING_LIST_INIT_NODUP; static struct string_list option_required_reference = STRING_LIST_INIT_NODUP; @@ -63,6 +68,8 @@ static int option_dissociate; static int max_jobs = -1; static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP; static struct list_objects_filter_options filter_options; +static struct string_list server_options = STRING_LIST_INIT_NODUP; +static int option_remote_submodules; static int recurse_submodules_cb(const struct option *opt, const char *arg, int unset) @@ -95,13 +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")), - { OPTION_CALLBACK, 0, "recursive", &option_recurse_submodules, - N_("pathspec"), N_("initialize submodules in the clone"), - PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, recurse_submodules_cb, - (intptr_t)"." }, { 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"), @@ -134,11 +138,17 @@ static struct option builtin_clone_options[] = { N_("separate git dir from working tree")), OPT_STRING_LIST('c', "config", &option_config, N_("key=value"), N_("set config inside the new repository")), + OPT_STRING_LIST(0, "server-option", &server_options, + N_("server-specific"), N_("option to transmit")), OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"), TRANSPORT_FAMILY_IPV4), OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), TRANSPORT_FAMILY_IPV6), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "remote-submodules", &option_remote_submodules, + N_("any cloned submodules will use their remote-tracking branch")), + OPT_BOOL(0, "sparse", &option_sparse_checkout, + N_("initialize sparse-checkout file to include only files at root")), OPT_END() }; @@ -351,8 +361,7 @@ static void setup_reference(void) add_one_reference, &required); } -static void copy_alternates(struct strbuf *src, struct strbuf *dst, - const char *src_repo) +static void copy_alternates(struct strbuf *src, const char *src_repo) { /* * Read from the source objects/info/alternates file @@ -389,58 +398,65 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst, fclose(in); } +static void mkdir_if_missing(const char *pathname, mode_t mode) +{ + struct stat st; + + if (!mkdir(pathname, mode)) + return; + + if (errno != EEXIST) + die_errno(_("failed to create directory '%s'"), pathname); + else if (stat(pathname, &st)) + die_errno(_("failed to stat '%s'"), pathname); + else if (!S_ISDIR(st.st_mode)) + die(_("%s exists and is not a directory"), pathname); +} + static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, - const char *src_repo, int src_baselen) + const char *src_repo) { - struct dirent *de; - struct stat buf; int src_len, dest_len; - DIR *dir; - - dir = opendir(src->buf); - if (!dir) - die_errno(_("failed to open '%s'"), src->buf); - - if (mkdir(dest->buf, 0777)) { - if (errno != EEXIST) - die_errno(_("failed to create directory '%s'"), dest->buf); - else if (stat(dest->buf, &buf)) - die_errno(_("failed to stat '%s'"), dest->buf); - else if (!S_ISDIR(buf.st_mode)) - die(_("%s exists and is not a directory"), dest->buf); - } + struct dir_iterator *iter; + int iter_status; + unsigned int flags; + struct strbuf realpath = STRBUF_INIT; + + mkdir_if_missing(dest->buf, 0777); + + flags = DIR_ITERATOR_PEDANTIC | DIR_ITERATOR_FOLLOW_SYMLINKS; + iter = dir_iterator_begin(src->buf, flags); + + if (!iter) + die_errno(_("failed to start iterator over '%s'"), src->buf); strbuf_addch(src, '/'); src_len = src->len; strbuf_addch(dest, '/'); dest_len = dest->len; - while ((de = readdir(dir)) != NULL) { + while ((iter_status = dir_iterator_advance(iter)) == ITER_OK) { strbuf_setlen(src, src_len); - strbuf_addstr(src, de->d_name); + strbuf_addstr(src, iter->relative_path); strbuf_setlen(dest, dest_len); - strbuf_addstr(dest, de->d_name); - if (stat(src->buf, &buf)) { - warning (_("failed to stat %s\n"), src->buf); - continue; - } - if (S_ISDIR(buf.st_mode)) { - if (de->d_name[0] != '.') - copy_or_link_directory(src, dest, - src_repo, src_baselen); + strbuf_addstr(dest, iter->relative_path); + + if (S_ISDIR(iter->st.st_mode)) { + mkdir_if_missing(dest->buf, 0777); continue; } /* Files that cannot be copied bit-for-bit... */ - if (!strcmp(src->buf + src_baselen, "/info/alternates")) { - copy_alternates(src, dest, src_repo); + if (!fspathcmp(iter->relative_path, "info/alternates")) { + copy_alternates(src, src_repo); continue; } if (unlink(dest->buf) && errno != ENOENT) die_errno(_("failed to unlink '%s'"), dest->buf); if (!option_no_hardlinks) { - if (!link(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); @@ -449,7 +465,13 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, if (copy_file_with_time(dest->buf, src->buf, 0666)) die_errno(_("failed to copy file to '%s'"), dest->buf); } - closedir(dir); + + if (iter_status != ITER_DONE) { + 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) @@ -467,7 +489,7 @@ static void clone_local(const char *src_repo, const char *dest_repo) get_common_dir(&dest, dest_repo); strbuf_addstr(&src, "/objects"); strbuf_addstr(&dest, "/objects"); - copy_or_link_directory(&src, &dest, src_repo, src.len); + copy_or_link_directory(&src, &dest, src_repo); strbuf_release(&src); strbuf_release(&dest); } @@ -489,7 +511,7 @@ static enum { static const char junk_leave_repo_msg[] = N_("Clone succeeded, but checkout failed.\n" "You can inspect what was checked out with 'git status'\n" - "and retry the checkout with 'git checkout -f HEAD'\n"); + "and retry with 'git restore --source=HEAD :/'\n"); static void remove_junk(void) { @@ -567,13 +589,19 @@ static struct ref *wanted_peer_refs(const struct ref *refs, warning(_("Could not find remote branch %s to clone."), option_branch); else { - get_fetch_map(remote_head, refspec, &tail, 0); + int i; + for (i = 0; i < refspec->nr; i++) + get_fetch_map(remote_head, &refspec->items[i], + &tail, 0); /* if --branch=tag, pull the requested tag explicitly */ get_fetch_map(remote_head, tag_refspec, &tail, 0); } - } else - get_fetch_map(refs, refspec, &tail, 0); + } else { + int i; + for (i = 0; i < refspec->nr; i++) + get_fetch_map(refs, &refspec->items[i], &tail, 0); + } if (!option_mirror && !option_single_branch && !option_no_tags) get_fetch_map(refs, tag_refspec, &tail, 0); @@ -615,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); @@ -694,7 +724,8 @@ static void update_head(const struct ref *our, const struct ref *remote, install_branch_config(0, head, option_origin, our->name); } } else if (our) { - struct commit *c = lookup_commit_reference(&our->old_oid); + struct commit *c = lookup_commit_reference(the_repository, + &our->old_oid); /* --branch specifies a non-branch (i.e. tags), detach HEAD */ update_ref(msg, "HEAD", &c->object.oid, NULL, REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR); @@ -709,6 +740,27 @@ static void update_head(const struct ref *our, const struct ref *remote, } } +static int git_sparse_checkout_init(const char *repo) +{ + struct argv_array argv = ARGV_ARRAY_INIT; + int result = 0; + argv_array_pushl(&argv, "-C", repo, "sparse-checkout", "init", NULL); + + /* + * We must apply the setting in the current process + * for the later checkout to use the sparse-checkout file. + */ + core_apply_sparse_checkout = 1; + + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + error(_("failed to initialize sparse-checkout")); + result = 1; + } + + argv_array_clear(&argv); + return result; +} + static int checkout(int submodule_progress) { struct object_id oid; @@ -731,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(); @@ -745,10 +797,12 @@ static int checkout(int submodule_progress) memset(&opts, 0, sizeof opts); opts.update = 1; opts.merge = 1; + opts.clone = 1; opts.fn = oneway_merge; 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); @@ -756,15 +810,17 @@ 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")); - err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1), + err |= run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid), oid_to_hex(&oid), "1", NULL); if (!err && (option_recurse_submodules.nr > 0)) { struct argv_array args = ARGV_ARRAY_INIT; - argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL); + argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL); if (option_shallow_submodules == 1) argv_array_push(&args, "--depth=1"); @@ -778,6 +834,16 @@ static int checkout(int submodule_progress) if (option_verbosity < 0) argv_array_push(&args, "--quiet"); + if (option_remote_submodules) { + argv_array_push(&args, "--remote"); + argv_array_push(&args, "--no-fetch"); + } + + if (option_single_branch >= 0) + argv_array_push(&args, option_single_branch ? + "--single-branch" : + "--no-single-branch"); + err = run_command_v_opt(args.argv, RUN_GIT_CMD); argv_array_clear(&args); } @@ -823,7 +889,7 @@ static void write_refspec_config(const char *src_ref_prefix, } else if (remote_head_points_at) { const char *head = remote_head_points_at->name; if (!skip_prefix(head, "refs/heads/", &head)) - die("BUG: remote HEAD points at non-head?"); + BUG("remote HEAD points at non-head?"); strbuf_addf(&value, "+%s:%s%s", remote_head_points_at->name, branch_top->buf, head); @@ -869,7 +935,7 @@ static void dissociate_from_references(void) free(alternates); } -static int dir_exists(const char *path) +static int path_exists(const char *path) { struct stat sb; return !stat(path, &sb); @@ -886,7 +952,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const struct ref *our_head_points_at; struct ref *mapped_refs; const struct ref *ref; - struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; + struct strbuf key = STRBUF_INIT; + struct strbuf default_refspec = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; const char *src_ref_prefix = "refs/heads/"; @@ -894,10 +961,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) int err = 0, complete_refs_before_fetch = 1; int submodule_progress; - struct refspec *refspec; - const char *fetch_pattern; - - fetch_if_missing = 0; + struct argv_array ref_prefixes = ARGV_ARRAY_INIT; packet_trace_identity("clone"); argc = parse_options(argc, argv, prefix, builtin_clone_options, @@ -951,7 +1015,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) dir = guess_dir_name(repo_name, is_bundle, option_bare); strip_trailing_slashes(dir); - dest_exists = dir_exists(dir); + dest_exists = path_exists(dir); if (dest_exists && !is_empty_dir(dir)) die(_("destination path '%s' already exists and is not " "an empty directory."), dir); @@ -962,7 +1026,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) work_tree = NULL; else { work_tree = getenv("GIT_WORK_TREE"); - if (work_tree && dir_exists(work_tree)) + if (work_tree && path_exists(work_tree)) die(_("working tree '%s' already exists."), work_tree); } @@ -990,7 +1054,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } if (real_git_dir) { - if (dir_exists(real_git_dir)) + if (path_exists(real_git_dir)) junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL; junk_git_dir = real_git_dir; } else { @@ -1044,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; @@ -1063,7 +1127,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin); } - strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf); strbuf_addf(&key, "remote.%s.url", option_origin); git_config_set(key.buf, repo); strbuf_reset(&key); @@ -1077,12 +1140,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_required_reference.nr || option_optional_reference.nr) setup_reference(); - fetch_pattern = value.buf; - refspec = parse_fetch_refspec(1, &fetch_pattern); - - strbuf_reset(&value); + if (option_sparse_checkout && git_sparse_checkout_init(dir)) + return 1; remote = remote_get(option_origin); + + strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix, + branch_top.buf); + refspec_append(&remote->fetch, default_refspec.buf); + transport = transport_get(remote, remote->url[0]); transport_set_verbosity(transport, option_verbosity, option_progress); transport->family = family; @@ -1126,19 +1192,32 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_set_option(transport, TRANS_OPT_UPLOADPACK, option_upload_pack); + if (server_options.nr) + transport->server_options = &server_options; + if (filter_options.choice) { + const char *spec = + expand_list_objects_filter_spec(&filter_options); transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, - filter_options.filter_spec); + spec); transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); } if (transport->smart_options && !deepen && !filter_options.choice) transport->smart_options->check_self_contained_and_connected = 1; - refs = transport_get_remote_refs(transport); + + argv_array_push(&ref_prefixes, "HEAD"); + refspec_ref_prefixes(&remote->fetch, &ref_prefixes); + if (option_branch) + expand_ref_prefix(&ref_prefixes, option_branch); + if (!option_no_tags) + argv_array_push(&ref_prefixes, "refs/tags/"); + + refs = transport_get_remote_refs(transport, &ref_prefixes); if (refs) { - mapped_refs = wanted_peer_refs(refs, refspec); + mapped_refs = wanted_peer_refs(refs, &remote->fetch); /* * transport_get_remote_refs() may return refs with null sha-1 * in mapped_refs (see struct transport->get_refs_list @@ -1193,7 +1272,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head_points_at, &branch_top); if (filter_options.choice) - partial_clone_register("origin", &filter_options); + partial_clone_register(option_origin, &filter_options); if (is_local) clone_local(path, git_dir); @@ -1202,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); @@ -1218,20 +1297,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_disconnect(transport); if (option_dissociate) { - close_all_packs(the_repository->objects); + close_object_store(the_repository->objects); dissociate_from_references(); } junk_mode = JUNK_LEAVE_REPO; - fetch_if_missing = 1; err = checkout(submodule_progress); strbuf_release(&reflog_msg); strbuf_release(&branch_top); strbuf_release(&key); - strbuf_release(&value); + strbuf_release(&default_refspec); junk_mode = JUNK_LEAVE_ALL; - free(refspec); + argv_array_clear(&ref_prefixes); return err; } diff --git a/builtin/column.c b/builtin/column.c index 0c3223d64b..e815e148aa 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -42,9 +42,8 @@ int cmd_column(int argc, const char **argv, const char *prefix) git_config(column_config, NULL); memset(&copts, 0, sizeof(copts)); - copts.width = term_columns(); copts.padding = 1; - argc = parse_options(argc, argv, "", options, builtin_column_usage, 0); + argc = parse_options(argc, argv, prefix, options, builtin_column_usage, 0); if (argc) usage_with_options(builtin_column_usage, options); if (real_command || command) { diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c new file mode 100644 index 0000000000..75455da138 --- /dev/null +++ b/builtin/commit-graph.c @@ -0,0 +1,302 @@ +#include "builtin.h" +#include "config.h" +#include "dir.h" +#include "lockfile.h" +#include "parse-options.h" +#include "repository.h" +#include "commit-graph.h" +#include "object-store.h" +#include "progress.h" +#include "tag.h" + +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[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " + "[--changed-paths] [--[no-]progress] <split options>"), + NULL +}; + +static const char * const builtin_commit_graph_verify_usage[] = { + N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), + NULL +}; + +static const char * const builtin_commit_graph_write_usage[] = { + N_("git commit-graph write [--object-dir <objdir>] [--append] " + "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " + "[--changed-paths] [--[no-]progress] <split options>"), + NULL +}; + +static struct opts_commit_graph { + const char *obj_dir; + int reachable; + int stdin_packs; + int stdin_commits; + int append; + int split; + int shallow; + int progress; + int enable_changed_paths; +} opts; + +static struct object_directory *find_odb(struct repository *r, + const char *obj_dir) +{ + 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) { + 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); + return odb; +} + +static int graph_verify(int argc, const char **argv) +{ + struct commit_graph *graph = NULL; + struct object_directory *odb = NULL; + char *graph_name; + int open_ok; + int fd; + struct stat st; + int flags = 0; + + static struct option builtin_commit_graph_verify_options[] = { + OPT_STRING(0, "object-dir", &opts.obj_dir, + N_("dir"), + N_("The object directory to store the graph")), + OPT_BOOL(0, "shallow", &opts.shallow, + N_("if the commit-graph is split, only verify the tip file")), + OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), + OPT_END(), + }; + + trace2_cmd_mode("verify"); + + opts.progress = isatty(2); + argc = parse_options(argc, argv, NULL, + builtin_commit_graph_verify_options, + builtin_commit_graph_verify_usage, 0); + + if (!opts.obj_dir) + opts.obj_dir = get_object_directory(); + if (opts.shallow) + flags |= COMMIT_GRAPH_VERIFY_SHALLOW; + if (opts.progress) + flags |= COMMIT_GRAPH_WRITE_PROGRESS; + + odb = find_odb(the_repository, opts.obj_dir); + graph_name = get_commit_graph_filename(odb); + open_ok = open_commit_graph(graph_name, &fd, &st); + if (!open_ok && errno != ENOENT) + die_errno(_("Could not open commit-graph '%s'"), graph_name); + + FREE_AND_NULL(graph_name); + + if (open_ok) + graph = load_commit_graph_one_fd_st(fd, &st, odb); + else + graph = read_commit_graph_one(the_repository, odb); + + /* Return failure if open_ok predicted success */ + if (!graph) + return !!open_ok; + + UNLEAK(graph); + return verify_commit_graph(the_repository, graph, flags); +} + +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 read_one_commit(struct oidset *commits, struct progress *progress, + const char *hash) +{ + struct object *result; + struct object_id oid; + const char *end; + + if (parse_oid_hex(hash, &oid, &end)) + return error(_("unexpected non-hex object ID: %s"), hash); + + result = deref_tag(the_repository, parse_object(the_repository, &oid), + NULL, 0); + if (!result) + return error(_("invalid object: %s"), hash); + else if (object_as_type(the_repository, result, OBJ_COMMIT, 1)) + oidset_insert(commits, &result->oid); + + display_progress(progress, oidset_size(commits)); + + return 0; +} + +static int graph_write(int argc, const char **argv) +{ + struct string_list pack_indexes = STRING_LIST_INIT_NODUP; + struct strbuf buf = STRBUF_INIT; + struct oidset commits = OIDSET_INIT; + struct object_directory *odb = NULL; + int result = 0; + enum commit_graph_write_flags flags = 0; + struct progress *progress = NULL; + + static struct option builtin_commit_graph_write_options[] = { + OPT_STRING(0, "object-dir", &opts.obj_dir, + N_("dir"), + N_("The object directory to store the graph")), + OPT_BOOL(0, "reachable", &opts.reachable, + N_("start walk at all refs")), + OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, + N_("scan pack-indexes listed by stdin for commits")), + OPT_BOOL(0, "stdin-commits", &opts.stdin_commits, + 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_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_("only expire files older than a given date-time")), + OPT_END(), + }; + + opts.progress = isatty(2); + split_opts.size_multiple = 2; + split_opts.max_commits = 0; + split_opts.expire_time = 0; + + trace2_cmd_mode("write"); + + argc = parse_options(argc, argv, NULL, + builtin_commit_graph_write_options, + builtin_commit_graph_write_usage, 0); + + if (opts.reachable + opts.stdin_packs + opts.stdin_commits > 1) + die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs")); + if (!opts.obj_dir) + opts.obj_dir = get_object_directory(); + if (opts.append) + flags |= COMMIT_GRAPH_WRITE_APPEND; + if (opts.split) + 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); + + if (opts.reachable) { + if (write_commit_graph_reachable(odb, flags, &split_opts)) + return 1; + return 0; + } + + if (opts.stdin_packs) { + while (strbuf_getline(&buf, stdin) != EOF) + string_list_append(&pack_indexes, + strbuf_detach(&buf, NULL)); + } else if (opts.stdin_commits) { + oidset_init(&commits, 0); + if (opts.progress) + progress = start_delayed_progress( + _("Collecting commits from input"), 0); + + while (strbuf_getline(&buf, stdin) != EOF) { + if (read_one_commit(&commits, progress, buf.buf)) { + result = 1; + goto cleanup; + } + } + + + } + + if (write_commit_graph(odb, + opts.stdin_packs ? &pack_indexes : NULL, + opts.stdin_commits ? &commits : NULL, + flags, + &split_opts)) + result = 1; + +cleanup: + string_list_clear(&pack_indexes, 0); + strbuf_release(&buf); + if (progress) + stop_progress(&progress); + return result; +} + +int cmd_commit_graph(int argc, const char **argv, const char *prefix) +{ + static struct option builtin_commit_graph_options[] = { + OPT_STRING(0, "object-dir", &opts.obj_dir, + N_("dir"), + N_("The object directory to store the graph")), + OPT_END(), + }; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_commit_graph_usage, + builtin_commit_graph_options); + + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, + builtin_commit_graph_options, + builtin_commit_graph_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + save_commit_buffer = 0; + + if (argc > 0) { + if (!strcmp(argv[0], "verify")) + return graph_verify(argc, argv); + if (!strcmp(argv[0], "write")) + return graph_write(argc, argv); + } + + usage_with_options(builtin_commit_graph_usage, + builtin_commit_graph_options); +} diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index ecf42191da..1031b9a491 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -5,13 +5,20 @@ */ #include "cache.h" #include "config.h" +#include "object-store.h" +#include "repository.h" #include "commit.h" #include "tree.h" #include "builtin.h" #include "utf8.h" #include "gpg-interface.h" +#include "parse-options.h" -static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1>"; +static const char * const commit_tree_usage[] = { + N_("git commit-tree [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...] " + "[(-F <file>)...] <tree>"), + NULL +}; static const char *sign_commit; @@ -21,7 +28,7 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p) struct commit_list *parents; for (parents = *parents_p; parents; parents = parents->next) { if (parents->item == parent) { - error("duplicate parent %s ignored", oid_to_hex(oid)); + error(_("duplicate parent %s ignored"), oid_to_hex(oid)); return; } parents_p = &parents->next; @@ -37,84 +44,100 @@ static int commit_tree_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int parse_parent_arg_callback(const struct option *opt, + const char *arg, int unset) +{ + struct object_id oid; + struct commit_list **parents = opt->value; + + BUG_ON_OPT_NEG_NOARG(unset, arg); + + if (get_oid_commit(arg, &oid)) + die(_("not a valid object name %s"), arg); + + assert_oid_type(&oid, OBJ_COMMIT); + new_parent(lookup_commit(the_repository, &oid), parents); + return 0; +} + +static int parse_message_arg_callback(const struct option *opt, + const char *arg, int unset) +{ + struct strbuf *buf = opt->value; + + BUG_ON_OPT_NEG_NOARG(unset, arg); + + if (buf->len) + strbuf_addch(buf, '\n'); + strbuf_addstr(buf, arg); + strbuf_complete_line(buf); + + return 0; +} + +static int parse_file_arg_callback(const struct option *opt, + const char *arg, int unset) +{ + int fd; + struct strbuf *buf = opt->value; + + BUG_ON_OPT_NEG_NOARG(unset, arg); + + if (buf->len) + strbuf_addch(buf, '\n'); + if (!strcmp(arg, "-")) + fd = 0; + else { + fd = open(arg, O_RDONLY); + if (fd < 0) + die_errno(_("git commit-tree: failed to open '%s'"), arg); + } + if (strbuf_read(buf, fd, 0) < 0) + die_errno(_("git commit-tree: failed to read '%s'"), arg); + if (fd && close(fd)) + die_errno(_("git commit-tree: failed to close '%s'"), arg); + + return 0; +} + int cmd_commit_tree(int argc, const char **argv, const char *prefix) { - int i, got_tree = 0; + static struct strbuf buffer = STRBUF_INIT; struct commit_list *parents = NULL; struct object_id tree_oid; struct object_id commit_oid; - struct strbuf buffer = STRBUF_INIT; + + struct option options[] = { + OPT_CALLBACK_F('p', NULL, &parents, N_("parent"), + N_("id of a parent commit object"), PARSE_OPT_NONEG, + parse_parent_arg_callback), + OPT_CALLBACK_F('m', NULL, &buffer, N_("message"), + N_("commit message"), PARSE_OPT_NONEG, + parse_message_arg_callback), + OPT_CALLBACK_F('F', NULL, &buffer, N_("file"), + N_("read commit log message from file"), PARSE_OPT_NONEG, + parse_file_arg_callback), + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), + N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_END() + }; git_config(commit_tree_config, NULL); if (argc < 2 || !strcmp(argv[1], "-h")) - usage(commit_tree_usage); - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "-p")) { - struct object_id oid; - if (argc <= ++i) - usage(commit_tree_usage); - if (get_oid_commit(argv[i], &oid)) - die("Not a valid object name %s", argv[i]); - assert_oid_type(&oid, OBJ_COMMIT); - new_parent(lookup_commit(&oid), &parents); - continue; - } - - if (skip_prefix(arg, "-S", &sign_commit)) - continue; + usage_with_options(commit_tree_usage, options); - if (!strcmp(arg, "--no-gpg-sign")) { - sign_commit = NULL; - continue; - } + argc = parse_options(argc, argv, prefix, options, commit_tree_usage, 0); - if (!strcmp(arg, "-m")) { - if (argc <= ++i) - usage(commit_tree_usage); - if (buffer.len) - strbuf_addch(&buffer, '\n'); - strbuf_addstr(&buffer, argv[i]); - strbuf_complete_line(&buffer); - continue; - } + if (argc != 1) + die(_("must give exactly one tree")); - if (!strcmp(arg, "-F")) { - int fd; - - if (argc <= ++i) - usage(commit_tree_usage); - if (buffer.len) - strbuf_addch(&buffer, '\n'); - if (!strcmp(argv[i], "-")) - fd = 0; - else { - fd = open(argv[i], O_RDONLY); - if (fd < 0) - die_errno("git commit-tree: failed to open '%s'", - argv[i]); - } - if (strbuf_read(&buffer, fd, 0) < 0) - die_errno("git commit-tree: failed to read '%s'", - argv[i]); - if (fd && close(fd)) - die_errno("git commit-tree: failed to close '%s'", - argv[i]); - continue; - } - - if (get_oid_tree(arg, &tree_oid)) - die("Not a valid object name %s", arg); - if (got_tree) - die("Cannot give more than one trees"); - got_tree = 1; - } + if (get_oid_tree(argv[0], &tree_oid)) + die(_("not a valid object name %s"), argv[0]); if (!buffer.len) { if (strbuf_read(&buffer, 0, 0) < 0) - die_errno("git commit-tree: failed to read"); + die_errno(_("git commit-tree: failed to read")); } if (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid, diff --git a/builtin/commit.c b/builtin/commit.c index 37fcb55ab0..d1b7396052 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -5,6 +5,7 @@ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "lockfile.h" @@ -32,6 +33,9 @@ #include "column.h" #include "sequencer.h" #include "mailmap.h" +#include "help.h" +#include "commit-reach.h" +#include "commit-graph.h" static const char * const builtin_commit_usage[] = { N_("git commit [<options>] [--] <pathspec>..."), @@ -55,16 +59,34 @@ 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 reset'\n"); +N_("Otherwise, please use 'git cherry-pick --skip'\n"); static const char empty_cherry_pick_advice_multi[] = -N_("If you wish to skip this commit, use:\n" +N_("and then use:\n" +"\n" +" git cherry-pick --continue\n" "\n" -" git reset\n" +"to resume cherry-picking the remaining commits.\n" +"If you wish to skip this commit, use:\n" "\n" -"Then \"git cherry-pick --continue\" will resume cherry-picking\n" -"the remaining commits.\n"); +" git cherry-pick --skip\n" +"\n"); + +static const char *color_status_slots[] = { + [WT_STATUS_HEADER] = "header", + [WT_STATUS_UPDATED] = "updated", + [WT_STATUS_CHANGED] = "changed", + [WT_STATUS_UNTRACKED] = "untracked", + [WT_STATUS_NOBRANCH] = "noBranch", + [WT_STATUS_UNMERGED] = "unmerged", + [WT_STATUS_LOCAL_BRANCH] = "localBranch", + [WT_STATUS_REMOTE_BRANCH] = "remoteBranch", + [WT_STATUS_ONBRANCH] = "branch", +}; static const char *use_message_buffer; static struct lock_file index_lock; /* real index */ @@ -88,9 +110,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff; static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int config_commit_verbose = -1; /* unspecified */ -static int no_post_rewrite, allow_empty_message; +static int no_post_rewrite, allow_empty_message, pathspec_file_nul; static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg; -static char *sign_commit; +static char *sign_commit, *pathspec_from_file; /* * The default commit message cleanup mode will remove the lines @@ -103,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; @@ -143,16 +164,24 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) return 0; } +static int opt_parse_rename_score(const struct option *opt, const char *arg, int unset) +{ + const char **value = opt->value; + + BUG_ON_OPT_NEG(unset); + + if (arg != NULL && *arg == '=') + arg = arg + 1; + + *value = arg; + return 0; +} + static void determine_whence(struct wt_status *s) { - if (file_exists(git_path_merge_head())) + if (file_exists(git_path_merge_head(the_repository))) whence = FROM_MERGE; - else if (file_exists(git_path_cherry_pick_head())) { - 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; @@ -160,10 +189,10 @@ static void determine_whence(struct wt_status *s) static void status_init_config(struct wt_status *s, config_fn_t fn) { - wt_status_prepare(s); + wt_status_prepare(the_repository, s); + init_diff_ui_defaults(); git_config(fn, s); determine_whence(s); - init_diff_ui_defaults(); s->hints = advice_status_hints; /* must come after git_config() */ } @@ -206,7 +235,7 @@ static int commit_index_files(void) * and return the paths that match the given pattern in list. */ static int list_paths(struct string_list *list, const char *with_tree, - const char *prefix, const struct pathspec *pattern) + const struct pathspec *pattern) { int i, ret; char *m; @@ -218,8 +247,7 @@ static int list_paths(struct string_list *list, const char *with_tree, if (with_tree) { char *max_prefix = common_prefix(pattern); - overlay_tree_on_index(&the_index, with_tree, - max_prefix ? max_prefix : prefix); + overlay_tree_on_index(&the_index, with_tree, max_prefix); free(max_prefix); } @@ -229,14 +257,14 @@ static int list_paths(struct string_list *list, const char *with_tree, if (ce->ce_flags & CE_UPDATE) continue; - if (!ce_path_match(ce, pattern, m)) + if (!ce_path_match(&the_index, ce, pattern, m)) continue; item = string_list_insert(list, ce->name); if (ce_skip_worktree(ce)) item->util = item; /* better a valid pointer than a fake one */ } - ret = report_path_error(m, pattern, prefix); + ret = report_path_error(m, pattern); free(m); return ret; } @@ -312,11 +340,31 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix PATHSPEC_PREFER_FULL, prefix, argv); + if (pathspec_from_file) { + if (interactive) + die(_("--pathspec-from-file is incompatible with --interactive/--patch")); + + if (all) + die(_("--pathspec-from-file with -a does not make sense")); + + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (!pathspec.nr && (also || (only && !amend && !allow_empty))) + die(_("No paths with --include/--only does not make sense.")); + if (read_cache_preload(&pathspec) < 0) die(_("index file corrupt")); if (interactive) { - char *old_index_env = NULL; + char *old_index_env = NULL, *old_repo_index_file; hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); @@ -324,16 +372,21 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to create temporary index")); - old_index_env = getenv(INDEX_ENVIRONMENT); - setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1); + old_repo_index_file = the_repository->index_file; + the_repository->index_file = + (char *)get_lock_file_path(&index_lock); + old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); + setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); if (interactive_add(argc, argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); + the_repository->index_file = old_repo_index_file; if (old_index_env && *old_index_env) setenv(INDEX_ENVIRONMENT, old_index_env, 1); else unsetenv(INDEX_ENVIRONMENT); + FREE_AND_NULL(old_index_env); discard_cache(); read_cache_from(get_lock_file_path(&index_lock)); @@ -421,11 +474,13 @@ 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", prefix, &pathspec)) + if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec)) exit(1); discard_cache(); @@ -478,14 +533,15 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->nowarn = nowarn; s->is_initial = get_oid(s->reference, &oid) ? 1 : 0; if (!s->is_initial) - hashcpy(s->sha1_commit, oid.hash); + oidcpy(&s->oid_commit, &oid); s->status_format = status_format; s->ignore_submodule_arg = ignore_submodule_arg; wt_status_collect(s); wt_status_print(s); + wt_status_collect_free_buffers(s); - return s->commitable; + return s->committable; } static int is_a_merge(const struct commit *current_head) @@ -496,7 +552,7 @@ static int is_a_merge(const struct commit *current_head) static void assert_split_ident(struct ident_split *id, const struct strbuf *buf) { if (split_ident_line(id, buf->buf, buf->len) || !id->date_begin) - die("BUG: unable to parse our own ident: %s", buf->buf); + BUG("unable to parse our own ident: %s", buf->buf); } static void export_one(const char *var, const char *s, const char *e, int hack) @@ -504,7 +560,7 @@ static void export_one(const char *var, const char *s, const char *e, int hack) struct strbuf buf = STRBUF_INIT; if (hack) strbuf_addch(&buf, hack); - strbuf_addf(&buf, "%.*s", (int)(e - s), s); + strbuf_add(&buf, s, e - s); setenv(var, buf.buf, 1); strbuf_release(&buf); } @@ -579,7 +635,8 @@ static void determine_author_info(struct strbuf *author_ident) set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } - strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT)); + strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, + IDENT_STRICT)); assert_split_ident(&author, author_ident); export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); @@ -631,12 +688,13 @@ static int prepare_to_commit(const char *index_file, const char *prefix, { struct stat statbuf; struct strbuf committer_ident = STRBUF_INIT; - int commitable; + int committable; struct strbuf sb = STRBUF_INIT; const char *hook_arg1 = NULL; const char *hook_arg2 = NULL; int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE); int old_display_comment_prefix; + int merge_contains_scissors = 0; /* This checks and barfs if author is badly specified */ determine_author_info(author_ident); @@ -696,21 +754,31 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (have_option_m) strbuf_addbuf(&sb, &message); hook_arg1 = "message"; - } else if (!stat(git_path_merge_msg(), &statbuf)) { + } else if (!stat(git_path_merge_msg(the_repository), &statbuf)) { + size_t merge_msg_start; + /* * prepend SQUASH_MSG here if it exists and a * "merge --squash" was originally performed */ - if (!stat(git_path_squash_msg(), &statbuf)) { - if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0) + if (!stat(git_path_squash_msg(the_repository), &statbuf)) { + if (strbuf_read_file(&sb, git_path_squash_msg(the_repository), 0) < 0) die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; } else hook_arg1 = "merge"; - if (strbuf_read_file(&sb, git_path_merge_msg(), 0) < 0) + + merge_msg_start = sb.len; + if (strbuf_read_file(&sb, git_path_merge_msg(the_repository), 0) < 0) die_errno(_("could not read MERGE_MSG")); - } else if (!stat(git_path_squash_msg(), &statbuf)) { - if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0) + + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS && + wt_status_locate_end(sb.buf + merge_msg_start, + sb.len - merge_msg_start) < + sb.len - merge_msg_start) + merge_contains_scissors = 1; + } else if (!stat(git_path_squash_msg(the_repository), &statbuf)) { + if (strbuf_read_file(&sb, git_path_squash_msg(the_repository), 0) < 0) die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; } else if (template_file) { @@ -726,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"; } @@ -776,7 +844,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, struct ident_split ci, ai; if (whence != FROM_COMMIT) { - if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS && + !merge_contains_scissors) wt_status_add_cut_line(s->fp); status_printf_ln(s, GIT_COLOR_NORMAL, whence == FROM_MERGE @@ -791,8 +860,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, " %s\n" "and try again.\n"), whence == FROM_MERGE ? - git_path_merge_head() : - git_path_cherry_pick_head()); + git_path_merge_head(the_repository) : + git_path_cherry_pick_head(the_repository)); } fprintf(s->fp, "\n"); @@ -801,10 +870,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix, _("Please enter the commit message for your changes." " Lines starting\nwith '%c' will be ignored, and an empty" " message aborts the commit.\n"), comment_line_char); - else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS && - whence == FROM_COMMIT) - wt_status_add_cut_line(s->fp); - else /* COMMIT_MSG_CLEANUP_SPACE, that is. */ + else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) { + if (whence == FROM_COMMIT && !merge_contains_scissors) + wt_status_add_cut_line(s->fp); + } else /* COMMIT_MSG_CLEANUP_SPACE, that is. */ status_printf(s, GIT_COLOR_NORMAL, _("Please enter the commit message for your changes." " Lines starting\n" @@ -848,8 +917,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix, saved_color_setting = s->use_color; s->use_color = 0; - commitable = run_status(s->fp, index_file, prefix, 1, s); + committable = run_status(s->fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; + string_list_clear(&s->change, 1); } else { struct object_id oid; const char *parent = "HEAD"; @@ -866,7 +936,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, for (i = 0; i < active_nr; i++) if (ce_intent_to_add(active_cache[i])) ita_nr++; - commitable = active_nr - ita_nr > 0; + committable = active_nr - ita_nr > 0; } else { /* * Unless the user did explicitly request a submodule @@ -882,7 +952,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (ignore_submodule_arg && !strcmp(ignore_submodule_arg, "all")) flags.ignore_submodules = 1; - commitable = index_differs_from(parent, &flags, 1); + committable = index_differs_from(the_repository, + parent, &flags, 1); } } strbuf_release(&committer_ident); @@ -894,18 +965,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix, * explicit --allow-empty. In the cherry-pick case, it may be * empty due to conflict resolution, which the user should okay. */ - if (!commitable && whence != FROM_MERGE && !allow_empty && + if (!committable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { + s->hints = advice_status_hints; s->display_comment_prefix = old_display_comment_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; } @@ -958,7 +1033,7 @@ static const char *find_author_by_nickname(const char *name) const char *av[20]; int ac = 0; - init_revisions(&revs, NULL); + repo_init_revisions(the_repository, &revs, NULL); strbuf_addf(&buf, "--author=%s", name); av[++ac] = "--all"; av[++ac] = "-i"; @@ -1006,6 +1081,10 @@ static void handle_untracked_files_arg(struct wt_status *s) s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; else if (!strcmp(untracked_files_arg, "all")) s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + /* + * Please update $__git_untracked_file_modes in + * git-completion.bash when you add new options + */ else die(_("Invalid untracked files mode '%s'"), untracked_files_arg); } @@ -1029,9 +1108,11 @@ static const char *read_commit_message(const char *name) static struct status_deferred_config { enum wt_status_format status_format; int show_branch; + enum ahead_behind_flags ahead_behind; } status_deferred_config = { STATUS_FORMAT_UNSPECIFIED, - -1 /* unspecified */ + -1, /* unspecified */ + AHEAD_BEHIND_UNSPECIFIED, }; static void finalize_deferred_config(struct wt_status *s) @@ -1058,6 +1139,17 @@ static void finalize_deferred_config(struct wt_status *s) if (s->show_branch < 0) s->show_branch = 0; + /* + * If the user did not give a "--[no]-ahead-behind" command + * line argument *AND* we will print in a human-readable format + * (short, long etc.) then we inherit from the status.aheadbehind + * config setting. In all other cases (and porcelain V[12] formats + * in particular), we inherit _FULL for backwards compatibility. + */ + if (use_deferred_config && + s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED) + s->ahead_behind_flags = status_deferred_config.ahead_behind; + if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED) s->ahead_behind_flags = AHEAD_BEHIND_FULL; } @@ -1091,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")); @@ -1114,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); @@ -1123,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); } @@ -1133,27 +1229,13 @@ static int parse_and_validate_options(int argc, const char *argv[], if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - if (argc == 0 && (also || (only && !amend && !allow_empty))) - die(_("No paths with --include/--only does not make sense.")); - if (!cleanup_arg || !strcmp(cleanup_arg, "default")) - cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_ALL : - COMMIT_MSG_CLEANUP_SPACE; - else if (!strcmp(cleanup_arg, "verbatim")) - cleanup_mode = COMMIT_MSG_CLEANUP_NONE; - else if (!strcmp(cleanup_arg, "whitespace")) - cleanup_mode = COMMIT_MSG_CLEANUP_SPACE; - else if (!strcmp(cleanup_arg, "strip")) - cleanup_mode = COMMIT_MSG_CLEANUP_ALL; - else if (!strcmp(cleanup_arg, "scissors")) - cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_SCISSORS : - COMMIT_MSG_CLEANUP_SPACE; - else - die(_("Invalid cleanup mode %s"), cleanup_arg); + cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor); handle_untracked_files_arg(s); if (all && argc > 0) - die(_("Paths with -a does not make sense.")); + die(_("paths '%s ...' with -a does not make sense"), + argv[0]); if (status_format != STATUS_FORMAT_NONE) dry_run = 1; @@ -1164,37 +1246,24 @@ static int parse_and_validate_options(int argc, const char *argv[], static int dry_run_commit(int argc, const char **argv, const char *prefix, const struct commit *current_head, struct wt_status *s) { - int commitable; + int committable; const char *index_file; index_file = prepare_index(argc, argv, prefix, current_head, 1); - commitable = run_status(stdout, index_file, prefix, 0, s); + committable = run_status(stdout, index_file, prefix, 0, s); rollback_index_files(); - return commitable ? 0 : 1; + return committable ? 0 : 1; } +define_list_config_array_extra(color_status_slots, {"added"}); + static int parse_status_slot(const char *slot) { - if (!strcasecmp(slot, "header")) - return WT_STATUS_HEADER; - if (!strcasecmp(slot, "branch")) - return WT_STATUS_ONBRANCH; - if (!strcasecmp(slot, "updated") || !strcasecmp(slot, "added")) + if (!strcasecmp(slot, "added")) return WT_STATUS_UPDATED; - if (!strcasecmp(slot, "changed")) - return WT_STATUS_CHANGED; - if (!strcasecmp(slot, "untracked")) - return WT_STATUS_UNTRACKED; - if (!strcasecmp(slot, "nobranch")) - return WT_STATUS_NOBRANCH; - if (!strcasecmp(slot, "unmerged")) - return WT_STATUS_UNMERGED; - if (!strcasecmp(slot, "localBranch")) - return WT_STATUS_LOCAL_BRANCH; - if (!strcasecmp(slot, "remoteBranch")) - return WT_STATUS_REMOTE_BRANCH; - return -1; + + return LOOKUP_CONFIG(color_status_slots, slot); } static int git_status_config(const char *k, const char *v, void *cb) @@ -1222,6 +1291,10 @@ static int git_status_config(const char *k, const char *v, void *cb) status_deferred_config.show_branch = git_config_bool(k, v); return 0; } + if (!strcmp(k, "status.aheadbehind")) { + status_deferred_config.ahead_behind = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "status.showstash")) { s->show_stash = git_config_bool(k, v); return 0; @@ -1260,12 +1333,33 @@ static int git_status_config(const char *k, const char *v, void *cb) return error(_("Invalid untracked files mode '%s'"), v); return 0; } + if (!strcmp(k, "diff.renamelimit")) { + if (s->rename_limit == -1) + s->rename_limit = git_config_int(k, v); + return 0; + } + if (!strcmp(k, "status.renamelimit")) { + s->rename_limit = git_config_int(k, v); + return 0; + } + if (!strcmp(k, "diff.renames")) { + if (s->detect_rename == -1) + s->detect_rename = git_config_rename(k, v); + return 0; + } + if (!strcmp(k, "status.renames")) { + s->detect_rename = git_config_rename(k, v); + return 0; + } return git_diff_ui_config(k, v, NULL); } int cmd_status(int argc, const char **argv, const char *prefix) { + static int no_renames = -1; + static const char *rename_score_arg = (const char *)-1; static struct wt_status s; + unsigned int progress_flag = 0; int fd; struct object_id oid; static struct option builtin_status_options[] = { @@ -1278,9 +1372,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) N_("show stash information")), OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags, N_("compute full ahead/behind values")), - { OPTION_CALLBACK, 0, "porcelain", &status_format, + OPT_CALLBACK_F(0, "porcelain", &status_format, N_("version"), N_("machine-readable output"), - PARSE_OPT_OPTARG, opt_parse_porcelain }, + PARSE_OPT_OPTARG, opt_parse_porcelain), OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), @@ -1298,6 +1392,10 @@ int cmd_status(int argc, const char **argv, const char *prefix) N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")), + OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")), + OPT_CALLBACK_F('M', "find-renames", &rename_score_arg, + N_("n"), N_("detect renames, optionally set similarity index"), + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score), OPT_END(), }; @@ -1322,8 +1420,13 @@ int cmd_status(int argc, const char **argv, const char *prefix) PATHSPEC_PREFER_FULL, prefix, argv); - read_cache_preload(&s.pathspec); - refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL); + if (status_format != STATUS_FORMAT_PORCELAIN && + status_format != STATUS_FORMAT_PORCELAIN_V2) + progress_flag = REFRESH_PROGRESS; + repo_read_index(the_repository); + refresh_index(&the_index, + REFRESH_QUIET|REFRESH_UNMERGED|progress_flag, + &s.pathspec, NULL, NULL); if (use_optional_locks()) fd = hold_locked_index(&index_lock, 0); @@ -1332,21 +1435,31 @@ int cmd_status(int argc, const char **argv, const char *prefix) s.is_initial = get_oid(s.reference, &oid) ? 1 : 0; if (!s.is_initial) - hashcpy(s.sha1_commit, oid.hash); + oidcpy(&s.oid_commit, &oid); s.ignore_submodule_arg = ignore_submodule_arg; s.status_format = status_format; s.verbose = verbose; + if (no_renames != -1) + s.detect_rename = !no_renames; + if ((intptr_t)rename_score_arg != -1) { + if (s.detect_rename < DIFF_DETECT_RENAME) + s.detect_rename = DIFF_DETECT_RENAME; + if (rename_score_arg) + s.rename_score = parse_rename_score(&rename_score_arg); + } wt_status_collect(&s); if (0 <= fd) - update_index_if_able(&the_index, &index_lock); + repo_update_index_if_able(the_repository, &index_lock); if (s.relative_paths) s.prefix = prefix; wt_status_print(&s); + wt_status_collect_free_buffers(&s); + return 0; } @@ -1379,31 +1492,8 @@ static int git_commit_config(const char *k, const char *v, void *cb) return git_status_config(k, v, s); } -int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) -{ - struct argv_array hook_env = ARGV_ARRAY_INIT; - va_list args; - int ret; - - argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); - - /* - * Let the hook know that no editor will be launched. - */ - if (!editor_is_used) - argv_array_push(&hook_env, "GIT_EDITOR=:"); - - va_start(args, name); - ret = run_hook_ve(hook_env.argv,name, args); - va_end(args); - argv_array_clear(&hook_env); - - return ret; -} - int cmd_commit(int argc, const char **argv, const char *prefix) { - const char *argv_gc_auto[] = {"gc", "--auto", NULL}; static struct wt_status s; static struct option builtin_commit_options[] = { OPT__QUIET(&quiet, N_("suppress summary after successful commit")), @@ -1422,7 +1512,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), - OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")), + OPT_CLEANUP(&cleanup_arg), OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, @@ -1451,6 +1541,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), /* end commit contents options */ OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty, @@ -1523,7 +1615,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!reflog_msg) reflog_msg = "commit (merge)"; pptr = commit_list_append(current_head, pptr); - fp = xfopen(git_path_merge_head(), "r"); + fp = xfopen(git_path_merge_head(the_repository), "r"); while (strbuf_getline_lf(&m, fp) != EOF) { struct commit *parent; @@ -1534,8 +1626,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } fclose(fp); strbuf_release(&m); - if (!stat(git_path_merge_mode(), &statbuf)) { - if (strbuf_read_file(&sb, git_path_merge_mode(), 0) < 0) + if (!stat(git_path_merge_mode(the_repository), &statbuf)) { + if (strbuf_read_file(&sb, git_path_merge_mode(the_repository), 0) < 0) die_errno(_("could not read MERGE_MODE")); if (!strcmp(sb.buf, "no-ff")) allow_fast_forward = 0; @@ -1544,8 +1636,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); } @@ -1558,11 +1652,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) die(_("could not read commit message: %s"), strerror(saved_errno)); } - if (verbose || /* Truncate the message just before the diff, if any. */ - cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) - strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len)); - if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE) - strbuf_stripspace(&sb, cleanup_mode == COMMIT_MSG_CLEANUP_ALL); + cleanup_message(&sb, cleanup_mode, verbose); if (message_is_empty(&sb, cleanup_mode) && !allow_empty_message) { rollback_index_files(); @@ -1576,7 +1666,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; @@ -1598,23 +1688,24 @@ int cmd_commit(int argc, const char **argv, const char *prefix) die("%s", err.buf); } - unlink(git_path_cherry_pick_head()); - unlink(git_path_revert_head()); - unlink(git_path_merge_head()); - unlink(git_path_merge_msg()); - unlink(git_path_merge_mode()); - unlink(git_path_squash_msg()); + sequencer_post_commit_cleanup(the_repository, 0); + unlink(git_path_merge_head(the_repository)); + unlink(git_path_merge_msg(the_repository)); + unlink(git_path_merge_mode(the_repository)); + unlink(git_path_squash_msg(the_repository)); if (commit_index_files()) - die (_("Repository has been updated, but unable to write\n" - "new_index file. Check that disk is not full and quota is\n" - "not exceeded, and then \"git reset HEAD\" to recover.")); + die(_("repository has been updated, but unable to write\n" + "new_index file. Check that disk is not full and quota is\n" + "not exceeded, and then \"git restore --staged :/\" to recover.")); + + git_test_write_commit_graph_or_die(); - rerere(0); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + repo_rerere(the_repository, 0); + run_auto_gc(quiet); run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { - commit_post_rewrite(current_head, &oid); + commit_post_rewrite(the_repository, current_head, &oid); } if (!quiet) { unsigned int flags = 0; @@ -1623,9 +1714,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) flags |= SUMMARY_INITIAL_COMMIT; if (author_date_is_interesting()) flags |= SUMMARY_SHOW_AUTHOR_DATE; - print_commit_summary(prefix, &oid, flags); + print_commit_summary(the_repository, prefix, + &oid, flags); } + apply_autostash(git_path_merge_autostash(the_repository)); + UNLEAK(err); UNLEAK(sb); return 0; diff --git a/builtin/config.c b/builtin/config.c index 01169dd628..ee4aef6a35 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -5,6 +5,7 @@ #include "parse-options.h" #include "urlmatch.h" #include "quote.h" +#include "worktree.h" static const char *const builtin_config_usage[] = { N_("git config [<options>]"), @@ -24,12 +25,15 @@ static char key_delim = ' '; static char term = '\n'; static int use_global_config, use_system_config, use_local_config; +static int use_worktree_config; static struct git_config_source given_config_source; -static int actions, types; -static int end_null; +static int actions, type; +static char *default_value; +static int end_nul; static int respect_includes_opt = -1; static struct config_options config_options; static int show_origin; +static int show_scope; #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1) @@ -55,17 +59,74 @@ static int show_origin; #define PAGING_ACTIONS (ACTION_LIST | ACTION_GET_ALL | \ ACTION_GET_REGEXP | ACTION_GET_URLMATCH) -#define TYPE_BOOL (1<<0) -#define TYPE_INT (1<<1) -#define TYPE_BOOL_OR_INT (1<<2) -#define TYPE_PATH (1<<3) -#define TYPE_EXPIRY_DATE (1<<4) +#define TYPE_BOOL 1 +#define TYPE_INT 2 +#define TYPE_BOOL_OR_INT 3 +#define TYPE_PATH 4 +#define TYPE_EXPIRY_DATE 5 +#define TYPE_COLOR 6 + +#define OPT_CALLBACK_VALUE(s, l, v, h, i) \ + { OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \ + PARSE_OPT_NONEG, option_parse_type, (i) } + +static NORETURN void usage_builtin_config(void); + +static int option_parse_type(const struct option *opt, const char *arg, + int unset) +{ + int new_type, *to_type; + + if (unset) { + *((int *) opt->value) = 0; + return 0; + } + + /* + * To support '--<type>' style flags, begin with new_type equal to + * opt->defval. + */ + new_type = opt->defval; + if (!new_type) { + if (!strcmp(arg, "bool")) + new_type = TYPE_BOOL; + else if (!strcmp(arg, "int")) + new_type = TYPE_INT; + else if (!strcmp(arg, "bool-or-int")) + new_type = TYPE_BOOL_OR_INT; + else if (!strcmp(arg, "path")) + new_type = TYPE_PATH; + else if (!strcmp(arg, "expiry-date")) + new_type = TYPE_EXPIRY_DATE; + else if (!strcmp(arg, "color")) + new_type = TYPE_COLOR; + else + die(_("unrecognized --type argument, %s"), arg); + } + + to_type = opt->value; + if (*to_type && *to_type != new_type) { + /* + * Complain when there is a new type not equal to the old type. + * This allows for combinations like '--int --type=int' and + * '--type=int --type=int', but disallows ones like '--type=bool + * --int' and '--type=bool + * --type=int'. + */ + error(_("only one type at a time")); + usage_builtin_config(); + } + *to_type = new_type; + + return 0; +} static struct option builtin_config_options[] = { OPT_GROUP(N_("Config file location")), OPT_BOOL(0, "global", &use_global_config, N_("use global config file")), OPT_BOOL(0, "system", &use_system_config, N_("use system config file")), OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")), + OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")), OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")), OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")), OPT_GROUP(N_("Action")), @@ -84,44 +145,69 @@ static struct option builtin_config_options[] = { OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR), OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL), OPT_GROUP(N_("Type")), - OPT_BIT(0, "bool", &types, N_("value is \"true\" or \"false\""), TYPE_BOOL), - OPT_BIT(0, "int", &types, N_("value is decimal number"), TYPE_INT), - OPT_BIT(0, "bool-or-int", &types, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), - OPT_BIT(0, "path", &types, N_("value is a path (file or directory name)"), TYPE_PATH), - OPT_BIT(0, "expiry-date", &types, N_("value is an expiry date"), TYPE_EXPIRY_DATE), + OPT_CALLBACK('t', "type", &type, "", N_("value is given this type"), option_parse_type), + OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), + OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), + OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), + OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), + OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE), OPT_GROUP(N_("Other")), - OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")), + OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")), OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")), OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")), + OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")), + OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")), OPT_END(), }; -static void check_argc(int argc, int min, int max) { +static NORETURN void usage_builtin_config(void) +{ + usage_with_options(builtin_config_usage, builtin_config_options); +} + +static void check_argc(int argc, int min, int max) +{ if (argc >= min && argc <= max) return; - error("wrong number of arguments"); - usage_with_options(builtin_config_usage, builtin_config_options); + if (min == max) + error(_("wrong number of arguments, should be %d"), min); + else + error(_("wrong number of arguments, should be from %d to %d"), + min, max); + usage_builtin_config(); } static void show_config_origin(struct strbuf *buf) { - const char term = end_null ? '\0' : '\t'; + const char term = end_nul ? '\0' : '\t'; strbuf_addstr(buf, current_config_origin_type()); strbuf_addch(buf, ':'); - if (end_null) + if (end_nul) strbuf_addstr(buf, current_config_name()); else quote_c_style(current_config_name(), buf, NULL, 0); strbuf_addch(buf, term); } +static void show_config_scope(struct strbuf *buf) +{ + const char term = end_nul ? '\0' : '\t'; + const char *scope = config_scope_name(current_config_scope()); + + strbuf_addstr(buf, N_(scope)); + strbuf_addch(buf, term); +} + static int show_all_config(const char *key_, const char *value_, void *cb) { - if (show_origin) { + if (show_origin || show_scope) { struct strbuf buf = STRBUF_INIT; - show_config_origin(&buf); + if (show_scope) + show_config_scope(&buf); + if (show_origin) + show_config_origin(&buf); /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ fwrite(buf.buf, 1, buf.len, stdout); strbuf_release(&buf); @@ -141,6 +227,8 @@ struct strbuf_list { static int format_config(struct strbuf *buf, const char *key_, const char *value_) { + if (show_scope) + show_config_scope(buf); if (show_origin) show_config_origin(buf); if (show_keys) @@ -149,30 +237,35 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value if (show_keys) strbuf_addch(buf, key_delim); - if (types == TYPE_INT) + if (type == TYPE_INT) strbuf_addf(buf, "%"PRId64, git_config_int64(key_, value_ ? value_ : "")); - else if (types == TYPE_BOOL) + else if (type == TYPE_BOOL) strbuf_addstr(buf, git_config_bool(key_, value_) ? "true" : "false"); - else if (types == TYPE_BOOL_OR_INT) { + else if (type == TYPE_BOOL_OR_INT) { int is_bool, v; v = git_config_bool_or_int(key_, value_, &is_bool); if (is_bool) strbuf_addstr(buf, v ? "true" : "false"); else strbuf_addf(buf, "%d", v); - } else if (types == TYPE_PATH) { + } else if (type == TYPE_PATH) { const char *v; if (git_config_pathname(&v, key_, value_) < 0) return -1; strbuf_addstr(buf, v); free((char *)v); - } else if (types == TYPE_EXPIRY_DATE) { + } else if (type == TYPE_EXPIRY_DATE) { timestamp_t t; if (git_config_expiry_date(&t, key_, value_) < 0) return -1; strbuf_addf(buf, "%"PRItime, t); + } else if (type == TYPE_COLOR) { + char v[COLOR_MAXLEN]; + if (git_config_color(v, key_, value_) < 0) + return -1; + strbuf_addstr(buf, v); } else if (value_) { strbuf_addstr(buf, value_); } else { @@ -228,7 +321,7 @@ static int get_value(const char *key_, const char *regex_) key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { - error("invalid key pattern: %s", key_); + error(_("invalid key pattern: %s"), key_); FREE_AND_NULL(key_regexp); ret = CONFIG_INVALID_PATTERN; goto free_strings; @@ -248,7 +341,7 @@ static int get_value(const char *key_, const char *regex_) regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { - error("invalid pattern: %s", regex_); + error(_("invalid pattern: %s"), regex_); FREE_AND_NULL(regexp); ret = CONFIG_INVALID_PATTERN; goto free_strings; @@ -258,6 +351,16 @@ static int get_value(const char *key_, const char *regex_) config_with_options(collect_config, &values, &given_config_source, &config_options); + if (!values.nr && default_value) { + struct strbuf *item; + ALLOC_GROW(values.items, values.nr + 1, values.alloc); + item = &values.items[values.nr++]; + strbuf_init(item, 0); + if (format_config(item, key_, default_value) < 0) + die(_("failed to format default config value: %s"), + default_value); + } + ret = !values.nr; for (i = 0; i < values.nr; i++) { @@ -287,7 +390,7 @@ static char *normalize_value(const char *key, const char *value) if (!value) return NULL; - if (types == 0 || types == TYPE_PATH || types == TYPE_EXPIRY_DATE) + if (type == 0 || type == TYPE_PATH || type == TYPE_EXPIRY_DATE) /* * We don't do normalization for TYPE_PATH here: If * the path is like ~/foobar/, we prefer to store @@ -296,11 +399,11 @@ static char *normalize_value(const char *key, const char *value) * Also don't do normalization for expiry dates. */ return xstrdup(value); - if (types == TYPE_INT) + if (type == TYPE_INT) return xstrfmt("%"PRId64, git_config_int64(key, value)); - if (types == TYPE_BOOL) + if (type == TYPE_BOOL) return xstrdup(git_config_bool(key, value) ? "true" : "false"); - if (types == TYPE_BOOL_OR_INT) { + if (type == TYPE_BOOL_OR_INT) { int is_bool, v; v = git_config_bool_or_int(key, value, &is_bool); if (!is_bool) @@ -308,8 +411,22 @@ static char *normalize_value(const char *key, const char *value) else return xstrdup(v ? "true" : "false"); } + if (type == TYPE_COLOR) { + char v[COLOR_MAXLEN]; + if (git_config_color(v, key, value)) + die(_("cannot parse color '%s'"), value); - die("BUG: cannot normalize type %d", types); + /* + * The contents of `v` now contain an ANSI escape + * sequence, not suitable for including within a + * configuration file. Treat the above as a + * "sanity-check", and return the given value, which we + * know is representable as valid color code. + */ + return xstrdup(value); + } + + BUG("cannot normalize type %d", type); } static int get_color_found; @@ -392,13 +509,13 @@ static int get_colorbool(const char *var, int print) static void check_write(void) { if (!given_config_source.file && !startup_info->have_repository) - die("not in a git directory"); + die(_("not in a git directory")); if (given_config_source.use_stdin) - die("writing to stdin is not supported"); + die(_("writing to stdin is not supported")); if (given_config_source.blob) - die("writing config blobs is not supported"); + die(_("writing config blobs is not supported")); } struct urlmatch_current_candidate_value { @@ -498,25 +615,30 @@ int cmd_config(int argc, const char **argv, const char *prefix) int nongit = !startup_info->have_repository; char *value; - given_config_source.file = getenv(CONFIG_ENVIRONMENT); + given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT)); argc = parse_options(argc, argv, prefix, builtin_config_options, builtin_config_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (use_global_config + use_system_config + use_local_config + + use_worktree_config + !!given_config_source.file + !!given_config_source.blob > 1) { - error("only one config file at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); + error(_("only one config file at a time")); + usage_builtin_config(); } if (use_local_config && nongit) die(_("--local can only be used inside a git repository")); + if (given_config_source.blob && nongit) + die(_("--blob can only be used inside a git repository")); + if (given_config_source.file && !strcmp(given_config_source.file, "-")) { given_config_source.file = NULL; given_config_source.use_stdin = 1; + given_config_source.scope = CONFIG_SCOPE_COMMAND; } if (use_global_config) { @@ -530,7 +652,9 @@ int cmd_config(int argc, const char **argv, const char *prefix) * location; error out even if XDG_CONFIG_HOME * is set and points at a sane location. */ - die("$HOME not set"); + die(_("$HOME not set")); + + given_config_source.scope = CONFIG_SCOPE_GLOBAL; if (access_or_warn(user_config, R_OK, 0) && xdg_config && !access_or_warn(xdg_config, R_OK, 0)) { @@ -541,16 +665,36 @@ int cmd_config(int argc, const char **argv, const char *prefix) free(xdg_config); } } - else if (use_system_config) + else if (use_system_config) { given_config_source.file = git_etc_gitconfig(); - else if (use_local_config) + given_config_source.scope = CONFIG_SCOPE_SYSTEM; + } else if (use_local_config) { given_config_source.file = git_pathdup("config"); - else if (given_config_source.file) { + given_config_source.scope = CONFIG_SCOPE_LOCAL; + } else if (use_worktree_config) { + struct worktree **worktrees = get_worktrees(0); + if (repository_format_worktree_config) + given_config_source.file = git_pathdup("config.worktree"); + else if (worktrees[0] && worktrees[1]) + die(_("--worktree cannot be used with multiple " + "working trees unless the config\n" + "extension worktreeConfig is enabled. " + "Please read \"CONFIGURATION FILE\"\n" + "section in \"git help worktree\" for details")); + else + given_config_source.file = git_pathdup("config"); + given_config_source.scope = CONFIG_SCOPE_LOCAL; + free_worktrees(worktrees); + } else if (given_config_source.file) { if (!is_absolute_path(given_config_source.file) && prefix) given_config_source.file = prefix_filename(prefix, given_config_source.file); + given_config_source.scope = CONFIG_SCOPE_COMMAND; + } else if (given_config_source.blob) { + given_config_source.scope = CONFIG_SCOPE_COMMAND; } + if (respect_includes_opt == -1) config_options.respect_includes = !given_config_source.file; else @@ -560,25 +704,20 @@ int cmd_config(int argc, const char **argv, const char *prefix) config_options.git_dir = get_git_dir(); } - if (end_null) { + if (end_nul) { term = '\0'; delim = '\n'; key_delim = '\n'; } - if (HAS_MULTI_BITS(types)) { - error("only one type at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); - } - - if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && types) { - error("--get-color and variable type are incoherent"); - usage_with_options(builtin_config_usage, builtin_config_options); + if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && type) { + error(_("--get-color and variable type are incoherent")); + usage_builtin_config(); } if (HAS_MULTI_BITS(actions)) { - error("only one action at a time."); - usage_with_options(builtin_config_usage, builtin_config_options); + error(_("only one action at a time")); + usage_builtin_config(); } if (actions == 0) switch (argc) { @@ -586,19 +725,24 @@ int cmd_config(int argc, const char **argv, const char *prefix) case 2: actions = ACTION_SET; break; case 3: actions = ACTION_SET_ALL; break; default: - usage_with_options(builtin_config_usage, builtin_config_options); + usage_builtin_config(); } if (omit_values && !(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) { - error("--name-only is only applicable to --list or --get-regexp"); - usage_with_options(builtin_config_usage, builtin_config_options); + error(_("--name-only is only applicable to --list or --get-regexp")); + usage_builtin_config(); } if (show_origin && !(actions & (ACTION_GET|ACTION_GET_ALL|ACTION_GET_REGEXP|ACTION_LIST))) { - error("--show-origin is only applicable to --get, --get-all, " - "--get-regexp, and --list."); - usage_with_options(builtin_config_usage, builtin_config_options); + error(_("--show-origin is only applicable to --get, --get-all, " + "--get-regexp, and --list")); + usage_builtin_config(); + } + + if (default_value && !(actions & ACTION_GET)) { + error(_("--default is only applicable to --get")); + usage_builtin_config(); } if (actions & PAGING_ACTIONS) @@ -610,10 +754,10 @@ int cmd_config(int argc, const char **argv, const char *prefix) &given_config_source, &config_options) < 0) { if (given_config_source.file) - die_errno("unable to read config file '%s'", + die_errno(_("unable to read config file '%s'"), given_config_source.file); else - die("error processing config file(s)"); + die(_("error processing config file(s)")); } } else if (actions == ACTION_EDIT) { @@ -621,11 +765,11 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_argc(argc, 0, 0); if (!given_config_source.file && nongit) - die("not in a git directory"); + die(_("not in a git directory")); if (given_config_source.use_stdin) - die("editing stdin is not supported"); + die(_("editing stdin is not supported")); if (given_config_source.blob) - die("editing blobs is not supported"); + die(_("editing blobs is not supported")); git_config(git_default_config, NULL); config_file = given_config_source.file ? xstrdup(given_config_source.file) : @@ -726,7 +870,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (ret < 0) return ret; if (ret == 0) - die("No such section!"); + die(_("no such section: %s"), argv[0]); } else if (actions == ACTION_REMOVE_SECTION) { int ret; @@ -737,7 +881,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (ret < 0) return ret; if (ret == 0) - die("No such section!"); + die(_("no such section: %s"), argv[0]); } else if (actions == ACTION_GET_COLOR) { check_argc(argc, 1, 2); diff --git a/builtin/count-objects.c b/builtin/count-objects.c index b054713e1a..3fae474f6f 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -66,7 +66,7 @@ static int count_loose(const struct object_id *oid, const char *path, void *data else { loose_size += on_disk_bytes(st); loose++; - if (verbose && has_sha1_pack(oid->hash)) + if (verbose && has_object_pack(oid)) packed_loose++; } return 0; @@ -78,10 +78,10 @@ static int count_cruft(const char *basename, const char *path, void *data) return 0; } -static int print_alternate(struct alternate_object_database *alt, void *data) +static int print_alternate(struct object_directory *odb, void *data) { printf("alternate: "); - quote_c_style(alt->path, NULL, stdout, 0); + quote_c_style(odb->path, NULL, stdout, 0); putchar('\n'); return 0; } @@ -123,7 +123,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) struct strbuf pack_buf = STRBUF_INIT; struct strbuf garbage_buf = STRBUF_INIT; - for (p = get_packed_git(the_repository); p; p = p->next) { + for (p = get_all_packs(the_repository); p; p = p->next) { if (!p->pack_local) continue; if (open_pack_index(p)) diff --git a/builtin/describe.c b/builtin/describe.c index de840f96a4..21d2cb9e57 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -1,3 +1,4 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "lockfile.h" @@ -6,18 +7,21 @@ #include "blob.h" #include "refs.h" #include "builtin.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "parse-options.h" #include "revision.h" #include "diff.h" #include "hashmap.h" #include "argv-array.h" #include "run-command.h" -#include "revision.h" +#include "object-store.h" #include "list-objects.h" +#include "commit-slab.h" #define MAX_TAGS (FLAG_BITS - 1) +define_commit_slab(commit_names, struct commit_name *); + static const char * const describe_usage[] = { N_("git describe [<options>] [<commit-ish>...]"), N_("git describe [<options>] --dirty"), @@ -37,6 +41,7 @@ static struct string_list patterns = STRING_LIST_INIT_NODUP; static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP; static int always; static const char *suffix, *dirty, *broken; +static struct commit_names commit_names; /* diff-index command arguments to check if working tree is dirty. */ static const char *diff_index_args[] = { @@ -49,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; }; @@ -57,20 +63,23 @@ static const char *prio_names[] = { N_("head"), N_("lightweight"), N_("annotated"), }; -static int commit_name_cmp(const void *unused_cmp_data, - const void *entry, - const void *entry_or_key, +static int commit_name_neq(const void *unused_cmp_data, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *peeled) { - const struct commit_name *cn1 = entry; - const struct commit_name *cn2 = entry_or_key; + const struct commit_name *cn1, *cn2; + + cn1 = container_of(eptr, const struct commit_name, entry); + cn2 = container_of(entry_or_key, const struct commit_name, entry); - return oidcmp(&cn1->peeled, peeled ? peeled : &cn2->peeled); + return !oideq(&cn1->peeled, peeled ? peeled : &cn2->peeled); } static inline struct commit_name *find_commit_name(const struct object_id *peeled) { - return hashmap_get_from_hash(&names, sha1hash(peeled->hash), peeled->hash); + return hashmap_get_entry_from_hash(&names, oidhash(peeled), peeled, + struct commit_name, entry); } static int replace_name(struct commit_name *e, @@ -88,13 +97,13 @@ static int replace_name(struct commit_name *e, struct tag *t; if (!e->tag) { - t = lookup_tag(&e->oid); + t = lookup_tag(the_repository, &e->oid); if (!t || parse_tag(t)) return 1; e->tag = t; } - t = lookup_tag(oid); + t = lookup_tag(the_repository, oid); if (!t || parse_tag(t)) return 0; *tag = t; @@ -117,13 +126,14 @@ static void add_to_known_names(const char *path, if (!e) { e = xmalloc(sizeof(struct commit_name)); oidcpy(&e->peeled, peeled); - hashmap_entry_init(e, sha1hash(peeled->hash)); - hashmap_add(&names, e); + hashmap_entry_init(&e->entry, oidhash(peeled)); + hashmap_add(&names, &e->entry); e->path = NULL; } e->tag = tag; e->prio = prio; e->name_checked = 0; + e->misnamed = 0; oidcpy(&e->oid, oid); free(e->path); e->path = xstrdup(path); @@ -185,7 +195,7 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi /* Is it annotated? */ if (!peel_ref(path, &peeled)) { - is_annotated = !!oidcmp(oid, &peeled); + is_annotated = !oideq(oid, &peeled); } else { oidcpy(&peeled, oid); is_annotated = 0; @@ -262,15 +272,16 @@ static unsigned long finish_depth_computation( static void append_name(struct commit_name *n, struct strbuf *dst) { if (n->prio == 2 && !n->tag) { - n->tag = lookup_tag(&n->oid); + n->tag = lookup_tag(the_repository, &n->oid); if (!n->tag || parse_tag(n->tag)) 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; } @@ -298,7 +309,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) unsigned long seen_commits = 0; unsigned int unannotated_cnt = 0; - cmit = lookup_commit_reference(oid); + cmit = lookup_commit_reference(the_repository, oid); n = find_commit_name(&cmit->object.oid); if (n && (tags || all || n->prio == 2)) { @@ -306,8 +317,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) * Exact match to an existing ref. */ append_name(n, dst); - if (longformat) - append_suffix(0, n->tag ? &n->tag->tagged->oid : oid, dst); + if (n->misnamed || longformat) + append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst); if (suffix) strbuf_addstr(dst, suffix); return; @@ -321,11 +332,15 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) if (!have_util) { struct hashmap_iter iter; struct commit *c; - struct commit_name *n = hashmap_iter_first(&names, &iter); - for (; n; n = hashmap_iter_next(&iter)) { - c = lookup_commit_reference_gently(&n->peeled, 1); + struct commit_name *n; + + init_commit_names(&commit_names); + hashmap_for_each_entry(&names, &iter, n, + entry /* member name */) { + c = lookup_commit_reference_gently(the_repository, + &n->peeled, 1); if (c) - c->util = n; + *commit_names_at(&commit_names, c) = n; } have_util = 1; } @@ -336,8 +351,11 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) while (list) { struct commit *c = pop_commit(&list); struct commit_list *parents = c->parents; + struct commit_name **slot; + seen_commits++; - n = c->util; + slot = commit_names_peek(&commit_names, c); + n = slot ? *slot : NULL; if (n) { if (!tags && !all && n->prio < 2) { unannotated_cnt++; @@ -361,11 +379,25 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) if (!(c->object.flags & t->flag_within)) t->depth++; } + /* Stop if last remaining path already covered by best candidate(s) */ if (annotated_cnt && !list) { - if (debug) - fprintf(stderr, _("finished search at %s\n"), - oid_to_hex(&c->object.oid)); - break; + int best_depth = INT_MAX; + unsigned best_within = 0; + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = &all_matches[cur_match]; + if (t->depth < best_depth) { + best_depth = t->depth; + best_within = t->flag_within; + } else if (t->depth == best_depth) { + best_within |= t->flag_within; + } + } + if ((c->object.flags & best_within) == best_within) { + if (debug) + fprintf(stderr, _("finished search at %s\n"), + oid_to_hex(&c->object.oid)); + break; + } } while (parents) { struct commit *p = parents->item; @@ -434,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); @@ -457,7 +489,7 @@ static void process_object(struct object *obj, const char *path, void *data) { struct process_commit_data *pcd = data; - if (!oidcmp(&pcd->looking_for, &obj->oid) && !pcd->dst->len) { + if (oideq(&pcd->looking_for, &obj->oid) && !pcd->dst->len) { reset_revision_walk(); describe_commit(&pcd->current_commit, pcd->dst); strbuf_addf(pcd->dst, ":%s", path); @@ -476,7 +508,7 @@ static void describe_blob(struct object_id oid, struct strbuf *dst) "--objects", "--in-commit-order", "--reverse", "HEAD", NULL); - init_revisions(&revs, NULL); + repo_init_revisions(the_repository, &revs, NULL); if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1) BUG("setup_revisions could not handle all args?"); @@ -498,11 +530,11 @@ static void describe(const char *arg, int last_one) if (get_oid(arg, &oid)) die(_("Not a valid object name %s"), arg); - cmit = lookup_commit_reference_gently(&oid, 1); + cmit = lookup_commit_reference_gently(the_repository, &oid, 1); if (cmit) describe_commit(&oid, &sb); - else if (oid_object_info(&oid, NULL) == OBJ_BLOB) + else if (oid_object_info(the_repository, &oid, NULL) == OBJ_BLOB) describe_blob(oid, &sb); else die(_("%s is neither a commit nor blob"), arg); @@ -584,7 +616,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) return cmd_name_rev(args.argc, args.argv, prefix); } - hashmap_init(&names, commit_name_cmp, NULL, 0); + hashmap_init(&names, commit_name_neq, NULL, 0); for_each_rawref(get_name, NULL); if (!hashmap_get_size(&names) && !always) die(_("No names found, cannot describe anything.")); @@ -612,19 +644,20 @@ int cmd_describe(int argc, const char **argv, const char *prefix) suffix = broken; } } else if (dirty) { - static struct lock_file index_lock; + struct lock_file index_lock = LOCK_INIT; struct rev_info revs; struct argv_array args = ARGV_ARRAY_INIT; int fd, result; - read_cache_preload(NULL); + setup_work_tree(); + read_cache(); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); fd = hold_locked_index(&index_lock, 0); if (0 <= fd) - update_index_if_able(&the_index, &index_lock); + repo_update_index_if_able(the_repository, &index_lock); - init_revisions(&revs, prefix); + repo_init_revisions(the_repository, &revs, prefix); argv_array_pushv(&args, diff_index_args); if (setup_revisions(args.argc, args.argv, &revs, NULL) != 1) BUG("malformed internal diff-index command line"); diff --git a/builtin/diff-files.c b/builtin/diff-files.c index e88493ffe5..86ae474fbf 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -3,6 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "diff.h" @@ -25,7 +26,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) usage(diff_files_usage); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; precompose_argv(argc, argv); diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 522f4fdffd..93ec642423 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -1,3 +1,4 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "diff.h" @@ -22,7 +23,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) usage(diff_cache_usage); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; precompose_argv(argc, argv); diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 473615117e..802363d0a2 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -1,3 +1,4 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "diff.h" @@ -5,12 +6,13 @@ #include "log-tree.h" #include "builtin.h" #include "submodule.h" +#include "repository.h" static struct rev_info log_tree_opt; static int diff_tree_commit_oid(const struct object_id *oid) { - struct commit *commit = lookup_commit_reference(oid); + struct commit *commit = lookup_commit_reference(the_repository, oid); if (!commit) return -1; return log_tree_commit(&log_tree_opt, commit); @@ -24,7 +26,7 @@ static int stdin_diff_commit(struct commit *commit, const char *p) /* Graft the fake parents locally to the commit */ while (isspace(*p++) && !parse_oid_hex(p, &oid, &p)) { - struct commit *parent = lookup_commit(&oid); + struct commit *parent = lookup_commit(the_repository, &oid); if (!pptr) { /* Free the real parent list */ free_commit_list(commit->parents); @@ -45,7 +47,7 @@ static int stdin_diff_trees(struct tree *tree1, const char *p) struct tree *tree2; if (!isspace(*p++) || parse_oid_hex(p, &oid, &p) || *p) return error("Need exactly two trees, separated by a space"); - tree2 = lookup_tree(&oid); + tree2 = lookup_tree(the_repository, &oid); if (!tree2 || parse_tree(tree2)) return -1; printf("%s %s\n", oid_to_hex(&tree1->object.oid), @@ -68,7 +70,7 @@ static int diff_tree_stdin(char *line) line[len-1] = 0; if (parse_oid_hex(line, &oid, &p)) return -1; - obj = parse_object(&oid); + obj = parse_object(the_repository, &oid); if (!obj) return -1; if (obj->type == OBJ_COMMIT) @@ -81,9 +83,13 @@ static int diff_tree_stdin(char *line) } static const char diff_tree_usage[] = -"git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " +"git diff-tree [--stdin] [-m] [-c | --cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " "[<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...]\n" " -r diff recursively\n" +" -c show combined diff for merge commits\n" +" --cc show combined diff for merge commits removing uninteresting hunks\n" +" --combined-all-paths\n" +" show name of file in all parents for combined diffs\n" " --root include the initial commit as diff against /dev/null\n" COMMON_DIFF_OPTIONS_HELP; @@ -103,13 +109,14 @@ 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")) usage(diff_tree_usage); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - init_revisions(opt, prefix); + repo_init_revisions(the_repository, opt, prefix); if (read_cache() < 0) die(_("index file corrupt")); opt->abbrev = 0; @@ -121,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; @@ -162,9 +177,11 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) int saved_nrl = 0; int saved_dcctc = 0; - if (opt->diffopt.detect_rename) - opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | - DIFF_SETUP_USE_CACHE); + if (opt->diffopt.detect_rename) { + if (!the_index.cache) + repo_read_index(the_repository); + opt->diffopt.setup |= DIFF_SETUP_USE_SIZE_CACHE; + } while (fgets(line, sizeof(line), stdin)) { struct object_id oid; diff --git a/builtin/diff.c b/builtin/diff.c index 16bfb22f73..8537b17bd5 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -3,6 +3,7 @@ * * Copyright (c) 2006 Junio C Hamano */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "lockfile.h" @@ -16,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 @@ -41,7 +42,7 @@ static void stuff_change(struct diff_options *opt, struct diff_filespec *one, *two; if (!is_null_oid(old_oid) && !is_null_oid(new_oid) && - !oidcmp(old_oid, new_oid) && (old_mode == new_mode)) + oideq(old_oid, new_oid) && (old_mode == new_mode)) return; if (opt->flags.reverse_diff) { @@ -102,7 +103,7 @@ static int builtin_diff_blobs(struct rev_info *revs, int argc, const char **argv, struct object_array_entry **blob) { - unsigned mode = canon_mode(S_IFREG | 0644); + const unsigned mode = canon_mode(S_IFREG | 0644); if (argc > 1) usage(builtin_diff_usage); @@ -212,7 +213,7 @@ static void refresh_index_quietly(void) discard_cache(); read_cache(); refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED); - update_index_if_able(&the_index, &lock_file); + repo_update_index_if_able(the_repository, &lock_file); } static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) @@ -318,40 +319,33 @@ int cmd_diff(int argc, const char **argv, const char *prefix) git_config(git_diff_ui_config, NULL); precompose_argv(argc, argv); - init_revisions(&rev, prefix); - - if (no_index && argc != i + 2) { - if (no_index == DIFF_NO_INDEX_IMPLICIT) { - /* - * There was no --no-index and there were not two - * paths. It is possible that the user intended - * to do an inside-repository operation. - */ - fprintf(stderr, "Not a git repository\n"); - fprintf(stderr, - "To compare two paths outside a working tree:\n"); - } - /* Give the usage message for non-repository usage and exit. */ - usagef("git diff %s <path> <path>", - no_index == DIFF_NO_INDEX_EXPLICIT ? - "--no-index" : "[--no-index]"); + repo_init_revisions(the_repository, &rev, prefix); - } - if (no_index) - /* If this is a no-index diff, just run it and exit there. */ - diff_no_index(&rev, argc, argv); - - /* Otherwise, we are doing the usual "git" diff */ - rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index; - - /* Scale to real terminal size and respect statGraphWidth config */ + /* Set up defaults that will apply to both no-index and regular diffs. */ rev.diffopt.stat_width = -1; rev.diffopt.stat_graph_width = -1; - - /* Default to let external and textconv be used */ rev.diffopt.flags.allow_external = 1; rev.diffopt.flags.allow_textconv = 1; + /* If this is a no-index diff, just run it and exit there. */ + if (no_index) + exit(diff_no_index(&rev, no_index == DIFF_NO_INDEX_IMPLICIT, + argc, argv)); + + + /* + * Otherwise, we are doing the usual "git" diff; set up any + * further defaults that apply to regular diffs. + */ + rev.diffopt.skip_stat_unmatch = !!diff_auto_refresh_index; + + /* + * Default to intent-to-add entries invisible in the + * index. This makes them show up as new files in diff-files + * and not at all in diff-cached. + */ + rev.diffopt.ita_invisible_in_index = 1; + if (nongit) die(_("Not a git repository")); argc = setup_revisions(argc, argv, &rev, NULL); @@ -379,7 +373,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) add_head_to_pending(&rev); if (!rev.pending.nr) { struct tree *tree; - tree = lookup_tree(the_hash_algo->empty_tree); + tree = lookup_tree(the_repository, + the_repository->hash_algo->empty_tree); add_pending_object(&rev, &tree->object, "HEAD"); } break; @@ -393,12 +388,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) const char *name = entry->name; int flags = (obj->flags & UNINTERESTING); if (!obj->parsed) - obj = parse_object(&obj->oid); - obj = deref_tag(obj, NULL, 0); + obj = parse_object(the_repository, &obj->oid); + obj = deref_tag(the_repository, obj, NULL, 0); if (!obj) die(_("invalid object '%s' given."), name); if (obj->type == OBJ_COMMIT) - obj = &((struct commit *)obj)->tree->object; + obj = &get_commit_tree(((struct commit *)obj))->object; if (obj->type == OBJ_TREE) { obj->flags |= flags; diff --git a/builtin/difftool.c b/builtin/difftool.c index ee8dce019e..c280e682b2 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -11,18 +11,19 @@ * * Copyright (C) 2016 Johannes Schindelin */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "builtin.h" #include "run-command.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "parse-options.h" #include "argv-array.h" #include "strbuf.h" #include "lockfile.h" +#include "object-store.h" #include "dir.h" -static char *diff_gui_tool; static int trust_exit_code; static const char *const builtin_difftool_usage[] = { @@ -32,11 +33,6 @@ static const char *const builtin_difftool_usage[] = { static int difftool_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, "diff.guitool")) { - diff_gui_tool = xstrdup(value); - return 0; - } - if (!strcmp(var, "difftool.trustexitcode")) { trust_exit_code = git_config_bool(var, value); return 0; @@ -63,14 +59,12 @@ static int parse_index_info(char *p, int *mode1, int *mode2, *mode2 = (int)strtol(p + 1, &p, 8); if (*p != ' ') return error("expected ' ', got '%c'", *p); - if (get_oid_hex(++p, oid1)) - return error("expected object ID, got '%s'", p + 1); - p += GIT_SHA1_HEXSZ; + if (parse_oid_hex(++p, oid1, (const char **)&p)) + return error("expected object ID, got '%s'", p); if (*p != ' ') return error("expected ' ', got '%c'", *p); - if (get_oid_hex(++p, oid2)) - return error("expected object ID, got '%s'", p + 1); - p += GIT_SHA1_HEXSZ; + if (parse_oid_hex(++p, oid2, (const char **)&p)) + return error("expected object ID, got '%s'", p); if (*p != ' ') return error("expected ' ', got '%c'", *p); *status = *++p; @@ -111,11 +105,11 @@ static int use_wt_file(const char *workdir, const char *name, int fd = open(buf.buf, O_RDONLY); if (fd >= 0 && - !index_fd(&wt_oid, fd, &st, OBJ_BLOB, name, 0)) { + !index_fd(&the_index, &wt_oid, fd, &st, OBJ_BLOB, name, 0)) { if (is_null_oid(oid)) { oidcpy(oid, &wt_oid); use = 1; - } else if (!oidcmp(oid, &wt_oid)) + } else if (oideq(oid, &wt_oid)) use = 1; } } @@ -131,12 +125,15 @@ struct working_tree_entry { }; static int working_tree_entry_cmp(const void *unused_cmp_data, - const void *entry, - const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *unused_keydata) { - const struct working_tree_entry *a = entry; - const struct working_tree_entry *b = entry_or_key; + const struct working_tree_entry *a, *b; + + a = container_of(eptr, const struct working_tree_entry, entry); + b = container_of(entry_or_key, const struct working_tree_entry, entry); + return strcmp(a->path, b->path); } @@ -151,12 +148,14 @@ struct pair_entry { }; static int pair_cmp(const void *unused_cmp_data, - const void *entry, - const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *unused_keydata) { - const struct pair_entry *a = entry; - const struct pair_entry *b = entry_or_key; + const struct pair_entry *a, *b; + + a = container_of(eptr, const struct pair_entry, entry); + b = container_of(entry_or_key, const struct pair_entry, entry); return strcmp(a->path, b->path); } @@ -167,14 +166,14 @@ static void add_left_or_right(struct hashmap *map, const char *path, struct pair_entry *e, *existing; FLEX_ALLOC_STR(e, path, path); - hashmap_entry_init(e, strhash(path)); - existing = hashmap_get(map, e, NULL); + hashmap_entry_init(&e->entry, strhash(path)); + existing = hashmap_get_entry(map, e, entry, NULL); if (existing) { free(e); e = existing; } else { e->left[0] = e->right[0] = '\0'; - hashmap_add(map, e); + hashmap_add(map, &e->entry); } strlcpy(is_right ? e->right : e->left, content, PATH_MAX); } @@ -185,12 +184,14 @@ struct path_entry { }; static int path_entry_cmp(const void *unused_cmp_data, - const void *entry, - const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *key) { - const struct path_entry *a = entry; - const struct path_entry *b = entry_or_key; + const struct path_entry *a, *b; + + a = container_of(eptr, const struct path_entry, entry); + b = container_of(entry_or_key, const struct path_entry, entry); return strcmp(a->path, key ? key : b->path); } @@ -240,8 +241,8 @@ static void changed_files(struct hashmap *result, const char *index_path, while (!strbuf_getline_nul(&buf, fp)) { struct path_entry *entry; FLEX_ALLOC_STR(entry, path, buf.buf); - hashmap_entry_init(entry, strhash(buf.buf)); - hashmap_add(result, entry); + hashmap_entry_init(&entry->entry, strhash(buf.buf)); + hashmap_add(result, &entry->entry); } fclose(fp); if (finish_command(&diff_files)) @@ -321,10 +322,10 @@ static int checkout_path(unsigned mode, struct object_id *oid, struct cache_entry *ce; int ret; - ce = make_cache_entry(mode, oid->hash, path, 0, 0); - ret = checkout_entry(ce, state, NULL); + ce = make_transient_cache_entry(mode, oid, path, 0); + ret = checkout_entry(ce, state, NULL, NULL); - free(ce); + discard_cache_entry(ce); return ret; } @@ -437,7 +438,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, strbuf_reset(&buf); strbuf_addf(&buf, "Subproject commit %s", oid_to_hex(&roid)); - if (!oidcmp(&loid, &roid)) + if (oideq(&loid, &roid)) strbuf_addstr(&buf, "-dirty"); add_left_or_right(&submodules, dst_path, buf.buf, 1); continue; @@ -467,12 +468,13 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, /* Avoid duplicate working_tree entries */ FLEX_ALLOC_STR(entry, path, dst_path); - hashmap_entry_init(entry, strhash(dst_path)); - if (hashmap_get(&working_tree_dups, entry, NULL)) { + hashmap_entry_init(&entry->entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, &entry->entry, + NULL)) { free(entry); continue; } - hashmap_add(&working_tree_dups, entry); + hashmap_add(&working_tree_dups, &entry->entry); if (!use_wt_file(workdir, dst_path, &roid)) { if (checkout_path(rmode, &roid, dst_path, @@ -488,7 +490,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, * index. */ struct cache_entry *ce2 = - make_cache_entry(rmode, roid.hash, + make_cache_entry(&wtindex, rmode, &roid, dst_path, 0, 0); add_index_entry(&wtindex, ce2, @@ -536,8 +538,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, * temporary file to both the left and right directories to show the * change in the recorded SHA1 for the submodule. */ - hashmap_iter_init(&submodules, &iter); - while ((entry = hashmap_iter_next(&iter))) { + hashmap_for_each_entry(&submodules, &iter, entry, + entry /* member name */) { if (*entry->left) { add_path(&ldir, ldir_len, entry->path); ensure_leading_directories(ldir.buf); @@ -555,8 +557,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, * shows only the link itself, not the contents of the link target. * This loop replicates that behavior. */ - hashmap_iter_init(&symlinks2, &iter); - while ((entry = hashmap_iter_next(&iter))) { + hashmap_for_each_entry(&symlinks2, &iter, entry, + entry /* member name */) { if (*entry->left) { add_path(&ldir, ldir_len, entry->path); ensure_leading_directories(ldir.buf); @@ -610,7 +612,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, continue; if (!indices_loaded) { - static struct lock_file lock; + struct lock_file lock = LOCK_INIT; strbuf_reset(&buf); strbuf_addf(&buf, "%s/wtindex", tmpdir); if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || @@ -688,22 +690,21 @@ static int run_file_diff(int prompt, const char *prefix, int cmd_difftool(int argc, const char **argv, const char *prefix) { int use_gui_tool = 0, dir_diff = 0, prompt = -1, symlinks = 0, - tool_help = 0; + tool_help = 0, no_index = 0; static char *difftool_cmd = NULL, *extcmd = NULL; struct option builtin_difftool_options[] = { OPT_BOOL('g', "gui", &use_gui_tool, N_("use `diff.guitool` instead of `diff.tool`")), OPT_BOOL('d', "dir-diff", &dir_diff, N_("perform a full-directory diff")), - { OPTION_SET_INT, 'y', "no-prompt", &prompt, NULL, + OPT_SET_INT_F('y', "no-prompt", &prompt, N_("do not prompt before launching a diff tool"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0}, - { OPTION_SET_INT, 0, "prompt", &prompt, NULL, NULL, - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, - NULL, 1 }, + 0, PARSE_OPT_NONEG), + OPT_SET_INT_F(0, "prompt", &prompt, NULL, + 1, PARSE_OPT_NONEG | PARSE_OPT_HIDDEN), OPT_BOOL(0, "symlinks", &symlinks, N_("use symlinks in dir-diff mode")), - OPT_STRING('t', "tool", &difftool_cmd, N_("<tool>"), + OPT_STRING('t', "tool", &difftool_cmd, N_("tool"), N_("use the specified diff tool")), OPT_BOOL(0, "tool-help", &tool_help, N_("print a list of diff tools that may be used with " @@ -711,8 +712,9 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "trust-exit-code", &trust_exit_code, N_("make 'git-difftool' exit when an invoked diff " "tool returns a non - zero exit code")), - OPT_STRING('x', "extcmd", &extcmd, N_("<command>"), + OPT_STRING('x', "extcmd", &extcmd, N_("command"), N_("specify a custom command for viewing diffs")), + OPT_ARGUMENT("no-index", &no_index, N_("passed to `diff`")), OPT_END() }; @@ -726,12 +728,21 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) if (tool_help) return print_tool_help(); - /* NEEDSWORK: once we no longer spawn anything, remove this */ - setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); - setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + if (!no_index && !startup_info->have_repository) + die(_("difftool requires worktree or --no-index")); + + if (!no_index){ + setup_work_tree(); + setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); + } else if (dir_diff) + die(_("--dir-diff is incompatible with --no-index")); + + if (use_gui_tool + !!difftool_cmd + !!extcmd > 1) + die(_("--gui, --tool and --extcmd are mutually exclusive")); - if (use_gui_tool && diff_gui_tool && *diff_gui_tool) - setenv("GIT_DIFF_TOOL", diff_gui_tool, 1); + if (use_gui_tool) + setenv("GIT_MERGETOOL_GUI", "true", 1); else if (difftool_cmd) { if (*difftool_cmd) setenv("GIT_DIFF_TOOL", difftool_cmd, 1); diff --git a/builtin/env--helper.c b/builtin/env--helper.c new file mode 100644 index 0000000000..23c214fff6 --- /dev/null +++ b/builtin/env--helper.c @@ -0,0 +1,95 @@ +#include "builtin.h" +#include "config.h" +#include "parse-options.h" + +static char const * const env__helper_usage[] = { + N_("git env--helper --type=[bool|ulong] <options> <env-var>"), + NULL +}; + +static enum { + ENV_HELPER_TYPE_BOOL = 1, + ENV_HELPER_TYPE_ULONG +} cmdmode = 0; + +static int option_parse_type(const struct option *opt, const char *arg, + int unset) +{ + if (!strcmp(arg, "bool")) + cmdmode = ENV_HELPER_TYPE_BOOL; + else if (!strcmp(arg, "ulong")) + cmdmode = ENV_HELPER_TYPE_ULONG; + else + die(_("unrecognized --type argument, %s"), arg); + + return 0; +} + +int cmd_env__helper(int argc, const char **argv, const char *prefix) +{ + int exit_code = 0; + const char *env_variable = NULL; + const char *env_default = NULL; + int ret; + int ret_int, default_int; + unsigned long ret_ulong, default_ulong; + struct option opts[] = { + OPT_CALLBACK_F(0, "type", &cmdmode, N_("type"), + N_("value is given this type"), PARSE_OPT_NONEG, + option_parse_type), + OPT_STRING(0, "default", &env_default, N_("value"), + N_("default for git_env_*(...) to fall back on")), + OPT_BOOL(0, "exit-code", &exit_code, + N_("be quiet only use git_env_*() value as exit code")), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, opts, env__helper_usage, + PARSE_OPT_KEEP_UNKNOWN); + if (env_default && !*env_default) + usage_with_options(env__helper_usage, opts); + if (!cmdmode) + usage_with_options(env__helper_usage, opts); + if (argc != 1) + usage_with_options(env__helper_usage, opts); + env_variable = argv[0]; + + switch (cmdmode) { + case ENV_HELPER_TYPE_BOOL: + if (env_default) { + default_int = git_parse_maybe_bool(env_default); + if (default_int == -1) { + error(_("option `--default' expects a boolean value with `--type=bool`, not `%s`"), + env_default); + usage_with_options(env__helper_usage, opts); + } + } else { + default_int = 0; + } + ret_int = git_env_bool(env_variable, default_int); + if (!exit_code) + puts(ret_int ? "true" : "false"); + ret = ret_int; + break; + case ENV_HELPER_TYPE_ULONG: + if (env_default) { + if (!git_parse_ulong(env_default, &default_ulong)) { + error(_("option `--default' expects an unsigned long value with `--type=ulong`, not `%s`"), + env_default); + usage_with_options(env__helper_usage, opts); + } + } else { + default_ulong = 0; + } + ret_ulong = git_env_ulong(env_variable, default_ulong); + if (!exit_code) + printf("%lu\n", ret_ulong); + ret = ret_ulong; + break; + default: + BUG("unknown <type> value"); + break; + } + + return !ret; +} diff --git a/builtin/fast-export.c b/builtin/fast-export.c index a15898d641..85868162ee 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -7,6 +7,8 @@ #include "cache.h" #include "config.h" #include "refs.h" +#include "refspec.h" +#include "object-store.h" #include "commit.h" #include "object.h" #include "tag.h" @@ -21,6 +23,7 @@ #include "quote.h" #include "remote.h" #include "blob.h" +#include "commit-slab.h" static const char *fast_export_usage[] = { N_("git fast-export [rev-list-opts]"), @@ -28,22 +31,27 @@ static const char *fast_export_usage[] = { }; static int progress; -static enum { ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = ABORT; -static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ERROR; +static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT; +static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT; +static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT; static int fake_missing_tagger; static int use_done_feature; static int no_data; static int full_tree; +static int reference_excluded_commits; +static int show_original_ids; +static int mark_tags; static struct string_list extra_refs = STRING_LIST_INIT_NODUP; -static struct refspec *refspecs; -static int refspecs_nr; +static struct string_list tag_refs = STRING_LIST_INIT_NODUP; +static struct refspec refspecs = REFSPEC_INIT_FETCH; static int anonymize; +static struct revision_sources revision_sources; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) { if (unset || !strcmp(arg, "abort")) - signed_tag_mode = ABORT; + signed_tag_mode = SIGNED_TAG_ABORT; else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) signed_tag_mode = VERBATIM; else if (!strcmp(arg, "warn")) @@ -61,7 +69,7 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt, const char *arg, int unset) { if (unset || !strcmp(arg, "abort")) - tag_of_filtered_mode = ERROR; + tag_of_filtered_mode = TAG_FILTERING_ABORT; else if (!strcmp(arg, "drop")) tag_of_filtered_mode = DROP; else if (!strcmp(arg, "rewrite")) @@ -71,6 +79,31 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt, return 0; } +static int parse_opt_reencode_mode(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + reencode_mode = REENCODE_ABORT; + return 0; + } + + switch (git_parse_maybe_bool(arg)) { + case 0: + reencode_mode = REENCODE_NO; + break; + case 1: + reencode_mode = REENCODE_YES; + break; + default: + if (!strcasecmp(arg, "abort")) + reencode_mode = REENCODE_ABORT; + else + return error("Unknown reencoding mode: %s", arg); + } + + return 0; +} + static struct decoration idnums; static uint32_t last_idnum; @@ -94,10 +127,15 @@ struct anonymized_entry { }; static int anonymized_entry_cmp(const void *unused_cmp_data, - const void *va, const void *vb, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *unused_keydata) { - const struct anonymized_entry *a = va, *b = vb; + const struct anonymized_entry *a, *b; + + a = container_of(eptr, const struct anonymized_entry, hash); + b = container_of(entry_or_key, const struct anonymized_entry, hash); + return a->orig_len != b->orig_len || memcmp(a->orig, b->orig, a->orig_len); } @@ -116,10 +154,10 @@ static const void *anonymize_mem(struct hashmap *map, if (!map->cmpfn) hashmap_init(map, anonymized_entry_cmp, NULL, 0); - hashmap_entry_init(&key, memhash(orig, *len)); + hashmap_entry_init(&key.hash, memhash(orig, *len)); key.orig = orig; key.orig_len = *len; - ret = hashmap_get(map, &key, NULL); + ret = hashmap_get_entry(map, &key, hash, NULL); if (!ret) { ret = xmalloc(sizeof(*ret)); @@ -128,7 +166,7 @@ static const void *anonymize_mem(struct hashmap *map, ret->orig_len = *len; ret->anon = generate(orig, len); ret->anon_len = *len; - hashmap_put(map, ret); + hashmap_put(map, &ret->hash); } *len = ret->anon_len; @@ -156,15 +194,14 @@ static void anonymize_path(struct strbuf *out, const char *path, } } -/* Since intptr_t is C99, we do not use it here */ -static inline uint32_t *mark_to_ptr(uint32_t mark) +static inline void *mark_to_ptr(uint32_t mark) { - return ((uint32_t *)NULL) + mark; + return (void *)(uintptr_t)mark; } static inline uint32_t ptr_to_mark(void * mark) { - return (uint32_t *)mark - (uint32_t *)NULL; + return (uint32_t)(uintptr_t)mark; } static inline void mark_object(struct object *object, uint32_t mark) @@ -185,6 +222,22 @@ static int get_object_mark(struct object *object) return ptr_to_mark(decoration); } +static struct commit *rewrite_commit(struct commit *p) +{ + for (;;) { + if (p->parents && p->parents->next) + break; + if (p->object.flags & UNINTERESTING) + break; + if (!(p->object.flags & TREESAME)) + break; + if (!p->parents) + return NULL; + p = p->parents->item; + } + return p; +} + static void show_progress(void) { static int counter = 0; @@ -228,21 +281,23 @@ static void export_blob(const struct object_id *oid) if (is_null_oid(oid)) return; - object = lookup_object(oid->hash); + object = lookup_object(the_repository, oid); if (object && object->flags & SHOWN) return; if (anonymize) { buf = anonymize_blob(&size); - object = (struct object *)lookup_blob(oid); + object = (struct object *)lookup_blob(the_repository, oid); eaten = 0; } else { buf = read_object_file(oid, &type, &size); if (!buf) - die ("Could not read blob %s", oid_to_hex(oid)); - if (check_object_signature(oid, buf, size, type_name(type)) < 0) - die("sha1 mismatch in blob %s", oid_to_hex(oid)); - object = parse_object_buffer(oid, type, size, buf, &eaten); + die("could not read blob %s", oid_to_hex(oid)); + if (check_object_signature(the_repository, oid, buf, size, + type_name(type)) < 0) + die("oid mismatch in blob %s", oid_to_hex(oid)); + object = parse_object_buffer(the_repository, oid, type, + size, buf, &eaten); } if (!object) @@ -250,9 +305,12 @@ static void export_blob(const struct object_id *oid) mark_next_object(object); - printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size); + printf("blob\nmark :%"PRIu32"\n", last_idnum); + if (show_original_ids) + printf("original-oid %s\n", oid_to_hex(oid)); + printf("data %"PRIuMAX"\n", (uintmax_t)size); if (size && fwrite(buf, size, 1, stdout) != 1) - die_errno ("Could not write blob '%s'", oid_to_hex(oid)); + die_errno("could not write blob '%s'", oid_to_hex(oid)); printf("\n"); show_progress(); @@ -327,17 +385,18 @@ static void print_path(const char *path) static void *generate_fake_oid(const void *old, size_t *len) { - static uint32_t counter = 1; /* avoid null sha1 */ - unsigned char *out = xcalloc(GIT_SHA1_RAWSZ, 1); - put_be32(out + GIT_SHA1_RAWSZ - 4, counter++); + static uint32_t counter = 1; /* avoid null oid */ + const unsigned hashsz = the_hash_algo->rawsz; + unsigned char *out = xcalloc(hashsz, 1); + put_be32(out + hashsz - 4, counter++); return out; } -static const unsigned char *anonymize_sha1(const struct object_id *oid) +static const struct object_id *anonymize_oid(const struct object_id *oid) { - static struct hashmap sha1s; - size_t len = GIT_SHA1_RAWSZ; - return anonymize_mem(&sha1s, generate_fake_oid, oid, &len); + static struct hashmap objs; + size_t len = the_hash_algo->rawsz; + return anonymize_mem(&objs, generate_fake_oid, oid, &len); } static void show_filemodify(struct diff_queue_struct *q, @@ -381,7 +440,7 @@ static void show_filemodify(struct diff_queue_struct *q, string_list_insert(changed, spec->path); putchar('\n'); - if (!oidcmp(&ospec->oid, &spec->oid) && + if (oideq(&ospec->oid, &spec->oid) && ospec->mode == spec->mode) break; } @@ -396,11 +455,12 @@ static void show_filemodify(struct diff_queue_struct *q, */ if (no_data || S_ISGITLINK(spec->mode)) printf("M %06o %s ", spec->mode, - sha1_to_hex(anonymize ? - anonymize_sha1(&spec->oid) : - spec->oid.hash)); + oid_to_hex(anonymize ? + anonymize_oid(&spec->oid) : + &spec->oid)); else { - struct object *object = lookup_object(spec->oid.hash); + struct object *object = lookup_object(the_repository, + &spec->oid); printf("M %06o :%d ", spec->mode, get_object_mark(object)); } @@ -426,7 +486,7 @@ static const char *find_encoding(const char *begin, const char *end) bol = memmem(begin, end ? end - begin : strlen(begin), needle, strlen(needle)); if (!bol) - return git_commit_encoding; + return NULL; bol += strlen(needle); eol = strchrnul(bol, '\n'); *eol = '\0'; @@ -517,7 +577,7 @@ static void anonymize_ident_line(const char **beg, const char **end) /* skip "committer", "author", "tagger", etc */ end_of_header = strchr(*beg, ' '); if (!end_of_header) - die("BUG: malformed line fed to anonymize_ident_line: %.*s", + BUG("malformed line fed to anonymize_ident_line: %.*s", (int)(*end - *beg), *beg); end_of_header++; strbuf_add(out, *beg, end_of_header - *beg); @@ -559,14 +619,14 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, commit_buffer = get_commit_buffer(commit, NULL); author = strstr(commit_buffer, "\nauthor "); if (!author) - die ("Could not find author in commit %s", - oid_to_hex(&commit->object.oid)); + die("could not find author in commit %s", + oid_to_hex(&commit->object.oid)); author++; author_end = strchrnul(author, '\n'); committer = strstr(author_end, "\ncommitter "); if (!committer) - die ("Could not find committer in commit %s", - oid_to_hex(&commit->object.oid)); + die("could not find committer in commit %s", + oid_to_hex(&commit->object.oid)); committer++; committer_end = strchrnul(committer, '\n'); message = strstr(committer_end, "\n\n"); @@ -575,14 +635,15 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, message += 2; if (commit->parents && - get_object_mark(&commit->parents->item->object) != 0 && + (get_object_mark(&commit->parents->item->object) != 0 || + reference_excluded_commits) && !full_tree) { parse_commit_or_die(commit->parents->item); - diff_tree_oid(&commit->parents->item->tree->object.oid, - &commit->tree->object.oid, "", &rev->diffopt); + diff_tree_oid(get_commit_tree_oid(commit->parents->item), + get_commit_tree_oid(commit), "", &rev->diffopt); } else - diff_root_tree_oid(&commit->tree->object.oid, + diff_root_tree_oid(get_commit_tree_oid(commit), "", &rev->diffopt); /* Export the referenced blobs, and remember the marks. */ @@ -590,7 +651,14 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) export_blob(&diff_queued_diff.queue[i]->two->oid); - refname = commit->util; + refname = *revision_sources_at(&revision_sources, commit); + /* + * FIXME: string_list_remove() below for each ref is overall + * O(N^2). Compared to a history walk and diffing trees, this is + * just lost in the noise in practice. However, theoretically a + * repo may have enough refs for this to become slow. + */ + string_list_remove(&extra_refs, refname, 0); if (anonymize) { refname = anonymize_refname(refname); anonymize_ident_line(&committer, &committer_end); @@ -598,16 +666,32 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, } mark_next_object(&commit->object); - if (anonymize) + if (anonymize) { reencoded = anonymize_commit_message(message); - else if (!is_encoding_utf8(encoding)) - reencoded = reencode_string(message, "UTF-8", encoding); + } else if (encoding) { + switch(reencode_mode) { + case REENCODE_YES: + reencoded = reencode_string(message, "UTF-8", encoding); + break; + case REENCODE_NO: + break; + case REENCODE_ABORT: + die("Encountered commit-specific encoding %s in commit " + "%s; use --reencode=[yes|no] to handle it", + encoding, oid_to_hex(&commit->object.oid)); + } + } if (!commit->parents) printf("reset %s\n", refname); - printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s", - refname, last_idnum, + printf("commit %s\nmark :%"PRIu32"\n", refname, last_idnum); + if (show_original_ids) + printf("original-oid %s\n", oid_to_hex(&commit->object.oid)); + printf("%.*s\n%.*s\n", (int)(author_end - author), author, - (int)(committer_end - committer), committer, + (int)(committer_end - committer), committer); + if (!reencoded && encoding) + printf("encoding %s\n", encoding); + printf("data %u\n%s", (unsigned)(reencoded ? strlen(reencoded) : message ? strlen(message) : 0), @@ -616,13 +700,21 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, unuse_commit_buffer(commit, commit_buffer); for (i = 0, p = commit->parents; p; p = p->next) { - int mark = get_object_mark(&p->item->object); - if (!mark) + struct object *obj = &p->item->object; + int mark = get_object_mark(obj); + + if (!mark && !reference_excluded_commits) continue; if (i == 0) - printf("from :%d\n", mark); + printf("from "); + else + printf("merge "); + if (mark) + printf(":%d\n", mark); else - printf("merge :%d\n", mark); + printf("%s\n", oid_to_hex(anonymize ? + anonymize_oid(&obj->oid) : + &obj->oid)); i++; } @@ -651,8 +743,11 @@ static void handle_tail(struct object_array *commits, struct rev_info *revs, struct commit *commit; while (commits->nr) { commit = (struct commit *)object_array_pop(commits); - if (has_unshown_parent(commit)) + if (has_unshown_parent(commit)) { + /* Queue again, to be handled later */ + add_object_array(&commit->object, NULL, commits); return; + } handle_commit(commit, revs, paths_of_changed_objects); } } @@ -684,7 +779,7 @@ static void handle_tag(const char *name, struct tag *tag) buf = read_object_file(&tag->object.oid, &type, &size); if (!buf) - die ("Could not read tag %s", oid_to_hex(&tag->object.oid)); + die("could not read tag %s", oid_to_hex(&tag->object.oid)); message = memmem(buf, size, "\n\n", 2); if (message) { message += 2; @@ -720,19 +815,19 @@ static void handle_tag(const char *name, struct tag *tag) "\n-----BEGIN PGP SIGNATURE-----\n"); if (signature) switch(signed_tag_mode) { - case ABORT: - die ("Encountered signed tag %s; use " - "--signed-tags=<mode> to handle it.", - oid_to_hex(&tag->object.oid)); + case SIGNED_TAG_ABORT: + die("encountered signed tag %s; use " + "--signed-tags=<mode> to handle it", + oid_to_hex(&tag->object.oid)); case WARN: - warning ("Exporting signed tag %s", - oid_to_hex(&tag->object.oid)); + warning("exporting signed tag %s", + oid_to_hex(&tag->object.oid)); /* fallthru */ case VERBATIM: break; case WARN_STRIP: - warning ("Stripping signature from tag %s", - oid_to_hex(&tag->object.oid)); + warning("stripping signature from tag %s", + oid_to_hex(&tag->object.oid)); /* fallthru */ case STRIP: message_size = signature + 1 - message; @@ -745,41 +840,51 @@ static void handle_tag(const char *name, struct tag *tag) tagged_mark = get_object_mark(tagged); if (!tagged_mark) { switch(tag_of_filtered_mode) { - case ABORT: - die ("Tag %s tags unexported object; use " - "--tag-of-filtered-object=<mode> to handle it.", - oid_to_hex(&tag->object.oid)); + case TAG_FILTERING_ABORT: + die("tag %s tags unexported object; use " + "--tag-of-filtered-object=<mode> to handle it", + oid_to_hex(&tag->object.oid)); case DROP: /* Ignore this tag altogether */ free(buf); return; case REWRITE: - if (tagged->type != OBJ_COMMIT) { - die ("Tag %s tags unexported %s!", - oid_to_hex(&tag->object.oid), - type_name(tagged->type)); - } - p = (struct commit *)tagged; - for (;;) { - if (p->parents && p->parents->next) - break; - if (p->object.flags & UNINTERESTING) - break; - if (!(p->object.flags & TREESAME)) - break; - if (!p->parents) - die ("Can't find replacement commit for tag %s\n", - oid_to_hex(&tag->object.oid)); - p = p->parents->item; + if (tagged->type == OBJ_TAG && !mark_tags) { + die(_("Error: Cannot export nested tags unless --mark-tags is specified.")); + } else if (tagged->type == OBJ_COMMIT) { + p = rewrite_commit((struct commit *)tagged); + if (!p) { + printf("reset %s\nfrom %s\n\n", + name, oid_to_hex(&null_oid)); + free(buf); + return; + } + tagged_mark = get_object_mark(&p->object); + } else { + /* tagged->type is either OBJ_BLOB or OBJ_TAG */ + tagged_mark = get_object_mark(tagged); } - tagged_mark = get_object_mark(&p->object); } } - if (starts_with(name, "refs/tags/")) - name += 10; - printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n", - name, tagged_mark, + if (tagged->type == OBJ_TAG) { + printf("reset %s\nfrom %s\n\n", + name, oid_to_hex(&null_oid)); + } + skip_prefix(name, "refs/tags/", &name); + printf("tag %s\n", name); + if (mark_tags) { + mark_next_object(&tag->object); + printf("mark :%"PRIu32"\n", last_idnum); + } + if (tagged_mark) + printf("from :%d\n", tagged_mark); + else + printf("from %s\n", oid_to_hex(&tagged->oid)); + + if (show_original_ids) + printf("original-oid %s\n", oid_to_hex(&tag->object.oid)); + printf("%.*s%sdata %d\n%.*s\n", (int)(tagger_end - tagger), tagger, tagger == tagger_end ? "" : "\n", (int)message_size, (int)message_size, message ? message : ""); @@ -796,8 +901,8 @@ static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name) /* handle nested tags */ while (tag && tag->object.type == OBJ_TAG) { - parse_object(&tag->object.oid); - string_list_append(&extra_refs, full_name)->util = tag; + parse_object(the_repository, &tag->object.oid); + string_list_append(&tag_refs, full_name)->util = tag; tag = (struct tag *)tag->tagged; } if (!tag) @@ -826,9 +931,9 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1) continue; - if (refspecs) { + if (refspecs.nr) { char *private; - private = apply_refspecs(refspecs, refspecs_nr, full_name); + private = apply_refspecs(&refspecs, full_name); if (private) { free(full_name); full_name = private; @@ -856,24 +961,30 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) } /* - * This ref will not be updated through a commit, lets make - * sure it gets properly updated eventually. + * Make sure this ref gets properly updated eventually, whether + * through a commit or manually at the end. */ - if (commit->util || commit->object.flags & SHOWN) + if (e->item->type != OBJ_TAG) string_list_append(&extra_refs, full_name)->util = commit; - if (!commit->util) - commit->util = full_name; + + if (!*revision_sources_at(&revision_sources, commit)) + *revision_sources_at(&revision_sources, commit) = full_name; } + + string_list_sort(&extra_refs); + string_list_remove_duplicates(&extra_refs, 0); } -static void handle_tags_and_duplicates(void) +static void handle_tags_and_duplicates(struct string_list *extras) { struct commit *commit; int i; - for (i = extra_refs.nr - 1; i >= 0; i--) { - const char *name = extra_refs.items[i].string; - struct object *object = extra_refs.items[i].util; + for (i = extras->nr - 1; i >= 0; i--) { + const char *name = extras->items[i].string; + struct object *object = extras->items[i].util; + int mark; + switch (object->type) { case OBJ_TAG: handle_tag(name, (struct tag *)object); @@ -882,9 +993,45 @@ static void handle_tags_and_duplicates(void) if (anonymize) name = anonymize_refname(name); /* create refs pointing to already seen commits */ - commit = (struct commit *)object; - printf("reset %s\nfrom :%d\n\n", name, - get_object_mark(&commit->object)); + commit = rewrite_commit((struct commit *)object); + if (!commit) { + /* + * Neither this object nor any of its + * ancestors touch any relevant paths, so + * it has been filtered to nothing. Delete + * it. + */ + printf("reset %s\nfrom %s\n\n", + name, oid_to_hex(&null_oid)); + continue; + } + + mark = get_object_mark(&commit->object); + if (!mark) { + /* + * Getting here means we have a commit which + * was excluded by a negative refspec (e.g. + * fast-export ^master master). If we are + * referencing excluded commits, set the ref + * to the exact commit. Otherwise, the user + * wants the branch exported but every commit + * in its history to be deleted, which basically + * just means deletion of the ref. + */ + if (!reference_excluded_commits) { + /* delete the ref */ + printf("reset %s\nfrom %s\n\n", + name, oid_to_hex(&null_oid)); + continue; + } + /* set ref to commit using oid, not mark */ + printf("reset %s\nfrom %s\n\n", name, + oid_to_hex(&commit->object.oid)); + continue; + } + + printf("reset %s\nfrom :%d\n\n", name, mark + ); show_progress(); break; } @@ -921,11 +1068,16 @@ static void export_marks(char *file) error("Unable to write marks file %s.", file); } -static void import_marks(char *input_file) +static void import_marks(char *input_file, int check_exists) { char line[512]; - FILE *f = xfopen(input_file, "r"); + FILE *f; + struct stat sb; + + if (check_exists && stat(input_file, &sb)) + return; + f = xfopen(input_file, "r"); while (fgets(line, sizeof(line), f)) { uint32_t mark; char *line_end, *mark_end; @@ -947,7 +1099,7 @@ static void import_marks(char *input_file) if (last_idnum < mark) last_idnum = mark; - type = oid_object_info(&oid, NULL); + type = oid_object_info(the_repository, &oid, NULL); if (type < 0) die("object not found: %s", oid_to_hex(&oid)); @@ -955,7 +1107,7 @@ static void import_marks(char *input_file) /* only commits */ continue; - commit = lookup_commit(&oid); + commit = lookup_commit(the_repository, &oid); if (!commit) die("not a commit? can't happen: %s", oid_to_hex(&oid)); @@ -974,13 +1126,13 @@ static void import_marks(char *input_file) static void handle_deletes(void) { int i; - for (i = 0; i < refspecs_nr; i++) { - struct refspec *refspec = &refspecs[i]; + for (i = 0; i < refspecs.nr; i++) { + struct refspec_item *refspec = &refspecs.items[i]; if (*refspec->src) continue; printf("reset %s\nfrom %s\n\n", - refspec->dst, sha1_to_hex(null_sha1)); + refspec->dst, oid_to_hex(&null_oid)); } } @@ -989,7 +1141,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) struct rev_info revs; struct object_array commits = OBJECT_ARRAY_INIT; struct commit *commit; - char *export_filename = NULL, *import_filename = NULL; + char *export_filename = NULL, + *import_filename = NULL, + *import_filename_if_exists = NULL; uint32_t lastimportid; struct string_list refspecs_list = STRING_LIST_INIT_NODUP; struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP; @@ -1002,10 +1156,17 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, N_("mode"), N_("select handling of tags that tag filtered objects"), parse_opt_tag_of_filtered_mode), + OPT_CALLBACK(0, "reencode", &reencode_mode, N_("mode"), + N_("select handling of commit messages in an alternate encoding"), + parse_opt_reencode_mode), OPT_STRING(0, "export-marks", &export_filename, N_("file"), N_("Dump marks to this file")), OPT_STRING(0, "import-marks", &import_filename, N_("file"), N_("Import marks from this file")), + OPT_STRING(0, "import-marks-if-exists", + &import_filename_if_exists, + N_("file"), + N_("Import marks from this file if it exists")), OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger, N_("Fake a tagger when tags lack one")), OPT_BOOL(0, "full-tree", &full_tree, @@ -1016,6 +1177,13 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), N_("Apply refspec to exported refs")), OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")), + OPT_BOOL(0, "reference-excluded-parents", + &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")), + OPT_BOOL(0, "show-original-ids", &show_original_ids, + N_("Show original object ids of blobs/commits")), + OPT_BOOL(0, "mark-tags", &mark_tags, + N_("Label tags with mark ids")), + OPT_END() }; @@ -1025,9 +1193,10 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) /* we handle encodings */ git_config(git_default_config, NULL); - init_revisions(&revs, prefix); + repo_init_revisions(the_repository, &revs, prefix); + init_revision_sources(&revision_sources); revs.topo_order = 1; - revs.show_source = 1; + revs.sources = &revision_sources; revs.rewrite_parents = 1; argc = parse_options(argc, argv, prefix, options, fast_export_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); @@ -1036,25 +1205,23 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) usage_with_options (fast_export_usage, options); if (refspecs_list.nr) { - const char **refspecs_str; int i; - ALLOC_ARRAY(refspecs_str, refspecs_list.nr); for (i = 0; i < refspecs_list.nr; i++) - refspecs_str[i] = refspecs_list.items[i].string; - - refspecs_nr = refspecs_list.nr; - refspecs = parse_fetch_refspec(refspecs_nr, refspecs_str); + refspec_append(&refspecs, refspecs_list.items[i].string); string_list_clear(&refspecs_list, 1); - free(refspecs_str); } if (use_done_feature) printf("feature done\n"); + if (import_filename && import_filename_if_exists) + die(_("Cannot pass both --import-marks and --import-marks-if-exists")); if (import_filename) - import_marks(import_filename); + import_marks(import_filename, 0); + else if (import_filename_if_exists) + import_marks(import_filename_if_exists, 1); lastimportid = last_idnum; if (import_filename && revs.prune_data.nr) @@ -1077,7 +1244,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) } } - handle_tags_and_duplicates(); + handle_tags_and_duplicates(&extra_refs); + handle_tags_and_duplicates(&tag_refs); handle_deletes(); if (export_filename && lastimportid != last_idnum) @@ -1086,7 +1254,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (use_done_feature) printf("done\n"); - free_refspec(refspecs_nr, refspecs); + refspec_clear(&refspecs); return 0; } diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index a7bc1366ab..4771100072 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -3,7 +3,8 @@ #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[] = "git fetch-pack [--all] [--stdin] [--quiet | -q] [--keep | -k] [--thin] " @@ -15,13 +16,14 @@ static void add_sought_entry(struct ref ***sought, int *nr, int *alloc, { struct ref *ref; struct object_id oid; + const char *p; - if (!get_oid_hex(name, &oid)) { - if (name[GIT_SHA1_HEXSZ] == ' ') { - /* <sha1> <ref>, find refname */ - name += GIT_SHA1_HEXSZ + 1; - } else if (name[GIT_SHA1_HEXSZ] == '\0') { - ; /* <sha1>, leave sha1 as name */ + if (!parse_oid_hex(name, &oid, &p)) { + if (*p == ' ') { + /* <oid> <ref>, find refname */ + name = p + 1; + } else if (*p == '\0') { + ; /* <oid>, leave oid as name */ } else { /* <ref>, clear cruft from oid */ oidclr(&oid); @@ -52,6 +54,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct fetch_pack_args args; struct oid_array shallow = OID_ARRAY_INIT; struct string_list deepen_not = STRING_LIST_INIT_DUP; + struct packet_reader reader; + enum protocol_version version; fetch_if_missing = 0; @@ -211,10 +215,27 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) if (!conn) return args.diag_url ? 0 : 1; } - get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL, &shallow); - ref = fetch_pack(&args, fd, conn, ref, dest, sought, nr_sought, - &shallow, pack_lockfile_ptr); + packet_reader_init(&reader, fd[0], NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_DIE_ON_ERR_PACKET); + + version = discover_version(&reader); + switch (version) { + case protocol_v2: + get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL); + break; + case protocol_v1: + case protocol_v0: + get_remote_heads(&reader, &ref, 0, NULL, &shallow); + break; + case protocol_unknown_version: + BUG("unknown protocol version"); + } + + ref = fetch_pack(&args, fd, ref, sought, nr_sought, + &shallow, pack_lockfile_ptr, version); if (pack_lockfile) { printf("lock %s\n", pack_lockfile); fflush(stdout); diff --git a/builtin/fetch.c b/builtin/fetch.c index dcdfc66f09..b5788c16bf 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -5,6 +5,9 @@ #include "config.h" #include "repository.h" #include "refs.h" +#include "refspec.h" +#include "object-store.h" +#include "oidset.h" #include "commit.h" #include "builtin.h" #include "string-list.h" @@ -20,6 +23,13 @@ #include "utf8.h" #include "packfile.h" #include "list-objects-filter-options.h" +#include "commit-reach.h" +#include "branch.h" +#include "promisor-remote.h" +#include "commit-graph.h" +#include "shallow.h" + +#define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000) static const char * const builtin_fetch_usage[] = { N_("git fetch [<options>] [<repository> [<refspec>...]]"), @@ -36,6 +46,8 @@ enum { }; static int fetch_prune_config = -1; /* unspecified */ +static int fetch_show_forced_updates = 1; +static uint64_t forced_updates_ms = 0; static int prune = -1; /* unspecified */ #define PRUNE_BY_DEFAULT 0 /* do we prune by default? */ @@ -43,10 +55,13 @@ static int fetch_prune_tags_config = -1; /* unspecified */ static int prune_tags = -1; /* unspecified */ #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */ -static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative; +static int all, append, dry_run, force, keep, multiple, update_head_ok; +static int verbosity, deepen_relative, set_upstream; static int progress = -1; +static int enable_auto_gc = 1; static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen; -static int max_children = 1; +static int max_jobs = -1, submodule_fetch_jobs_config = -1; +static int fetch_parallel_config = 1; static enum transport_family family; static const char *depth; static const char *deepen_since; @@ -59,9 +74,11 @@ static const char *submodule_prefix = ""; static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND; static int shown_url = 0; -static int refmap_alloc, refmap_nr; -static const char **refmap_array; +static struct refspec refmap = REFSPEC_INIT_FETCH; static struct list_objects_filter_options filter_options; +static struct string_list server_options = STRING_LIST_INIT_DUP; +static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP; +static int fetch_write_commit_graph = -1; static int git_fetch_config(const char *k, const char *v, void *cb) { @@ -75,6 +92,11 @@ static int git_fetch_config(const char *k, const char *v, void *cb) return 0; } + if (!strcmp(k, "fetch.showforcedupdates")) { + fetch_show_forced_updates = git_config_bool(k, v); + return 0; + } + if (!strcmp(k, "submodule.recurse")) { int r = git_config_bool(k, v) ? RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; @@ -82,39 +104,33 @@ static int git_fetch_config(const char *k, const char *v, void *cb) } if (!strcmp(k, "submodule.fetchjobs")) { - max_children = parse_submodule_fetchjobs(k, v); + submodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v); return 0; } else if (!strcmp(k, "fetch.recursesubmodules")) { recurse_submodules = parse_fetch_recurse_submodules_arg(k, v); return 0; } - return git_default_config(k, v, cb); -} - -static int gitmodules_fetch_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "submodule.fetchjobs")) { - max_children = parse_submodule_fetchjobs(var, value); - return 0; - } else if (!strcmp(var, "fetch.recursesubmodules")) { - recurse_submodules = parse_fetch_recurse_submodules_arg(var, value); + if (!strcmp(k, "fetch.parallel")) { + fetch_parallel_config = git_config_int(k, v); + if (fetch_parallel_config < 0) + die(_("fetch.parallel cannot be negative")); return 0; } - return 0; + return git_default_config(k, v, cb); } static int parse_refmap_arg(const struct option *opt, const char *arg, int unset) { - ALLOC_GROW(refmap_array, refmap_nr + 1, refmap_alloc); + BUG_ON_OPT_NEG(unset); /* * "git fetch --refmap='' origin foo" * can be used to tell the command not to store anywhere */ - if (*arg) - refmap_array[refmap_nr++] = arg; + refspec_append(&refmap, arg); + return 0; } @@ -122,26 +138,28 @@ static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "all", &all, N_("fetch from all remotes")), + OPT_BOOL(0, "set-upstream", &set_upstream, + N_("set upstream for git pull/fetch")), OPT_BOOL('a', "append", &append, N_("append to .git/FETCH_HEAD instead of overwriting")), OPT_STRING(0, "upload-pack", &upload_pack, N_("path"), N_("path to upload pack on remote end")), - OPT__FORCE(&force, N_("force overwrite of local branch"), 0), + OPT__FORCE(&force, N_("force overwrite of local reference"), 0), OPT_BOOL('m', "multiple", &multiple, N_("fetch from multiple remotes")), OPT_SET_INT('t', "tags", &tags, N_("fetch all tags and associated objects"), TAGS_SET), OPT_SET_INT('n', NULL, &tags, N_("do not fetch all tags (--no-tags)"), TAGS_UNSET), - OPT_INTEGER('j', "jobs", &max_children, + OPT_INTEGER('j', "jobs", &max_jobs, N_("number of submodules fetched in parallel")), OPT_BOOL('p', "prune", &prune, N_("prune remote-tracking branches no longer on remote")), OPT_BOOL('P', "prune-tags", &prune_tags, N_("prune local tags no longer on remote and clobber changed tags")), - { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, N_("on-demand"), + OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"), N_("control recursive fetching of submodules"), - PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules }, + PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), OPT_BOOL(0, "dry-run", &dry_run, N_("dry run")), OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")), @@ -156,25 +174,34 @@ static struct option builtin_fetch_options[] = { N_("deepen history of shallow clone, excluding rev")), OPT_INTEGER(0, "deepen", &deepen_relative, N_("deepen history of shallow clone")), - { OPTION_SET_INT, 0, "unshallow", &unshallow, NULL, - N_("convert to a complete repository"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1 }, + OPT_SET_INT_F(0, "unshallow", &unshallow, + N_("convert to a complete repository"), + 1, PARSE_OPT_NONEG), { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"), N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN }, - { OPTION_CALLBACK, 0, "recurse-submodules-default", + OPT_CALLBACK_F(0, "recurse-submodules-default", &recurse_submodules_default, N_("on-demand"), N_("default for recursive fetching of submodules " "(lower priority than config files)"), - PARSE_OPT_HIDDEN, option_fetch_parse_recurse_submodules }, + PARSE_OPT_HIDDEN, option_fetch_parse_recurse_submodules), OPT_BOOL(0, "update-shallow", &update_shallow, N_("accept refs that update .git/shallow")), - { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"), - N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg }, + OPT_CALLBACK_F(0, "refmap", NULL, N_("refmap"), + N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg), + OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")), OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"), TRANSPORT_FAMILY_IPV4), OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), TRANSPORT_FAMILY_IPV6), + OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"), + N_("report that we have only objects reachable from this object")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "auto-gc", &enable_auto_gc, + N_("run 'gc --auto' after fetching")), + OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates, + N_("check for forced-updates on all updated branches")), + OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph, + N_("write the commit-graph after fetching")), OPT_END() }; @@ -202,7 +229,7 @@ static void add_merge_config(struct ref **head, for (i = 0; i < branch->merge_nr; i++) { struct ref *rm, **old_tail = *tail; - struct refspec refspec; + struct refspec_item refspec; for (rm = *head; rm; rm = rm->next) { if (branch_merge_matches(branch, i, rm->name)) { @@ -231,40 +258,92 @@ static void add_merge_config(struct ref **head, } } -static int add_existing(const char *refname, const struct object_id *oid, - int flag, void *cbdata) -{ - struct string_list *list = (struct string_list *)cbdata; - struct string_list_item *item = string_list_insert(list, refname); - struct object_id *old_oid = xmalloc(sizeof(*old_oid)); - - oidcpy(old_oid, oid); - item->util = old_oid; - return 0; -} - -static int will_fetch(struct ref **head, const unsigned char *sha1) +static void create_fetch_oidset(struct ref **head, struct oidset *out) { struct ref *rm = *head; while (rm) { - if (!hashcmp(rm->old_oid.hash, sha1)) - return 1; + oidset_insert(out, &rm->old_oid); rm = rm->next; } +} + +struct refname_hash_entry { + struct hashmap_entry ent; + struct object_id oid; + int ignore; + char refname[FLEX_ARRAY]; +}; + +static int refname_hash_entry_cmp(const void *hashmap_cmp_fn_data, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata) +{ + const struct refname_hash_entry *e1, *e2; + + e1 = container_of(eptr, const struct refname_hash_entry, ent); + e2 = container_of(entry_or_key, const struct refname_hash_entry, ent); + return strcmp(e1->refname, keydata ? keydata : e2->refname); +} + +static struct refname_hash_entry *refname_hash_add(struct hashmap *map, + const char *refname, + const struct object_id *oid) +{ + struct refname_hash_entry *ent; + size_t len = strlen(refname); + + FLEX_ALLOC_MEM(ent, refname, refname, len); + hashmap_entry_init(&ent->ent, strhash(refname)); + oidcpy(&ent->oid, oid); + hashmap_add(map, &ent->ent); + return ent; +} + +static int add_one_refname(const char *refname, + const struct object_id *oid, + int flag, void *cbdata) +{ + struct hashmap *refname_map = cbdata; + + (void) refname_hash_add(refname_map, refname, oid); return 0; } -static void find_non_local_tags(struct transport *transport, - struct ref **head, - struct ref ***tail) +static void refname_hash_init(struct hashmap *map) { - struct string_list existing_refs = STRING_LIST_INIT_DUP; - struct string_list remote_refs = STRING_LIST_INIT_NODUP; + hashmap_init(map, refname_hash_entry_cmp, NULL, 0); +} + +static int refname_hash_exists(struct hashmap *map, const char *refname) +{ + return !!hashmap_get_from_hash(map, strhash(refname), refname); +} + +static void clear_item(struct refname_hash_entry *item) +{ + item->ignore = 1; +} + +static void find_non_local_tags(const struct ref *refs, + struct ref **head, + struct ref ***tail) +{ + struct hashmap existing_refs; + struct hashmap remote_refs; + struct oidset fetch_oids = OIDSET_INIT; + struct string_list remote_refs_list = STRING_LIST_INIT_NODUP; + struct string_list_item *remote_ref_item; const struct ref *ref; - struct string_list_item *item = NULL; + struct refname_hash_entry *item = NULL; + const int quick_flags = OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT; + + refname_hash_init(&existing_refs); + refname_hash_init(&remote_refs); + create_fetch_oidset(head, &fetch_oids); - for_each_ref(add_existing, &existing_refs); - for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) { + for_each_ref(add_one_refname, &existing_refs); + for (ref = refs; ref; ref = ref->next) { if (!starts_with(ref->name, "refs/tags/")) continue; @@ -276,13 +355,11 @@ static void find_non_local_tags(struct transport *transport, */ if (ends_with(ref->name, "^{}")) { if (item && - !has_object_file_with_flags(&ref->old_oid, - OBJECT_INFO_QUICK) && - !will_fetch(head, ref->old_oid.hash) && - !has_sha1_file_with_flags(item->util, - OBJECT_INFO_QUICK) && - !will_fetch(head, item->util)) - item->util = NULL; + !has_object_file_with_flags(&ref->old_oid, quick_flags) && + !oidset_contains(&fetch_oids, &ref->old_oid) && + !has_object_file_with_flags(&item->oid, quick_flags) && + !oidset_contains(&fetch_oids, &item->oid)) + clear_item(item); item = NULL; continue; } @@ -294,52 +371,63 @@ static void find_non_local_tags(struct transport *transport, * fetch. */ if (item && - !has_sha1_file_with_flags(item->util, OBJECT_INFO_QUICK) && - !will_fetch(head, item->util)) - item->util = NULL; + !has_object_file_with_flags(&item->oid, quick_flags) && + !oidset_contains(&fetch_oids, &item->oid)) + clear_item(item); item = NULL; /* skip duplicates and refs that we already have */ - if (string_list_has_string(&remote_refs, ref->name) || - string_list_has_string(&existing_refs, ref->name)) + if (refname_hash_exists(&remote_refs, ref->name) || + refname_hash_exists(&existing_refs, ref->name)) continue; - item = string_list_insert(&remote_refs, ref->name); - item->util = (void *)&ref->old_oid; + item = refname_hash_add(&remote_refs, ref->name, &ref->old_oid); + string_list_insert(&remote_refs_list, ref->name); } - string_list_clear(&existing_refs, 1); + hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent); /* * We may have a final lightweight tag that needs to be * checked to see if it needs fetching. */ if (item && - !has_sha1_file_with_flags(item->util, OBJECT_INFO_QUICK) && - !will_fetch(head, item->util)) - item->util = NULL; + !has_object_file_with_flags(&item->oid, quick_flags) && + !oidset_contains(&fetch_oids, &item->oid)) + clear_item(item); /* - * For all the tags in the remote_refs string list, + * For all the tags in the remote_refs_list, * add them to the list of refs to be fetched */ - for_each_string_list_item(item, &remote_refs) { + for_each_string_list_item(remote_ref_item, &remote_refs_list) { + const char *refname = remote_ref_item->string; + struct ref *rm; + unsigned int hash = strhash(refname); + + item = hashmap_get_entry_from_hash(&remote_refs, hash, refname, + struct refname_hash_entry, ent); + if (!item) + BUG("unseen remote ref?"); + /* Unless we have already decided to ignore this item... */ - if (item->util) - { - struct ref *rm = alloc_ref(item->string); - rm->peer_ref = alloc_ref(item->string); - oidcpy(&rm->old_oid, item->util); - **tail = rm; - *tail = &rm->next; - } - } + if (item->ignore) + continue; - string_list_clear(&remote_refs, 0); + rm = alloc_ref(item->refname); + rm->peer_ref = alloc_ref(item->refname); + oidcpy(&rm->old_oid, &item->oid); + **tail = rm; + *tail = &rm->next; + } + hashmap_free_entries(&remote_refs, struct refname_hash_entry, ent); + string_list_clear(&remote_refs_list, 0); + oidset_clear(&fetch_oids); } -static struct ref *get_ref_map(struct transport *transport, - struct refspec *refspecs, int refspec_count, +static struct ref *get_ref_map(struct remote *remote, + const struct ref *remote_refs, + struct refspec *rs, int tags, int *autotags) { int i; @@ -350,15 +438,14 @@ static struct ref *get_ref_map(struct transport *transport, /* opportunistically-updated references: */ struct ref *orefs = NULL, **oref_tail = &orefs; - const struct ref *remote_refs = transport_get_remote_refs(transport); + struct hashmap existing_refs; - if (refspec_count) { + if (rs->nr) { struct refspec *fetch_refspec; - int fetch_refspec_nr; - for (i = 0; i < refspec_count; i++) { - get_fetch_map(remote_refs, &refspecs[i], &tail, 0); - if (refspecs[i].dst && refspecs[i].dst[0]) + for (i = 0; i < rs->nr; i++) { + get_fetch_map(remote_refs, &rs->items[i], &tail, 0); + if (rs->items[i].dst && rs->items[i].dst[0]) *autotags = 1; } /* Merge everything on the command line (but not --tags) */ @@ -385,34 +472,30 @@ static struct ref *get_ref_map(struct transport *transport, * by ref_remove_duplicates() in favor of one of these * opportunistic entries with FETCH_HEAD_IGNORE. */ - if (refmap_array) { - fetch_refspec = parse_fetch_refspec(refmap_nr, refmap_array); - fetch_refspec_nr = refmap_nr; - } else { - fetch_refspec = transport->remote->fetch; - fetch_refspec_nr = transport->remote->fetch_refspec_nr; - } + if (refmap.nr) + fetch_refspec = &refmap; + else + fetch_refspec = &remote->fetch; - for (i = 0; i < fetch_refspec_nr; i++) - get_fetch_map(ref_map, &fetch_refspec[i], &oref_tail, 1); - } else if (refmap_array) { + for (i = 0; i < fetch_refspec->nr; i++) + get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1); + } else if (refmap.nr) { die("--refmap option is only meaningful with command-line refspec(s)."); } else { /* Use the defaults */ - struct remote *remote = transport->remote; struct branch *branch = branch_get(NULL); int has_merge = branch_has_merge_config(branch); if (remote && - (remote->fetch_refspec_nr || + (remote->fetch.nr || /* Note: has_merge implies non-NULL branch->remote_name */ (has_merge && !strcmp(branch->remote_name, remote->name)))) { - for (i = 0; i < remote->fetch_refspec_nr; i++) { - get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0); - if (remote->fetch[i].dst && - remote->fetch[i].dst[0]) + for (i = 0; i < remote->fetch.nr; i++) { + get_fetch_map(remote_refs, &remote->fetch.items[i], &tail, 0); + if (remote->fetch.items[i].dst && + remote->fetch.items[i].dst[0]) *autotags = 1; if (!i && !has_merge && ref_map && - !remote->fetch[0].pattern) + !remote->fetch.items[0].pattern) ref_map->fetch_head_status = FETCH_HEAD_MERGE; } /* @@ -438,7 +521,7 @@ static struct ref *get_ref_map(struct transport *transport, /* also fetch all tags */ get_fetch_map(remote_refs, tag_refspec, &tail, 0); else if (tags == TAGS_DEFAULT && *autotags) - find_non_local_tags(transport, &ref_map, &tail); + find_non_local_tags(remote_refs, &ref_map, &tail); /* Now append any refs to be updated opportunistically: */ *tail = orefs; @@ -447,7 +530,29 @@ static struct ref *get_ref_map(struct transport *transport, tail = &rm->next; } - return ref_remove_duplicates(ref_map); + ref_map = ref_remove_duplicates(ref_map); + + refname_hash_init(&existing_refs); + for_each_ref(add_one_refname, &existing_refs); + + for (rm = ref_map; rm; rm = rm->next) { + if (rm->peer_ref) { + const char *refname = rm->peer_ref->name; + struct refname_hash_entry *peer_item; + unsigned int hash = strhash(refname); + + peer_item = hashmap_get_entry_from_hash(&existing_refs, + hash, refname, + struct refname_hash_entry, ent); + if (peer_item) { + struct object_id *old_oid = &peer_item->oid; + oidcpy(&rm->peer_ref->old_oid, old_oid); + } + } + } + hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent); + + return ref_map; } #define STORE_REF_ERROR_OTHER 1 @@ -504,7 +609,7 @@ static void adjust_refcol_width(const struct ref *ref) int max, rlen, llen, len; /* uptodate lines are only shown on high verbosity level */ - if (!verbosity && !oidcmp(&ref->peer_ref->old_oid, &ref->old_oid)) + if (!verbosity && oideq(&ref->peer_ref->old_oid, &ref->old_oid)) return; max = term_columns(); @@ -569,9 +674,14 @@ static int find_and_replace(struct strbuf *haystack, const char *needle, const char *placeholder) { - const char *p = strstr(haystack->buf, needle); + const char *p = NULL; int plen, nlen; + nlen = strlen(needle); + if (ends_with(haystack->buf, needle)) + p = haystack->buf + haystack->len - nlen; + else + p = strstr(haystack->buf, needle); if (!p) return 0; @@ -579,7 +689,6 @@ static int find_and_replace(struct strbuf *haystack, return 0; plen = strlen(p); - nlen = strlen(needle); if (plen > nlen && p[nlen] != '/') return 0; @@ -636,12 +745,13 @@ static int update_local_ref(struct ref *ref, enum object_type type; struct branch *current_branch = branch_get(NULL); const char *pretty_ref = prettify_refname(ref->name); + int fast_forward = 0; - type = oid_object_info(&ref->new_oid, NULL); + type = oid_object_info(the_repository, &ref->new_oid, NULL); if (type < 0) die(_("object %s not found"), oid_to_hex(&ref->new_oid)); - if (!oidcmp(&ref->old_oid, &ref->new_oid)) { + if (oideq(&ref->old_oid, &ref->new_oid)) { if (verbosity > 0) format_display(display, '=', _("[up to date]"), NULL, remote, pretty_ref, summary_width); @@ -664,16 +774,24 @@ static int update_local_ref(struct ref *ref, if (!is_null_oid(&ref->old_oid) && starts_with(ref->name, "refs/tags/")) { - int r; - r = s_update_ref("updating tag", ref, 0); - format_display(display, r ? '!' : 't', _("[tag update]"), - r ? _("unable to update local ref") : NULL, - remote, pretty_ref, summary_width); - return r; + if (force || ref->force) { + int r; + r = s_update_ref("updating tag", ref, 0); + format_display(display, r ? '!' : 't', _("[tag update]"), + r ? _("unable to update local ref") : NULL, + remote, pretty_ref, summary_width); + return r; + } else { + format_display(display, '!', _("[rejected]"), _("would clobber existing tag"), + remote, pretty_ref, summary_width); + return 1; + } } - current = lookup_commit_reference_gently(&ref->old_oid, 1); - updated = lookup_commit_reference_gently(&ref->new_oid, 1); + current = lookup_commit_reference_gently(the_repository, + &ref->old_oid, 1); + updated = lookup_commit_reference_gently(the_repository, + &ref->new_oid, 1); if (!current || !updated) { const char *msg; const char *what; @@ -695,9 +813,6 @@ static int update_local_ref(struct ref *ref, what = _("[new ref]"); } - if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && - (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(&ref->new_oid); r = s_update_ref(msg, ref, 0); format_display(display, r ? '!' : '*', what, r ? _("unable to update local ref") : NULL, @@ -705,15 +820,21 @@ static int update_local_ref(struct ref *ref, return r; } - if (in_merge_bases(current, updated)) { + if (fetch_show_forced_updates) { + uint64_t t_before = getnanotime(); + fast_forward = in_merge_bases(current, updated); + forced_updates_ms += (getnanotime() - t_before) / 1000000; + } else { + fast_forward = 1; + } + + if (fast_forward) { struct strbuf quickref = STRBUF_INIT; int r; + strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV); strbuf_addstr(&quickref, ".."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); - if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && - (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(&ref->new_oid); r = s_update_ref("fast-forward", ref, 1); format_display(display, r ? '!' : ' ', quickref.buf, r ? _("unable to update local ref") : NULL, @@ -726,9 +847,6 @@ static int update_local_ref(struct ref *ref, strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV); strbuf_addstr(&quickref, "..."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); - if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && - (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(&ref->new_oid); r = s_update_ref("forced-update", ref, 1); format_display(display, r ? '!' : '+', quickref.buf, r ? _("unable to update local ref") : _("forced update"), @@ -756,8 +874,17 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid) return 0; } +static const char warn_show_forced_updates[] = +N_("Fetch normally indicates which branches had a forced update,\n" + "but that check has been disabled. To re-enable, use '--show-forced-updates'\n" + "flag or run 'git config fetch.showForcedUpdates true'."); +static const char warn_time_show_forced_updates[] = +N_("It took %.2f seconds to check forced updates. You can use\n" + "'--no-show-forced-updates' or run 'git config fetch.showForcedUpdates false'\n" + " to avoid this check.\n"); + static int store_updated_refs(const char *raw_url, const char *remote_name, - struct ref *ref_map) + int connectivity_checked, struct ref *ref_map) { FILE *fp; struct commit *commit; @@ -766,7 +893,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, const char *what, *kind; struct ref *rm; char *url; - const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(); + const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(the_repository); int want_status; int summary_width = transport_summary_width(ref_map); @@ -779,10 +906,14 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, else url = xstrdup("foreign"); - rm = ref_map; - if (check_connected(iterate_ref_map, &rm, NULL)) { - rc = error(_("%s did not send all necessary objects\n"), url); - goto abort; + if (!connectivity_checked) { + struct check_connected_options opt = CHECK_CONNECTED_INIT; + + rm = ref_map; + if (check_connected(iterate_ref_map, &rm, &opt)) { + rc = error(_("%s did not send all necessary objects\n"), url); + goto abort; + } } prepare_format_display(ref_map); @@ -806,7 +937,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, continue; } - commit = lookup_commit_reference_gently(&rm->old_oid, + commit = lookup_commit_reference_gently(the_repository, + &rm->old_oid, 1); if (!commit) rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; @@ -821,23 +953,19 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, ref->force = rm->peer_ref->force; } + if (recurse_submodules != RECURSE_SUBMODULES_OFF) + check_for_new_submodule_commits(&rm->old_oid); if (!strcmp(rm->name, "HEAD")) { kind = ""; what = ""; } - else if (starts_with(rm->name, "refs/heads/")) { + else if (skip_prefix(rm->name, "refs/heads/", &what)) kind = "branch"; - what = rm->name + 11; - } - else if (starts_with(rm->name, "refs/tags/")) { + else if (skip_prefix(rm->name, "refs/tags/", &what)) kind = "tag"; - what = rm->name + 10; - } - else if (starts_with(rm->name, "refs/remotes/")) { + else if (skip_prefix(rm->name, "refs/remotes/", &what)) kind = "remote-tracking branch"; - what = rm->name + 13; - } else { kind = ""; what = rm->name; @@ -904,6 +1032,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, " 'git remote prune %s' to remove any old, conflicting " "branches"), remote_name); + if (advice_fetch_show_forced_updates) { + if (!fetch_show_forced_updates) { + warning(_(warn_show_forced_updates)); + } else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) { + warning(_(warn_time_show_forced_updates), + forced_updates_ms / 1000.0); + } + } + abort: strbuf_release(¬e); free(url); @@ -916,10 +1053,11 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, * everything we are going to fetch already exists and is connected * locally. */ -static int quickfetch(struct ref *ref_map) +static int check_exist_and_connected(struct ref *ref_map) { struct ref *rm = ref_map; struct check_connected_options opt = CHECK_CONNECTED_INIT; + struct ref *r; /* * If we are deepening a shallow clone we already have these @@ -930,28 +1068,60 @@ static int quickfetch(struct ref *ref_map) */ if (deepen) return -1; + + /* + * check_connected() allows objects to merely be promised, but + * we need all direct targets to exist. + */ + for (r = rm; r; r = r->next) { + if (!has_object_file_with_flags(&r->old_oid, + OBJECT_INFO_SKIP_FETCH_OBJECT)) + return -1; + } + opt.quiet = 1; return check_connected(iterate_ref_map, &rm, &opt); } static int fetch_refs(struct transport *transport, struct ref *ref_map) { - int ret = quickfetch(ref_map); - if (ret) + int ret = check_exist_and_connected(ref_map); + if (ret) { + trace2_region_enter("fetch", "fetch_refs", the_repository); ret = transport_fetch_refs(transport, ref_map); + trace2_region_leave("fetch", "fetch_refs", the_repository); + } if (!ret) - ret |= store_updated_refs(transport->url, - transport->remote->name, - ref_map); + /* + * Keep the new pack's ".keep" file around to allow the caller + * time to update refs to reference the new objects. + */ + return 0; + transport_unlock_pack(transport); + return ret; +} + +/* Update local refs based on the ref values fetched from a remote */ +static int consume_refs(struct transport *transport, struct ref *ref_map) +{ + int connectivity_checked = transport->smart_options + ? transport->smart_options->connectivity_checked : 0; + int ret; + trace2_region_enter("fetch", "consume_refs", the_repository); + ret = store_updated_refs(transport->url, + transport->remote->name, + connectivity_checked, + ref_map); transport_unlock_pack(transport); + trace2_region_leave("fetch", "consume_refs", the_repository); return ret; } -static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map, - const char *raw_url) +static int prune_refs(struct refspec *rs, struct ref *ref_map, + const char *raw_url) { int url_len, i, result = 0; - struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map); + struct ref *ref, *stale_refs = get_stale_heads(rs, ref_map); char *url; int summary_width = transport_summary_width(stale_refs); const char *dangling_msg = dry_run @@ -1018,7 +1188,7 @@ static void check_not_current_branch(struct ref *ref_map) static int truncate_fetch_head(void) { - const char *filename = git_path_fetch_head(); + const char *filename = git_path_fetch_head(the_repository); FILE *fp = fopen_for_writing(filename); if (!fp) @@ -1038,9 +1208,44 @@ static void set_option(struct transport *transport, const char *name, const char name, transport->url); } + +static int add_oid(const char *refname, const struct object_id *oid, int flags, + void *cb_data) +{ + struct oid_array *oids = cb_data; + + oid_array_append(oids, oid); + return 0; +} + +static void add_negotiation_tips(struct git_transport_options *smart_options) +{ + struct oid_array *oids = xcalloc(1, sizeof(*oids)); + int i; + + for (i = 0; i < negotiation_tip.nr; i++) { + const char *s = negotiation_tip.items[i].string; + int old_nr; + if (!has_glob_specials(s)) { + struct object_id oid; + if (get_oid(s, &oid)) + die("%s is not a valid object", s); + oid_array_append(oids, &oid); + continue; + } + old_nr = oids->nr; + for_each_glob_ref(add_oid, s, oids); + if (old_nr == oids->nr) + warning("Ignoring --negotiation-tip=%s because it does not match any refs", + s); + } + smart_options->negotiation_tips = oids; +} + static struct transport *prepare_transport(struct remote *remote, int deepen) { struct transport *transport; + transport = transport_get(remote, NULL); transport_set_verbosity(transport, verbosity, progress); transport->family = family; @@ -1060,10 +1265,17 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) if (update_shallow) set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes"); if (filter_options.choice) { - set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, - filter_options.filter_spec); + const char *spec = + expand_list_objects_filter_spec(&filter_options); + set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec); set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); } + if (negotiation_tip.nr) { + if (transport->smart_options) + add_negotiation_tips(transport->smart_options); + else + warning("Ignoring --negotiation-tip because the protocol does not support it."); + } return transport; } @@ -1088,7 +1300,8 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - fetch_refs(transport, ref_map); + if (!fetch_refs(transport, ref_map)) + consume_refs(transport, ref_map); if (gsecondary) { transport_disconnect(gsecondary); @@ -1097,15 +1310,14 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map) } static int do_fetch(struct transport *transport, - struct refspec *refs, int ref_count) + struct refspec *rs) { - struct string_list existing_refs = STRING_LIST_INIT_DUP; struct ref *ref_map; - struct ref *rm; int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; - - for_each_ref(add_existing, &existing_refs); + const struct ref *remote_refs; + struct argv_array ref_prefixes = ARGV_ARRAY_INIT; + int must_list_refs = 1; if (tags == TAGS_DEFAULT) { if (transport->remote->fetch_tags == 2) @@ -1121,22 +1333,45 @@ static int do_fetch(struct transport *transport, goto cleanup; } - ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags); - if (!update_head_ok) - check_not_current_branch(ref_map); + if (rs->nr) { + int i; - for (rm = ref_map; rm; rm = rm->next) { - if (rm->peer_ref) { - struct string_list_item *peer_item = - string_list_lookup(&existing_refs, - rm->peer_ref->name); - if (peer_item) { - struct object_id *old_oid = peer_item->util; - oidcpy(&rm->peer_ref->old_oid, old_oid); + refspec_ref_prefixes(rs, &ref_prefixes); + + /* + * We can avoid listing refs if all of them are exact + * OIDs + */ + must_list_refs = 0; + for (i = 0; i < rs->nr; i++) { + if (!rs->items[i].exact_sha1) { + must_list_refs = 1; + break; } } + } else if (transport->remote && transport->remote->fetch.nr) + refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes); + + if (tags == TAGS_SET || tags == TAGS_DEFAULT) { + must_list_refs = 1; + if (ref_prefixes.argc) + argv_array_push(&ref_prefixes, "refs/tags/"); } + if (must_list_refs) { + trace2_region_enter("fetch", "remote_refs", the_repository); + remote_refs = transport_get_remote_refs(transport, &ref_prefixes); + trace2_region_leave("fetch", "remote_refs", the_repository); + } else + remote_refs = NULL; + + argv_array_clear(&ref_prefixes); + + ref_map = get_ref_map(transport->remote, remote_refs, rs, + tags, &autotags); + if (!update_head_ok) + check_not_current_branch(ref_map); + if (tags == TAGS_DEFAULT && autotags) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); if (prune) { @@ -1145,20 +1380,64 @@ static int do_fetch(struct transport *transport, * explicitly (via command line or configuration); we * don't care whether --tags was specified. */ - if (ref_count) { - prune_refs(refs, ref_count, ref_map, transport->url); + if (rs->nr) { + prune_refs(rs, ref_map, transport->url); } else { - prune_refs(transport->remote->fetch, - transport->remote->fetch_refspec_nr, + prune_refs(&transport->remote->fetch, ref_map, transport->url); } } - if (fetch_refs(transport, ref_map)) { + if (fetch_refs(transport, ref_map) || consume_refs(transport, ref_map)) { free_refs(ref_map); retcode = 1; goto cleanup; } + + if (set_upstream) { + struct branch *branch = branch_get("HEAD"); + struct ref *rm; + struct ref *source_ref = NULL; + + /* + * We're setting the upstream configuration for the + * current branch. The relevant upstream is the + * fetched branch that is meant to be merged with the + * current one, i.e. the one fetched to FETCH_HEAD. + * + * When there are several such branches, consider the + * request ambiguous and err on the safe side by doing + * nothing and just emit a warning. + */ + for (rm = ref_map; rm; rm = rm->next) { + if (!rm->peer_ref) { + if (source_ref) { + warning(_("multiple branches detected, incompatible with --set-upstream")); + goto skip; + } else { + source_ref = rm; + } + } + } + if (source_ref) { + if (!strcmp(source_ref->name, "HEAD") || + starts_with(source_ref->name, "refs/heads/")) + install_branch_config(0, + branch->name, + transport->remote->name, + source_ref->name); + else if (starts_with(source_ref->name, "refs/remotes/")) + warning(_("not setting upstream for a remote remote-tracking branch")); + else if (starts_with(source_ref->name, "refs/tags/")) + warning(_("not setting upstream for a remote tag")); + else + warning(_("unknown branch type")); + } else { + warning(_("no source branch found.\n" + "you need to specify exactly one branch with the --set-upstream option.")); + } + } + skip: free_refs(ref_map); /* if neither --no-tags nor --tags was specified, do automated tag @@ -1166,14 +1445,13 @@ static int do_fetch(struct transport *transport, if (tags == TAGS_DEFAULT && autotags) { struct ref **tail = &ref_map; ref_map = NULL; - find_non_local_tags(transport, &ref_map, &tail); + find_non_local_tags(remote_refs, &ref_map, &tail); if (ref_map) backfill_tags(transport, ref_map); free_refs(ref_map); } cleanup: - string_list_clear(&existing_refs, 1); return retcode; } @@ -1256,7 +1534,62 @@ static void add_options_to_argv(struct argv_array *argv) } -static int fetch_multiple(struct string_list *list) +/* Fetch multiple remotes in parallel */ + +struct parallel_fetch_state { + const char **argv; + struct string_list *remotes; + int next, result; +}; + +static int fetch_next_remote(struct child_process *cp, struct strbuf *out, + void *cb, void **task_cb) +{ + struct parallel_fetch_state *state = cb; + char *remote; + + if (state->next < 0 || state->next >= state->remotes->nr) + return 0; + + remote = state->remotes->items[state->next++].string; + *task_cb = remote; + + argv_array_pushv(&cp->args, state->argv); + argv_array_push(&cp->args, remote); + cp->git_cmd = 1; + + if (verbosity >= 0) + printf(_("Fetching %s\n"), remote); + + return 1; +} + +static int fetch_failed_to_start(struct strbuf *out, void *cb, void *task_cb) +{ + struct parallel_fetch_state *state = cb; + const char *remote = task_cb; + + state->result = error(_("Could not fetch %s"), remote); + + return 0; +} + +static int fetch_finished(int result, struct strbuf *out, + void *cb, void *task_cb) +{ + struct parallel_fetch_state *state = cb; + const char *remote = task_cb; + + if (result) { + strbuf_addf(out, _("could not fetch '%s' (exit code: %d)\n"), + remote, result); + state->result = -1; + } + + return 0; +} + +static int fetch_multiple(struct string_list *list, int max_children) { int i, result = 0; struct argv_array argv = ARGV_ARRAY_INIT; @@ -1267,23 +1600,38 @@ static int fetch_multiple(struct string_list *list) return errcode; } - argv_array_pushl(&argv, "fetch", "--append", NULL); + argv_array_pushl(&argv, "fetch", "--append", "--no-auto-gc", + "--no-write-commit-graph", NULL); add_options_to_argv(&argv); - for (i = 0; i < list->nr; i++) { - const char *name = list->items[i].string; - argv_array_push(&argv, name); - if (verbosity >= 0) - printf(_("Fetching %s\n"), name); - if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { - error(_("Could not fetch %s"), name); - result = 1; + if (max_children != 1 && list->nr != 1) { + struct parallel_fetch_state state = { argv.argv, list, 0, 0 }; + + argv_array_push(&argv, "--end-of-options"); + result = run_processes_parallel_tr2(max_children, + &fetch_next_remote, + &fetch_failed_to_start, + &fetch_finished, + &state, + "fetch", "parallel/fetch"); + + if (!result) + result = state.result; + } else + for (i = 0; i < list->nr; i++) { + const char *name = list->items[i].string; + argv_array_push(&argv, name); + if (verbosity >= 0) + printf(_("Fetching %s\n"), name); + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + error(_("Could not fetch %s"), name); + result = 1; + } + argv_array_pop(&argv); } - argv_array_pop(&argv); - } argv_array_clear(&argv); - return result; + return !!result; } /* @@ -1303,45 +1651,34 @@ static inline void fetch_one_setup_partial(struct remote *remote) * If no prior partial clone/fetch and the current fetch DID NOT * request a partial-fetch, do a normal fetch. */ - if (!repository_format_partial_clone && !filter_options.choice) + if (!has_promisor_remote() && !filter_options.choice) return; /* - * If this is the FIRST partial-fetch request, we enable partial - * on this repo and remember the given filter-spec as the default - * for subsequent fetches to this remote. + * If this is a partial-fetch request, we enable partial on + * this repo if not already enabled and remember the given + * filter-spec as the default for subsequent fetches to this + * remote. */ - if (!repository_format_partial_clone && filter_options.choice) { + if (filter_options.choice) { partial_clone_register(remote->name, &filter_options); return; } /* - * We are currently limited to only ONE promisor remote and only - * allow partial-fetches from the promisor remote. - */ - if (strcmp(remote->name, repository_format_partial_clone)) { - if (filter_options.choice) - die(_("--filter can only be used with the remote configured in core.partialClone")); - return; - } - - /* * Do a partial-fetch from the promisor remote using either the * explicitly given filter-spec or inherit the filter-spec from * the config. */ if (!filter_options.choice) - partial_clone_get_default_filter_spec(&filter_options); + partial_clone_get_default_filter_spec(&filter_options, remote->name); return; } static int fetch_one(struct remote *remote, int argc, const char **argv, int prune_tags_ok) { - static const char **refs = NULL; - struct refspec *refspec; - int ref_nr = 0; - int j = 0; + struct refspec rs = REFSPEC_INIT_FETCH; + int i; int exit_code; int maybe_prune_tags; int remote_via_config = remote_is_configured(remote, 0); @@ -1374,37 +1711,36 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, int pru maybe_prune_tags = prune_tags_ok && prune_tags; if (maybe_prune_tags && remote_via_config) - add_prune_tags_to_fetch_refspec(remote); - - if (argc > 0 || (maybe_prune_tags && !remote_via_config)) { - size_t nr_alloc = st_add3(argc, maybe_prune_tags, 1); - refs = xcalloc(nr_alloc, sizeof(const char *)); - if (maybe_prune_tags) { - refs[j++] = xstrdup("refs/tags/*:refs/tags/*"); - ref_nr++; + refspec_append(&remote->fetch, TAG_REFSPEC); + + if (maybe_prune_tags && (argc || !remote_via_config)) + refspec_append(&rs, TAG_REFSPEC); + + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "tag")) { + char *tag; + i++; + if (i >= argc) + die(_("You need to specify a tag name.")); + + tag = xstrfmt("refs/tags/%s:refs/tags/%s", + argv[i], argv[i]); + refspec_append(&rs, tag); + free(tag); + } else { + refspec_append(&rs, argv[i]); } } - if (argc > 0) { - int i; - for (i = 0; i < argc; i++) { - if (!strcmp(argv[i], "tag")) { - i++; - if (i >= argc) - die(_("You need to specify a tag name.")); - refs[j++] = xstrfmt("refs/tags/%s:refs/tags/%s", - argv[i], argv[i]); - } else - refs[j++] = argv[i]; - ref_nr++; - } - } + if (server_options.nr) + gtransport->server_options = &server_options; sigchain_push_common(unlock_pack_on_signal); atexit(unlock_pack); - refspec = parse_fetch_refspec(ref_nr, refs); - exit_code = do_fetch(gtransport, refspec, ref_nr); - free_refspec(ref_nr, refspec); + sigchain_push(SIGPIPE, SIG_IGN); + exit_code = do_fetch(gtransport, &rs); + sigchain_pop(SIGPIPE); + refspec_clear(&rs); transport_disconnect(gtransport); gtransport = NULL; return exit_code; @@ -1417,18 +1753,16 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) struct remote *remote = NULL; int result = 0; int prune_tags_ok = 1; - struct argv_array argv_gc_auto = ARGV_ARRAY_INIT; packet_trace_identity("fetch"); - fetch_if_missing = 0; - /* Record the command line for the reflog */ strbuf_addstr(&default_rla, "fetch"); for (i = 1; i < argc; i++) strbuf_addf(&default_rla, " %s", argv[i]); - config_from_gitmodules(gitmodules_fetch_config, NULL); + fetch_config_from_gitmodules(&submodule_fetch_jobs_config, + &recurse_submodules); git_config(git_fetch_config, NULL); argc = parse_options(argc, argv, prefix, @@ -1444,7 +1778,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (unshallow) { if (depth) die(_("--depth and --unshallow cannot be used together")); - else if (!is_repository_shallow()) + else if (!is_repository_shallow(the_repository)) die(_("--unshallow on a complete repository does not make sense")); else depth = xstrfmt("%d", INFINITE_DEPTH); @@ -1456,7 +1790,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth || deepen_since || deepen_not.nr) deepen = 1; - if (filter_options.choice && !repository_format_partial_clone) + if (filter_options.choice && !has_promisor_remote()) die("--filter can only be used when extensions.partialClone is set"); if (all) { @@ -1490,18 +1824,31 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } if (remote) { - if (filter_options.choice || repository_format_partial_clone) + if (filter_options.choice || has_promisor_remote()) fetch_one_setup_partial(remote); result = fetch_one(remote, argc, argv, prune_tags_ok); } else { + int max_children = max_jobs; + if (filter_options.choice) - die(_("--filter can only be used with the remote configured in core.partialClone")); + die(_("--filter can only be used with the remote " + "configured in extensions.partialclone")); + + if (max_children < 0) + max_children = fetch_parallel_config; + /* TODO should this also die if we have a previous partial-clone? */ - result = fetch_multiple(&list); + result = fetch_multiple(&list, max_children); } if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { struct argv_array options = ARGV_ARRAY_INIT; + int max_children = max_jobs; + + if (max_children < 0) + max_children = submodule_fetch_jobs_config; + if (max_children < 0) + max_children = fetch_parallel_config; add_options_to_argv(&options); result = fetch_populated_submodules(the_repository, @@ -1516,13 +1863,24 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) string_list_clear(&list, 0); - close_all_packs(the_repository->objects); + prepare_repo_settings(the_repository); + if (fetch_write_commit_graph > 0 || + (fetch_write_commit_graph < 0 && + the_repository->settings.fetch_write_commit_graph)) { + int commit_graph_flags = COMMIT_GRAPH_WRITE_SPLIT; + + if (progress) + commit_graph_flags |= COMMIT_GRAPH_WRITE_PROGRESS; + + write_commit_graph_reachable(the_repository->objects->odb, + commit_graph_flags, + NULL); + } + + close_object_store(the_repository->objects); - argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); - if (verbosity < 0) - argv_array_push(&argv_gc_auto, "--quiet"); - run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD); - argv_array_clear(&argv_gc_auto); + if (enable_auto_gc) + run_auto_gc(verbosity < 0); return result; } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index bd680be687..48a8699de7 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -1,661 +1,13 @@ #include "builtin.h" -#include "cache.h" #include "config.h" -#include "refs.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 "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 && oidcmp(&table->item[i].given, given)) - continue; - if (commit && oidcmp(&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; - struct src_data *src_data; - struct string_list_item *item; - int pulling_head = 0; - struct object_id oid; - - if (len < GIT_SHA1_HEXSZ + 3 || line[GIT_SHA1_HEXSZ] != '\t') - return 1; - - if (starts_with(line + GIT_SHA1_HEXSZ + 1, "not-for-merge")) - return 0; - - if (line[GIT_SHA1_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 += GIT_SHA1_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 (starts_with(line, "branch ")) { - origin_data->is_local_branch = 1; - origin = line + 7; - string_list_append(&src_data->branch, origin); - src_data->head_status |= 2; - } else if (starts_with(line, "tag ")) { - origin = line; - string_list_append(&src_data->tag, origin + 4); - 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(parse_object(oid), oid_to_hex(oid), GIT_SHA1_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'); - struct object_id oid; - struct commit *parent; - struct object *obj; - - len = newline ? newline - p : strlen(p); - pos += len + !!newline; - - if (len < GIT_SHA1_HEXSZ + 3 || - get_oid_hex(p, &oid) || - p[GIT_SHA1_HEXSZ] != '\t' || - p[GIT_SHA1_HEXSZ + 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(&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(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 (!oidcmp(&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"); - init_revisions(&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/for-each-ref.c b/builtin/for-each-ref.c index e931be9ce4..57489e4eab 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -37,8 +37,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")), OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT__COLOR(&format.use_color, N_("respect format colors")), - OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), - N_("field name to sort on"), &parse_opt_ref_sorting), + OPT_REF_SORT(sorting_tail), OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"), N_("print only refs which points at the given object"), parse_opt_object_name), @@ -71,7 +70,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (!sorting) sorting = ref_default_sorting(); - sorting->ignore_case = icase; + ref_sorting_icase_all(sorting, icase); filter.ignore_case = icase; filter.name_patterns = argv; diff --git a/builtin/fsck.c b/builtin/fsck.c index 087360a675..f02cbdb439 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -1,3 +1,4 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "cache.h" #include "repository.h" @@ -18,6 +19,8 @@ #include "decorate.h" #include "packfile.h" #include "object-store.h" +#include "run-command.h" +#include "worktree.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@ -35,8 +38,6 @@ static int check_strict; static int keep_cache_objects; static struct fsck_options fsck_walk_options = FSCK_OPTIONS_DEFAULT; static struct fsck_options fsck_obj_options = FSCK_OPTIONS_DEFAULT; -static struct object_id head_oid; -static const char *head_points_at; static int errors_found; static int write_lost_and_found; static int verbose; @@ -47,34 +48,25 @@ static int name_objects; #define ERROR_REACHABLE 02 #define ERROR_PACK 04 #define ERROR_REFS 010 +#define ERROR_COMMIT_GRAPH 020 +#define ERROR_MULTI_PACK_INDEX 040 -static const char *describe_object(struct object *obj) +static const char *describe_object(const struct object_id *oid) { - static struct strbuf buf = STRBUF_INIT; - char *name = name_objects ? - lookup_decoration(fsck_walk_options.object_names, obj) : NULL; - - strbuf_reset(&buf); - strbuf_addstr(&buf, oid_to_hex(&obj->oid)); - if (name) - strbuf_addf(&buf, " (%s)", name); - - return buf.buf; + return fsck_describe_object(&fsck_walk_options, oid); } -static const char *printable_type(struct object *obj) +static const char *printable_type(const struct object_id *oid, + enum object_type type) { const char *ret; - if (obj->type == OBJ_NONE) { - enum object_type type = oid_object_info(&obj->oid, NULL); - if (type > 0) - object_as_type(obj, type, 0); - } + if (type == OBJ_NONE) + type = oid_object_info(the_repository, oid, NULL); - ret = type_name(obj->type); + ret = type_name(type); if (!ret) - ret = "unknown"; + ret = _("unknown"); return ret; } @@ -102,25 +94,38 @@ static int fsck_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -static void objreport(struct object *obj, const char *msg_type, - const char *err) -{ - fprintf(stderr, "%s in %s %s: %s\n", - msg_type, printable_type(obj), describe_object(obj), err); -} - static int objerror(struct object *obj, const char *err) { errors_found |= ERROR_OBJECT; - objreport(obj, "error", err); + /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */ + fprintf_ln(stderr, _("error in %s %s: %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid), err); return -1; } static int fsck_error_func(struct fsck_options *o, - struct object *obj, int type, const char *message) -{ - objreport(obj, (type == FSCK_WARN) ? "warning" : "error", message); - return (type == FSCK_WARN) ? 0 : 1; + const struct object_id *oid, + enum object_type object_type, + int msg_type, const char *message) +{ + switch (msg_type) { + case FSCK_WARN: + /* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */ + fprintf_ln(stderr, _("warning in %s %s: %s"), + printable_type(oid, object_type), + describe_object(oid), message); + return 0; + case FSCK_ERROR: + /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */ + fprintf_ln(stderr, _("error in %s %s: %s"), + printable_type(oid, object_type), + describe_object(oid), message); + return 1; + default: + BUG("%d (FSCK_IGNORE?) should never trigger this callback", + msg_type); + } } static struct object_array pending; @@ -136,17 +141,19 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt */ if (!obj) { /* ... these references to parent->fld are safe here */ - printf("broken link from %7s %s\n", - printable_type(parent), describe_object(parent)); - printf("broken link from %7s %s\n", - (type == OBJ_ANY ? "unknown" : type_name(type)), "unknown"); + printf_ln(_("broken link from %7s %s"), + printable_type(&parent->oid, parent->type), + describe_object(&parent->oid)); + printf_ln(_("broken link from %7s %s"), + (type == OBJ_ANY ? _("unknown") : type_name(type)), + _("unknown")); errors_found |= ERROR_REACHABLE; return 1; } if (type != OBJ_ANY && obj->type != type) /* ... and the reference to parent is safe here */ - objerror(parent, "wrong object type in link"); + objerror(parent, _("wrong object type in link")); if (obj->flags & REACHABLE) return 0; @@ -162,10 +169,12 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!(obj->flags & HAS_OBJ)) { if (parent && !has_object_file(&obj->oid)) { - printf("broken link from %7s %s\n", - printable_type(parent), describe_object(parent)); - printf(" to %7s %s\n", - printable_type(obj), describe_object(obj)); + printf_ln(_("broken link from %7s %s\n" + " to %7s %s"), + printable_type(&parent->oid, parent->type), + describe_object(&parent->oid), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); errors_found |= ERROR_REACHABLE; } return 1; @@ -214,6 +223,48 @@ static int mark_used(struct object *obj, int type, void *data, struct fsck_optio return 0; } +static void mark_unreachable_referents(const struct object_id *oid) +{ + struct fsck_options options = FSCK_OPTIONS_DEFAULT; + struct object *obj = lookup_object(the_repository, oid); + + if (!obj || !(obj->flags & HAS_OBJ)) + return; /* not part of our original set */ + if (obj->flags & REACHABLE) + return; /* reachable objects already traversed */ + + /* + * Avoid passing OBJ_NONE to fsck_walk, which will parse the object + * (and we want to avoid parsing blobs). + */ + if (obj->type == OBJ_NONE) { + enum object_type type = oid_object_info(the_repository, + &obj->oid, NULL); + if (type > 0) + object_as_type(the_repository, obj, type, 0); + } + + options.walk = mark_used; + fsck_walk(obj, NULL, &options); +} + +static int mark_loose_unreachable_referents(const struct object_id *oid, + const char *path, + void *data) +{ + mark_unreachable_referents(oid); + return 0; +} + +static int mark_packed_unreachable_referents(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + mark_unreachable_referents(oid); + return 0; +} + /* * Check a single reachable object */ @@ -227,10 +278,11 @@ static void check_reachable_object(struct object *obj) if (!(obj->flags & HAS_OBJ)) { if (is_promisor_object(&obj->oid)) return; - if (has_sha1_pack(obj->oid.hash)) + if (has_object_pack(&obj->oid)) return; /* it is in pack - forget about it */ - printf("missing %s %s\n", printable_type(obj), - describe_object(obj)); + printf_ln(_("missing %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); errors_found |= ERROR_REACHABLE; return; } @@ -255,8 +307,9 @@ static void check_unreachable_object(struct object *obj) * since this is something that is prunable. */ if (show_unreachable) { - printf("unreachable %s %s\n", printable_type(obj), - describe_object(obj)); + printf_ln(_("unreachable %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); return; } @@ -274,27 +327,28 @@ static void check_unreachable_object(struct object *obj) */ if (!(obj->flags & USED)) { if (show_dangling) - printf("dangling %s %s\n", printable_type(obj), - describe_object(obj)); + printf_ln(_("dangling %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); if (write_lost_and_found) { char *filename = git_pathdup("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", - describe_object(obj)); + describe_object(&obj->oid)); FILE *f; if (safe_create_leading_directories_const(filename)) { - error("Could not create lost-found"); + error(_("could not create lost-found")); free(filename); return; } f = xfopen(filename, "w"); if (obj->type == OBJ_BLOB) { if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1)) - die_errno("Could not write '%s'", filename); + die_errno(_("could not write '%s'"), filename); } else - fprintf(f, "%s\n", describe_object(obj)); + fprintf(f, "%s\n", describe_object(&obj->oid)); if (fclose(f)) - die_errno("Could not finish '%s'", + die_errno(_("could not finish '%s'"), filename); free(filename); } @@ -311,7 +365,7 @@ static void check_unreachable_object(struct object *obj) static void check_object(struct object *obj) { if (verbose) - fprintf(stderr, "Checking %s\n", describe_object(obj)); + fprintf_ln(stderr, _("Checking %s"), describe_object(&obj->oid)); if (obj->flags & REACHABLE) check_reachable_object(obj); @@ -326,10 +380,30 @@ static void check_connectivity(void) /* Traverse the pending reachable objects */ traverse_reachable(); + /* + * With --connectivity-only, we won't have actually opened and marked + * unreachable objects with USED. Do that now to make --dangling, etc + * accurate. + */ + if (connectivity_only && (show_dangling || write_lost_and_found)) { + /* + * Even though we already have a "struct object" for each of + * these in memory, we must not iterate over the internal + * object hash as we do below. Our loop would potentially + * resize the hash, making our iteration invalid. + * + * Instead, we'll just go back to the source list of objects, + * and ignore any that weren't present in our earlier + * traversal. + */ + for_each_loose_object(mark_loose_unreachable_referents, NULL, 0); + for_each_packed_object(mark_packed_unreachable_referents, NULL, 0); + } + /* Look up all the requirements, warn about missing objects.. */ max = get_max_object_index(); if (verbose) - fprintf(stderr, "Checking connectivity (%d objects)\n", max); + fprintf_ln(stderr, _("Checking connectivity (%d objects)"), max); for (i = 0; i < max; i++) { struct object *obj = get_indexed_object(i); @@ -339,7 +413,7 @@ static void check_connectivity(void) } } -static int fsck_obj(struct object *obj) +static int fsck_obj(struct object *obj, void *buffer, unsigned long size) { int err; @@ -348,12 +422,13 @@ static int fsck_obj(struct object *obj) obj->flags |= SEEN; if (verbose) - fprintf(stderr, "Checking %s %s\n", - printable_type(obj), describe_object(obj)); + fprintf_ln(stderr, _("Checking %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); if (fsck_walk(obj, NULL, &fsck_obj_options)) - objerror(obj, "broken links"); - err = fsck_object(obj, NULL, 0, &fsck_obj_options); + objerror(obj, _("broken links")); + err = fsck_object(obj, buffer, size, &fsck_obj_options); if (err) goto out; @@ -361,17 +436,19 @@ static int fsck_obj(struct object *obj) struct commit *commit = (struct commit *) obj; if (!commit->parents && show_root) - printf("root %s\n", describe_object(&commit->object)); + printf_ln(_("root %s"), + describe_object(&commit->object.oid)); } if (obj->type == OBJ_TAG) { struct tag *tag = (struct tag *) obj; if (show_tags && tag->tagged) { - printf("tagged %s %s", printable_type(tag->tagged), - describe_object(tag->tagged)); - printf(" (%s) in %s\n", tag->tag, - describe_object(&tag->object)); + printf_ln(_("tagged %s %s (%s) in %s"), + printable_type(&tag->tagged->oid, tag->tagged->type), + describe_object(&tag->tagged->oid), + tag->tag, + describe_object(&tag->object.oid)); } } @@ -379,7 +456,8 @@ out: if (obj->type == OBJ_TREE) free_tree_buffer((struct tree *)obj); if (obj->type == OBJ_COMMIT) - free_commit_buffer((struct commit *)obj); + free_commit_buffer(the_repository->parsed_objects, + (struct commit *)obj); return err; } @@ -391,14 +469,16 @@ static int fsck_obj_buffer(const struct object_id *oid, enum object_type type, * verify_packfile(), data_valid variable for details. */ struct object *obj; - obj = parse_object_buffer(oid, type, size, buffer, eaten); + obj = parse_object_buffer(the_repository, oid, type, size, buffer, + eaten); if (!obj) { errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", oid_to_hex(oid)); + return error(_("%s: object corrupt or missing"), + oid_to_hex(oid)); } obj->flags &= ~(REACHABLE | SEEN); obj->flags |= HAS_OBJ; - return fsck_obj(obj); + return fsck_obj(obj, buffer, size); } static int default_refs; @@ -409,16 +489,17 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, struct object *obj; if (!is_null_oid(oid)) { - obj = lookup_object(oid->hash); + obj = lookup_object(the_repository, oid); if (obj && (obj->flags & HAS_OBJ)) { - if (timestamp && name_objects) - add_decoration(fsck_walk_options.object_names, - obj, - xstrfmt("%s@{%"PRItime"}", refname, timestamp)); + if (timestamp) + fsck_put_object_name(&fsck_walk_options, oid, + "%s@{%"PRItime"}", + refname, timestamp); obj->flags |= USED; mark_object_reachable(obj); } else if (!is_promisor_object(oid)) { - error("%s: invalid reflog entry %s", refname, oid_to_hex(oid)); + error(_("%s: invalid reflog entry %s"), + refname, oid_to_hex(oid)); errors_found |= ERROR_REACHABLE; } } @@ -431,8 +512,8 @@ static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid const char *refname = cb_data; if (verbose) - fprintf(stderr, "Checking reflog %s->%s\n", - oid_to_hex(ooid), oid_to_hex(noid)); + fprintf_ln(stderr, _("Checking reflog %s->%s"), + oid_to_hex(ooid), oid_to_hex(noid)); fsck_handle_reflog_oid(refname, ooid, 0); fsck_handle_reflog_oid(refname, noid, timestamp); @@ -442,7 +523,11 @@ static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid static int fsck_handle_reflog(const char *logname, const struct object_id *oid, int flag, void *cb_data) { - for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname); + struct strbuf refname = STRBUF_INIT; + + strbuf_worktree_ref(cb_data, &refname, logname); + for_each_reflog_ent(refname.buf, fsck_handle_reflog_ent, refname.buf); + strbuf_release(&refname); return 0; } @@ -451,7 +536,7 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, { struct object *obj; - obj = parse_object(oid); + obj = parse_object(the_repository, oid); if (!obj) { if (is_promisor_object(oid)) { /* @@ -461,32 +546,53 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, default_refs++; return 0; } - error("%s: invalid sha1 pointer %s", refname, oid_to_hex(oid)); + error(_("%s: invalid sha1 pointer %s"), + refname, oid_to_hex(oid)); errors_found |= ERROR_REACHABLE; /* We'll continue with the rest despite the error.. */ return 0; } if (obj->type != OBJ_COMMIT && is_branch(refname)) { - error("%s: not a commit", refname); + error(_("%s: not a commit"), refname); errors_found |= ERROR_REFS; } default_refs++; obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(refname)); + fsck_put_object_name(&fsck_walk_options, + oid, "%s", refname); mark_object_reachable(obj); return 0; } +static int fsck_head_link(const char *head_ref_name, + const char **head_points_at, + struct object_id *head_oid); + static void get_default_heads(void) { - if (head_points_at && !is_null_oid(&head_oid)) - fsck_handle_ref("HEAD", &head_oid, 0, NULL); + struct worktree **worktrees, **p; + const char *head_points_at; + struct object_id head_oid; + for_each_rawref(fsck_handle_ref, NULL); - if (include_reflogs) - for_each_reflog(fsck_handle_reflog, NULL); + + worktrees = get_worktrees(0); + for (p = worktrees; *p; p++) { + struct worktree *wt = *p; + struct strbuf ref = STRBUF_INIT; + + strbuf_worktree_ref(wt, &ref, "HEAD"); + fsck_head_link(ref.buf, &head_points_at, &head_oid); + if (head_points_at && !is_null_oid(&head_oid)) + fsck_handle_ref(ref.buf, &head_oid, 0, NULL); + strbuf_release(&ref); + + if (include_reflogs) + refs_for_each_reflog(get_worktree_ref_store(wt), + fsck_handle_reflog, wt); + } + free_worktrees(worktrees); /* * Not having any default heads isn't really fatal, but @@ -501,55 +607,55 @@ static void get_default_heads(void) * "show_unreachable" flag. */ if (!default_refs) { - fprintf(stderr, "notice: No default references\n"); + fprintf_ln(stderr, _("notice: No default references")); show_unreachable = 0; } } -static struct object *parse_loose_object(const struct object_id *oid, - const char *path) +static int fsck_loose(const struct object_id *oid, const char *path, void *data) { struct object *obj; - void *contents; enum object_type type; unsigned long size; + void *contents; int eaten; - if (read_loose_object(path, oid, &type, &size, &contents) < 0) - return NULL; + if (read_loose_object(path, oid, &type, &size, &contents) < 0) { + errors_found |= ERROR_OBJECT; + error(_("%s: object corrupt or missing: %s"), + oid_to_hex(oid), path); + return 0; /* keep checking other objects */ + } if (!contents && type != OBJ_BLOB) - die("BUG: read_loose_object streamed a non-blob"); - - obj = parse_object_buffer(oid, type, size, contents, &eaten); + BUG("read_loose_object streamed a non-blob"); - if (!eaten) - free(contents); - return obj; -} - -static int fsck_loose(const struct object_id *oid, const char *path, void *data) -{ - struct object *obj = parse_loose_object(oid, path); + obj = parse_object_buffer(the_repository, oid, type, size, + contents, &eaten); if (!obj) { errors_found |= ERROR_OBJECT; - error("%s: object corrupt or missing: %s", + error(_("%s: object could not be parsed: %s"), oid_to_hex(oid), path); + if (!eaten) + free(contents); return 0; /* keep checking other objects */ } obj->flags &= ~(REACHABLE | SEEN); obj->flags |= HAS_OBJ; - if (fsck_obj(obj)) + if (fsck_obj(obj, contents, size)) errors_found |= ERROR_OBJECT; - return 0; + + if (!eaten) + free(contents); + return 0; /* keep checking other objects, even if we saw an error */ } static int fsck_cruft(const char *basename, const char *path, void *data) { if (!starts_with(basename, "tmp_obj_")) - fprintf(stderr, "bad sha1 file: %s\n", path); + fprintf_ln(stderr, _("bad sha1 file: %s"), path); return 0; } @@ -564,7 +670,7 @@ static void fsck_object_dir(const char *path) struct progress *progress = NULL; if (verbose) - fprintf(stderr, "Checking object directory\n"); + fprintf_ln(stderr, _("Checking object directory")); if (show_progress) progress = start_progress(_("Checking object directories"), 256); @@ -575,33 +681,37 @@ static void fsck_object_dir(const char *path) stop_progress(&progress); } -static int fsck_head_link(void) +static int fsck_head_link(const char *head_ref_name, + const char **head_points_at, + struct object_id *head_oid) { int null_is_error = 0; if (verbose) - fprintf(stderr, "Checking HEAD link\n"); + fprintf_ln(stderr, _("Checking %s link"), head_ref_name); - head_points_at = resolve_ref_unsafe("HEAD", 0, &head_oid, NULL); - if (!head_points_at) { + *head_points_at = resolve_ref_unsafe(head_ref_name, 0, head_oid, NULL); + if (!*head_points_at) { errors_found |= ERROR_REFS; - return error("Invalid HEAD"); + return error(_("invalid %s"), head_ref_name); } - if (!strcmp(head_points_at, "HEAD")) + if (!strcmp(*head_points_at, head_ref_name)) /* detached HEAD */ null_is_error = 1; - else if (!starts_with(head_points_at, "refs/heads/")) { + else if (!starts_with(*head_points_at, "refs/heads/")) { errors_found |= ERROR_REFS; - return error("HEAD points to something strange (%s)", - head_points_at); + return error(_("%s points to something strange (%s)"), + head_ref_name, *head_points_at); } - if (is_null_oid(&head_oid)) { + if (is_null_oid(head_oid)) { if (null_is_error) { errors_found |= ERROR_REFS; - return error("HEAD: detached HEAD points at nothing"); + return error(_("%s: detached HEAD points at nothing"), + head_ref_name); } - fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n", - head_points_at + 11); + fprintf_ln(stderr, + _("notice: %s points to an unborn branch (%s)"), + head_ref_name, *head_points_at + 11); } return 0; } @@ -612,23 +722,21 @@ static int fsck_cache_tree(struct cache_tree *it) int err = 0; if (verbose) - fprintf(stderr, "Checking cache tree\n"); + fprintf_ln(stderr, _("Checking cache tree")); if (0 <= it->entry_count) { - struct object *obj = parse_object(&it->oid); + struct object *obj = parse_object(the_repository, &it->oid); if (!obj) { - error("%s: invalid sha1 pointer in cache-tree", + error(_("%s: invalid sha1 pointer in cache-tree"), oid_to_hex(&it->oid)); errors_found |= ERROR_REFS; return 1; } obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(":")); + fsck_put_object_name(&fsck_walk_options, &it->oid, ":"); mark_object_reachable(obj); if (obj->type != OBJ_TREE) - err |= objerror(obj, "non-tree in cache-tree"); + err |= objerror(obj, _("non-tree in cache-tree")); } for (i = 0; i < it->subtree_nr; i++) err |= fsck_cache_tree(it->down[i]->cache_tree); @@ -637,7 +745,7 @@ static int fsck_cache_tree(struct cache_tree *it) static void mark_object_for_connectivity(const struct object_id *oid) { - struct object *obj = lookup_unknown_object(oid->hash); + struct object *obj = lookup_unknown_object(oid); obj->flags |= HAS_OBJ; } @@ -684,13 +792,13 @@ static struct option fsck_opts[] = { int cmd_fsck(int argc, const char **argv, const char *prefix) { int i; - struct alternate_object_database *alt; + struct object_directory *odb; /* fsck knows how to handle missing promisor objects */ fetch_if_missing = 0; errors_found = 0; - check_replace_refs = 0; + read_replace_refs = 0; argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); @@ -711,24 +819,17 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } if (name_objects) - fsck_walk_options.object_names = - xcalloc(1, sizeof(struct decoration)); + fsck_enable_object_names(&fsck_walk_options); git_config(fsck_config, NULL); - fsck_head_link(); if (connectivity_only) { for_each_loose_object(mark_loose_for_connectivity, NULL, 0); for_each_packed_object(mark_packed_for_connectivity, NULL, 0); } else { - struct alternate_object_database *alt_odb_list; - - fsck_object_dir(get_object_directory()); - prepare_alt_odb(the_repository); - alt_odb_list = the_repository->objects->alt_odb_list; - for (alt = alt_odb_list; alt; alt = alt->next) - fsck_object_dir(alt->path); + for (odb = the_repository->objects->odb; odb; odb = odb->next) + fsck_object_dir(odb->path); if (check_full) { struct packed_git *p; @@ -736,7 +837,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) struct progress *progress = NULL; if (show_progress) { - for (p = get_packed_git(the_repository); p; + for (p = get_all_packs(the_repository); p; p = p->next) { if (open_pack_index(p)) continue; @@ -745,40 +846,44 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) progress = start_progress(_("Checking objects"), total); } - for (p = get_packed_git(the_repository); p; + for (p = get_all_packs(the_repository); p; p = p->next) { /* verify gives error messages itself */ - if (verify_pack(p, fsck_obj_buffer, + if (verify_pack(the_repository, + p, fsck_obj_buffer, progress, count)) errors_found |= ERROR_PACK; count += p->num_objects; } stop_progress(&progress); } + + if (fsck_finish(&fsck_obj_options)) + errors_found |= ERROR_OBJECT; } for (i = 0; i < argc; i++) { const char *arg = argv[i]; struct object_id oid; if (!get_oid(arg, &oid)) { - struct object *obj = lookup_object(oid.hash); + struct object *obj = lookup_object(the_repository, + &oid); if (!obj || !(obj->flags & HAS_OBJ)) { if (is_promisor_object(&oid)) continue; - error("%s: object missing", oid_to_hex(&oid)); + error(_("%s: object missing"), oid_to_hex(&oid)); errors_found |= ERROR_OBJECT; continue; } obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(arg)); + fsck_put_object_name(&fsck_walk_options, &oid, + "%s", arg); mark_object_reachable(obj); continue; } - error("invalid parameter: expected sha1, got '%s'", arg); + error(_("invalid parameter: expected sha1, got '%s'"), arg); errors_found |= ERROR_OBJECT; } @@ -804,15 +909,14 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) mode = active_cache[i]->ce_mode; if (S_ISGITLINK(mode)) continue; - blob = lookup_blob(&active_cache[i]->oid); + blob = lookup_blob(the_repository, + &active_cache[i]->oid); if (!blob) continue; obj = &blob->object; obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, - xstrfmt(":%s", active_cache[i]->name)); + fsck_put_object_name(&fsck_walk_options, &obj->oid, + ":%s", active_cache[i]->name); mark_object_reachable(obj); } if (active_cache_tree) @@ -820,5 +924,38 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } check_connectivity(); + + if (!git_config_get_bool("core.commitgraph", &i) && i) { + struct child_process commit_graph_verify = CHILD_PROCESS_INIT; + const char *verify_argv[] = { "commit-graph", "verify", NULL, NULL, NULL }; + + prepare_alt_odb(the_repository); + for (odb = the_repository->objects->odb; odb; odb = odb->next) { + child_process_init(&commit_graph_verify); + commit_graph_verify.argv = verify_argv; + commit_graph_verify.git_cmd = 1; + verify_argv[2] = "--object-dir"; + verify_argv[3] = odb->path; + if (run_command(&commit_graph_verify)) + errors_found |= ERROR_COMMIT_GRAPH; + } + } + + if (!git_config_get_bool("core.multipackindex", &i) && i) { + struct child_process midx_verify = CHILD_PROCESS_INIT; + const char *midx_argv[] = { "multi-pack-index", "verify", NULL, NULL, NULL }; + + prepare_alt_odb(the_repository); + for (odb = the_repository->objects->odb; odb; odb = odb->next) { + child_process_init(&midx_verify); + midx_verify.argv = midx_argv; + midx_verify.git_cmd = 1; + midx_argv[2] = "--object-dir"; + midx_argv[3] = odb->path; + if (run_command(&midx_verify)) + errors_found |= ERROR_MULTI_PACK_INDEX; + } + } + return errors_found; } diff --git a/builtin/gc.c b/builtin/gc.c index 3e67124eaa..8e0b9cf41b 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -20,8 +20,14 @@ #include "sigchain.h" #include "argv-array.h" #include "commit.h" +#include "commit-graph.h" #include "packfile.h" #include "object-store.h" +#include "pack.h" +#include "pack-objects.h" +#include "blob.h" +#include "tree.h" +#include "promisor-remote.h" #define FAILED_RUN "failed to run %s" @@ -41,6 +47,8 @@ static timestamp_t gc_log_expire_time; static const char *gc_log_expire = "1.day.ago"; static const char *prune_expire = "2.weeks.ago"; static const char *prune_worktrees_expire = "3.months.ago"; +static unsigned long big_pack_threshold; +static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT; static struct argv_array reflog = ARGV_ARRAY_INIT; @@ -108,6 +116,19 @@ static void process_log_file_on_signal(int signo) raise(signo); } +static int gc_config_is_timestamp_never(const char *var) +{ + const char *value; + timestamp_t expire; + + if (!git_config_get_value(var, &value) && value) { + if (parse_expiry_date(value, &expire)) + die(_("failed to parse '%s' value '%s'"), var, value); + return expire == 0; + } + return 0; +} + static void gc_config(void) { const char *value; @@ -119,6 +140,10 @@ static void gc_config(void) pack_refs = git_config_bool("gc.packrefs", value); } + if (gc_config_is_timestamp_never("gc.reflogexpire") && + gc_config_is_timestamp_never("gc.reflogexpireunreachable")) + prune_reflogs = 0; + git_config_get_int("gc.aggressivewindow", &aggressive_window); git_config_get_int("gc.aggressivedepth", &aggressive_depth); git_config_get_int("gc.auto", &gc_auto_threshold); @@ -128,6 +153,9 @@ static void gc_config(void) git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire); git_config_get_expiry("gc.logexpiry", &gc_log_expire); + git_config_get_ulong("gc.bigpackthreshold", &big_pack_threshold); + git_config_get_ulong("pack.deltacachesize", &max_delta_cache_size); + git_config(git_default_config, NULL); } @@ -144,9 +172,7 @@ static int too_many_loose_objects(void) int auto_threshold; int num_loose = 0; int needed = 0; - - if (gc_auto_threshold <= 0) - return 0; + const unsigned hexsz_loose = the_hash_algo->hexsz - 2; dir = opendir(git_path("objects/17")); if (!dir) @@ -154,8 +180,8 @@ static int too_many_loose_objects(void) auto_threshold = DIV_ROUND_UP(gc_auto_threshold, 256); while ((ent = readdir(dir)) != NULL) { - if (strspn(ent->d_name, "0123456789abcdef") != 38 || - ent->d_name[38] != '\0') + if (strspn(ent->d_name, "0123456789abcdef") != hexsz_loose || + ent->d_name[hexsz_loose] != '\0') continue; if (++num_loose > auto_threshold) { needed = 1; @@ -166,6 +192,28 @@ static int too_many_loose_objects(void) return needed; } +static struct packed_git *find_base_packs(struct string_list *packs, + unsigned long limit) +{ + struct packed_git *p, *base = NULL; + + for (p = get_all_packs(the_repository); p; p = p->next) { + if (!p->pack_local) + continue; + if (limit) { + if (p->pack_size >= limit) + string_list_append(packs, p->pack_name); + } else if (!base || base->pack_size < p->pack_size) { + base = p; + } + } + + if (base) + string_list_append(packs, base->pack_name); + + return base; +} + static int too_many_packs(void) { struct packed_git *p; @@ -174,7 +222,7 @@ static int too_many_packs(void) if (gc_auto_pack_limit <= 0) return 0; - for (cnt = 0, p = get_packed_git(the_repository); p; p = p->next) { + for (cnt = 0, p = get_all_packs(the_repository); p; p = p->next) { if (!p->pack_local) continue; if (p->pack_keep) @@ -188,7 +236,86 @@ static int too_many_packs(void) return gc_auto_pack_limit < cnt; } -static void add_repack_all_option(void) +static uint64_t total_ram(void) +{ +#if defined(HAVE_SYSINFO) + struct sysinfo si; + + if (!sysinfo(&si)) + return si.totalram; +#elif defined(HAVE_BSD_SYSCTL) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM)) + int64_t physical_memory; + int mib[2]; + size_t length; + + mib[0] = CTL_HW; +# if defined(HW_MEMSIZE) + mib[1] = HW_MEMSIZE; +# else + mib[1] = HW_PHYSMEM; +# endif + length = sizeof(int64_t); + if (!sysctl(mib, 2, &physical_memory, &length, NULL, 0)) + return physical_memory; +#elif defined(GIT_WINDOWS_NATIVE) + MEMORYSTATUSEX memInfo; + + memInfo.dwLength = sizeof(MEMORYSTATUSEX); + if (GlobalMemoryStatusEx(&memInfo)) + return memInfo.ullTotalPhys; +#endif + return 0; +} + +static uint64_t estimate_repack_memory(struct packed_git *pack) +{ + unsigned long nr_objects = approximate_object_count(); + size_t os_cache, heap; + + if (!pack || !nr_objects) + return 0; + + /* + * First we have to scan through at least one pack. + * Assume enough room in OS file cache to keep the entire pack + * or we may accidentally evict data of other processes from + * the cache. + */ + os_cache = pack->pack_size + pack->index_size; + /* then pack-objects needs lots more for book keeping */ + heap = sizeof(struct object_entry) * nr_objects; + /* + * internal rev-list --all --objects takes up some memory too, + * let's say half of it is for blobs + */ + heap += sizeof(struct blob) * nr_objects / 2; + /* + * and the other half is for trees (commits and tags are + * usually insignificant) + */ + heap += sizeof(struct tree) * nr_objects / 2; + /* and then obj_hash[], underestimated in fact */ + heap += sizeof(struct object *) * nr_objects; + /* revindex is used also */ + heap += sizeof(struct revindex_entry) * nr_objects; + /* + * read_sha1_file() (either at delta calculation phase, or + * writing phase) also fills up the delta base cache + */ + heap += delta_base_cache_limit; + /* and of course pack-objects has its own delta cache */ + heap += max_delta_cache_size; + + return os_cache + heap; +} + +static int keep_one_pack(struct string_list_item *item, void *data) +{ + argv_array_pushf(&repack, "--keep-pack=%s", basename(item->string)); + return 0; +} + +static void add_repack_all_option(struct string_list *keep_pack) { if (prune_expire && !strcmp(prune_expire, "now")) argv_array_push(&repack, "-a"); @@ -197,11 +324,14 @@ static void add_repack_all_option(void) if (prune_expire) argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire); } + + if (keep_pack) + for_each_string_list(keep_pack, keep_one_pack, NULL); } static void add_repack_incremental_option(void) { - argv_array_push(&repack, "--no-write-bitmap-index"); + argv_array_push(&repack, "--no-write-bitmap-index"); } static int need_to_gc(void) @@ -219,9 +349,35 @@ static int need_to_gc(void) * we run "repack -A -d -l". Otherwise we tell the caller * there is no need. */ - if (too_many_packs()) - add_repack_all_option(); - else if (too_many_loose_objects()) + if (too_many_packs()) { + struct string_list keep_pack = STRING_LIST_INIT_NODUP; + + if (big_pack_threshold) { + find_base_packs(&keep_pack, big_pack_threshold); + if (keep_pack.nr >= gc_auto_pack_limit) { + big_pack_threshold = 0; + string_list_clear(&keep_pack, 0); + find_base_packs(&keep_pack, 0); + } + } else { + struct packed_git *p = find_base_packs(&keep_pack, 0); + uint64_t mem_have, mem_want; + + mem_have = total_ram(); + mem_want = estimate_repack_memory(p); + + /* + * Only allow 1/2 of memory for pack-objects, leave + * the rest for the OS and other processes in the + * system. + */ + if (!mem_have || mem_want < mem_have / 2) + string_list_clear(&keep_pack, 0); + } + + add_repack_all_option(&keep_pack); + string_list_clear(&keep_pack, 0); + } else if (too_many_loose_objects()) add_repack_incremental_option(); else return 0; @@ -234,7 +390,7 @@ static int need_to_gc(void) /* return NULL on success, else hostname running the gc */ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) { - static struct lock_file lock; + struct lock_file lock = LOCK_INIT; char my_host[HOST_NAME_MAX + 1]; struct strbuf sb = STRBUF_INIT; struct stat st; @@ -299,10 +455,16 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) return NULL; } +/* + * Returns 0 if there was no previous error and gc can proceed, 1 if + * gc should not proceed due to an error in the last run. Prints a + * message and returns -1 if an error occurred while reading gc.log + */ static int report_last_gc_error(void) { struct strbuf sb = STRBUF_INIT; int ret = 0; + ssize_t len; struct stat st; char *gc_log_path = git_pathdup("gc.log"); @@ -310,39 +472,53 @@ static int report_last_gc_error(void) if (errno == ENOENT) goto done; - ret = error_errno(_("Can't stat %s"), gc_log_path); + ret = error_errno(_("cannot stat '%s'"), gc_log_path); goto done; } if (st.st_mtime < gc_log_expire_time) goto done; - ret = strbuf_read_file(&sb, gc_log_path, 0); - if (ret > 0) - ret = error(_("The last gc run reported the following. " + len = strbuf_read_file(&sb, gc_log_path, 0); + if (len < 0) + ret = error_errno(_("cannot read '%s'"), gc_log_path); + else if (len > 0) { + /* + * A previous gc failed. Report the error, and don't + * bother with an automatic gc run since it is likely + * to fail in the same way. + */ + warning(_("The last gc run reported the following. " "Please correct the root cause\n" "and remove %s.\n" "Automatic cleanup will not be performed " "until the file is removed.\n\n" "%s"), gc_log_path, sb.buf); + ret = 1; + } strbuf_release(&sb); done: free(gc_log_path); return ret; } -static int gc_before_repack(void) +static void gc_before_repack(void) { + /* + * We may be called twice, as both the pre- and + * post-daemonized phases will call us, but running these + * commands more than once is pointless and wasteful. + */ + static int done = 0; + if (done++) + return; + if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, pack_refs_cmd.argv[0]); + die(FAILED_RUN, pack_refs_cmd.argv[0]); if (prune_reflogs && run_command_v_opt(reflog.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, reflog.argv[0]); - - pack_refs = 0; - prune_reflogs = 0; - return 0; + die(FAILED_RUN, reflog.argv[0]); } int cmd_gc(int argc, const char **argv, const char *prefix) @@ -354,6 +530,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) const char *name; pid_t pid; int daemonized = 0; + int keep_base_pack = -1; + timestamp_t dummy; struct option builtin_gc_options[] = { OPT__QUIET(&quiet, N_("suppress progress reporting")), @@ -366,6 +544,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) OPT_BOOL_F(0, "force", &force, N_("force running gc even if there may be another gc running"), PARSE_OPT_NOCOMPLETE), + OPT_BOOL(0, "keep-largest-pack", &keep_base_pack, + N_("repack all other packs except the largest pack")), OPT_END() }; @@ -382,7 +562,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) /* default expiry time, overwritten in gc_config */ gc_config(); if (parse_expiry_date(gc_log_expire, &gc_log_expire_time)) - die(_("Failed to parse gc.logexpiry value %s"), gc_log_expire); + die(_("failed to parse gc.logexpiry value %s"), gc_log_expire); if (pack_refs < 0) pack_refs = !is_bare_repository(); @@ -392,6 +572,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (argc > 0) usage_with_options(builtin_gc_usage, builtin_gc_options); + if (prune_expire && parse_expiry_date(prune_expire, &dummy)) + die(_("failed to parse prune expiry value %s"), prune_expire); + if (aggressive) { argv_array_push(&repack, "-f"); if (aggressive_depth > 0) @@ -416,13 +599,17 @@ int cmd_gc(int argc, const char **argv, const char *prefix) fprintf(stderr, _("See \"git help gc\" for manual housekeeping.\n")); } if (detach_auto) { - if (report_last_gc_error()) - return -1; + int ret = report_last_gc_error(); + if (ret < 0) + /* an I/O error occurred, already reported */ + exit(128); + if (ret == 1) + /* Last gc --auto failed. Skip this one. */ + return 0; if (lock_repo_for_gc(force, &pid)) return 0; - if (gc_before_repack()) - return -1; + gc_before_repack(); /* dies on failure */ delete_tempfile(&pidfile); /* @@ -431,8 +618,19 @@ int cmd_gc(int argc, const char **argv, const char *prefix) */ daemonized = !daemonize(); } - } else - add_repack_all_option(); + } else { + struct string_list keep_pack = STRING_LIST_INIT_NODUP; + + if (keep_base_pack != -1) { + if (keep_base_pack) + find_base_packs(&keep_pack, 0); + } else if (big_pack_threshold) { + find_base_packs(&keep_pack, big_pack_threshold); + } + + add_repack_all_option(&keep_pack); + string_list_clear(&keep_pack, 0); + } name = lock_repo_for_gc(force, &pid); if (name) { @@ -451,38 +649,46 @@ int cmd_gc(int argc, const char **argv, const char *prefix) atexit(process_log_file_at_exit); } - if (gc_before_repack()) - return -1; + gc_before_repack(); if (!repository_format_precious_objects) { + close_object_store(the_repository->objects); if (run_command_v_opt(repack.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, repack.argv[0]); + die(FAILED_RUN, repack.argv[0]); if (prune_expire) { argv_array_push(&prune, prune_expire); if (quiet) argv_array_push(&prune, "--no-progress"); - if (repository_format_partial_clone) + if (has_promisor_remote()) argv_array_push(&prune, "--exclude-promisor-objects"); if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, prune.argv[0]); + die(FAILED_RUN, prune.argv[0]); } } if (prune_worktrees_expire) { argv_array_push(&prune_worktrees, prune_worktrees_expire); if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, prune_worktrees.argv[0]); + die(FAILED_RUN, prune_worktrees.argv[0]); } if (run_command_v_opt(rerere.argv, RUN_GIT_CMD)) - return error(FAILED_RUN, rerere.argv[0]); + die(FAILED_RUN, rerere.argv[0]); report_garbage = report_pack_garbage; reprepare_packed_git(the_repository); - if (pack_garbage.nr > 0) + if (pack_garbage.nr > 0) { + close_object_store(the_repository->objects); clean_pack_garbage(); + } + + prepare_repo_settings(the_repository); + if (the_repository->settings.gc_write_commit_graph == 1) + write_commit_graph_reachable(the_repository->objects->odb, + !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0, + NULL); if (auto_gc && too_many_loose_objects()) warning(_("There are too many unreachable loose objects; " diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c index 2706fcfaf2..491af9202d 100644 --- a/builtin/get-tar-commit-id.c +++ b/builtin/get-tar-commit-id.c @@ -21,6 +21,8 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) char *content = buffer + RECORDSIZE; const char *comment; ssize_t n; + long len; + char *end; if (argc != 1) usage(builtin_get_tar_commit_id_usage); @@ -32,10 +34,18 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) die_errno("git get-tar-commit-id: EOF before reading tar header"); if (header->typeflag[0] != 'g') return 1; - if (!skip_prefix(content, "52 comment=", &comment)) + + len = strtol(content, &end, 10); + if (errno == ERANGE || end == content || len < 0) + return 1; + if (!skip_prefix(end, " comment=", &comment)) + return 1; + len -= comment - content; + if (len < 1 || !(len % 2) || + hash_algo_by_length((len - 1) / 2) == GIT_HASH_UNKNOWN) return 1; - if (write_in_full(1, comment, 41) < 0) + if (write_in_full(1, comment, len) < 0) die_errno("git get-tar-commit-id: write error"); return 0; diff --git a/builtin/grep.c b/builtin/grep.c index 5f32d2ce84..a5056f395a 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -3,6 +3,7 @@ * * Copyright (c) 2006 Junio C Hamano */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "repository.h" #include "config.h" @@ -23,6 +24,7 @@ #include "submodule.h" #include "submodule-config.h" #include "object-store.h" +#include "packfile.h" static char const * const grep_usage[] = { N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), @@ -31,10 +33,8 @@ static char const * const grep_usage[] = { static int recurse_submodules; -#define GREP_NUM_THREADS_DEFAULT 8 static int num_threads; -#ifndef NO_PTHREADS static pthread_t *threads; /* We use one producer thread and THREADS consumer @@ -70,13 +70,11 @@ static pthread_mutex_t grep_mutex; static inline void grep_lock(void) { - assert(num_threads); pthread_mutex_lock(&grep_mutex); } static inline void grep_unlock(void) { - assert(num_threads); pthread_mutex_unlock(&grep_mutex); } @@ -93,8 +91,11 @@ static pthread_cond_t cond_result; static int skip_first_line; -static void add_work(struct grep_opt *opt, const struct grep_source *gs) +static void add_work(struct grep_opt *opt, struct grep_source *gs) { + if (opt->binary != GREP_BINARY_TEXT) + grep_source_load_driver(gs, opt->repo->index); + grep_lock(); while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) { @@ -102,8 +103,6 @@ static void add_work(struct grep_opt *opt, const struct grep_source *gs) } todo[todo_end].source = *gs; - if (opt->binary != GREP_BINARY_TEXT) - grep_source_load_driver(&todo[todo_end].source); todo[todo_end].done = 0; strbuf_reset(&todo[todo_end].out); todo_end = (todo_end + 1) % ARRAY_SIZE(todo); @@ -201,12 +200,12 @@ static void start_threads(struct grep_opt *opt) int i; pthread_mutex_init(&grep_mutex, NULL); - pthread_mutex_init(&grep_read_mutex, NULL); pthread_mutex_init(&grep_attr_mutex, NULL); pthread_cond_init(&cond_add, NULL); pthread_cond_init(&cond_write, NULL); pthread_cond_init(&cond_result, NULL); grep_use_locks = 1; + enable_obj_read_lock(); for (i = 0; i < ARRAY_SIZE(todo); i++) { strbuf_init(&todo[i].out, 0); @@ -233,6 +232,9 @@ static int wait_all(void) int hit = 0; int i; + if (!HAVE_THREADS) + BUG("Never call this function unless you have started threads"); + grep_lock(); all_work_added = 1; @@ -255,22 +257,15 @@ static int wait_all(void) free(threads); pthread_mutex_destroy(&grep_mutex); - pthread_mutex_destroy(&grep_read_mutex); pthread_mutex_destroy(&grep_attr_mutex); pthread_cond_destroy(&cond_add); pthread_cond_destroy(&cond_write); pthread_cond_destroy(&cond_result); grep_use_locks = 0; + disable_obj_read_lock(); return hit; } -#else /* !NO_PTHREADS */ - -static int wait_all(void) -{ - return 0; -} -#endif static int grep_cmd_config(const char *var, const char *value, void *cb) { @@ -283,17 +278,15 @@ static int grep_cmd_config(const char *var, const char *value, void *cb) if (num_threads < 0) die(_("invalid number of threads specified (%d) for %s"), num_threads, var); -#ifdef NO_PTHREADS - else if (num_threads && num_threads != 1) { + else if (!HAVE_THREADS && num_threads > 1) { /* * TRANSLATORS: %s is the configuration * variable for tweaking threads, currently * grep.threads */ warning(_("no threads support, ignoring %s"), var); - num_threads = 0; + num_threads = 1; } -#endif } if (!strcmp(var, "submodule.recurse")) @@ -302,14 +295,36 @@ static int grep_cmd_config(const char *var, const char *value, void *cb) return st; } -static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size) +static void grep_source_name(struct grep_opt *opt, const char *filename, + int tree_name_len, struct strbuf *out) { - void *data; + 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; + } - grep_read_lock(); - data = read_object_file(oid, type, size); - grep_read_unlock(); - return data; + 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, @@ -319,27 +334,18 @@ 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); -#ifndef NO_PTHREADS - if (num_threads) { + if (num_threads > 1) { /* * add_work() copies gs and thus assumes ownership of * its fields, so do not call grep_source_clear() */ add_work(opt, &gs); return 0; - } else -#endif - { + } else { int hit; hit = grep_source(opt, &gs); @@ -354,25 +360,18 @@ 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); -#ifndef NO_PTHREADS - if (num_threads) { + if (num_threads > 1) { /* * add_work() copies gs and thus assumes ownership of * its fields, so do not call grep_source_clear() */ add_work(opt, &gs); return 0; - } else -#endif - { + } else { int hit; hit = grep_source(opt, &gs); @@ -407,27 +406,41 @@ static void run_pager(struct grep_opt *opt, const char *prefix) exit(status); } -static int grep_cache(struct grep_opt *opt, struct repository *repo, +static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached); static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, struct tree_desc *tree, struct strbuf *base, int tn_len, - int check_attr, struct repository *repo); + int check_attr); -static int grep_submodule(struct grep_opt *opt, struct repository *superproject, +static int grep_submodule(struct grep_opt *opt, const struct pathspec *pathspec, const struct object_id *oid, - const char *filename, const char *path) + const char *filename, const char *path, int cached) { - struct repository submodule; + struct repository subrepo; + struct repository *superproject = opt->repo; + const struct submodule *sub; + struct grep_opt subopt; int hit; + sub = submodule_from_path(superproject, &null_oid, path); + if (!is_submodule_active(superproject, path)) return 0; - if (repo_submodule_init(&submodule, superproject, path)) + if (repo_submodule_init(&subrepo, superproject, sub)) return 0; - repo_read_gitmodules(&submodule); + /* + * NEEDSWORK: repo_read_gitmodules() might call + * add_to_alternates_memory() via config_from_gitmodules(). This + * operation causes a race condition with concurrent object readings + * performed by the worker threads. That's why we need obj_read_lock() + * here. It should be removed once it's no longer necessary to add the + * subrepo's odbs to the in-memory alternates list. + */ + obj_read_lock(); + repo_read_gitmodules(&subrepo, 0); /* * NEEDSWORK: This adds the submodule's object directory to the list of @@ -439,9 +452,11 @@ static int grep_submodule(struct grep_opt *opt, struct repository *superproject, * store is no longer global and instead is a member of the repository * object. */ - grep_read_lock(); - add_to_alternates_memory(submodule.objects->objectdir); - grep_read_unlock(); + add_to_alternates_memory(subrepo.objects->odb->path); + obj_read_unlock(); + + memcpy(&subopt, opt, sizeof(subopt)); + subopt.repo = &subrepo; if (oid) { struct object *object; @@ -450,13 +465,12 @@ static int grep_submodule(struct grep_opt *opt, struct repository *superproject, unsigned long size; struct strbuf base = STRBUF_INIT; + obj_read_lock(); object = parse_object_or_die(oid, oid_to_hex(oid)); - - grep_read_lock(); - data = read_object_with_reference(&object->oid, tree_type, + obj_read_unlock(); + data = read_object_with_reference(&subrepo, + &object->oid, tree_type, &size, NULL); - grep_read_unlock(); - if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&object->oid)); @@ -464,21 +478,22 @@ static int grep_submodule(struct grep_opt *opt, struct repository *superproject, strbuf_addch(&base, '/'); init_tree_desc(&tree, data, size); - hit = grep_tree(opt, pathspec, &tree, &base, base.len, - object->type == OBJ_COMMIT, &submodule); + hit = grep_tree(&subopt, pathspec, &tree, &base, base.len, + object->type == OBJ_COMMIT); strbuf_release(&base); free(data); } else { - hit = grep_cache(opt, &submodule, pathspec, 1); + hit = grep_cache(&subopt, pathspec, cached); } - repo_clear(&submodule); + repo_clear(&subrepo); return hit; } -static int grep_cache(struct grep_opt *opt, struct repository *repo, +static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached) { + struct repository *repo = opt->repo; int hit = 0; int nr; struct strbuf name = STRBUF_INIT; @@ -488,7 +503,8 @@ static int grep_cache(struct grep_opt *opt, struct repository *repo, strbuf_addstr(&name, repo->submodule_prefix); } - repo_read_index(repo); + if (repo_read_index(repo) < 0) + die(_("index file corrupt")); for (nr = 0; nr < repo->index->cache_nr; nr++) { const struct cache_entry *ce = repo->index->cache[nr]; @@ -496,7 +512,7 @@ static int grep_cache(struct grep_opt *opt, struct repository *repo, strbuf_addstr(&name, ce->name); if (S_ISREG(ce->ce_mode) && - match_pathspec(pathspec, name.buf, name.len, 0, NULL, + match_pathspec(repo->index, pathspec, name.buf, name.len, 0, NULL, S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode))) { /* @@ -514,8 +530,9 @@ static int grep_cache(struct grep_opt *opt, struct repository *repo, hit |= grep_file(opt, name.buf); } } else if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && - submodule_path_match(pathspec, name.buf, NULL)) { - hit |= grep_submodule(opt, repo, pathspec, NULL, ce->name, ce->name); + submodule_path_match(repo->index, pathspec, name.buf, NULL)) { + hit |= grep_submodule(opt, pathspec, NULL, ce->name, + ce->name, cached); } else { continue; } @@ -537,8 +554,9 @@ static int grep_cache(struct grep_opt *opt, struct repository *repo, static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, struct tree_desc *tree, struct strbuf *base, int tn_len, - int check_attr, struct repository *repo) + int check_attr) { + struct repository *repo = opt->repo; int hit = 0; enum interesting match = entry_not_interesting; struct name_entry entry; @@ -555,7 +573,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, if (match != all_entries_interesting) { strbuf_addstr(&name, base->buf + tn_len); - match = tree_entry_interesting(&entry, &name, + match = tree_entry_interesting(repo->index, + &entry, &name, 0, pathspec); strbuf_setlen(&name, name_base_len); @@ -568,7 +587,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_add(base, entry.path, te_len); if (S_ISREG(entry.mode)) { - hit |= grep_oid(opt, entry.oid, base->buf, tn_len, + hit |= grep_oid(opt, &entry.oid, base->buf, tn_len, check_attr ? base->buf + tn_len : NULL); } else if (S_ISDIR(entry.mode)) { enum object_type type; @@ -576,19 +595,20 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, void *data; unsigned long size; - data = lock_and_read_oid_file(entry.oid, &type, &size); + data = read_object_file(&entry.oid, &type, &size); if (!data) die(_("unable to read tree (%s)"), - oid_to_hex(entry.oid)); + oid_to_hex(&entry.oid)); strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); hit |= grep_tree(opt, pathspec, &sub, base, tn_len, - check_attr, repo); + check_attr); free(data); } else if (recurse_submodules && S_ISGITLINK(entry.mode)) { - hit |= grep_submodule(opt, repo, pathspec, entry.oid, - base->buf, base->buf + tn_len); + hit |= grep_submodule(opt, pathspec, &entry.oid, + base->buf, base->buf + tn_len, + 1); /* ignored */ } strbuf_setlen(base, old_baselen); @@ -602,8 +622,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, } static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, - struct object *obj, const char *name, const char *path, - struct repository *repo) + struct object *obj, const char *name, const char *path) { if (obj->type == OBJ_BLOB) return grep_oid(opt, &obj->oid, name, 0, path); @@ -614,11 +633,9 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct strbuf base; int hit, len; - grep_read_lock(); - data = read_object_with_reference(&obj->oid, tree_type, + data = read_object_with_reference(opt->repo, + &obj->oid, tree_type, &size, NULL); - grep_read_unlock(); - if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); @@ -630,7 +647,7 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, } init_tree_desc(&tree, data, size); hit = grep_tree(opt, pathspec, &tree, &base, base.len, - obj->type == OBJ_COMMIT, repo); + obj->type == OBJ_COMMIT); strbuf_release(&base); free(data); return hit; @@ -639,7 +656,6 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, } static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, - struct repository *repo, const struct object_array *list) { unsigned int i; @@ -648,15 +664,21 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, for (i = 0; i < nr; i++) { struct object *real_obj; - real_obj = deref_tag(list->objects[i].item, NULL, 0); + + obj_read_lock(); + real_obj = deref_tag(opt->repo, list->objects[i].item, + NULL, 0); + obj_read_unlock(); /* load the gitmodules file for this rev */ if (recurse_submodules) { - submodule_free(); + submodule_free(opt->repo); + obj_read_lock(); gitmodules_config_oid(&real_obj->oid); + obj_read_unlock(); } - if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path, - repo)) { + if (grep_object(opt, pathspec, real_obj, list->objects[i].name, + list->objects[i].path)) { hit = 1; if (opt->status_only) break; @@ -677,10 +699,8 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, if (exc_std) setup_standard_excludes(&dir); - fill_directory(&dir, &the_index, pathspec); + fill_directory(&dir, opt->repo->index, pathspec); for (i = 0; i < dir.nr; i++) { - if (!dir_path_match(dir.entries[i], pathspec, 0, NULL)) - continue; hit |= grep_file(opt, dir.entries[i]->name); if (hit && opt->status_only) break; @@ -711,11 +731,14 @@ static int context_callback(const struct option *opt, const char *arg, static int file_callback(const struct option *opt, const char *arg, int unset) { struct grep_opt *grep_opt = opt->value; - int from_stdin = !strcmp(arg, "-"); + int from_stdin; FILE *patterns; int lno = 0; struct strbuf sb = STRBUF_INIT; + BUG_ON_OPT_NEG(unset); + + from_stdin = !strcmp(arg, "-"); patterns = from_stdin ? stdin : fopen(arg, "r"); if (!patterns) die_errno(_("cannot open '%s'"), arg); @@ -736,6 +759,8 @@ static int file_callback(const struct option *opt, const char *arg, int unset) static int not_callback(const struct option *opt, const char *arg, int unset) { struct grep_opt *grep_opt = opt->value; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT); return 0; } @@ -743,6 +768,8 @@ static int not_callback(const struct option *opt, const char *arg, int unset) static int and_callback(const struct option *opt, const char *arg, int unset) { struct grep_opt *grep_opt = opt->value; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND); return 0; } @@ -750,6 +777,8 @@ static int and_callback(const struct option *opt, const char *arg, int unset) static int open_callback(const struct option *opt, const char *arg, int unset) { struct grep_opt *grep_opt = opt->value; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN); return 0; } @@ -757,6 +786,8 @@ static int open_callback(const struct option *opt, const char *arg, int unset) static int close_callback(const struct option *opt, const char *arg, int unset) { struct grep_opt *grep_opt = opt->value; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN); return 0; } @@ -765,6 +796,7 @@ static int pattern_callback(const struct option *opt, const char *arg, int unset) { struct grep_opt *grep_opt = opt->value; + BUG_ON_OPT_NEG(unset); append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN); return 0; } @@ -811,6 +843,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) GREP_BINARY_NOMATCH), OPT_BOOL(0, "textconv", &opt.allow_textconv, N_("process binary files with textconv filters")), + OPT_SET_INT('r', "recursive", &opt.max_depth, + N_("search in subdirectories (default)"), -1), { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, N_("depth"), N_("descend at most <depth> levels"), PARSE_OPT_NONEG, NULL, 1 }, @@ -829,6 +863,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) GREP_PATTERN_TYPE_PCRE), OPT_GROUP(""), OPT_BOOL('n', "line-number", &opt.linenum, N_("show line numbers")), + OPT_BOOL(0, "column", &opt.columnnum, N_("show column number of first match")), OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1), OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1), OPT_NEGBIT(0, "full-name", &opt.relative, @@ -843,6 +878,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOL_F('z', "null", &opt.null_following_name, N_("print NUL after filenames"), PARSE_OPT_NOCOMPLETE), + OPT_BOOL('o', "only-matching", &opt.only_matching, + N_("show only matching parts of a line")), OPT_BOOL('c', "count", &opt.count, N_("show the number of matches instead of matching lines")), OPT__COLOR(&opt.color, N_("highlight matches")), @@ -869,27 +906,27 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_GROUP(""), OPT_CALLBACK('f', NULL, &opt, N_("file"), N_("read patterns from file"), file_callback), - { OPTION_CALLBACK, 'e', NULL, &opt, N_("pattern"), - N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback }, - { OPTION_CALLBACK, 0, "and", &opt, NULL, - N_("combine patterns specified with -e"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback }, + OPT_CALLBACK_F('e', NULL, &opt, N_("pattern"), + N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback), + OPT_CALLBACK_F(0, "and", &opt, NULL, + N_("combine patterns specified with -e"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback), OPT_BOOL(0, "or", &dummy, ""), - { OPTION_CALLBACK, 0, "not", &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback }, - { OPTION_CALLBACK, '(', NULL, &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, - open_callback }, - { OPTION_CALLBACK, ')', NULL, &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, - close_callback }, + OPT_CALLBACK_F(0, "not", &opt, NULL, "", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback), + OPT_CALLBACK_F('(', NULL, &opt, NULL, "", + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, + open_callback), + OPT_CALLBACK_F(')', NULL, &opt, NULL, "", + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, + close_callback), OPT__QUIET(&opt.status_only, N_("indicate hit with exit status without output")), OPT_BOOL(0, "all-match", &opt.all_match, N_("show only matches from files that match all patterns")), - { OPTION_SET_INT, 0, "debug", &opt.debug, NULL, - N_("show parse tree for grep expression"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1 }, + OPT_SET_INT_F(0, "debug", &opt.debug, + N_("show parse tree for grep expression"), + 1, PARSE_OPT_HIDDEN), OPT_GROUP(""), { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, N_("pager"), N_("show matching files in the pager"), @@ -901,9 +938,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; - init_grep_defaults(); + init_grep_defaults(the_repository); git_config(grep_cmd_config, NULL); - grep_init(&opt, prefix); + grep_init(&opt, the_repository, prefix); /* * If there is no -- then the paths must exist in the working @@ -929,6 +966,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* die the same way as if we did it at the beginning */ setup_git_directory(); } + /* Ignore --recurse-submodules if --no-index is given or implied */ + if (!use_index) + recurse_submodules = 0; /* * skip a -- separator; we know it cannot be @@ -960,7 +1000,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } if (!opt.pattern_list) - die(_("no pattern given.")); + die(_("no pattern given")); + + /* --only-matching has no effect with --invert. */ + if (opt.invert) + opt.only_matching = 0; /* * We have to find "--" in a separate pass, because its presence @@ -996,7 +1040,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) break; } - if (get_oid_with_context(arg, GET_OID_RECORD_PATH, + if (get_oid_with_context(the_repository, arg, + GET_OID_RECORD_PATH, &oid, &oc)) { if (seen_dashdash) die(_("unable to resolve revision: %s"), arg); @@ -1028,39 +1073,49 @@ int cmd_grep(int argc, const char **argv, const char *prefix) pathspec.recursive = 1; pathspec.recurse_submodules = !!recurse_submodules; -#ifndef NO_PTHREADS - if (list.nr || cached || show_in_pager) - num_threads = 0; - else if (num_threads == 0) - num_threads = GREP_NUM_THREADS_DEFAULT; - else if (num_threads < 0) - die(_("invalid number of threads specified (%d)"), num_threads); - if (num_threads == 1) - num_threads = 0; -#else - if (num_threads) + if (recurse_submodules && untracked) + die(_("--untracked not supported with --recurse-submodules")); + + if (show_in_pager) { + if (num_threads > 1) + warning(_("invalid option combination, ignoring --threads")); + num_threads = 1; + } else if (!HAVE_THREADS && num_threads > 1) { warning(_("no threads support, ignoring --threads")); - num_threads = 0; -#endif + num_threads = 1; + } else if (num_threads < 0) + die(_("invalid number of threads specified (%d)"), num_threads); + else if (num_threads == 0) + num_threads = HAVE_THREADS ? online_cpus() : 1; - if (!num_threads) + if (num_threads > 1) { + if (!HAVE_THREADS) + BUG("Somebody got num_threads calculation wrong!"); + if (!(opt.name_only || opt.unmatch_name_only || opt.count) + && (opt.pre_context || opt.post_context || + opt.file_break || opt.funcbody)) + skip_first_line = 1; + + /* + * Pre-read gitmodules (if not read already) and force eager + * initialization of packed_git to prevent racy lazy + * reading/initialization once worker threads are started. + */ + if (recurse_submodules) + repo_read_gitmodules(the_repository, 1); + if (startup_info->have_repository) + (void)get_packed_git(the_repository); + + start_threads(&opt); + } else { /* * The compiled patterns on the main path are only * used when not using threading. Otherwise - * start_threads() below calls compile_grep_patterns() + * start_threads() above calls compile_grep_patterns() * for each thread. */ compile_grep_patterns(&opt); - -#ifndef NO_PTHREADS - if (num_threads) { - if (!(opt.name_only || opt.unmatch_name_only || opt.count) - && (opt.pre_context || opt.post_context || - opt.file_break || opt.funcbody)) - skip_first_line = 1; - start_threads(&opt); } -#endif if (show_in_pager && (cached || list.nr)) die(_("--open-files-in-pager only works on the worktree")); @@ -1080,42 +1135,40 @@ int cmd_grep(int argc, const char **argv, const char *prefix) strbuf_addf(&buf, "+/%s%s", strcmp("less", pager) ? "" : "*", opt.pattern_list->pattern); - string_list_append(&path_list, buf.buf); - strbuf_detach(&buf, NULL); + string_list_append(&path_list, + strbuf_detach(&buf, NULL)); } } - if (recurse_submodules && (!use_index || untracked)) - die(_("option not supported with --recurse-submodules.")); - if (!show_in_pager && !opt.status_only) setup_pager(); if (!use_index && (untracked || cached)) - die(_("--cached or --untracked cannot be used with --no-index.")); + die(_("--cached or --untracked cannot be used with --no-index")); if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; hit = grep_directory(&opt, &pathspec, use_exclude, use_index); } else if (0 <= opt_exclude) { - die(_("--[no-]exclude-standard cannot be used for tracked contents.")); + die(_("--[no-]exclude-standard cannot be used for tracked contents")); } else if (!list.nr) { if (!cached) setup_work_tree(); - hit = grep_cache(&opt, the_repository, &pathspec, cached); + hit = grep_cache(&opt, &pathspec, cached); } else { if (cached) - die(_("both --cached and trees are given.")); + die(_("both --cached and trees are given")); - hit = grep_objects(&opt, &pathspec, the_repository, &list); + hit = grep_objects(&opt, &pathspec, &list); } - if (num_threads) + if (num_threads > 1) hit |= wait_all(); if (hit && show_in_pager) run_pager(&opt, prefix); clear_pathspec(&pathspec); free_grep_patterns(&opt); + grep_destroy(); return !hit; } diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 526da5c185..640ef4ded5 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -6,10 +6,11 @@ */ #include "builtin.h" #include "config.h" +#include "object-store.h" #include "blob.h" #include "quote.h" #include "parse-options.h" -#include "exec_cmd.h" +#include "exec-cmd.h" /* * This is to create corrupt objects for debugging and as such it @@ -39,7 +40,8 @@ static void hash_fd(int fd, const char *type, const char *path, unsigned flags, if (fstat(fd, &st) < 0 || (literally ? hash_literally(&oid, fd, type, flags) - : index_fd(&oid, fd, &st, type_from_string(type), path, flags))) + : index_fd(the_repository->index, &oid, fd, &st, + type_from_string(type), path, flags))) die((flags & HASH_WRITE_OBJECT) ? "Unable to add %s to database" : "Unable to hash %s", path); @@ -106,7 +108,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) int i; const char *errstr = NULL; - argc = parse_options(argc, argv, NULL, hash_object_options, + argc = parse_options(argc, argv, prefix, hash_object_options, hash_object_usage, 0); if (flags & HASH_WRITE_OBJECT) diff --git a/builtin/help.c b/builtin/help.c index 598867cfea..299206eb57 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -4,11 +4,13 @@ #include "cache.h" #include "config.h" #include "builtin.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "parse-options.h" #include "run-command.h" #include "column.h" +#include "config-list.h" #include "help.h" +#include "alias.h" #ifndef DEFAULT_HELP_FORMAT #define DEFAULT_HELP_FORMAT "man" @@ -36,6 +38,8 @@ static const char *html_path; static int show_all = 0; static int show_guides = 0; +static int show_config; +static int verbose = 1; static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; static int exclude_guides; @@ -43,11 +47,14 @@ static struct option builtin_help_options[] = { OPT_BOOL('a', "all", &show_all, N_("print all available commands")), OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")), OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")), + OPT_BOOL('c', "config", &show_config, N_("print all configuration variable names")), + OPT_SET_INT_F(0, "config-for-completion", &show_config, "", 2, PARSE_OPT_HIDDEN), OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), HELP_FORMAT_WEB), OPT_SET_INT('i', "info", &help_format, N_("show info page"), HELP_FORMAT_INFO), + OPT__VERBOSE(&verbose, N_("print command description")), OPT_END(), }; @@ -56,6 +63,91 @@ static const char * const builtin_help_usage[] = { NULL }; +struct slot_expansion { + const char *prefix; + const char *placeholder; + void (*fn)(struct string_list *list, const char *prefix); + int found; +}; + +static void list_config_help(int for_human) +{ + struct slot_expansion slot_expansions[] = { + { "advice", "*", list_config_advices }, + { "color.branch", "<slot>", list_config_color_branch_slots }, + { "color.decorate", "<slot>", list_config_color_decorate_slots }, + { "color.diff", "<slot>", list_config_color_diff_slots }, + { "color.grep", "<slot>", list_config_color_grep_slots }, + { "color.interactive", "<slot>", list_config_color_interactive_slots }, + { "color.remote", "<slot>", list_config_color_sideband_slots }, + { "color.status", "<slot>", list_config_color_status_slots }, + { "fsck", "<msg-id>", list_config_fsck_msg_ids }, + { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids }, + { NULL, NULL, NULL } + }; + const char **p; + struct slot_expansion *e; + struct string_list keys = STRING_LIST_INIT_DUP; + int i; + + for (p = config_name_list; *p; p++) { + const char *var = *p; + struct strbuf sb = STRBUF_INIT; + + for (e = slot_expansions; e->prefix; e++) { + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder); + if (!strcasecmp(var, sb.buf)) { + e->fn(&keys, e->prefix); + e->found++; + break; + } + } + strbuf_release(&sb); + if (!e->prefix) + string_list_append(&keys, var); + } + + for (e = slot_expansions; e->prefix; e++) + if (!e->found) + BUG("slot_expansion %s.%s is not used", + e->prefix, e->placeholder); + + string_list_sort(&keys); + for (i = 0; i < keys.nr; i++) { + const char *var = keys.items[i].string; + const char *wildcard, *tag, *cut; + + if (for_human) { + puts(var); + continue; + } + + wildcard = strchr(var, '*'); + tag = strchr(var, '<'); + + if (!wildcard && !tag) { + puts(var); + continue; + } + + if (wildcard && !tag) + cut = wildcard; + else if (!wildcard && tag) + cut = tag; + else + cut = wildcard < tag ? wildcard : tag; + + /* + * We may produce duplicates, but that's up to + * git-completion.bash to handle + */ + printf("%.*s\n", (int)(cut - var), var); + } + string_list_clear(&keys, 0); +} + static enum help_format parse_help_format(const char *format) { if (!strcmp(format, "man")) @@ -64,6 +156,10 @@ static enum help_format parse_help_format(const char *format) return HELP_FORMAT_INFO; if (!strcmp(format, "web") || !strcmp(format, "html")) return HELP_FORMAT_WEB; + /* + * Please update _git_config() in git-completion.bash when you + * add new help formats. + */ die(_("unrecognized help format '%s'"), format); } @@ -232,7 +328,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; @@ -400,38 +496,6 @@ static void show_html_page(const char *git_cmd) open_html(page_path.buf); } -static struct { - const char *name; - const char *help; -} common_guides[] = { - { "attributes", N_("Defining attributes per path") }, - { "everyday", N_("Everyday Git With 20 Commands Or So") }, - { "glossary", N_("A Git glossary") }, - { "ignore", N_("Specifies intentionally untracked files to ignore") }, - { "modules", N_("Defining submodule properties") }, - { "revisions", N_("Specifying revisions and ranges for Git") }, - { "tutorial", N_("A tutorial introduction to Git (for version 1.5.1 or newer)") }, - { "workflows", N_("An overview of recommended workflows with Git") }, -}; - -static void list_common_guides_help(void) -{ - int i, longest = 0; - - for (i = 0; i < ARRAY_SIZE(common_guides); i++) { - if (longest < strlen(common_guides[i].name)) - longest = strlen(common_guides[i].name); - } - - puts(_("The common Git guides are:\n")); - for (i = 0; i < ARRAY_SIZE(common_guides); i++) { - printf(" %s ", common_guides[i].name); - mput_char(' ', longest - strlen(common_guides[i].name)); - puts(_(common_guides[i].help)); - } - putchar('\n'); -} - static const char *check_git_cmd(const char* cmd) { char *alias; @@ -441,9 +505,37 @@ static const char *check_git_cmd(const char* cmd) alias = alias_lookup(cmd); if (alias) { - printf_ln(_("'%s' is aliased to '%s'"), cmd, alias); - free(alias); - exit(0); + const char **argv; + int count; + + /* + * handle_builtin() in git.c rewrites "git cmd --help" + * to "git help --exclude-guides cmd", so we can use + * exclude_guides to distinguish "git cmd --help" from + * "git help cmd". In the latter case, or if cmd is an + * alias for a shell command, just print the alias + * definition. + */ + if (!exclude_guides || alias[0] == '!') { + printf_ln(_("'%s' is aliased to '%s'"), cmd, alias); + free(alias); + exit(0); + } + /* + * Otherwise, we pretend that the command was "git + * word0 --help". We use split_cmdline() to get the + * first word of the alias, to ensure that we use the + * same rules as when the alias is actually + * used. split_cmdline() modifies alias in-place. + */ + fprintf_ln(stderr, _("'%s' is aliased to '%s'"), cmd, alias); + count = split_cmdline(alias, &argv); + if (count < 0) + die(_("bad alias.%s string: %s"), cmd, + split_cmdline_strerror(count)); + free(argv); + UNLEAK(alias); + return alias; } if (exclude_guides) @@ -463,11 +555,29 @@ int cmd_help(int argc, const char **argv, const char *prefix) if (show_all) { git_config(git_help_config, NULL); + if (verbose) { + setup_pager(); + list_all_cmds_help(); + return 0; + } printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); load_command_list("git-", &main_cmds, &other_cmds); list_commands(colopts, &main_cmds, &other_cmds); } + if (show_config) { + int for_human = show_config == 1; + + if (!for_human) { + list_config_help(for_human); + return 0; + } + setup_pager(); + list_config_help(for_human); + printf("\n%s\n", _("'git help config' for more information")); + return 0; + } + if (show_guides) list_common_guides_help(); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index d81473e722..f176dd28c8 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -9,11 +9,12 @@ #include "tree.h" #include "progress.h" #include "fsck.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "streaming.h" #include "thread-utils.h" #include "packfile.h" #include "object-store.h" +#include "promisor-remote.h" static const char index_pack_usage[] = "git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; @@ -42,9 +43,7 @@ struct base_data { }; struct thread_local { -#ifndef NO_PTHREADS pthread_t thread; -#endif struct base_data *base_cache; size_t base_cache_used; int pack_fd; @@ -98,8 +97,6 @@ static uint32_t input_crc32; static int input_fd, output_fd; static const char *curr_pack; -#ifndef NO_PTHREADS - static struct thread_local *thread_data; static int nr_dispatched; static int threads_active; @@ -179,26 +176,6 @@ static void cleanup_thread(void) free(thread_data); } -#else - -#define read_lock() -#define read_unlock() - -#define counter_lock() -#define counter_unlock() - -#define work_lock() -#define work_unlock() - -#define deepest_delta_lock() -#define deepest_delta_unlock() - -#define type_cas_lock() -#define type_cas_unlock() - -#endif - - static int mark_link(struct object *obj, int type, void *data, struct fsck_options *options) { if (!obj) @@ -223,7 +200,7 @@ static unsigned check_object(struct object *obj) if (!(obj->flags & FLAG_CHECKED)) { unsigned long size; - int type = oid_object_info(&obj->oid, &size); + int type = oid_object_info(the_repository, &obj->oid, &size); if (type <= 0) die(_("did not receive expected object %s"), oid_to_hex(&obj->oid)); @@ -243,8 +220,16 @@ static unsigned check_objects(void) unsigned i, max, foreign_nr = 0; max = get_max_object_index(); - for (i = 0; i < max; i++) + + if (verbose) + progress = start_delayed_progress(_("Checking objects"), max); + + for (i = 0; i < max; i++) { foreign_nr += check_object(get_indexed_object(i)); + display_progress(progress, i + 1); + } + + stop_progress(&progress); return foreign_nr; } @@ -364,22 +349,20 @@ static NORETURN void bad_object(off_t offset, const char *format, ...) static inline struct thread_local *get_thread_data(void) { -#ifndef NO_PTHREADS - if (threads_active) - return pthread_getspecific(key); - assert(!threads_active && - "This should only be reached when all threads are gone"); -#endif + if (HAVE_THREADS) { + if (threads_active) + return pthread_getspecific(key); + assert(!threads_active && + "This should only be reached when all threads are gone"); + } return ¬hread_data; } -#ifndef NO_PTHREADS static void set_thread_data(struct thread_local *data) { if (threads_active) pthread_setspecific(key, data); } -#endif static struct base_data *alloc_base_data(void) { @@ -450,7 +433,8 @@ static void *unpack_entry_data(off_t offset, unsigned long size, int hdrlen; if (!is_delta_type(type)) { - hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", type_name(type), size) + 1; + hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX, + type_name(type),(uintmax_t)size) + 1; the_hash_algo->init_fn(&c); the_hash_algo->update_fn(&c, hdr, hdrlen); } else @@ -719,9 +703,9 @@ static void find_ref_delta_children(const struct object_id *oid, *last_index = -1; return; } - while (first > 0 && !oidcmp(&ref_deltas[first - 1].oid, oid)) + while (first > 0 && oideq(&ref_deltas[first - 1].oid, oid)) --first; - while (last < end && !oidcmp(&ref_deltas[last + 1].oid, oid)) + while (last < end && oideq(&ref_deltas[last + 1].oid, oid)) ++last; *first_index = first; *last_index = last; @@ -773,7 +757,8 @@ static int check_collison(struct object_entry *entry) memset(&data, 0, sizeof(data)); data.entry = entry; - data.st = open_istream(&entry->idx.oid, &type, &size, NULL); + data.st = open_istream(the_repository, &entry->idx.oid, &type, &size, + NULL); if (!data.st) return -1; if (size != entry->size || type != entry->type) @@ -797,7 +782,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, if (startup_info->have_repository) { read_lock(); collision_test_needed = - has_sha1_file_with_flags(oid->hash, OBJECT_INFO_QUICK); + has_object_file_with_flags(oid, OBJECT_INFO_QUICK); read_unlock(); } @@ -812,7 +797,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, enum object_type has_type; unsigned long has_size; read_lock(); - has_type = oid_object_info(oid, &has_size); + has_type = oid_object_info(the_repository, oid, &has_size); if (has_type < 0) die(_("cannot read existing object info %s"), oid_to_hex(oid)); if (has_type != type || has_size != size) @@ -832,11 +817,14 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, if (strict || do_fsck_object) { read_lock(); if (type == OBJ_BLOB) { - struct blob *blob = lookup_blob(oid); + struct blob *blob = lookup_blob(the_repository, oid); if (blob) blob->object.flags |= FLAG_CHECKED; else die(_("invalid blob object %s"), oid_to_hex(oid)); + if (do_fsck_object && + fsck_object(&blob->object, (void *)data, size, &fsck_options)) + die(_("fsck error in packed object")); } else { struct object *obj; int eaten; @@ -848,13 +836,14 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, * we do not need to free the memory here, as the * buf is deleted by the caller. */ - obj = parse_object_buffer(oid, type, size, buf, + obj = parse_object_buffer(the_repository, oid, type, + size, buf, &eaten); if (!obj) die(_("invalid %s"), type_name(type)); if (do_fsck_object && fsck_object(obj, buf, size, &fsck_options)) - die(_("Error in object")); + die(_("fsck error in packed object")); if (strict && fsck_walk(obj, NULL, &fsck_options)) die(_("Not all child objects of %s are reachable"), oid_to_hex(&obj->oid)); @@ -866,7 +855,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, if (obj->type == OBJ_COMMIT) { struct commit *commit = (struct commit *) obj; if (detach_commit_buffer(commit, NULL) != data) - die("BUG: parse_object_buffer transmogrified our buffer"); + BUG("parse_object_buffer transmogrified our buffer"); } obj->flags |= FLAG_CHECKED; } @@ -960,7 +949,7 @@ static void resolve_delta(struct object_entry *delta_obj, free(delta_data); if (!result->data) bad_object(delta_obj->idx.offset, _("failed to apply delta")); - hash_object_file(result->data, result->size, + hash_object_file(the_hash_algo, result->data, result->size, type_name(delta_obj->real_type), &delta_obj->idx.oid); sha1_object(result->data, NULL, result->size, delta_obj->real_type, &delta_obj->idx.oid); @@ -1015,7 +1004,9 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA, base->obj->real_type)) - die("BUG: child->real_type != OBJ_REF_DELTA"); + die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)", + (uintmax_t)child->idx.offset, + oid_to_hex(&base->obj->idx.oid)); resolve_delta(child, base, result); if (base->ref_first == base->ref_last && base->ofs_last == -1) @@ -1088,7 +1079,6 @@ static void resolve_base(struct object_entry *obj) find_unresolved_deltas(base_obj); } -#ifndef NO_PTHREADS static void *threaded_second_pass(void *data) { set_thread_data(data); @@ -1112,7 +1102,6 @@ static void *threaded_second_pass(void *data) } return NULL; } -#endif /* * First pass: @@ -1162,7 +1151,7 @@ static void parse_pack_objects(unsigned char *hash) /* Check pack integrity */ flush(); the_hash_algo->final_fn(hash, &input_ctx); - if (hashcmp(fill(the_hash_algo->rawsz), hash)) + if (!hasheq(fill(the_hash_algo->rawsz), hash)) die(_("pack is corrupted (SHA1 mismatch)")); use(the_hash_algo->rawsz); @@ -1209,7 +1198,6 @@ static void resolve_deltas(void) progress = start_progress(_("Resolving deltas"), nr_ref_deltas + nr_ofs_deltas); -#ifndef NO_PTHREADS nr_dispatched = 0; if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) { init_thread(); @@ -1225,7 +1213,6 @@ static void resolve_deltas(void) cleanup_thread(); return; } -#endif for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; @@ -1271,12 +1258,12 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha nr_objects - nr_objects_initial); stop_progress_msg(&progress, msg.buf); strbuf_release(&msg); - hashclose(f, tail_hash, 0); + finalize_hashfile(f, tail_hash, 0); hashcpy(read_hash, pack_hash); fixup_pack_header_footer(output_fd, pack_hash, curr_pack, nr_objects, read_hash, consumed_bytes-the_hash_algo->rawsz); - if (hashcmp(read_hash, tail_hash) != 0) + if (!hasheq(read_hash, tail_hash)) die(_("Unexpected tail checksum for %s " "(disk corruption?)"), curr_pack); } @@ -1368,6 +1355,24 @@ static void fix_unresolved_deltas(struct hashfile *f) sorted_by_pos[i] = &ref_deltas[i]; QSORT(sorted_by_pos, nr_ref_deltas, delta_pos_compare); + if (has_promisor_remote()) { + /* + * Prefetch the delta bases. + */ + struct oid_array to_fetch = OID_ARRAY_INIT; + for (i = 0; i < nr_ref_deltas; i++) { + struct ref_delta_entry *d = sorted_by_pos[i]; + if (!oid_object_info_extended(the_repository, &d->oid, + NULL, + OBJECT_INFO_FOR_PREFETCH)) + continue; + oid_array_append(&to_fetch, &d->oid); + } + promisor_remote_get_direct(the_repository, + to_fetch.oid, to_fetch.nr); + oid_array_clear(&to_fetch); + } + for (i = 0; i < nr_ref_deltas; i++) { struct ref_delta_entry *d = sorted_by_pos[i]; enum object_type type; @@ -1380,8 +1385,9 @@ static void fix_unresolved_deltas(struct hashfile *f) if (!base_obj->data) continue; - if (check_object_signature(&d->oid, base_obj->data, - base_obj->size, type_name(type))) + if (check_object_signature(the_repository, &d->oid, + base_obj->data, base_obj->size, + type_name(type))) die(_("local object %s is corrupt"), oid_to_hex(&d->oid)); base_obj->obj = append_obj_to_pack(f, d->oid.hash, base_obj->data, base_obj->size, type); @@ -1479,12 +1485,19 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } else chmod(final_index_name, 0444); + if (do_fsck_object) { + struct packed_git *p; + p = add_packed_git(final_index_name, strlen(final_index_name), 0); + if (p) + install_packed_git(the_repository, p); + } + if (!from_stdin) { - printf("%s\n", sha1_to_hex(hash)); + printf("%s\n", hash_to_hex(hash)); } else { struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s\t%s\n", report, sha1_to_hex(hash)); + strbuf_addf(&buf, "%s\t%s\n", report, hash_to_hex(hash)); write_or_die(1, buf.buf, buf.len); strbuf_release(&buf); @@ -1520,11 +1533,10 @@ static int git_index_pack_config(const char *k, const char *v, void *cb) if (nr_threads < 0) die(_("invalid number of threads specified (%d)"), nr_threads); -#ifdef NO_PTHREADS - if (nr_threads != 1) + if (!HAVE_THREADS && nr_threads != 1) { warning(_("no threads support, ignoring %s"), k); - nr_threads = 1; -#endif + nr_threads = 1; + } return 0; } return git_default_config(k, v, cb); @@ -1543,12 +1555,13 @@ static void read_v2_anomalous_offsets(struct packed_git *p, { const uint32_t *idx1, *idx2; uint32_t i; + const uint32_t hashwords = the_hash_algo->rawsz / sizeof(uint32_t); /* The address of the 4-byte offset table */ idx1 = (((const uint32_t *)p->index_data) + 2 /* 8-byte header */ + 256 /* fan out */ - + 5 * p->num_objects /* 20-byte SHA-1 table */ + + hashwords * p->num_objects /* object ID table */ + p->num_objects /* CRC32 table */ ); @@ -1593,7 +1606,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) /* * Get rid of the idx file as we do not need it anymore. * NEEDSWORK: extract this bit from free_pack_by_name() in - * sha1_file.c, perhaps? It shouldn't matter very much as we + * sha1-file.c, perhaps? It shouldn't matter very much as we * know we haven't installed this pack (hence we never have * read anything from it). */ @@ -1616,10 +1629,10 @@ static void show_pack_info(int stat_only) chain_histogram[obj_stat[i].delta_depth - 1]++; if (stat_only) continue; - printf("%s %-6s %lu %lu %"PRIuMAX, + printf("%s %-6s %"PRIuMAX" %"PRIuMAX" %"PRIuMAX, oid_to_hex(&obj->idx.oid), - type_name(obj->real_type), obj->size, - (unsigned long)(obj[1].idx.offset - obj->idx.offset), + type_name(obj->real_type), (uintmax_t)obj->size, + (uintmax_t)(obj[1].idx.offset - obj->idx.offset), (uintmax_t)obj->idx.offset); if (is_delta_type(obj->type)) { struct object_entry *bobj = &objects[obj_stat[i].base_object_no]; @@ -1660,15 +1673,17 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) int report_end_of_input = 0; /* - * index-pack never needs to fetch missing objects, since it only - * accesses the repo to do hash collision checks + * index-pack never needs to fetch missing objects except when + * REF_DELTA bases are missing (which are explicitly handled). It only + * accesses the repo to do hash collision checks and to check which + * REF_DELTA bases need to be fetched. */ fetch_if_missing = 0; if (argc == 2 && !strcmp(argv[1], "-h")) usage(index_pack_usage); - check_replace_refs = 0; + read_replace_refs = 0; fsck_options.walk = mark_link; reset_pack_idx_option(&opts); @@ -1711,12 +1726,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) nr_threads = strtoul(arg+10, &end, 0); if (!arg[10] || *end || nr_threads < 0) usage(index_pack_usage); -#ifdef NO_PTHREADS - if (nr_threads != 1) - warning(_("no threads support, " - "ignoring %s"), arg); - nr_threads = 1; -#endif + if (!HAVE_THREADS && nr_threads != 1) { + warning(_("no threads support, ignoring %s"), arg); + nr_threads = 1; + } } else if (starts_with(arg, "--pack_header=")) { struct pack_header *hdr; char *c; @@ -1779,14 +1792,12 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (strict) opts.flags |= WRITE_IDX_STRICT; -#ifndef NO_PTHREADS - if (!nr_threads) { + if (HAVE_THREADS && !nr_threads) { nr_threads = online_cpus(); /* An experiment showed that more threads does not mean faster */ if (nr_threads > 3) nr_threads = 3; } -#endif curr_pack = open_pack_file(pack_name); parse_pack_header(); @@ -1820,6 +1831,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) pack_hash); else close(input_fd); + + if (do_fsck_object && fsck_finish(&fsck_options)) + die(_("fsck error in pack objects")); + free(objects); strbuf_release(&index_name_buf); if (pack_name == NULL) diff --git a/builtin/init-db.c b/builtin/init-db.c index 68ff4ad75a..0b7222e718 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -7,7 +7,7 @@ #include "config.h" #include "refs.h" #include "builtin.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "parse-options.h" #ifndef DEFAULT_GIT_TEMPLATE_DIR @@ -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; @@ -73,7 +75,8 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, continue; else if (S_ISLNK(st_template.st_mode)) { struct strbuf lnk = STRBUF_INIT; - if (strbuf_readlink(&lnk, template_path->buf, 0) < 0) + if (strbuf_readlink(&lnk, template_path->buf, + st_template.st_size) < 0) die_errno(_("cannot readlink '%s'"), template_path->buf); if (symlink(lnk.buf, path->buf)) die_errno(_("cannot symlink '%s' '%s'"), @@ -95,7 +98,7 @@ static void copy_templates(const char *template_dir) struct strbuf path = STRBUF_INIT; struct strbuf template_path = STRBUF_INIT; size_t template_len; - struct repository_format template_format; + struct repository_format template_format = REPOSITORY_FORMAT_INIT; struct strbuf err = STRBUF_INIT; DIR *dir; char *to_free = NULL; @@ -117,7 +120,7 @@ static void copy_templates(const char *template_dir) dir = opendir(template_path.buf); if (!dir) { - warning(_("templates not found %s"), template_dir); + warning(_("templates not found in %s"), template_dir); goto free_return; } @@ -147,6 +150,7 @@ free_return: free(to_free); strbuf_release(&path); strbuf_release(&template_path); + clear_repository_format(&template_format); } static int git_init_db_config(const char *k, const char *v, void *cb) @@ -154,6 +158,9 @@ static int git_init_db_config(const char *k, const char *v, void *cb) if (!strcmp(k, "init.templatedir")) return git_config_pathname(&init_db_template_dir, k, v); + if (starts_with(k, "core.")) + return platform_core_config(k, v, cb); + return 0; } @@ -171,19 +178,43 @@ 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; struct strbuf err = STRBUF_INIT; /* Just look for `init.templatedir` */ + init_db_template_dir = NULL; /* re-set in case it was set before */ git_config(git_init_db_config, NULL); /* @@ -238,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"); @@ -334,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; @@ -350,16 +399,19 @@ 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; + /* Just look for `core.hidedotfiles` */ + git_config(git_init_db_config, NULL); + safe_create_dir(git_dir, 0); init_is_bare_repository = is_bare_repository(); @@ -369,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); + + validate_hash_algorithm(&repo_fmt, hash); - reinit = create_default_files(template_dir, original_git_dir); + reinit = create_default_files(template_dir, original_git_dir, &repo_fmt); create_object_directory(); @@ -391,7 +445,7 @@ int init_db(const char *git_dir, const char *real_git_dir, else if (get_shared_repository() == PERM_EVERYBODY) xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY); else - die("BUG: invalid value for shared_repository"); + BUG("invalid value for shared_repository"); git_config_set("core.sharedrepository", buf); git_config_set("receive.denyNonFastforwards", "true"); } @@ -450,6 +504,7 @@ static int guess_repository_type(const char *git_dir) static int shared_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); *((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP; return 0; } @@ -472,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")), @@ -484,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() }; @@ -492,6 +551,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) if (real_git_dir && !is_absolute_path(real_git_dir)) real_git_dir = real_pathdup(real_git_dir, 1); + if (template_dir && *template_dir && !is_absolute_path(template_dir)) + template_dir = absolute_pathdup(template_dir); + if (argc == 1) { int mkdir_tried = 0; retry: @@ -533,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); @@ -540,8 +608,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR * without --bare. Catch the error early. */ - git_dir = getenv(GIT_DIR_ENVIRONMENT); - work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT); + git_dir = xstrdup_or_null(getenv(GIT_DIR_ENVIRONMENT)); + work_tree = xstrdup_or_null(getenv(GIT_WORK_TREE_ENVIRONMENT)); if ((!git_dir || is_bare_repository_cfg == 1) && work_tree) die(_("%s (or --work-tree=<directory>) not allowed without " "specifying %s (or --git-dir=<directory>)"), @@ -580,7 +648,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) } UNLEAK(real_git_dir); + UNLEAK(git_dir); + 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/interpret-trailers.c b/builtin/interpret-trailers.c index b742539d4d..84748eafc0 100644 --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c @@ -10,6 +10,7 @@ #include "parse-options.h" #include "string-list.h" #include "trailer.h" +#include "config.h" static const char * const git_interpret_trailers_usage[] = { N_("git interpret-trailers [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"), @@ -80,6 +81,8 @@ static int parse_opt_parse(const struct option *opt, const char *arg, v->only_trailers = 1; v->only_input = 1; v->unfold = 1; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); return 0; } @@ -102,13 +105,16 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")), OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply config rules")), OPT_BOOL(0, "unfold", &opts.unfold, N_("join whitespace-continued values")), - { OPTION_CALLBACK, 0, "parse", &opts, NULL, N_("set parsing options"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse }, + OPT_CALLBACK_F(0, "parse", &opts, NULL, N_("set parsing options"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse), + OPT_BOOL(0, "no-divider", &opts.no_divider, N_("do not treat --- specially")), OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"), N_("trailer(s) to add"), option_parse_trailer), OPT_END() }; + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, options, git_interpret_trailers_usage, 0); diff --git a/builtin/log.c b/builtin/log.c index 71f68a3e4f..d104d5c688 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -4,9 +4,11 @@ * (C) Copyright 2006 Linus Torvalds * 2006 Junio Hamano */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "refs.h" +#include "object-store.h" #include "color.h" #include "commit.h" #include "diff.h" @@ -28,8 +30,14 @@ #include "mailmap.h" #include "gpg-interface.h" #include "progress.h" +#include "commit-slab.h" +#include "repository.h" +#include "commit-reach.h" +#include "interdiff.h" +#include "range-diff.h" #define MAIL_DEFAULT_WRAP 72 +#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100 /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -38,9 +46,10 @@ 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; +static int use_mailmap_config = 1; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; @@ -56,9 +65,14 @@ struct line_opt_callback_data { struct string_list args; }; +static int session_is_interactive(void) +{ + return isatty(1) || pager_in_use(); +} + static int auto_decoration_style(void) { - return (isatty(1) || pager_in_use()) ? DECORATE_SHORT_REFS : 0; + return session_is_interactive() ? DECORATE_SHORT_REFS : 0; } static int parse_decoration_style(const char *value) @@ -77,6 +91,10 @@ static int parse_decoration_style(const char *value) return DECORATE_SHORT_REFS; else if (!strcmp(value, "auto")) return auto_decoration_style(); + /* + * Please update _git_log() in git-completion.bash when you + * add new decoration styles. + */ return -1; } @@ -101,6 +119,8 @@ static int log_line_range_callback(const struct option *option, const char *arg, { struct line_opt_callback_data *data = option->value; + BUG_ON_OPT_NEG(unset); + if (!arg) return -1; @@ -112,7 +132,7 @@ static int log_line_range_callback(const struct option *option, const char *arg, static void init_log_defaults(void) { - init_grep_defaults(); + init_grep_defaults(the_repository); init_diff_ui_defaults(); decoration_style = auto_decoration_style(); @@ -132,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) @@ -142,23 +163,27 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct rev_info *rev, struct setup_revision_opt *opt) { struct userformat_want w; - int quiet = 0, source = 0, mailmap = 0; + 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, N_("pattern"), N_("do not decorate refs that match <pattern>")), - { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), - PARSE_OPT_OPTARG, decorate_callback}, + OPT_CALLBACK_F(0, "decorate", NULL, NULL, N_("decorate options"), + PARSE_OPT_OPTARG, decorate_callback), OPT_CALLBACK('L', NULL, &line_cb, "n,m:file", N_("Process line range n,m in file, counting from 1"), log_line_range_callback), @@ -188,14 +213,16 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (!rev->show_notes_given && (!rev->pretty_given || w.notes)) rev->show_notes = 1; if (rev->show_notes) - init_display_notes(&rev->notes_opt); + load_display_notes(&rev->notes_opt); if ((rev->diffopt.pickaxe_opts & DIFF_PICKAXE_KINDS_MASK) || rev->diffopt.filter || rev->diffopt.flags.follow_renames) rev->always_show_header = 0; - if (source) - rev->show_source = 1; + if (source || w.source) { + init_revision_sources(&revision_sources); + rev->sources = &revision_sources; + } if (mailmap) { rev->mailmap = xcalloc(1, sizeof(struct string_list)); @@ -214,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); } @@ -235,7 +274,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, * This gives a rough estimate for how many commits we * will print out in the list. */ -static int estimate_commit_count(struct rev_info *rev, struct commit_list *list) +static int estimate_commit_count(struct commit_list *list) { int n = 0; @@ -273,7 +312,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) switch (simplify_commit(revs, commit)) { case commit_show: if (show_header) { - int n = estimate_commit_count(revs, list); + int n = estimate_commit_count(list); show_early_header(revs, "incomplete", n); show_header = 0; } @@ -317,7 +356,7 @@ static void early_output(int signal) show_early_output = log_show_early; } -static void setup_early_output(struct rev_info *rev) +static void setup_early_output(void) { struct sigaction sa; @@ -348,7 +387,7 @@ static void setup_early_output(struct rev_info *rev) static void finish_early_output(struct rev_info *rev) { - int n = estimate_commit_count(rev, rev->commits); + int n = estimate_commit_count(rev->commits); signal(SIGALRM, SIG_IGN); show_early_header(rev, "done", n); } @@ -360,7 +399,7 @@ static int cmd_log_walk(struct rev_info *rev) int saved_dcctc = 0, close_file = rev->diffopt.close_file; if (rev->early_output) - setup_early_output(rev); + setup_early_output(); if (prepare_revision_walk(rev)) die(_("revision walk setup failed")); @@ -386,7 +425,8 @@ static int cmd_log_walk(struct rev_info *rev) * We may show a given commit multiple times when * walking the reflogs. */ - free_commit_buffer(commit); + free_commit_buffer(the_repository->parsed_objects, + commit); free_commit_list(commit->parents); commit->parents = NULL; } @@ -415,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; @@ -461,7 +505,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) init_log_defaults(); git_config(git_log_config, NULL); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); rev.diff = 1; rev.simplify_history = 0; memset(&opt, 0, sizeof(opt)); @@ -473,7 +517,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) return cmd_log_walk(&rev); } -static void show_tagger(char *buf, int len, struct rev_info *rev) +static void show_tagger(const char *buf, struct rev_info *rev) { struct strbuf out = STRBUF_INIT; struct pretty_print_context pp = {0}; @@ -497,11 +541,13 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c !rev->diffopt.flags.allow_textconv) return stream_blob_to_fd(1, oid, NULL, 0); - if (get_oid_with_context(obj_name, GET_OID_RECORD_PATH, + if (get_oid_with_context(the_repository, obj_name, + GET_OID_RECORD_PATH, &oidc, &obj_context)) - die(_("Not a valid object name %s"), obj_name); + die(_("not a valid object name %s"), obj_name); if (!obj_context.path || - !textconv_object(obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) { + !textconv_object(the_repository, obj_context.path, + obj_context.mode, &oidc, 1, &buf, &size)) { free(obj_context.path); return stream_blob_to_fd(1, oid, NULL, 0); } @@ -522,16 +568,16 @@ static int show_tag_object(const struct object_id *oid, struct rev_info *rev) int offset = 0; if (!buf) - return error(_("Could not read object %s"), oid_to_hex(oid)); + return error(_("could not read object %s"), oid_to_hex(oid)); assert(type == OBJ_TAG); while (offset < size && buf[offset] != '\n') { int new_offset = offset + 1; + const char *ident; while (new_offset < size && buf[new_offset++] != '\n') ; /* do nothing */ - if (starts_with(buf + offset, "tagger ")) - show_tagger(buf + offset + 7, - new_offset - offset - 7, rev); + if (skip_prefix(buf + offset, "tagger ", &ident)) + show_tagger(ident, rev); offset = new_offset; } @@ -578,7 +624,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); memset(&match_all, 0, sizeof(match_all)); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); rev.diff = 1; rev.always_show_header = 1; rev.no_walk = REVISION_WALK_NO_WALK_SORTED; @@ -603,6 +649,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) break; case OBJ_TAG: { struct tag *t = (struct tag *)o; + struct object_id *oid = get_tagged_oid(t); if (rev.shown_one) putchar('\n'); @@ -614,10 +661,10 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.shown_one = 1; if (ret) break; - o = parse_object(&t->tagged->oid); + o = parse_object(the_repository, oid); if (!o) - ret = error(_("Could not read object %s"), - oid_to_hex(&t->tagged->oid)); + ret = error(_("could not read object %s"), + oid_to_hex(oid)); objects[i].item = o; i--; break; @@ -629,8 +676,9 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive((struct tree *)o, "", 0, 0, &match_all, - show_tree_object, rev.diffopt.file); + read_tree_recursive(the_repository, (struct tree *)o, "", + 0, 0, &match_all, show_tree_object, + rev.diffopt.file); rev.shown_one = 1; break; case OBJ_COMMIT: @@ -640,7 +688,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) ret = cmd_log_walk(&rev); break; default: - ret = error(_("Unknown type: %d"), o->type); + ret = error(_("unknown type: %d"), o->type); } } free(objects); @@ -658,7 +706,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) init_log_defaults(); git_config(git_log_config, NULL); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); init_reflog_walk(&rev.reflog_info); rev.verbose_header = 1; memset(&opt, 0, sizeof(opt)); @@ -697,7 +745,7 @@ int cmd_log(int argc, const char **argv, const char *prefix) init_log_defaults(); git_config(git_log_config, NULL); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); rev.always_show_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; @@ -739,23 +787,53 @@ static void add_header(const char *value) item->string[len] = '\0'; } -#define THREAD_SHALLOW 1 -#define THREAD_DEEP 2 -static int thread; +enum cover_setting { + COVER_UNSET, + COVER_OFF, + COVER_ON, + COVER_AUTO +}; + +enum thread_level { + THREAD_UNSET, + THREAD_SHALLOW, + THREAD_DEEP +}; + +enum cover_from_description { + COVER_FROM_NONE, + COVER_FROM_MESSAGE, + COVER_FROM_SUBJECT, + COVER_FROM_AUTO +}; + +static enum thread_level thread; static int do_signoff; static int base_auto; static char *from; static const char *signature = git_version_string; static const char *signature_file; -static int config_cover_letter; +static enum cover_setting config_cover_letter; static const char *config_output_directory; +static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE; +static int show_notes; +static struct display_notes_opt notes_opt; -enum { - COVER_UNSET, - COVER_OFF, - COVER_ON, - COVER_AUTO -}; +static enum cover_from_description parse_cover_from_description(const char *arg) +{ + if (!arg || !strcmp(arg, "default")) + return COVER_FROM_MESSAGE; + else if (!strcmp(arg, "none")) + return COVER_FROM_NONE; + else if (!strcmp(arg, "message")) + return COVER_FROM_MESSAGE; + else if (!strcmp(arg, "subject")) + return COVER_FROM_SUBJECT; + else if (!strcmp(arg, "auto")) + return COVER_FROM_AUTO; + else + die(_("%s: invalid cover from description mode"), arg); +} static int git_format_config(const char *var, const char *value, void *cb) { @@ -808,7 +886,7 @@ static int git_format_config(const char *var, const char *value, void *cb) thread = THREAD_SHALLOW; return 0; } - thread = git_config_bool(var, value) && THREAD_SHALLOW; + thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET; return 0; } if (!strcmp(var, "format.signoff")) { @@ -844,6 +922,20 @@ static int git_format_config(const char *var, const char *value, void *cb) from = NULL; return 0; } + if (!strcmp(var, "format.notes")) { + int b = git_parse_maybe_bool(value); + if (b < 0) + enable_ref_display_notes(¬es_opt, &show_notes, value); + else if (b) + enable_default_display_notes(¬es_opt, &show_notes); + else + disable_display_notes(¬es_opt, &show_notes); + return 0; + } + if (!strcmp(var, "format.coverfromdescription")) { + cover_from_description_mode = parse_cover_from_description(value); + return 0; + } return git_log_config(var, value, cb); } @@ -878,7 +970,7 @@ static int open_next_file(struct commit *commit, const char *subject, printf("%s\n", filename.buf + outdir_offset); if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) { - error_errno(_("Cannot open patch file %s"), filename.buf); + error_errno(_("cannot open patch file %s"), filename.buf); strbuf_release(&filename); return -1; } @@ -895,22 +987,22 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) unsigned flags1, flags2; if (rev->pending.nr != 2) - die(_("Need exactly one range.")); + die(_("need exactly one range")); o1 = rev->pending.objects[0].item; o2 = rev->pending.objects[1].item; flags1 = o1->flags; flags2 = o2->flags; - c1 = lookup_commit_reference(&o1->oid); - c2 = lookup_commit_reference(&o2->oid); + c1 = lookup_commit_reference(the_repository, &o1->oid); + c2 = lookup_commit_reference(the_repository, &o2->oid); if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) - die(_("Not a range.")); + die(_("not a range")); - init_patch_ids(ids); + init_patch_ids(the_repository, ids); /* given a range a..b get all patch ids for b..a */ - init_revisions(&check_rev, rev->prefix); + repo_init_revisions(the_repository, &check_rev, rev->prefix); check_rev.max_parents = 1; o1->flags ^= UNINTERESTING; o2->flags ^= UNINTERESTING; @@ -950,20 +1042,6 @@ static void print_signature(FILE *file) putc('\n', file); } -static void add_branch_description(struct strbuf *buf, const char *branch_name) -{ - struct strbuf desc = STRBUF_INIT; - if (!branch_name || !*branch_name) - return; - read_branch_desc(&desc, branch_name); - if (desc.len) { - strbuf_addch(buf, '\n'); - strbuf_addbuf(buf, &desc); - strbuf_addch(buf, '\n'); - } - strbuf_release(&desc); -} - static char *find_branch_name(struct rev_info *rev) { int i, positive = -1; @@ -986,12 +1064,87 @@ static char *find_branch_name(struct rev_info *rev) tip_oid = &rev->cmdline.rev[positive].item->oid; if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref) && skip_prefix(full_ref, "refs/heads/", &v) && - !oidcmp(tip_oid, &branch_oid)) + oideq(tip_oid, &branch_oid)) branch = xstrdup(v); free(full_ref); return branch; } +static void show_diffstat(struct rev_info *rev, + struct commit *origin, struct commit *head) +{ + struct diff_options opts; + + memcpy(&opts, &rev->diffopt, sizeof(opts)); + opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; + diff_setup_done(&opts); + + diff_tree_oid(get_commit_tree_oid(origin), + get_commit_tree_oid(head), + "", &opts); + diffcore_std(&opts); + diff_flush(&opts); + + fprintf(rev->diffopt.file, "\n"); +} + +static void prepare_cover_text(struct pretty_print_context *pp, + const char *branch_name, + struct strbuf *sb, + const char *encoding, + int need_8bit_cte) +{ + const char *subject = "*** SUBJECT HERE ***"; + const char *body = "*** BLURB HERE ***"; + struct strbuf description_sb = STRBUF_INIT; + struct strbuf subject_sb = STRBUF_INIT; + + if (cover_from_description_mode == COVER_FROM_NONE) + goto do_pp; + + if (branch_name && *branch_name) + read_branch_desc(&description_sb, branch_name); + if (!description_sb.len) + goto do_pp; + + if (cover_from_description_mode == COVER_FROM_SUBJECT || + cover_from_description_mode == COVER_FROM_AUTO) + body = format_subject(&subject_sb, description_sb.buf, " "); + + if (cover_from_description_mode == COVER_FROM_MESSAGE || + (cover_from_description_mode == COVER_FROM_AUTO && + subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN)) + body = description_sb.buf; + else + subject = subject_sb.buf; + +do_pp: + pp_title_line(pp, &subject, sb, encoding, need_8bit_cte); + pp_remainder(pp, &body, sb, 0); + + strbuf_release(&description_sb); + strbuf_release(&subject_sb); +} + +static int get_notes_refs(struct string_list_item *item, void *arg) +{ + argv_array_pushf(arg, "--notes=%s", item->string); + return 0; +} + +static void get_notes_args(struct argv_array *arg, struct rev_info *rev) +{ + if (!rev->show_notes) { + argv_array_push(arg, "--no-notes"); + } else if (rev->notes_opt.use_default_notes > 0 || + (rev->notes_opt.use_default_notes == -1 && + !rev->notes_opt.extra_notes_refs.nr)) { + argv_array_push(arg, "--notes"); + } else { + for_each_string_list(&rev->notes_opt.extra_notes_refs, get_notes_refs, arg); + } +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, struct commit *origin, int nr, struct commit **list, @@ -999,27 +1152,24 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, int quiet) { const char *committer; - const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; - const char *msg; struct shortlog log; struct strbuf sb = STRBUF_INIT; int i; const char *encoding = "UTF-8"; - struct diff_options opts; int need_8bit_cte = 0; struct pretty_print_context pp = {0}; struct commit *head = list[0]; if (!cmit_fmt_is_mail(rev->commit_format)) - die(_("Cover letter needs email format")); + die(_("cover letter needs email format")); committer = git_committer_info(0); if (!use_stdout && open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) - return; + die(_("failed to create cover-letter file")); - log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte); + log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte, 0); for (i = 0; !need_8bit_cte && i < nr; i++) { const char *buf = get_commit_buffer(list[i], NULL); @@ -1031,15 +1181,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, if (!branch_name) branch_name = find_branch_name(rev); - msg = body; pp.fmt = CMIT_FMT_EMAIL; pp.date_mode.type = DATE_RFC2822; pp.rev = rev; pp.print_email_subject = 1; pp_user_info(&pp, NULL, &sb, committer, encoding); - pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); - pp_remainder(&pp, &msg, &sb, 0); - add_branch_description(&sb, branch_name); + prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte); fprintf(rev->diffopt.file, "%s\n", sb.buf); strbuf_release(&sb); @@ -1055,25 +1202,32 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, shortlog_output(&log); - /* - * We can only do diffstat with a unique reference point - */ - if (!origin) - return; - - memcpy(&opts, &rev->diffopt, sizeof(opts)); - opts.output_format = DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; - opts.stat_width = MAIL_DEFAULT_WRAP; - - diff_setup_done(&opts); + /* We can only do diffstat with a unique reference point */ + if (origin) + show_diffstat(rev, origin, head); - diff_tree_oid(&origin->tree->object.oid, - &head->tree->object.oid, - "", &opts); - diffcore_std(&opts); - diff_flush(&opts); + if (rev->idiff_oid1) { + fprintf_ln(rev->diffopt.file, "%s", rev->idiff_title); + show_interdiff(rev, 0); + } - fprintf(rev->diffopt.file, "\n"); + if (rev->rdiff1) { + /* + * Pass minimum required diff-options to range-diff; others + * can be added later if deemed desirable. + */ + struct diff_options opts; + struct argv_array other_arg = ARGV_ARRAY_INIT; + diff_setup(&opts); + opts.file = rev->diffopt.file; + opts.use_color = rev->diffopt.use_color; + diff_setup_done(&opts); + fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title); + get_notes_args(&other_arg, rev); + show_range_diff(rev->rdiff1, rev->rdiff2, + rev->creation_factor, 1, &opts, &other_arg); + argv_array_clear(&other_arg); + } } static const char *clean_message_id(const char *msg_id) @@ -1127,6 +1281,8 @@ static int keep_subject = 0; static int keep_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); ((struct rev_info *)opt->value)->total = -1; keep_subject = 1; return 0; @@ -1137,6 +1293,7 @@ static int subject_prefix = 0; static int subject_prefix_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); subject_prefix = 1; ((struct rev_info *)opt->value)->subject_prefix = arg; return 0; @@ -1144,6 +1301,8 @@ static int subject_prefix_callback(const struct option *opt, const char *arg, static int rfc_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); return subject_prefix_callback(opt, "RFC PATCH", unset); } @@ -1152,6 +1311,7 @@ static int numbered_cmdline_opt = 0; static int numbered_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_ARG(arg); *(int *)opt->value = numbered_cmdline_opt = unset ? 0 : 1; if (unset) auto_number = 0; @@ -1161,6 +1321,7 @@ static int numbered_callback(const struct option *opt, const char *arg, static int no_numbered_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); return numbered_callback(opt, arg, 1); } @@ -1168,21 +1329,26 @@ static int output_directory_callback(const struct option *opt, const char *arg, int unset) { const char **dir = (const char **)opt->value; + BUG_ON_OPT_NEG(unset); if (*dir) - die(_("Two output directories?")); + die(_("two output directories?")); *dir = arg; return 0; } static int thread_callback(const struct option *opt, const char *arg, int unset) { - int *thread = (int *)opt->value; + enum thread_level *thread = (enum thread_level *)opt->value; if (unset) - *thread = 0; + *thread = THREAD_UNSET; else if (!arg || !strcmp(arg, "shallow")) *thread = THREAD_SHALLOW; else if (!strcmp(arg, "deep")) *thread = THREAD_DEEP; + /* + * Please update _git_formatpatch() in git-completion.bash + * when you add new options. + */ else return 1; return 0; @@ -1221,7 +1387,7 @@ static int header_callback(const struct option *opt, const char *arg, int unset) string_list_clear(&extra_to, 0); string_list_clear(&extra_cc, 0); } else { - add_header(arg); + add_header(arg); } return 0; } @@ -1276,8 +1442,8 @@ static struct commit *get_base_commit(const char *base_commit, if (base_commit && strcmp(base_commit, "auto")) { base = lookup_commit_reference_by_name(base_commit); if (!base) - die(_("Unknown commit %s"), base_commit); - } else if ((base_commit && !strcmp(base_commit, "auto")) || base_auto) { + die(_("unknown commit %s"), base_commit); + } else if ((base_commit && !strcmp(base_commit, "auto"))) { struct branch *curr_branch = branch_get(NULL); const char *upstream = branch_get_upstream(curr_branch, NULL); if (upstream) { @@ -1286,18 +1452,18 @@ static struct commit *get_base_commit(const char *base_commit, struct object_id oid; if (get_oid(upstream, &oid)) - die(_("Failed to resolve '%s' as a valid ref."), upstream); + die(_("failed to resolve '%s' as a valid ref"), upstream); commit = lookup_commit_or_die(&oid, "upstream base"); base_list = get_merge_bases_many(commit, total, list); /* There should be one and only one merge base. */ if (!base_list || base_list->next) - die(_("Could not find exact merge base.")); + die(_("could not find exact merge base")); base = base_list->item; free_commit_list(base_list); } else { - die(_("Failed to get upstream, if you want to record base commit automatically,\n" + die(_("failed to get upstream, if you want to record base commit automatically,\n" "please use git branch --set-upstream-to to track a remote branch.\n" - "Or you could specify base commit by --base=<base-commit-id> manually.")); + "Or you could specify base commit by --base=<base-commit-id> manually")); } } @@ -1315,7 +1481,7 @@ static struct commit *get_base_commit(const char *base_commit, struct commit_list *merge_base; merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]); if (!merge_base || merge_base->next) - die(_("Failed to find exact merge base")); + die(_("failed to find exact merge base")); rev[i] = merge_base->item; } @@ -1337,6 +1503,8 @@ static struct commit *get_base_commit(const char *base_commit, return base; } +define_commit_slab(commit_base, int); + static void prepare_bases(struct base_tree_info *bases, struct commit *base, struct commit **list, @@ -1345,24 +1513,26 @@ static void prepare_bases(struct base_tree_info *bases, struct commit *commit; struct rev_info revs; struct diff_options diffopt; + struct commit_base commit_base; int i; if (!base) return; - diff_setup(&diffopt); + init_commit_base(&commit_base); + repo_diff_setup(the_repository, &diffopt); diffopt.flags.recursive = 1; diff_setup_done(&diffopt); oidcpy(&bases->base_commit, &base->object.oid); - init_revisions(&revs, NULL); + repo_init_revisions(the_repository, &revs, NULL); revs.max_parents = 1; revs.topo_order = 1; for (i = 0; i < total; i++) { list[i]->object.flags &= ~UNINTERESTING; add_pending_object(&revs, &list[i]->object, "rev_list"); - list[i]->util = (void *)1; + *commit_base_at(&commit_base, list[i]) = 1; } base->object.flags |= UNINTERESTING; add_pending_object(&revs, &base->object, "base"); @@ -1376,15 +1546,16 @@ static void prepare_bases(struct base_tree_info *bases, while ((commit = get_revision(&revs)) != NULL) { struct object_id oid; struct object_id *patch_id; - if (commit->util) + if (*commit_base_at(&commit_base, commit)) continue; - if (commit_patch_id(commit, &diffopt, &oid, 0)) + if (commit_patch_id(commit, &diffopt, &oid, 0, 1)) die(_("cannot get patch id")); ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id); patch_id = bases->patch_id + bases->nr_patch_id; oidcpy(patch_id, &oid); bases->nr_patch_id++; } + clear_commit_base(&commit_base); } static void print_bases(struct base_tree_info *bases, FILE *file) @@ -1408,6 +1579,36 @@ static void print_bases(struct base_tree_info *bases, FILE *file) oidclr(&bases->base_commit); } +static const char *diff_title(struct strbuf *sb, int reroll_count, + const char *generic, const char *rerolled) +{ + if (reroll_count <= 0) + strbuf_addstr(sb, generic); + else /* RFC may be v0, so allow -v1 to diff against v0 */ + strbuf_addf(sb, rerolled, reroll_count - 1); + return sb->buf; +} + +static void infer_range_diff_ranges(struct strbuf *r1, + struct strbuf *r2, + const char *prev, + struct commit *origin, + struct commit *head) +{ + const char *head_oid = oid_to_hex(&head->object.oid); + + if (!strstr(prev, "..")) { + strbuf_addf(r1, "%s..%s", head_oid, prev); + strbuf_addf(r2, "%s..%s", prev, head_oid); + } else if (!origin) { + die(_("failed to infer range-diff ranges")); + } else { + strbuf_addstr(r1, prev); + strbuf_addf(r2, "%s..%s", + oid_to_hex(&origin->object.oid), head_oid); + } +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -1430,19 +1631,27 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int use_patch_format = 0; int quiet = 0; int reroll_count = -1; + char *cover_from_description_arg = NULL; char *branch_name = NULL; char *base_commit = NULL; struct base_tree_info bases; int show_progress = 0; struct progress *progress = NULL; + struct oid_array idiff_prev = OID_ARRAY_INIT; + struct strbuf idiff_title = STRBUF_INIT; + const char *rdiff_prev = NULL; + struct strbuf rdiff1 = STRBUF_INIT; + struct strbuf rdiff2 = STRBUF_INIT; + struct strbuf rdiff_title = STRBUF_INIT; + int creation_factor = -1; const struct option builtin_format_patch_options[] = { - { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, + OPT_CALLBACK_F('n', "numbered", &numbered, NULL, N_("use [PATCH n/m] even with a single patch"), - PARSE_OPT_NOARG, numbered_callback }, - { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL, + PARSE_OPT_NOARG, numbered_callback), + OPT_CALLBACK_F('N', "no-numbered", &numbered, NULL, N_("use [PATCH] even with multiple patches"), - PARSE_OPT_NOARG, no_numbered_callback }, + PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback), OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")), OPT_BOOL(0, "stdout", &use_stdout, N_("print patches to standard out")), @@ -1456,49 +1665,50 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")), OPT_INTEGER('v', "reroll-count", &reroll_count, N_("mark the series as Nth re-roll")), - { OPTION_CALLBACK, 0, "rfc", &rev, NULL, + OPT_CALLBACK_F(0, "rfc", &rev, NULL, N_("Use [RFC PATCH] instead of [PATCH]"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback }, - { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback), + OPT_STRING(0, "cover-from-description", &cover_from_description_arg, + N_("cover-from-description-mode"), + N_("generate parts of a cover letter based on a branch's description")), + OPT_CALLBACK_F(0, "subject-prefix", &rev, N_("prefix"), N_("Use [<prefix>] instead of [PATCH]"), - PARSE_OPT_NONEG, subject_prefix_callback }, - { OPTION_CALLBACK, 'o', "output-directory", &output_directory, + PARSE_OPT_NONEG, subject_prefix_callback), + OPT_CALLBACK_F('o', "output-directory", &output_directory, N_("dir"), N_("store resulting files in <dir>"), - PARSE_OPT_NONEG, output_directory_callback }, - { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL, + PARSE_OPT_NONEG, output_directory_callback), + OPT_CALLBACK_F('k', "keep-subject", &rev, NULL, N_("don't strip/add [PATCH]"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, + PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback), OPT_BOOL(0, "no-binary", &no_binary_diff, N_("don't output binary diffs")), OPT_BOOL(0, "zero-commit", &zero_commit, N_("output all-zero hash in From header")), OPT_BOOL(0, "ignore-if-in-upstream", &ignore_if_in_upstream, N_("don't include a patch matching a commit upstream")), - { OPTION_SET_INT, 'p', "no-stat", &use_patch_format, NULL, - N_("show patch format instead of default (patch + stat)"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1}, + OPT_SET_INT_F('p', "no-stat", &use_patch_format, + N_("show patch format instead of default (patch + stat)"), + 1, PARSE_OPT_NONEG), OPT_GROUP(N_("Messaging")), - { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"), - N_("add email header"), 0, header_callback }, - { OPTION_CALLBACK, 0, "to", NULL, N_("email"), N_("add To: header"), - 0, to_callback }, - { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"), - 0, cc_callback }, - { OPTION_CALLBACK, 0, "from", &from, N_("ident"), + OPT_CALLBACK(0, "add-header", NULL, N_("header"), + N_("add email header"), header_callback), + OPT_CALLBACK(0, "to", NULL, N_("email"), N_("add To: header"), to_callback), + OPT_CALLBACK(0, "cc", NULL, N_("email"), N_("add Cc: header"), cc_callback), + OPT_CALLBACK_F(0, "from", &from, N_("ident"), N_("set From address to <ident> (or committer ident if absent)"), - PARSE_OPT_OPTARG, from_callback }, + PARSE_OPT_OPTARG, from_callback), OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"), N_("make first mail a reply to <message-id>")), - { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"), + OPT_CALLBACK_F(0, "attach", &rev, N_("boundary"), N_("attach the patch"), PARSE_OPT_OPTARG, - attach_callback }, - { OPTION_CALLBACK, 0, "inline", &rev, N_("boundary"), + attach_callback), + OPT_CALLBACK_F(0, "inline", &rev, N_("boundary"), N_("inline the patch"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, - inline_callback }, - { OPTION_CALLBACK, 0, "thread", &thread, N_("style"), + inline_callback), + OPT_CALLBACK_F(0, "thread", &thread, N_("style"), N_("enable message threading, styles: shallow, deep"), - PARSE_OPT_OPTARG, thread_callback }, + PARSE_OPT_OPTARG, thread_callback), OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), OPT_STRING(0, "base", &base_commit, N_("base-commit"), @@ -1508,6 +1718,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) OPT__QUIET(&quiet, N_("don't print the patch filenames")), OPT_BOOL(0, "progress", &show_progress, N_("show progress while generating patches")), + OPT_CALLBACK(0, "interdiff", &idiff_prev, N_("rev"), + N_("show changes against <rev> in cover letter or single patch"), + parse_opt_object_name), + OPT_STRING(0, "range-diff", &rdiff_prev, N_("refspec"), + N_("show changes against <refspec> in cover letter or single patch")), + OPT_INTEGER(0, "creation-factor", &creation_factor, + N_("percentage by which creation is weighted")), OPT_END() }; @@ -1515,9 +1732,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; init_log_defaults(); + init_display_notes(¬es_opt); git_config(git_format_config, NULL); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, 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; @@ -1528,6 +1749,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def = "HEAD"; s_r_opt.revarg_opt = REVARG_COMMITTISH; + if (base_auto) + base_commit = "auto"; + if (default_attach) { rev.mime_boundary = default_attach; rev.no_inline = 1; @@ -1543,6 +1767,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + if (cover_from_description_arg) + cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); + if (0 < reroll_count) { struct strbuf sprefix = STRBUF_INIT; strbuf_addf(&sprefix, "%s v%d", @@ -1597,14 +1824,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) numbered = 0; if (numbered && keep_subject) - die (_("-n and -k are mutually exclusive.")); + die(_("-n and -k are mutually exclusive")); if (keep_subject && subject_prefix) - die (_("--subject-prefix/--rfc and -k are mutually exclusive.")); + die(_("--subject-prefix/--rfc and -k are mutually exclusive")); rev.preserve_subject = keep_subject; argc = setup_revisions(argc, argv, &rev, &s_r_opt); if (argc > 1) - die (_("unrecognized argument: %s"), argv[1]); + die(_("unrecognized argument: %s"), argv[1]); if (rev.diffopt.output_format & DIFF_FORMAT_NAME) die(_("--name-only does not make sense")); @@ -1629,7 +1856,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.diffopt.flags.binary = 1; if (rev.show_notes) - init_display_notes(&rev.notes_opt); + load_display_notes(&rev.notes_opt); if (!output_directory && !use_stdout) output_directory = config_output_directory; @@ -1640,12 +1867,28 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) setup_pager(); if (output_directory) { + int saved; if (rev.diffopt.use_color != GIT_COLOR_ALWAYS) rev.diffopt.use_color = GIT_COLOR_NEVER; if (use_stdout) die(_("standard output, or directory, which one?")); + /* + * We consider <outdir> as 'outside of gitdir', therefore avoid + * applying adjust_shared_perm in s-c-l-d. + */ + saved = get_shared_repository(); + set_shared_repository(0); + switch (safe_create_leading_directories_const(output_directory)) { + case SCLD_OK: + case SCLD_EXISTS: + break; + default: + die(_("could not create leading directories " + "of '%s'"), output_directory); + } + set_shared_repository(saved); if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) - die_errno(_("Could not create directory '%s'"), + die_errno(_("could not create directory '%s'"), output_directory); } @@ -1692,8 +1935,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) /* Don't say anything if head and upstream are the same. */ if (rev.pending.nr == 2) { struct object_array_entry *o = rev.pending.objects; - if (oidcmp(&o[0].item->oid, &o[1].item->oid) == 0) - return 0; + if (oideq(&o[0].item->oid, &o[1].item->oid)) + goto done; } get_patch_ids(&rev, &ids); } @@ -1717,7 +1960,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (nr == 0) /* nothing to do */ - return 0; + goto done; total = nr; if (cover_letter == -1) { if (config_cover_letter == COVER_AUTO) @@ -1730,6 +1973,35 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (numbered) rev.total = total + start_number - 1; + if (idiff_prev.nr) { + if (!cover_letter && total != 1) + die(_("--interdiff requires --cover-letter or single patch")); + rev.idiff_oid1 = &idiff_prev.oid[idiff_prev.nr - 1]; + rev.idiff_oid2 = get_commit_tree_oid(list[0]); + rev.idiff_title = diff_title(&idiff_title, reroll_count, + _("Interdiff:"), + _("Interdiff against v%d:")); + } + + if (creation_factor < 0) + creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; + else if (!rdiff_prev) + die(_("--creation-factor requires --range-diff")); + + if (rdiff_prev) { + if (!cover_letter && total != 1) + die(_("--range-diff requires --cover-letter or single patch")); + + infer_range_diff_ranges(&rdiff1, &rdiff2, rdiff_prev, + origin, list[0]); + rev.rdiff1 = rdiff1.buf; + rev.rdiff2 = rdiff2.buf; + rev.creation_factor = creation_factor; + rev.rdiff_title = diff_title(&rdiff_title, reroll_count, + _("Range-diff:"), + _("Range-diff against v%d:")); + } + if (!signature) { ; /* --no-signature inhibits all signatures */ } else if (signature && signature != git_version_string) { @@ -1743,9 +2015,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } memset(&bases, 0, sizeof(bases)); - if (base_commit || base_auto) { + if (base_commit) { struct commit *base = get_base_commit(base_commit, list, nr); reset_revision_walk(); + clear_object_flags(UNINTERESTING); prepare_bases(&bases, base, list, nr); } @@ -1766,6 +2039,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) print_signature(rev.diffopt.file); total++; start_number--; + /* interdiff/range-diff in cover-letter; omit from patches */ + rev.idiff_oid1 = NULL; + rev.rdiff1 = NULL; } rev.add_signoff = do_signoff; @@ -1814,9 +2090,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout && open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) - die(_("Failed to create output files")); + die(_("failed to create output files")); shown = log_tree_commit(&rev, commit); - free_commit_buffer(commit); + free_commit_buffer(the_repository->parsed_objects, + commit); /* We put one extra blank line between formatted * patches and this flag is used by log-tree code @@ -1846,6 +2123,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) string_list_clear(&extra_hdr, 0); if (ignore_if_in_upstream) free_patch_ids(&ids); + +done: + oid_array_clear(&idiff_prev); + strbuf_release(&idiff_title); + strbuf_release(&rdiff1); + strbuf_release(&rdiff2); + strbuf_release(&rdiff_title); return 0; } @@ -1853,7 +2137,8 @@ static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) { struct object_id oid; if (get_oid(arg, &oid) == 0) { - struct commit *commit = lookup_commit_reference(&oid); + struct commit *commit = lookup_commit_reference(the_repository, + &oid); if (commit) { commit->object.flags |= flags; add_pending_object(revs, &commit->object, arg); @@ -1925,25 +2210,25 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) } } - init_revisions(&revs, prefix); + repo_init_revisions(the_repository, &revs, prefix); revs.max_parents = 1; if (add_pending_commit(head, &revs, 0)) - die(_("Unknown commit %s"), head); + die(_("unknown commit %s"), head); if (add_pending_commit(upstream, &revs, UNINTERESTING)) - die(_("Unknown commit %s"), upstream); + die(_("unknown commit %s"), upstream); /* Don't say anything if head and upstream are the same. */ if (revs.pending.nr == 2) { struct object_array_entry *o = revs.pending.objects; - if (oidcmp(&o[0].item->oid, &o[1].item->oid) == 0) + if (oideq(&o[0].item->oid, &o[1].item->oid)) return 0; } get_patch_ids(&revs, &ids); if (limit && add_pending_commit(limit, &revs, UNINTERESTING)) - die(_("Unknown commit %s"), limit); + die(_("unknown commit %s"), limit); /* reverse the list of commits */ if (prepare_revision_walk(&revs)) diff --git a/builtin/ls-files.c b/builtin/ls-files.c index a71f6bd088..30a4c10334 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -5,7 +5,6 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "repository.h" #include "config.h" @@ -63,7 +62,7 @@ static void write_eolinfo(const struct index_state *istate, struct stat st; const char *i_txt = ""; const char *w_txt = ""; - const char *a_txt = get_convert_attr_ascii(path); + const char *a_txt = get_convert_attr_ascii(istate, path); if (ce && S_ISREG(ce->ce_mode)) i_txt = get_cached_convert_stats_ascii(istate, ce->name); @@ -113,26 +112,28 @@ static void print_debug(const struct cache_entry *ce) if (debug_mode) { const struct stat_data *sd = &ce->ce_stat_data; - printf(" ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); - printf(" mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); - printf(" dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino); - printf(" uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid); - printf(" size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags); + printf(" ctime: %u:%u\n", sd->sd_ctime.sec, sd->sd_ctime.nsec); + printf(" mtime: %u:%u\n", sd->sd_mtime.sec, sd->sd_mtime.nsec); + printf(" dev: %u\tino: %u\n", sd->sd_dev, sd->sd_ino); + printf(" uid: %u\tgid: %u\n", sd->sd_uid, sd->sd_gid); + printf(" size: %u\tflags: %x\n", sd->sd_size, ce->ce_flags); } } -static void show_dir_entry(const char *tag, struct dir_entry *ent) +static void show_dir_entry(const struct index_state *istate, + const char *tag, struct dir_entry *ent) { int len = max_prefix_len; if (len > ent->len) die("git ls-files: internal error - directory entry not superset of prefix"); - if (!dir_path_match(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(NULL, NULL, ent->name); + write_eolinfo(istate, NULL, ent->name); write_name(ent->name); } @@ -145,7 +146,7 @@ static void show_other_files(const struct index_state *istate, struct dir_entry *ent = dir->entries[i]; if (!index_name_is_other(istate, ent->name, ent->len)) continue; - show_dir_entry(tag_other, ent); + show_dir_entry(istate, tag_other, ent); } } @@ -166,7 +167,7 @@ static void show_killed_files(const struct index_state *istate, */ pos = index_name_pos(istate, ent->name, ent->len); if (0 <= pos) - die("BUG: killed-file %.*s not found", + BUG("killed-file %.*s not found", ent->len, ent->name); pos = -pos - 1; while (pos < istate->cache_nr && @@ -196,7 +197,7 @@ static void show_killed_files(const struct index_state *istate, } } if (killed) - show_dir_entry(tag_killed, dir->entries[i]); + show_dir_entry(istate, tag_killed, dir->entries[i]); } } @@ -205,17 +206,19 @@ static void show_files(struct repository *repo, struct dir_struct *dir); static void show_submodule(struct repository *superproject, struct dir_struct *dir, const char *path) { - struct repository submodule; + struct repository subrepo; + const struct submodule *sub = submodule_from_path(superproject, + &null_oid, path); - if (repo_submodule_init(&submodule, superproject, path)) + if (repo_submodule_init(&subrepo, superproject, sub)) return; - if (repo_read_index(&submodule) < 0) + if (repo_read_index(&subrepo) < 0) die("index file corrupt"); - show_files(&submodule, dir); + show_files(&subrepo, dir); - repo_clear(&submodule); + repo_clear(&subrepo); } static void show_ce(struct repository *repo, struct dir_struct *dir, @@ -228,7 +231,7 @@ static void show_ce(struct repository *repo, struct dir_struct *dir, if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && is_submodule_active(repo, ce->name)) { show_submodule(repo, dir, ce->name); - } else if (match_pathspec(&pathspec, fullname, strlen(fullname), + } else if (match_pathspec(repo->index, &pathspec, fullname, strlen(fullname), max_prefix_len, ps_matched, S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode))) { @@ -264,7 +267,7 @@ static void show_ru_info(const struct index_state *istate) len = strlen(path); if (len < max_prefix_len) continue; /* outside of the prefix */ - if (!match_pathspec(&pathspec, path, len, + if (!match_pathspec(istate, &pathspec, path, len, max_prefix_len, ps_matched, 0)) continue; /* uninterested */ for (i = 0; i < 3; i++) { @@ -371,7 +374,7 @@ static void prune_index(struct index_state *istate, first = pos; last = istate->cache_nr; while (last > first) { - int next = (last + first) >> 1; + int next = first + ((last - first) >> 1); const struct cache_entry *ce = istate->cache[next]; if (!strncmp(ce->name, prefix, prefixlen)) { first = next+1; @@ -440,7 +443,7 @@ void overlay_tree_on_index(struct index_state *istate, PATHSPEC_PREFER_CWD, prefix, matchbuf); } else memset(&pathspec, 0, sizeof(pathspec)); - if (read_tree(tree, 1, &pathspec, istate)) + if (read_tree(the_repository, tree, 1, &pathspec, istate)) die("unable to read tree entries %s", tree_name); for (i = 0; i < istate->cache_nr; i++) { @@ -474,6 +477,8 @@ static int option_parse_exclude(const struct option *opt, { struct string_list *exclude_list = opt->value; + BUG_ON_OPT_NEG(unset); + exc_given = 1; string_list_append(exclude_list, arg); @@ -485,8 +490,10 @@ static int option_parse_exclude_from(const struct option *opt, { struct dir_struct *dir = opt->value; + BUG_ON_OPT_NEG(unset); + exc_given = 1; - add_excludes_from_file(dir, arg); + add_patterns_from_file(dir, arg); return 0; } @@ -496,6 +503,9 @@ static int option_parse_exclude_standard(const struct option *opt, { struct dir_struct *dir = opt->value; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + exc_given = 1; setup_standard_excludes(dir); @@ -507,7 +517,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) int require_work_tree = 0, show_tag = 0, i; const char *max_prefix; struct dir_struct dir; - struct exclude_list *el; + struct pattern_list *pl; struct string_list exclude_list = STRING_LIST_INIT_NODUP; struct option builtin_ls_files_options[] = { /* Think twice before adding "--nul" synonym to this */ @@ -545,20 +555,21 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("show unmerged files in the output")), OPT_BOOL(0, "resolve-undo", &show_resolve_undo, N_("show resolve-undo information")), - { OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"), + OPT_CALLBACK_F('x', "exclude", &exclude_list, N_("pattern"), N_("skip files matching pattern"), - 0, option_parse_exclude }, - { OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"), + PARSE_OPT_NONEG, option_parse_exclude), + OPT_CALLBACK_F('X', "exclude-from", &dir, N_("file"), N_("exclude patterns are read from <file>"), - 0, option_parse_exclude_from }, + PARSE_OPT_NONEG, option_parse_exclude_from), OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"), N_("read additional per-directory exclude patterns in <file>")), - { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL, + OPT_CALLBACK_F(0, "exclude-standard", &dir, NULL, N_("add the standard git exclusions"), - PARSE_OPT_NOARG, option_parse_exclude_standard }, - { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL, - N_("make the output relative to the project top directory"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + option_parse_exclude_standard), + OPT_SET_INT_F(0, "full-name", &prefix_len, + N_("make the output relative to the project top directory"), + 0, PARSE_OPT_NONEG), OPT_BOOL(0, "recurse-submodules", &recurse_submodules, N_("recurse through submodules")), OPT_BOOL(0, "error-unmatch", &error_unmatch, @@ -584,9 +595,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) argc = parse_options(argc, argv, prefix, builtin_ls_files_options, ls_files_usage, 0); - el = add_exclude_list(&dir, EXC_CMDL, "--exclude option"); + pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option"); for (i = 0; i < exclude_list.nr; i++) { - add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args); + add_pattern(exclude_list.items[i].string, "", 0, pl, --exclude_args); } if (show_tag || show_valid_bit || show_fsmonitor_bit) { tag_cached = "H "; @@ -670,7 +681,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (ps_matched) { int bad; - bad = report_path_error(ps_matched, &pathspec, prefix); + bad = report_path_error(ps_matched, &pathspec); if (bad) fprintf(stderr, "Did you forget to 'git add'?\n"); diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 540d56429f..6ef519514b 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -1,7 +1,9 @@ #include "builtin.h" #include "cache.h" #include "transport.h" +#include "ref-filter.h" #include "remote.h" +#include "refs.h" static const char * const ls_remote_usage[] = { N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=<exec>]\n" @@ -43,10 +45,15 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) int show_symref_target = 0; const char *uploadpack = NULL; const char **pattern = NULL; + struct argv_array ref_prefixes = ARGV_ARRAY_INIT; + int i; + struct string_list server_options = STRING_LIST_INIT_DUP; struct remote *remote; struct transport *transport; const struct ref *ref; + struct ref_array ref_array; + static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; struct option options[] = { OPT__QUIET(&quiet, N_("do not print remote URL")), @@ -60,14 +67,18 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) OPT_BIT(0, "refs", &flags, N_("do not show peeled tags"), REF_NORMAL), OPT_BOOL(0, "get-url", &get_url, N_("take url.<base>.insteadOf into account")), + OPT_REF_SORT(sorting_tail), OPT_SET_INT_F(0, "exit-code", &status, N_("exit with exit code 2 if no matching refs are found"), 2, PARSE_OPT_NOCOMPLETE), OPT_BOOL(0, "symref", &show_symref_target, N_("show underlying ref in addition to the object pointed by it")), + OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")), OPT_END() }; + memset(&ref_array, 0, sizeof(ref_array)); + argc = parse_options(argc, argv, prefix, options, ls_remote_usage, PARSE_OPT_STOP_AT_NON_OPTION); dest = argv[0]; @@ -75,10 +86,16 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (argc > 1) { int i; pattern = xcalloc(argc, sizeof(const char *)); - for (i = 1; i < argc; i++) + for (i = 1; i < argc; i++) { pattern[i - 1] = xstrfmt("*/%s", argv[i]); + } } + if (flags & REF_TAGS) + argv_array_push(&ref_prefixes, "refs/tags/"); + if (flags & REF_HEADS) + argv_array_push(&ref_prefixes, "refs/heads/"); + remote = remote_get(dest); if (!remote) { if (dest) @@ -90,28 +107,46 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (get_url) { printf("%s\n", *remote->url); + UNLEAK(sorting); return 0; } transport = transport_get(remote, NULL); if (uploadpack != NULL) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); + if (server_options.nr) + transport->server_options = &server_options; - ref = transport_get_remote_refs(transport); - if (transport_disconnect(transport)) + ref = transport_get_remote_refs(transport, &ref_prefixes); + if (transport_disconnect(transport)) { + UNLEAK(sorting); return 1; + } if (!dest && !quiet) fprintf(stderr, "From %s\n", *remote->url); for ( ; ref; ref = ref->next) { + struct ref_array_item *item; if (!check_ref_type(ref, flags)) continue; if (!tail_match(pattern, ref->name)) continue; + item = ref_array_push(&ref_array, ref->name, &ref->old_oid); + item->symref = xstrdup_or_null(ref->symref); + } + + if (sorting) + ref_array_sort(sorting, &ref_array); + + for (i = 0; i < ref_array.nr; i++) { + const struct ref_array_item *ref = ref_array.items[i]; if (show_symref_target && ref->symref) - printf("ref: %s\t%s\n", ref->symref, ref->name); - printf("%s\t%s\n", oid_to_hex(&ref->old_oid), ref->name); + printf("ref: %s\t%s\n", ref->symref, ref->refname); + printf("%s\t%s\n", oid_to_hex(&ref->objectname), ref->refname); status = 0; /* we found something */ } + + UNLEAK(sorting); + ref_array_clear(&ref_array); return status; } diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index d44b4f9c27..7cad3f24eb 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -5,6 +5,7 @@ */ #include "cache.h" #include "config.h" +#include "object-store.h" #include "blob.h" #include "tree.h" #include "commit.h" @@ -94,12 +95,12 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, char size_text[24]; if (!strcmp(type, blob_type)) { unsigned long size; - if (oid_object_info(oid, &size) == OBJ_BAD) + if (oid_object_info(the_repository, oid, &size) == OBJ_BAD) xsnprintf(size_text, sizeof(size_text), "BAD"); else xsnprintf(size_text, sizeof(size_text), - "%lu", size); + "%"PRIuMAX, (uintmax_t)size); } else xsnprintf(size_text, sizeof(size_text), "-"); printf("%06o %s %s %7s\t", mode, type, @@ -184,5 +185,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) tree = parse_tree_indirect(&oid); if (!tree) die("not a tree object"); - return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); + return !!read_tree_recursive(the_repository, tree, "", 0, 0, + &pathspec, show_tree, NULL); } diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 3b7600150b..6719ac198d 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -6,6 +6,8 @@ #include "diff.h" #include "revision.h" #include "parse-options.h" +#include "repository.h" +#include "commit-reach.h" static int show_merge_base(struct commit **rev, int rev_nr, int show_all) { @@ -42,7 +44,7 @@ static struct commit *get_commit_reference(const char *arg) if (get_oid(arg, &revkey)) die("Not a valid object name %s", arg); - r = lookup_commit_reference(&revkey); + r = lookup_commit_reference(the_repository, &revkey); if (!r) die("Not a valid commit name %s", arg); @@ -109,104 +111,25 @@ static int handle_is_ancestor(int argc, const char **argv) return 1; } -struct rev_collect { - struct commit **commit; - int nr; - int alloc; - unsigned int initial : 1; -}; - -static void add_one_commit(struct object_id *oid, struct rev_collect *revs) -{ - struct commit *commit; - - if (is_null_oid(oid)) - return; - - commit = lookup_commit(oid); - if (!commit || - (commit->object.flags & TMP_MARK) || - parse_commit(commit)) - return; - - ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc); - revs->commit[revs->nr++] = commit; - commit->object.flags |= TMP_MARK; -} - -static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *ident, timestamp_t timestamp, - int tz, const char *message, void *cbdata) -{ - struct rev_collect *revs = cbdata; - - if (revs->initial) { - revs->initial = 0; - add_one_commit(ooid, revs); - } - add_one_commit(noid, revs); - return 0; -} - static int handle_fork_point(int argc, const char **argv) { struct object_id oid; - char *refname; + struct commit *derived, *fork_point; const char *commitname; - struct rev_collect revs; - struct commit *derived; - struct commit_list *bases; - int i, ret = 0; - - 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(&oid); - memset(&revs, 0, sizeof(revs)); - revs.initial = 1; - for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); + derived = lookup_commit_reference(the_repository, &oid); - if (!revs.nr && !get_oid(refname, &oid)) - add_one_commit(&oid, &revs); + fork_point = get_fork_point(argv[0], derived); - for (i = 0; i < revs.nr; i++) - revs.commit[i]->object.flags &= ~TMP_MARK; - - bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit); - - /* - * There should be one and only one merge base, when we found - * a common ancestor among reflog entries. - */ - if (!bases || bases->next) { - ret = 1; - goto cleanup_return; - } - - /* And the found one must be one of the reflog entries */ - for (i = 0; i < revs.nr; i++) - if (&bases->item->object == &revs.commit[i]->object) - break; /* found */ - if (revs.nr <= i) { - ret = 1; /* not found */ - goto cleanup_return; - } - - printf("%s\n", oid_to_hex(&bases->item->object.oid)); + if (!fork_point) + return 1; -cleanup_return: - free_commit_list(bases); - return ret; + printf("%s\n", oid_to_hex(&fork_point->object.oid)); + return 0; } int cmd_merge_base(int argc, const char **argv, const char *prefix) diff --git a/builtin/merge-file.c b/builtin/merge-file.c index b08803e611..06a2f90c48 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -15,6 +15,8 @@ static int label_cb(const struct option *opt, const char *arg, int unset) static int label_count = 0; const char **names = (const char **)opt->value; + BUG_ON_OPT_NEG(unset); + if (label_count >= 3) return error("too many labels on the command line"); names[label_count++] = arg; diff --git a/builtin/merge-index.c b/builtin/merge-index.c index c99443b095..38ea6ad6ca 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -1,3 +1,4 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "run-command.h" diff --git a/builtin/merge-ours.c b/builtin/merge-ours.c index c84c6e05e9..4594507420 100644 --- a/builtin/merge-ours.c +++ b/builtin/merge-ours.c @@ -7,6 +7,7 @@ * * Pretend we resolved the heads, but declare our tree trumps everybody else. */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "git-compat-util.h" #include "builtin.h" #include "diff.h" @@ -26,7 +27,7 @@ int cmd_merge_ours(int argc, const char **argv, const char *prefix) */ if (read_cache() < 0) die_errno("read_cache failed"); - if (index_differs_from("HEAD", NULL, 0)) + if (index_differs_from(the_repository, "HEAD", NULL, 0)) exit(2); exit(0); } diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index 0dd9021958..a4bfd8fc51 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -1,3 +1,4 @@ +#include "cache.h" #include "builtin.h" #include "commit.h" #include "tag.h" @@ -7,16 +8,16 @@ static const char builtin_merge_recursive_usage[] = "git %s <base>... -- <head> <remote> ..."; -static const char *better_branch_name(const char *branch) +static char *better_branch_name(const char *branch) { - static char githead_env[8 + GIT_SHA1_HEXSZ + 1]; + static char githead_env[8 + GIT_MAX_HEXSZ + 1]; char *name; - if (strlen(branch) != GIT_SHA1_HEXSZ) - return branch; + if (strlen(branch) != the_hash_algo->hexsz) + return xstrdup(branch); xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch); name = getenv(githead_env); - return name ? name : branch; + return xstrdup(name ? name : branch); } int cmd_merge_recursive(int argc, const char **argv, const char *prefix) @@ -26,9 +27,10 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) int i, failed; struct object_id h1, h2; struct merge_options o; + char *better1, *better2; struct commit *result; - init_merge_options(&o); + init_merge_options(&o, the_repository); if (argv[0] && ends_with(argv[0], "-subtree")) o.subtree_shift = ""; @@ -62,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (argc - i != 3) /* "--" "<head>" "<remote>" */ die(_("not handling anything other than two heads merge.")); + if (repo_read_index_unmerged(the_repository)) + die_resolve_conflict("merge"); + o.branch1 = argv[++i]; o.branch2 = argv[++i]; @@ -70,13 +75,17 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (get_oid(o.branch2, &h2)) die(_("could not resolve ref '%s'"), o.branch2); - o.branch1 = better_branch_name(o.branch1); - o.branch2 = better_branch_name(o.branch2); + o.branch1 = better1 = better_branch_name(o.branch1); + o.branch2 = better2 = better_branch_name(o.branch2); if (o.verbosity >= 3) printf(_("Merging %s with %s\n"), o.branch1, o.branch2); failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result); + + free(better1); + free(better2); + if (failed < 0) return 128; /* die() error code */ return failed; diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 32736e0b10..e72714a5a8 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -1,8 +1,11 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" +#include "object-store.h" +#include "repository.h" #include "blob.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "merge-blobs.h" static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; @@ -74,7 +77,8 @@ static void *result(struct merge_list *entry, unsigned long *size) their = NULL; if (entry) their = entry->blob; - return merge_blobs(path, base, our, their, size); + return merge_blobs(the_repository->index, path, + base, our, their, size); } static void *origin(struct merge_list *entry, unsigned long *size) @@ -108,7 +112,8 @@ static void show_diff(struct merge_list *entry) xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; - ecb.outf = show_outf; + ecb.out_hunk = NULL; + ecb.out_line = show_outf; ecb.priv = NULL; src.ptr = origin(entry, &size); @@ -151,15 +156,15 @@ static void show_result(void) /* An empty entry never compares same, not even to another empty entry */ static int same_entry(struct name_entry *a, struct name_entry *b) { - return a->oid && - b->oid && - !oidcmp(a->oid, b->oid) && + return !is_null_oid(&a->oid) && + !is_null_oid(&b->oid) && + oideq(&a->oid, &b->oid) && a->mode == b->mode; } static int both_empty(struct name_entry *a, struct name_entry *b) { - return !(a->oid || b->oid); + return is_null_oid(&a->oid) && is_null_oid(&b->oid); } static struct merge_list *create_entry(unsigned stage, unsigned mode, const struct object_id *oid, const char *path) @@ -169,14 +174,15 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const stru res->stage = stage; res->path = path; res->mode = mode; - res->blob = lookup_blob(oid); + res->blob = lookup_blob(the_repository, oid); return res; } static char *traverse_path(const struct traverse_info *info, const struct name_entry *n) { - char *path = xmallocz(traverse_path_len(info, n)); - return make_traverse_path(path, info, n); + struct strbuf buf = STRBUF_INIT; + strbuf_make_traverse_path(&buf, info, n->path, n->pathlen); + return strbuf_detach(&buf, NULL); } static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result) @@ -189,8 +195,8 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s return; path = traverse_path(info, result); - orig = create_entry(2, ours->mode, ours->oid, path); - final = create_entry(0, result->mode, result->oid, path); + orig = create_entry(2, ours->mode, &ours->oid, path); + final = create_entry(0, result->mode, &result->oid, path); final->link = orig; @@ -200,6 +206,7 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s static void unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) { + struct repository *r = the_repository; char *newbase; struct name_entry *p; struct tree_desc t[3]; @@ -214,10 +221,10 @@ static void unresolved_directory(const struct traverse_info *info, newbase = traverse_path(info, p); -#define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->oid : NULL) - buf0 = fill_tree_descriptor(t + 0, ENTRY_OID(n + 0)); - buf1 = fill_tree_descriptor(t + 1, ENTRY_OID(n + 1)); - buf2 = fill_tree_descriptor(t + 2, ENTRY_OID(n + 2)); +#define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? &(e)->oid : NULL) + buf0 = fill_tree_descriptor(r, t + 0, ENTRY_OID(n + 0)); + buf1 = fill_tree_descriptor(r, t + 1, ENTRY_OID(n + 1)); + buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2)); #undef ENTRY_OID merge_trees(t, newbase); @@ -240,7 +247,7 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info path = entry->path; else path = traverse_path(info, n); - link = create_entry(stage, n->mode, n->oid, path); + link = create_entry(stage, n->mode, &n->oid, path); link->link = entry; return link; } @@ -315,7 +322,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s } if (same_entry(entry+0, entry+1)) { - if (entry[2].oid && !S_ISDIR(entry[2].mode)) { + if (!is_null_oid(&entry[2].oid) && !S_ISDIR(entry[2].mode)) { /* We did not touch, they modified -- take theirs */ resolve(info, entry+1, entry+2); return mask; @@ -343,17 +350,19 @@ static void merge_trees(struct tree_desc t[3], const char *base) setup_traverse_info(&info, base); info.fn = threeway_callback; - traverse_trees(3, t, &info); + traverse_trees(&the_index, 3, t, &info); } -static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) +static void *get_tree_descriptor(struct repository *r, + struct tree_desc *desc, + const char *rev) { struct object_id oid; void *buf; - if (get_oid(rev, &oid)) + if (repo_get_oid(r, rev, &oid)) die("unknown rev %s", rev); - buf = fill_tree_descriptor(desc, &oid); + buf = fill_tree_descriptor(r, desc, &oid); if (!buf) die("%s is not a tree", rev); return buf; @@ -361,15 +370,16 @@ static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) int cmd_merge_tree(int argc, const char **argv, const char *prefix) { + struct repository *r = the_repository; struct tree_desc t[3]; void *buf1, *buf2, *buf3; if (argc != 4) usage(merge_tree_usage); - buf1 = get_tree_descriptor(t+0, argv[1]); - buf2 = get_tree_descriptor(t+1, argv[2]); - buf3 = get_tree_descriptor(t+2, argv[3]); + buf1 = get_tree_descriptor(r, t+0, argv[1]); + buf2 = get_tree_descriptor(r, t+1, argv[2]); + buf3 = get_tree_descriptor(r, t+2, argv[3]); merge_trees(t, ""); free(buf1); free(buf2); diff --git a/builtin/merge.c b/builtin/merge.c index 9db5a2cf16..7da707bf55 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -6,6 +6,7 @@ * Based on git-merge.sh by Junio C Hamano. */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "parse-options.h" @@ -14,6 +15,7 @@ #include "run-command.h" #include "diff.h" #include "refs.h" +#include "refspec.h" #include "commit.h" #include "diffcore.h" #include "revision.h" @@ -34,6 +36,11 @@ #include "string-list.h" #include "packfile.h" #include "tag.h" +#include "alias.h" +#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) @@ -53,9 +60,10 @@ static const char * const builtin_merge_usage[] = { }; static int show_diffstat = 1, shortlog_len = -1, squash; -static int option_commit = 1; +static int option_commit = -1; static int option_edit = -1; static int allow_trivial = 1, have_message, verify_signatures; +static int check_trust_level = 1; static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies; @@ -68,13 +76,15 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int quit_current_merge; static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; static int default_to_upstream = 1; static int signoff; static const char *sign_commit; -static int verify_msg = 1; +static int autostash; +static int no_verify; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -94,6 +104,9 @@ enum ff_type { static enum ff_type fast_forward = FF_ALLOW; +static const char *cleanup_arg; +static enum commit_msg_cleanup_mode cleanup_mode; + static int option_parse_message(const struct option *opt, const char *arg, int unset) { @@ -109,6 +122,38 @@ static int option_parse_message(const struct option *opt, return 0; } +static enum parse_opt_result option_read_message(struct parse_opt_ctx_t *ctx, + const struct option *opt, + const char *arg_not_used, + int unset) +{ + struct strbuf *buf = opt->value; + const char *arg; + + BUG_ON_OPT_ARG(arg_not_used); + if (unset) + BUG("-F cannot be negated"); + + if (ctx->opt) { + arg = ctx->opt; + ctx->opt = NULL; + } else if (ctx->argc > 1) { + ctx->argc--; + arg = *++ctx->argv; + } else + return error(_("option `%s' requires a value"), opt->long_name); + + if (buf->len) + strbuf_addch(buf, '\n'); + if (ctx->prefix && !is_absolute_path(arg)) + arg = prefix_filename(ctx->prefix, arg); + if (strbuf_read_file(buf, arg, 0) < 0) + return error(_("could not read file '%s'"), arg); + have_message = 1; + + return 0; +} + static struct strategy *get_strategy(const char *name) { int i; @@ -192,14 +237,15 @@ static int option_parse_x(const struct option *opt, static int option_parse_n(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_ARG(arg); show_diffstat = unset; return 0; } static struct option builtin_merge_options[] = { - { OPTION_CALLBACK, 'n', NULL, NULL, NULL, + OPT_CALLBACK_F('n', NULL, NULL, NULL, N_("do not show a diffstat at the end of the merge"), - PARSE_OPT_NOARG, option_parse_n }, + PARSE_OPT_NOARG, option_parse_n), OPT_BOOL(0, "stat", &show_diffstat, N_("show a diffstat at the end of the merge")), OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")), @@ -212,10 +258,11 @@ static struct option builtin_merge_options[] = { N_("perform a commit if the merge succeeds (default)")), OPT_BOOL('e', "edit", &option_edit, N_("edit message before committing")), + OPT_CLEANUP(&cleanup_arg), OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW), - { OPTION_SET_INT, 0, "ff-only", &fast_forward, NULL, - N_("abort if fast-forward is not possible"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY }, + OPT_SET_INT_F(0, "ff-only", &fast_forward, + N_("abort if fast-forward is not possible"), + FF_ONLY, PARSE_OPT_NONEG), OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_BOOL(0, "verify-signatures", &verify_signatures, N_("verify that the named commit has a valid GPG signature")), @@ -226,9 +273,14 @@ static struct option builtin_merge_options[] = { OPT_CALLBACK('m', "message", &merge_msg, N_("message"), N_("merge commit message (for a non-fast-forward merge)"), option_parse_message), + { OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"), + N_("read message from file"), PARSE_OPT_NONEG, + NULL, 0, option_read_message }, OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "quit", &quit_current_merge, + N_("--abort but leave index and working tree alone")), OPT_BOOL(0, "continue", &continue_current_merge, N_("continue the current in-progress merge")), OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, @@ -236,20 +288,13 @@ 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, "verify", &verify_msg, N_("verify commit-msg hook")), + OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")), OPT_END() }; -/* Cleans up metadata that is uninteresting after a succeeded merge. */ -static void drop_save(void) -{ - unlink(git_path_merge_head()); - unlink(git_path_merge_msg()); - unlink(git_path_merge_mode()); -} - static int save_state(struct object_id *stash) { int len; @@ -280,7 +325,7 @@ out: return rc; } -static void read_empty(unsigned const char *sha1, int verbose) +static void read_empty(const struct object_id *oid, int verbose) { int i = 0; const char *args[7]; @@ -290,15 +335,15 @@ static void read_empty(unsigned const char *sha1, int verbose) args[i++] = "-v"; args[i++] = "-m"; args[i++] = "-u"; - args[i++] = EMPTY_TREE_SHA1_HEX; - args[i++] = sha1_to_hex(sha1); + args[i++] = empty_tree_oid_hex(); + args[i++] = oid_to_hex(oid); args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) die(_("read-tree failed")); } -static void reset_hard(unsigned const char *sha1, int verbose) +static void reset_hard(const struct object_id *oid, int verbose) { int i = 0; const char *args[6]; @@ -308,7 +353,7 @@ static void reset_hard(unsigned const char *sha1, int verbose) args[i++] = "-v"; args[i++] = "--reset"; args[i++] = "-u"; - args[i++] = sha1_to_hex(sha1); + args[i++] = oid_to_hex(oid); args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) @@ -324,7 +369,7 @@ static void restore_state(const struct object_id *head, if (is_null_oid(stash)) return; - reset_hard(head->hash, 1); + reset_hard(head, 1); args[2] = oid_to_hex(stash); @@ -343,7 +388,7 @@ static void finish_up_to_date(const char *msg) { if (verbosity >= 0) printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); - drop_save(); + remove_merge_branch_state(the_repository); } static void squash_message(struct commit *commit, struct commit_list *remoteheads) @@ -355,7 +400,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead printf(_("Squash commit -- not updating HEAD\n")); - init_revisions(&rev, NULL); + repo_init_revisions(the_repository, &rev, NULL); rev.ignore_merges = 1; rev.commit_format = CMIT_FMT_MEDIUM; @@ -380,7 +425,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead oid_to_hex(&commit->object.oid)); pretty_print_commit(&ctx, commit, &out); } - write_file_buf(git_path_squash_msg(), out.buf, out.len); + write_file_buf(git_path_squash_msg(the_repository), out.buf, out.len); strbuf_release(&out); } @@ -405,20 +450,19 @@ static void finish(struct commit *head_commit, if (verbosity >= 0 && !merge_msg.len) printf(_("No merge message -- not updating HEAD\n")); else { - const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", new_head, head, 0, UPDATE_REFS_DIE_ON_ERR); /* * We ignore errors in 'gc --auto', since the * user should see them. */ - close_all_packs(the_repository->objects); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + close_object_store(the_repository->objects); + run_auto_gc(verbosity < 0); } } if (new_head && show_diffstat) { struct diff_options opts; - diff_setup(&opts); + repo_diff_setup(the_repository, &opts); opts.stat_width = -1; /* use full terminal width */ opts.stat_graph_width = -1; /* respect statGraphWidth config */ opts.output_format |= @@ -433,6 +477,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); } @@ -443,6 +488,7 @@ static void merge_name(const char *remote, struct strbuf *msg) struct object_id branch_head; struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; + struct merge_remote_desc *desc; const char *ptr; char *found_ref; int len, early; @@ -515,16 +561,13 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_release(&truname); } - if (remote_head->util) { - struct merge_remote_desc *desc; - desc = merge_remote_util(remote_head); - if (desc && desc->obj && desc->obj->type == OBJ_TAG) { - strbuf_addf(msg, "%s\t\t%s '%s'\n", - oid_to_hex(&desc->obj->oid), - type_name(desc->obj->type), - remote); - goto cleanup; - } + desc = merge_remote_util(remote_head); + if (desc && desc->obj && desc->obj->type == OBJ_TAG) { + strbuf_addf(msg, "%s\t\t%s '%s'\n", + oid_to_hex(&desc->obj->oid), + type_name(desc->obj->type), + remote); + goto cleanup; } strbuf_addf(msg, "%s\t\tcommit '%s'\n", @@ -544,7 +587,7 @@ static void parse_branch_merge_options(char *bmo) argc = split_cmdline(bmo, &argv); if (argc < 0) die(_("Bad branch.%s.mergeoptions string: %s"), branch, - split_cmdline_strerror(argc)); + _(split_cmdline_strerror(argc))); REALLOC_ARRAY(argv, argc + 2); MOVE_ARRAY(argv + 1, argv, argc + 1); argc++; @@ -557,10 +600,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; @@ -574,6 +619,8 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_twohead, k, v); else if (!strcmp(k, "pull.octopus")) return git_config_string(&pull_octopus, k, v); + else if (!strcmp(k, "commit.cleanup")) + return git_config_string(&cleanup_arg, k, v); else if (!strcmp(k, "merge.renormalize")) option_renormalize = git_config_bool(k, v); else if (!strcmp(k, "merge.ff")) { @@ -590,6 +637,11 @@ static int git_merge_config(const char *k, const char *v, void *cb) } else if (!strcmp(k, "commit.gpgsign")) { sign_commit = git_config_bool(k, v) ? "" : NULL; 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); @@ -647,16 +699,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *remoteheads, struct commit *head) { - static struct lock_file lock; const char *head_arg = "HEAD"; - hold_locked_index(&lock, LOCK_DIE_ON_ERROR); - refresh_cache(REFRESH_QUIET); - if (write_locked_index(&the_index, &lock, - COMMIT_LOCK | SKIP_IF_UNCHANGED)) + if (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0) return error(_("Unable to write index.")); if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { + struct lock_file lock = LOCK_INIT; int clean, x; struct commit *result; struct commit_list *reversed = NULL; @@ -668,7 +717,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, return 2; } - init_merge_options(&o); + init_merge_options(&o, the_repository); if (!strcmp(strategy, "subtree")) o.subtree_shift = ""; @@ -693,11 +742,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, exit(128); if (write_locked_index(&the_index, &lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) - die (_("unable to write %s"), get_index_file()); + die(_("unable to write %s"), get_index_file()); return clean ? 0 : 1; } else { - return try_merge_command(strategy, xopts_nr, xopts, - common, head_arg, remoteheads); + return try_merge_command(the_repository, + strategy, xopts_nr, xopts, + common, head_arg, remoteheads); } } @@ -741,7 +791,7 @@ static void add_strategies(const char *string, unsigned attr) static void read_merge_msg(struct strbuf *msg) { - const char *filename = git_path_merge_msg(); + const char *filename = git_path_merge_msg(the_repository); strbuf_reset(msg); if (strbuf_read_file(msg, filename, 0) < 0) die_errno(_("Could not read from '%s'"), filename); @@ -761,39 +811,64 @@ static void abort_commit(struct commit_list *remoteheads, const char *err_msg) static const char merge_editor_comment[] = N_("Please enter a commit message to explain why this merge is necessary,\n" "especially if it merges an updated upstream into a topic branch.\n" - "\n" - "Lines starting with '%c' will be ignored, and an empty message aborts\n" + "\n"); + +static const char scissors_editor_comment[] = +N_("An empty message aborts the commit.\n"); + +static const char no_scissors_editor_comment[] = +N_("Lines starting with '%c' will be ignored, and an empty message aborts\n" "the commit.\n"); static void write_merge_heads(struct commit_list *); static void prepare_to_commit(struct commit_list *remoteheads) { struct strbuf msg = STRBUF_INIT; + const char *index_file = get_index_file(); + + if (!no_verify && run_commit_hook(0 < option_edit, index_file, "pre-merge-commit", NULL)) + abort_commit(remoteheads, NULL); + /* + * Re-read the index as pre-merge-commit hook could have updated it, + * and write it out as a tree. We must do this before we invoke + * the editor and after we invoke run_status above. + */ + if (find_hook("pre-merge-commit")) + discard_cache(); + read_cache_from(index_file); strbuf_addbuf(&msg, &merge_msg); - strbuf_addch(&msg, '\n'); if (squash) BUG("the control must not reach here under --squash"); - if (0 < option_edit) - strbuf_commented_addf(&msg, _(merge_editor_comment), comment_line_char); + if (0 < option_edit) { + strbuf_addch(&msg, '\n'); + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) { + wt_status_append_cut_line(&msg); + strbuf_commented_addf(&msg, "\n"); + } + strbuf_commented_addf(&msg, _(merge_editor_comment)); + strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ? + scissors_editor_comment : + no_scissors_editor_comment), comment_line_char); + } if (signoff) append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0); write_merge_heads(remoteheads); - write_file_buf(git_path_merge_msg(), msg.buf, msg.len); + write_file_buf(git_path_merge_msg(the_repository), msg.buf, msg.len); if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg", - git_path_merge_msg(), "merge", NULL)) + git_path_merge_msg(the_repository), "merge", NULL)) abort_commit(remoteheads, NULL); if (0 < option_edit) { - if (launch_editor(git_path_merge_msg(), NULL, NULL)) + if (launch_editor(git_path_merge_msg(the_repository), NULL, NULL)) abort_commit(remoteheads, NULL); } - if (verify_msg && run_commit_hook(0 < option_edit, get_index_file(), + if (!no_verify && run_commit_hook(0 < option_edit, get_index_file(), "commit-msg", - git_path_merge_msg(), NULL)) + git_path_merge_msg(the_repository), NULL)) abort_commit(remoteheads, NULL); read_merge_msg(&msg); - strbuf_stripspace(&msg, 0 < option_edit); + cleanup_message(&msg, cleanup_mode, 0); if (!msg.len) abort_commit(remoteheads, _("Empty commit message.")); strbuf_release(&merge_msg); @@ -805,12 +880,8 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { struct object_id result_tree, result_commit; struct commit_list *parents, **pptr = &parents; - static struct lock_file lock; - hold_locked_index(&lock, LOCK_DIE_ON_ERROR); - refresh_cache(REFRESH_QUIET); - if (write_locked_index(&the_index, &lock, - COMMIT_LOCK | SKIP_IF_UNCHANGED)) + if (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0) return error(_("Unable to write index.")); write_tree_trivial(&result_tree); @@ -822,7 +893,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) &result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, &result_commit, "In-index merge"); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -837,11 +908,11 @@ static int finish_automerge(struct commit *head, struct strbuf buf = STRBUF_INIT; struct object_id result_commit; + write_tree_trivial(result_tree); free_commit_list(common); parents = remoteheads; if (!head_subsumed || fast_forward == FF_NO) commit_list_insert(head, &parents); - strbuf_addch(&merge_msg, '\n'); prepare_to_commit(remoteheads); if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents, &result_commit, NULL, sign_commit)) @@ -849,7 +920,7 @@ static int finish_automerge(struct commit *head, strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, remoteheads, &result_commit, buf.buf); strbuf_release(&buf); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -859,14 +930,22 @@ static int suggest_conflicts(void) FILE *fp; struct strbuf msgbuf = STRBUF_INIT; - filename = git_path_merge_msg(); + filename = git_path_merge_msg(the_repository); fp = xfopen(filename, "a"); - append_conflicts_hint(&msgbuf); + /* + * We can't use cleanup_mode because if we're not using the editor, + * get_cleanup_mode will return COMMIT_MSG_CLEANUP_SPACE instead, even + * though the message is meant to be processed later by git-commit. + * Thus, we will get the cleanup mode which is returned when we _are_ + * using an editor. + */ + append_conflicts_hint(&the_index, &msgbuf, + get_cleanup_mode(cleanup_arg, 1)); fputs(msgbuf.buf, fp); strbuf_release(&msgbuf); fclose(fp); - rerere(allow_rerere_auto); + repo_rerere(the_repository, allow_rerere_auto); printf(_("Automatic merge failed; " "fix conflicts and then commit the result.\n")); return 1; @@ -878,7 +957,7 @@ static int evaluate_result(void) struct rev_info rev; /* Check how many files differ. */ - init_revisions(&rev, ""); + repo_init_revisions(the_repository, &rev, ""); setup_revisions(0, NULL, &rev, NULL); rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; @@ -932,19 +1011,22 @@ static void write_merge_heads(struct commit_list *remoteheads) for (j = remoteheads; j; j = j->next) { struct object_id *oid; struct commit *c = j->item; - if (c->util && merge_remote_util(c)->obj) { - oid = &merge_remote_util(c)->obj->oid; + struct merge_remote_desc *desc; + + desc = merge_remote_util(c); + if (desc && desc->obj) { + oid = &desc->obj->oid; } else { oid = &c->object.oid; } strbuf_addf(&buf, "%s\n", oid_to_hex(oid)); } - write_file_buf(git_path_merge_head(), buf.buf, buf.len); + write_file_buf(git_path_merge_head(the_repository), buf.buf, buf.len); strbuf_reset(&buf); if (fast_forward == FF_NO) strbuf_addstr(&buf, "no-ff"); - write_file_buf(git_path_merge_mode(), buf.buf, buf.len); + write_file_buf(git_path_merge_mode(the_repository), buf.buf, buf.len); strbuf_release(&buf); } @@ -952,7 +1034,8 @@ static void write_merge_state(struct commit_list *remoteheads) { write_merge_heads(remoteheads); strbuf_addch(&merge_msg, '\n'); - write_file_buf(git_path_merge_msg(), merge_msg.buf, merge_msg.len); + write_file_buf(git_path_merge_msg(the_repository), merge_msg.buf, + merge_msg.len); } static int default_edit_option(void) @@ -1031,11 +1114,12 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge const char *filename; int fd, pos, npos; struct strbuf fetch_head_file = STRBUF_INIT; + const unsigned hexsz = the_hash_algo->hexsz; if (!merge_names) merge_names = &fetch_head_file; - filename = git_path_fetch_head(); + filename = git_path_fetch_head(the_repository); fd = open(filename, O_RDONLY); if (fd < 0) die_errno(_("could not open '%s' for reading"), filename); @@ -1056,16 +1140,16 @@ static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge else npos = merge_names->len; - if (npos - pos < GIT_SHA1_HEXSZ + 2 || + if (npos - pos < hexsz + 2 || get_oid_hex(merge_names->buf + pos, &oid)) commit = NULL; /* bad */ - else if (memcmp(merge_names->buf + pos + GIT_SHA1_HEXSZ, "\t\t", 2)) + else if (memcmp(merge_names->buf + pos + hexsz, "\t\t", 2)) continue; /* not-for-merge */ else { - char saved = merge_names->buf[pos + GIT_SHA1_HEXSZ]; - merge_names->buf[pos + GIT_SHA1_HEXSZ] = '\0'; + char saved = merge_names->buf[pos + hexsz]; + merge_names->buf[pos + hexsz] = '\0'; commit = get_merge_parent(merge_names->buf + pos); - merge_names->buf[pos + GIT_SHA1_HEXSZ] = saved; + merge_names->buf[pos + hexsz] = saved; } if (!commit) { if (ptr) @@ -1152,7 +1236,7 @@ static int merging_a_throwaway_tag(struct commit *commit) tag_ref = xstrfmt("refs/tags/%s", ((struct tag *)merge_remote_util(commit)->obj)->tag); if (!read_ref(tag_ref, &oid) && - !oidcmp(&oid, &merge_remote_util(commit)->obj->oid)) + oideq(&oid, &merge_remote_util(commit)->obj->oid)) is_throwaway_tag = 0; else is_throwaway_tag = 1; @@ -1183,14 +1267,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix) branch = branch_to_free = resolve_refdup("HEAD", 0, &head_oid, NULL); if (branch) skip_prefix(branch, "refs/heads/", &branch); + + init_diff_ui_defaults(); + git_config(git_merge_config, NULL); + if (!branch || is_null_oid(&head_oid)) head_commit = NULL; else head_commit = lookup_commit_or_die(&head_oid, "HEAD"); - init_diff_ui_defaults(); - git_config(git_merge_config, NULL); - if (branch_mergeoptions) parse_branch_merge_options(branch_mergeoptions); argc = parse_options(argc, argv, prefix, builtin_merge_options, @@ -1204,16 +1289,36 @@ 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"), builtin_merge_usage, builtin_merge_options); - if (!file_exists(git_path_merge_head())) + 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; + } + + if (quit_current_merge) { + if (orig_argc != 2) + usage_msg_opt(_("--quit expects no arguments"), + builtin_merge_usage, + builtin_merge_options); + + remove_merge_branch_state(the_repository); goto done; } @@ -1225,7 +1330,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) usage_msg_opt(_("--continue expects no arguments"), builtin_merge_usage, builtin_merge_options); - if (!file_exists(git_path_merge_head())) + if (!file_exists(git_path_merge_head(the_repository))) die(_("There is no merge in progress (MERGE_HEAD missing).")); /* Invoke 'git commit' */ @@ -1236,7 +1341,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (read_cache_unmerged()) die_resolve_conflict("merge"); - if (file_exists(git_path_merge_head())) { + if (file_exists(git_path_merge_head(the_repository))) { /* * There is no unmerged entry, don't advise 'git * add/rm <file>', just 'git commit'. @@ -1247,7 +1352,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else die(_("You have not concluded your merge (MERGE_HEAD exists).")); } - if (file_exists(git_path_cherry_pick_head())) { + if (file_exists(git_path_cherry_pick_head(the_repository))) { if (advice_resolve_conflict) die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" "Please, commit your changes before you merge.")); @@ -1256,15 +1361,30 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } resolve_undo_clear(); + if (option_edit < 0) + option_edit = default_edit_option(); + + cleanup_mode = get_cleanup_mode(cleanup_arg, 0 < option_edit); + if (verbosity < 0) show_diffstat = 0; if (squash) { if (fast_forward == FF_NO) die(_("You cannot combine --squash with --no-ff.")); + if (option_commit > 0) + die(_("You cannot combine --squash with --commit.")); + /* + * squash can now silently disable option_commit - this is not + * a problem as it is only overriding the default, not a user + * supplied option. + */ option_commit = 0; } + if (option_commit < 0) + option_commit = 1; + if (!argc) { if (default_to_upstream) argc = setup_with_upstream(&argv); @@ -1296,8 +1416,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("%s - not something we can merge"), argv[0]); if (remoteheads->next) die(_("Can merge only exactly one commit into empty head")); + + if (verify_signatures) + verify_merge_signature(remoteheads->item, verbosity, + check_trust_level); + remote_head_oid = &remoteheads->item->object.oid; - read_empty(remote_head_oid->hash, 0); + read_empty(remote_head_oid, 0); update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; @@ -1317,31 +1442,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (verify_signatures) { for (p = remoteheads; p; p = p->next) { - struct commit *commit = p->item; - char hex[GIT_MAX_HEXSZ + 1]; - struct signature_check signature_check; - memset(&signature_check, 0, sizeof(signature_check)); - - check_commit_signature(commit, &signature_check); - - find_unique_abbrev_r(hex, &commit->object.oid, DEFAULT_ABBREV); - switch (signature_check.result) { - case 'G': - break; - case 'U': - die(_("Commit %s has an untrusted GPG signature, " - "allegedly by %s."), hex, signature_check.signer); - case 'B': - die(_("Commit %s has a bad GPG signature " - "allegedly by %s."), hex, signature_check.signer); - default: /* 'N' */ - die(_("Commit %s does not have a GPG signature."), hex); - } - if (verbosity >= 0 && signature_check.result == 'G') - printf(_("Commit %s has a good GPG signature by %s\n"), - hex, signature_check.signer); - - signature_check_clear(&signature_check); + verify_merge_signature(p->item, verbosity, + check_trust_level); } } @@ -1361,9 +1463,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix) fast_forward = FF_NO; } - if (option_edit < 0) - option_edit = default_edit_option(); - if (!use_strategies) { if (!remoteheads) ; /* already up-to-date */ @@ -1410,7 +1509,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } else if (fast_forward != FF_NO && !remoteheads->next && !common->next && - !oidcmp(&common->item->object.oid, &head_commit->object.oid)) { + oideq(&common->item->object.oid, &head_commit->object.oid)) { /* Again the most common case of merging one remote. */ struct strbuf msg = STRBUF_INIT; struct commit *commit; @@ -1432,7 +1531,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } - if (checkout_fast_forward(&head_commit->object.oid, + 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, overwrite_ignore)) { ret = 1; @@ -1440,7 +1544,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } finish(head_commit, remoteheads, &commit->object.oid, msg.buf); - drop_save(); + remove_merge_branch_state(the_repository); goto done; } else if (!remoteheads->next && common->next) ; @@ -1483,7 +1587,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * HEAD^^" would be missed. */ common_one = get_merge_bases(head_commit, j->item); - if (oidcmp(&common_one->item->object.oid, &j->item->object.oid)) { + if (!oideq(&common_one->item->object.oid, &j->item->object.oid)) { up_to_date = 0; break; } @@ -1497,6 +1601,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); @@ -1515,8 +1624,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) save_state(&stash)) oidclr(&stash); - for (i = 0; i < use_strategies_nr; i++) { - int ret; + for (i = 0; !merge_was_ok && i < use_strategies_nr; i++) { + int ret, cnt; if (i) { printf(_("Rewinding the tree to pristine...\n")); restore_state(&head_commit->object.oid, &stash); @@ -1533,40 +1642,26 @@ int cmd_merge(int argc, const char **argv, const char *prefix) ret = try_merge_strategy(use_strategies[i]->name, common, remoteheads, head_commit); - if (!option_commit && !ret) { - merge_was_ok = 1; - /* - * This is necessary here just to avoid writing - * the tree, but later we will *not* exit with - * status code 1 because merge_was_ok is set. - */ - ret = 1; - } - - if (ret) { - /* - * The backend exits with 1 when conflicts are - * left to be resolved, with 2 when it does not - * handle the given merge at all. - */ - if (ret == 1) { - int cnt = evaluate_result(); - - if (best_cnt <= 0 || cnt <= best_cnt) { - best_strategy = use_strategies[i]->name; - best_cnt = cnt; + /* + * The backend exits with 1 when conflicts are + * left to be resolved, with 2 when it does not + * handle the given merge at all. + */ + if (ret < 2) { + if (!ret) { + if (option_commit) { + /* Automerge succeeded. */ + automerge_was_ok = 1; + break; } + merge_was_ok = 1; + } + cnt = (use_strategies_nr > 1) ? evaluate_result() : 0; + if (best_cnt <= 0 || cnt <= best_cnt) { + best_strategy = use_strategies[i]->name; + best_cnt = cnt; } - if (merge_was_ok) - break; - else - continue; } - - /* Automerge succeeded. */ - write_tree_trivial(&result_tree); - automerge_was_ok = 1; - break; } /* @@ -1605,9 +1700,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/mktag.c b/builtin/mktag.c index 9f5a50a8fd..4982d3a93e 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -1,5 +1,7 @@ #include "builtin.h" #include "tag.h" +#include "replace-object.h" +#include "object-store.h" /* * A signature file has a very simple fixed format: four lines @@ -24,11 +26,14 @@ static int verify_object(const struct object_id *oid, const char *expected_type) enum object_type type; unsigned long size; void *buffer = read_object_file(oid, &type, &size); - const struct object_id *repl = lookup_replace_object(oid); + const struct object_id *repl = lookup_replace_object(the_repository, oid); if (buffer) { - if (type == type_from_string(expected_type)) - ret = check_object_signature(repl, buffer, size, expected_type); + if (type == type_from_string(expected_type)) { + ret = check_object_signature(the_repository, repl, + buffer, size, + expected_type); + } free(buffer); } return ret; diff --git a/builtin/mktree.c b/builtin/mktree.c index 263c530315..891991b00d 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -7,6 +7,7 @@ #include "quote.h" #include "tree.h" #include "parse-options.h" +#include "object-store.h" static struct treeent { unsigned mode; @@ -66,7 +67,7 @@ static const char *mktree_usage[] = { NULL }; -static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_missing) +static void mktree_line(char *buf, int nul_term_line, int allow_missing) { char *ptr, *ntr; const char *p; @@ -97,7 +98,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss *ntr++ = 0; /* now at the beginning of SHA1 */ - path = ntr + 41; /* at the beginning of name */ + path = (char *)p + 1; /* at the beginning of name */ if (!nul_term_line && path[0] == '"') { struct strbuf p_uq = STRBUF_INIT; if (unquote_c_style(&p_uq, path, NULL)) @@ -116,7 +117,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss } /* Check the type of object identified by sha1 */ - obj_type = oid_object_info(&oid, NULL); + obj_type = oid_object_info(the_repository, &oid, NULL); if (obj_type < 0) { if (allow_missing) { ; /* no problem - missing objects are presumed to be of the right type */ @@ -171,7 +172,7 @@ int cmd_mktree(int ac, const char **av, const char *prefix) break; die("input format error: (blank line only valid in batch mode)"); } - mktree_line(sb.buf, sb.len, nul_term_line, allow_missing); + mktree_line(sb.buf, nul_term_line, allow_missing); } if (is_batch_mode && got_eof && used < 1) { /* diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c new file mode 100644 index 0000000000..5bf88cd2a8 --- /dev/null +++ b/builtin/multi-pack-index.c @@ -0,0 +1,70 @@ +#include "builtin.h" +#include "cache.h" +#include "config.h" +#include "parse-options.h" +#include "midx.h" +#include "trace2.h" + +static char const * const builtin_multi_pack_index_usage[] = { + N_("git multi-pack-index [<options>] (write|verify|expire|repack --batch-size=<size>)"), + NULL +}; + +static struct opts_multi_pack_index { + const char *object_dir; + unsigned long batch_size; + int progress; +} opts; + +int cmd_multi_pack_index(int argc, const char **argv, + const char *prefix) +{ + unsigned flags = 0; + + static struct option builtin_multi_pack_index_options[] = { + OPT_FILENAME(0, "object-dir", &opts.object_dir, + N_("object directory containing set of packfile and pack-index pairs")), + OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), + OPT_MAGNITUDE(0, "batch-size", &opts.batch_size, + N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), + OPT_END(), + }; + + git_config(git_default_config, NULL); + + opts.progress = isatty(2); + argc = parse_options(argc, argv, prefix, + builtin_multi_pack_index_options, + builtin_multi_pack_index_usage, 0); + + if (!opts.object_dir) + opts.object_dir = get_object_directory(); + if (opts.progress) + flags |= MIDX_PROGRESS; + + if (argc == 0) + usage_with_options(builtin_multi_pack_index_usage, + builtin_multi_pack_index_options); + + if (argc > 1) { + die(_("too many arguments")); + return 1; + } + + trace2_cmd_mode(argv[0]); + + if (!strcmp(argv[0], "repack")) + return midx_repack(the_repository, opts.object_dir, + (size_t)opts.batch_size, flags); + if (opts.batch_size) + die(_("--batch-size option is only for 'repack' subcommand")); + + if (!strcmp(argv[0], "write")) + return write_midx_file(opts.object_dir, flags); + if (!strcmp(argv[0], "verify")) + return verify_midx_file(the_repository, opts.object_dir, flags); + if (!strcmp(argv[0], "expire")) + return expire_midx_packs(the_repository, opts.object_dir, flags); + + die(_("unrecognized subcommand: %s"), argv[0]); +} diff --git a/builtin/mv.c b/builtin/mv.c index 6d141f7a53..be15ba7044 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -3,6 +3,7 @@ * * Copyright (C) 2006 Johannes Schindelin */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "config.h" #include "pathspec.h" @@ -72,7 +73,6 @@ static const char *add_slash(const char *path) return path; } -static struct lock_file lock_file; #define SUBMODULE_WITH_GITDIR ((const char *)1) static void prepare_move_submodule(const char *src, int first, @@ -131,6 +131,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; + struct lock_file lock_file = LOCK_INIT; git_config(git_default_config, NULL); @@ -276,10 +277,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix) die_errno(_("renaming '%s' failed"), src); } if (submodule_gitfile[i]) { - if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) - connect_work_tree_and_git_dir(dst, submodule_gitfile[i]); if (!update_path_in_gitmodules(src, dst)) gitmodules_modified = 1; + if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) + connect_work_tree_and_git_dir(dst, + submodule_gitfile[i], + 1); } if (mode == WORKING_DIRECTORY) diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 387ddf85d2..a9dcd25e46 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -1,31 +1,51 @@ #include "builtin.h" #include "cache.h" +#include "repository.h" #include "config.h" #include "commit.h" #include "tag.h" #include "refs.h" #include "parse-options.h" +#include "prio-queue.h" #include "sha1-lookup.h" +#include "commit-slab.h" -#define CUTOFF_DATE_SLOP 86400 /* one day */ +/* + * One day. See the 'name a rev shortly after epoch' test in t6120 when + * changing this value + */ +#define CUTOFF_DATE_SLOP 86400 -typedef struct rev_name { - const char *tip_name; +struct rev_name { + char *tip_name; timestamp_t taggerdate; int generation; int distance; int from_tag; -} rev_name; +}; + +define_commit_slab(commit_rev_name, struct rev_name); static timestamp_t cutoff = TIME_MAX; +static struct commit_rev_name rev_names; /* How many generations are maximally preferred over _one_ merge traversal? */ #define MERGE_TRAVERSAL_WEIGHT 65535 +static int is_valid_rev_name(const struct rev_name *name) +{ + return name && (name->generation || name->tip_name); +} + +static struct rev_name *get_commit_rev_name(const struct commit *commit) +{ + struct rev_name *name = commit_rev_name_peek(&rev_names, commit); + + return is_valid_rev_name(name) ? name : NULL; +} + static int is_better_name(struct rev_name *name, - const char *tip_name, timestamp_t taggerdate, - int generation, int distance, int from_tag) { @@ -60,69 +80,135 @@ static int is_better_name(struct rev_name *name, return 0; } -static void name_rev(struct commit *commit, - const char *tip_name, timestamp_t taggerdate, - int generation, int distance, int from_tag, - int deref) +static struct rev_name *create_or_update_name(struct commit *commit, + timestamp_t taggerdate, + int generation, int distance, + int from_tag) { - struct rev_name *name = (struct rev_name *)commit->util; - struct commit_list *parents; - int parent_number = 1; - char *to_free = NULL; + struct rev_name *name = commit_rev_name_at(&rev_names, commit); + + if (is_valid_rev_name(name)) { + if (!is_better_name(name, taggerdate, distance, from_tag)) + return NULL; + + /* + * This string might still be shared with ancestors + * (generation > 0). We can release it here regardless, + * because the new name that has just won will be better + * for them as well, so name_rev() will replace these + * stale pointers when it processes the parents. + */ + if (!name->generation) + free(name->tip_name); + } - parse_commit(commit); + name->taggerdate = taggerdate; + name->generation = generation; + name->distance = distance; + name->from_tag = from_tag; - if (commit->date < cutoff) - return; - - if (deref) { - tip_name = to_free = xstrfmt("%s^0", tip_name); + return name; +} - if (generation) - die("generation: %d, but deref?", generation); +static char *get_parent_name(const struct rev_name *name, int parent_number) +{ + struct strbuf sb = STRBUF_INIT; + size_t len; + + strip_suffix(name->tip_name, "^0", &len); + if (name->generation > 0) { + strbuf_grow(&sb, len + + 1 + decimal_width(name->generation) + + 1 + decimal_width(parent_number)); + strbuf_addf(&sb, "%.*s~%d^%d", (int)len, name->tip_name, + name->generation, parent_number); + } else { + strbuf_grow(&sb, len + + 1 + decimal_width(parent_number)); + strbuf_addf(&sb, "%.*s^%d", (int)len, name->tip_name, + parent_number); } + return strbuf_detach(&sb, NULL); +} - if (name == NULL) { - name = xmalloc(sizeof(rev_name)); - commit->util = name; - goto copy_data; - } else if (is_better_name(name, tip_name, taggerdate, - generation, distance, from_tag)) { -copy_data: - name->tip_name = tip_name; - name->taggerdate = taggerdate; - name->generation = generation; - name->distance = distance; - name->from_tag = from_tag; - } else { - free(to_free); +static void name_rev(struct commit *start_commit, + const char *tip_name, timestamp_t taggerdate, + int from_tag, int deref) +{ + struct prio_queue queue; + struct commit *commit; + struct commit **parents_to_queue = NULL; + size_t parents_to_queue_nr, parents_to_queue_alloc = 0; + struct rev_name *start_name; + + parse_commit(start_commit); + if (start_commit->date < cutoff) return; - } - for (parents = commit->parents; - parents; - parents = parents->next, parent_number++) { - if (parent_number > 1) { - size_t len; - char *new_name; - - strip_suffix(tip_name, "^0", &len); - if (generation > 0) - new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name, - generation, parent_number); - else - new_name = xstrfmt("%.*s^%d", (int)len, tip_name, - parent_number); - - name_rev(parents->item, new_name, taggerdate, 0, - distance + MERGE_TRAVERSAL_WEIGHT, - from_tag, 0); - } else { - name_rev(parents->item, tip_name, taggerdate, - generation + 1, distance + 1, - from_tag, 0); + start_name = create_or_update_name(start_commit, taggerdate, 0, 0, + from_tag); + if (!start_name) + return; + if (deref) + start_name->tip_name = xstrfmt("%s^0", tip_name); + else + start_name->tip_name = xstrdup(tip_name); + + memset(&queue, 0, sizeof(queue)); /* Use the prio_queue as LIFO */ + prio_queue_put(&queue, start_commit); + + while ((commit = prio_queue_get(&queue))) { + struct rev_name *name = get_commit_rev_name(commit); + struct commit_list *parents; + int parent_number = 1; + + parents_to_queue_nr = 0; + + for (parents = commit->parents; + parents; + parents = parents->next, parent_number++) { + struct commit *parent = parents->item; + struct rev_name *parent_name; + int generation, distance; + + parse_commit(parent); + if (parent->date < cutoff) + continue; + + if (parent_number > 1) { + generation = 0; + distance = name->distance + MERGE_TRAVERSAL_WEIGHT; + } else { + generation = name->generation + 1; + distance = name->distance + 1; + } + + parent_name = create_or_update_name(parent, taggerdate, + generation, + distance, from_tag); + if (parent_name) { + if (parent_number > 1) + parent_name->tip_name = + get_parent_name(name, + parent_number); + else + parent_name->tip_name = name->tip_name; + ALLOC_GROW(parents_to_queue, + parents_to_queue_nr + 1, + parents_to_queue_alloc); + parents_to_queue[parents_to_queue_nr] = parent; + parents_to_queue_nr++; + } } + + /* The first parent must come out first from the prio_queue */ + while (parents_to_queue_nr) + prio_queue_put(&queue, + parents_to_queue[--parents_to_queue_nr]); } + + clear_prio_queue(&queue); + free(parents_to_queue); } static int subpath_matches(const char *path, const char *filter) @@ -143,10 +229,10 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) { if (shorten_unambiguous) refname = shorten_unambiguous_ref(refname, 0); - else if (starts_with(refname, "refs/heads/")) - refname = refname + 11; - else if (starts_with(refname, "refs/")) - refname = refname + 5; + else if (skip_prefix(refname, "refs/heads/", &refname)) + ; /* refname already advanced */ + else + skip_prefix(refname, "refs/", &refname); return refname; } @@ -161,6 +247,10 @@ static struct tip_table { struct tip_table_entry { struct object_id oid; const char *refname; + struct commit *commit; + timestamp_t taggerdate; + unsigned int from_tag:1; + unsigned int deref:1; } *table; int nr; int alloc; @@ -168,13 +258,18 @@ static struct tip_table { } tip_table; static void add_to_tip_table(const struct object_id *oid, const char *refname, - int shorten_unambiguous) + int shorten_unambiguous, struct commit *commit, + timestamp_t taggerdate, int from_tag, int deref) { refname = name_ref_abbrev(refname, shorten_unambiguous); ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); oidcpy(&tip_table.table[tip_table.nr].oid, oid); tip_table.table[tip_table.nr].refname = xstrdup(refname); + tip_table.table[tip_table.nr].commit = commit; + tip_table.table[tip_table.nr].taggerdate = taggerdate; + tip_table.table[tip_table.nr].from_tag = from_tag; + tip_table.table[tip_table.nr].deref = deref; tip_table.nr++; tip_table.sorted = 0; } @@ -185,12 +280,30 @@ static int tipcmp(const void *a_, const void *b_) return oidcmp(&a->oid, &b->oid); } +static int cmp_by_tag_and_age(const void *a_, const void *b_) +{ + const struct tip_table_entry *a = a_, *b = b_; + int cmp; + + /* Prefer tags. */ + cmp = b->from_tag - a->from_tag; + if (cmp) + return cmp; + + /* Older is better. */ + if (a->taggerdate < b->taggerdate) + return -1; + return a->taggerdate != b->taggerdate; +} + static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data) { - struct object *o = parse_object(oid); + struct object *o = parse_object(the_repository, oid); struct name_ref_data *data = cb_data; int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; + int from_tag = 0; + struct commit *commit = NULL; timestamp_t taggerdate = TIME_MAX; if (data->tags_only && !starts_with(path, "refs/tags/")) @@ -239,29 +352,44 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo return 0; } - add_to_tip_table(oid, path, can_abbreviate_output); - while (o && o->type == OBJ_TAG) { struct tag *t = (struct tag *) o; if (!t->tagged) break; /* broken repository */ - o = parse_object(&t->tagged->oid); + o = parse_object(the_repository, &t->tagged->oid); deref = 1; taggerdate = t->date; } if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - int from_tag = starts_with(path, "refs/tags/"); - + commit = (struct commit *)o; + from_tag = starts_with(path, "refs/tags/"); if (taggerdate == TIME_MAX) - taggerdate = ((struct commit *)o)->date; - path = name_ref_abbrev(path, can_abbreviate_output); - name_rev(commit, xstrdup(path), taggerdate, 0, 0, - from_tag, deref); + taggerdate = commit->date; } + + add_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate, + from_tag, deref); return 0; } +static void name_tips(void) +{ + int i; + + /* + * Try to set better names first, so that worse ones spread + * less. + */ + QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age); + for (i = 0; i < tip_table.nr; i++) { + struct tip_table_entry *e = &tip_table.table[i]; + if (e->commit) { + name_rev(e->commit, e->refname, e->taggerdate, + e->from_tag, e->deref); + } + } +} + static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) { struct tip_table_entry *table = table_; @@ -291,23 +419,22 @@ static const char *get_exact_ref_match(const struct object *o) static const char *get_rev_name(const struct object *o, struct strbuf *buf) { struct rev_name *n; - struct commit *c; + const struct commit *c; if (o->type != OBJ_COMMIT) return get_exact_ref_match(o); - c = (struct commit *) o; - n = c->util; + c = (const struct commit *) o; + n = get_commit_rev_name(c); if (!n) return NULL; if (!n->generation) return n->tip_name; else { - int len = strlen(n->tip_name); - if (len > 2 && !strcmp(n->tip_name + len - 2, "^0")) - len -= 2; strbuf_reset(buf); - strbuf_addf(buf, "%.*s~%d", len, n->tip_name, n->generation); + strbuf_addstr(buf, n->tip_name); + strbuf_strip_suffix(buf, "^0"); + strbuf_addf(buf, "~%d", n->generation); return buf->buf; } } @@ -344,25 +471,27 @@ static char const * const name_rev_usage[] = { static void name_rev_line(char *p, struct name_ref_data *data) { struct strbuf buf = STRBUF_INIT; - int forty = 0; + int counter = 0; char *p_start; + const unsigned hexsz = the_hash_algo->hexsz; + for (p_start = p; *p; p++) { #define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f')) if (!ishex(*p)) - forty = 0; - else if (++forty == GIT_SHA1_HEXSZ && + counter = 0; + else if (++counter == hexsz && !ishex(*(p+1))) { struct object_id oid; const char *name = NULL; char c = *(p+1); int p_len = p - p_start + 1; - forty = 0; + counter = 0; *(p+1) = 0; - if (!get_oid(p - (GIT_SHA1_HEXSZ - 1), &oid)) { + if (!get_oid(p - (hexsz - 1), &oid)) { struct object *o = - lookup_object(oid.hash); + lookup_object(the_repository, &oid); if (o) name = get_rev_name(o, &buf); } @@ -372,7 +501,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) continue; if (data->name_only) - printf("%.*s%s", p_len - GIT_SHA1_HEXSZ, p_start, name); + printf("%.*s%s", p_len - hexsz, p_start, name); else printf("%.*s (%s)", p_len, p_start, name); p_start = p + 1; @@ -413,6 +542,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) OPT_END(), }; + init_commit_rev_name(&rev_names); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); if (all + transform_stdin + !!argc > 1) { @@ -434,9 +564,10 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) } commit = NULL; - object = parse_object(&oid); + object = parse_object(the_repository, &oid); if (object) { - struct object *peeled = deref_tag(object, *argv, 0); + struct object *peeled = deref_tag(the_repository, + object, *argv, 0); if (peeled && peeled->type == OBJ_COMMIT) commit = (struct commit *)peeled; } @@ -463,9 +594,15 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) add_object_array(object, *argv, &revs); } - if (cutoff) - cutoff = cutoff - CUTOFF_DATE_SLOP; + if (cutoff) { + /* check for undeflow */ + if (cutoff > TIME_MIN + CUTOFF_DATE_SLOP) + cutoff = cutoff - CUTOFF_DATE_SLOP; + else + cutoff = TIME_MIN; + } for_each_ref(name_ref, &data); + name_tips(); if (transform_stdin) { char buffer[2048]; diff --git a/builtin/notes.c b/builtin/notes.c index 921e08d5bf..2987c08a2e 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -11,10 +11,12 @@ #include "config.h" #include "builtin.h" #include "notes.h" +#include "object-store.h" +#include "repository.h" #include "blob.h" #include "pretty.h" #include "refs.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "run-command.h" #include "parse-options.h" #include "string-list.h" @@ -213,6 +215,8 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) { struct note_data *d = opt->value; + BUG_ON_OPT_NEG(unset); + strbuf_grow(&d->buf, strlen(arg) + 2); if (d->buf.len) strbuf_addch(&d->buf, '\n'); @@ -227,6 +231,8 @@ static int parse_file_arg(const struct option *opt, const char *arg, int unset) { struct note_data *d = opt->value; + BUG_ON_OPT_NEG(unset); + if (d->buf.len) strbuf_addch(&d->buf, '\n'); if (!strcmp(arg, "-")) { @@ -248,15 +254,15 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) enum object_type type; unsigned long len; + BUG_ON_OPT_NEG(unset); + if (d->buf.len) strbuf_addch(&d->buf, '\n'); if (get_oid(arg, &object)) die(_("failed to resolve '%s' as a valid ref."), arg); - if (!(buf = read_object_file(&object, &type, &len))) { - free(buf); + if (!(buf = read_object_file(&object, &type, &len))) die(_("failed to read object '%s'."), arg); - } if (type != OBJ_BLOB) { free(buf); die(_("cannot read note data from non-blob object '%s'."), arg); @@ -271,6 +277,7 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) static int parse_reedit_arg(const struct option *opt, const char *arg, int unset) { struct note_data *d = opt->value; + BUG_ON_OPT_NEG(unset); d->use_editor = 1; return parse_reuse_arg(opt, arg, unset); } @@ -323,10 +330,10 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) } if (!rewrite_cmd) { - commit_notes(t, msg); + commit_notes(the_repository, t, msg); free_notes(t); } else { - finish_copy_notes_for_rewrite(c, msg); + finish_copy_notes_for_rewrite(the_repository, c, msg); } strbuf_release(&buf); return ret; @@ -399,18 +406,18 @@ static int add(int argc, const char **argv, const char *prefix) const struct object_id *note; struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &d, N_("message"), + OPT_CALLBACK_F('m', "message", &d, N_("message"), N_("note contents as a string"), PARSE_OPT_NONEG, - parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &d, N_("file"), + parse_msg_arg), + OPT_CALLBACK_F('F', "file", &d, N_("file"), N_("note contents in a file"), PARSE_OPT_NONEG, - parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"), + parse_file_arg), + OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"), N_("reuse and edit specified note object"), PARSE_OPT_NONEG, - parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"), + parse_reedit_arg), + OPT_CALLBACK_F('C', "reuse-message", &d, N_("object"), N_("reuse specified note object"), PARSE_OPT_NONEG, - parse_reuse_arg}, + parse_reuse_arg), OPT_BOOL(0, "allow-empty", &allow_empty, N_("allow storing empty note")), OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE), @@ -461,13 +468,15 @@ static int add(int argc, const char **argv, const char *prefix) if (d.buf.len || allow_empty) { write_note_data(&d, &new_note); if (add_note(t, &object, &new_note, combine_notes_overwrite)) - die("BUG: combine_notes_overwrite failed"); - commit_notes(t, "Notes added by 'git notes add'"); + BUG("combine_notes_overwrite failed"); + commit_notes(the_repository, t, + "Notes added by 'git notes add'"); } else { fprintf(stderr, _("Removing note for object %s\n"), oid_to_hex(&object)); remove_note(t, object.hash); - commit_notes(t, "Notes removed by 'git notes add'"); + commit_notes(the_repository, t, + "Notes removed by 'git notes add'"); } free_note_data(&d); @@ -504,7 +513,7 @@ static int copy(int argc, const char **argv, const char *prefix) } } - if (argc < 2) { + if (argc < 1) { error(_("too few parameters")); usage_with_options(git_notes_copy_usage, options); } @@ -544,8 +553,9 @@ static int copy(int argc, const char **argv, const char *prefix) } if (add_note(t, &object, from_note, combine_notes_overwrite)) - die("BUG: combine_notes_overwrite failed"); - commit_notes(t, "Notes added by 'git notes copy'"); + BUG("combine_notes_overwrite failed"); + commit_notes(the_repository, t, + "Notes added by 'git notes copy'"); out: free_notes(t); return retval; @@ -562,18 +572,18 @@ static int append_edit(int argc, const char **argv, const char *prefix) const char * const *usage; struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &d, N_("message"), + OPT_CALLBACK_F('m', "message", &d, N_("message"), N_("note contents as a string"), PARSE_OPT_NONEG, - parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &d, N_("file"), + parse_msg_arg), + OPT_CALLBACK_F('F', "file", &d, N_("file"), N_("note contents in a file"), PARSE_OPT_NONEG, - parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"), + parse_file_arg), + OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"), N_("reuse and edit specified note object"), PARSE_OPT_NONEG, - parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"), + parse_reedit_arg), + OPT_CALLBACK_F('C', "reuse-message", &d, N_("object"), N_("reuse specified note object"), PARSE_OPT_NONEG, - parse_reuse_arg}, + parse_reuse_arg), OPT_BOOL(0, "allow-empty", &allow_empty, N_("allow storing empty note")), OPT_END() @@ -612,7 +622,7 @@ static int append_edit(int argc, const char **argv, const char *prefix) strbuf_grow(&d.buf, size + 1); if (d.buf.len && prev_buf && size) - strbuf_insert(&d.buf, 0, "\n", 1); + strbuf_insertstr(&d.buf, 0, "\n"); if (prev_buf && size) strbuf_insert(&d.buf, 0, prev_buf, size); free(prev_buf); @@ -621,7 +631,7 @@ static int append_edit(int argc, const char **argv, const char *prefix) if (d.buf.len || allow_empty) { write_note_data(&d, &new_note); if (add_note(t, &object, &new_note, combine_notes_overwrite)) - die("BUG: combine_notes_overwrite failed"); + BUG("combine_notes_overwrite failed"); logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]); } else { fprintf(stderr, _("Removing note for object %s\n"), @@ -629,7 +639,7 @@ static int append_edit(int argc, const char **argv, const char *prefix) remove_note(t, object.hash); logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]); } - commit_notes(t, logmsg); + commit_notes(the_repository, t, logmsg); free(logmsg); free_note_data(&d); @@ -710,7 +720,7 @@ static int merge_commit(struct notes_merge_options *o) if (get_oid("NOTES_MERGE_PARTIAL", &oid)) die(_("failed to read ref NOTES_MERGE_PARTIAL")); - else if (!(partial = lookup_commit_reference(&oid))) + else if (!(partial = lookup_commit_reference(the_repository, &oid))) die(_("could not find commit from NOTES_MERGE_PARTIAL.")); else if (parse_commit(partial)) die(_("could not parse commit from NOTES_MERGE_PARTIAL.")); @@ -735,7 +745,7 @@ static int merge_commit(struct notes_merge_options *o) memset(&pretty_ctx, 0, sizeof(pretty_ctx)); format_commit_message(partial, "%s", &msg, &pretty_ctx); strbuf_trim(&msg); - strbuf_insert(&msg, 0, "notes: ", 7); + strbuf_insertstr(&msg, 0, "notes: "); update_ref(msg.buf, o->local_ref, &oid, is_null_oid(&parent_oid) ? NULL : &parent_oid, 0, UPDATE_REFS_DIE_ON_ERR); @@ -778,13 +788,13 @@ static int merge(int argc, const char **argv, const char *prefix) N_("resolve notes conflicts using the given strategy " "(manual/ours/theirs/union/cat_sort_uniq)")), OPT_GROUP(N_("Committing unmerged notes")), - { OPTION_SET_INT, 0, "commit", &do_commit, NULL, - N_("finalize notes merge by committing unmerged notes"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1}, + OPT_SET_INT_F(0, "commit", &do_commit, + N_("finalize notes merge by committing unmerged notes"), + 1, PARSE_OPT_NONEG), OPT_GROUP(N_("Aborting notes merge resolution")), - { OPTION_SET_INT, 0, "abort", &do_abort, NULL, - N_("abort notes merge"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1}, + OPT_SET_INT_F(0, "abort", &do_abort, + N_("abort notes merge"), + 1, PARSE_OPT_NONEG), OPT_END() }; @@ -806,7 +816,7 @@ static int merge(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_merge_usage, options); } - init_notes_merge_options(&o); + init_notes_merge_options(the_repository, &o); o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT; if (do_abort) @@ -831,7 +841,7 @@ static int merge(int argc, const char **argv, const char *prefix) const char *short_ref = NULL; if (!skip_prefix(o.local_ref, "refs/notes/", &short_ref)) - die("BUG: local ref %s is outside of refs/notes/", + BUG("local ref %s is outside of refs/notes/", o.local_ref); strbuf_addf(&merge_key, "notes.%s.mergeStrategy", short_ref); @@ -930,7 +940,8 @@ static int remove_cmd(int argc, const char **argv, const char *prefix) strbuf_release(&sb); } if (!retval) - commit_notes(t, "Notes removed by 'git notes remove'"); + commit_notes(the_repository, t, + "Notes removed by 'git notes remove'"); free_notes(t); return retval; } @@ -958,7 +969,8 @@ static int prune(int argc, const char **argv, const char *prefix) prune_notes(t, (verbose ? NOTES_PRUNE_VERBOSE : 0) | (show_only ? NOTES_PRUNE_VERBOSE|NOTES_PRUNE_DRYRUN : 0) ); if (!show_only) - commit_notes(t, "Notes removed by 'git notes prune'"); + commit_notes(the_repository, t, + "Notes removed by 'git notes prune'"); free_notes(t); return 0; } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 4bdae5a1d8..c5b433a23f 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -24,12 +24,30 @@ #include "streaming.h" #include "thread-utils.h" #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" #include "object-store.h" +#include "dir.h" +#include "midx.h" +#include "trace2.h" +#include "shallow.h" + +#define IN_PACK(obj) oe_in_pack(&to_pack, obj) +#define SIZE(obj) oe_size(&to_pack, obj) +#define SET_SIZE(obj,size) oe_set_size(&to_pack, obj, size) +#define DELTA_SIZE(obj) oe_delta_size(&to_pack, obj) +#define DELTA(obj) oe_delta(&to_pack, obj) +#define DELTA_CHILD(obj) oe_delta_child(&to_pack, obj) +#define DELTA_SIBLING(obj) oe_delta_sibling(&to_pack, obj) +#define SET_DELTA(obj, val) oe_set_delta(&to_pack, obj, val) +#define SET_DELTA_EXT(obj, oid) oe_set_delta_ext(&to_pack, obj, oid) +#define SET_DELTA_SIZE(obj, val) oe_set_delta_size(&to_pack, obj, val) +#define SET_DELTA_CHILD(obj, val) oe_set_delta_child(&to_pack, obj, val) +#define SET_DELTA_SIBLING(obj, val) oe_set_delta_sibling(&to_pack, obj, val) static const char *pack_usage[] = { N_("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]"), @@ -45,7 +63,9 @@ static const char *pack_usage[] = { static struct packing_data to_pack; static struct pack_idx_entry **written_list; -static uint32_t nr_result, nr_written; +static uint32_t nr_result, nr_written, nr_seen; +static struct bitmap_index *bitmap_git; +static uint32_t write_layer; static int non_empty; static int reuse_delta = 1, reuse_object = 1; @@ -55,7 +75,8 @@ static int pack_loose_unreachable; static int local; static int have_non_local_packs; static int incremental; -static int ignore_packed_keep; +static int ignore_packed_keep_on_disk; +static int ignore_packed_keep_in_core; static int allow_ofs_delta; static struct pack_idx_option pack_idx_opts; static const char *base_name; @@ -65,22 +86,31 @@ static unsigned long pack_size_limit; static int depth = 50; static int delta_search_threads; static int pack_to_stdout; +static int sparse; +static int thin; static int num_preferred_base; static struct progress *progress_state; static struct packed_git *reuse_packfile; static uint32_t reuse_packfile_objects; -static off_t reuse_packfile_offset; +static struct bitmap *reuse_packfile_bitmap; static int use_bitmap_index_default = 1; static int use_bitmap_index = -1; -static int write_bitmap_index; -static uint16_t write_bitmap_options; +static int allow_pack_reuse = 1; +static enum { + WRITE_BITMAP_FALSE = 0, + WRITE_BITMAP_QUIET, + WRITE_BITMAP_TRUE, +} write_bitmap_index; +static uint16_t write_bitmap_options = BITMAP_OPT_HASH_CACHE; static int exclude_promisor_objects; +static int use_delta_islands; + static unsigned long delta_cache_size = 0; -static unsigned long max_delta_cache_size = 256 * 1024 * 1024; +static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; static unsigned long cache_max_small_delta_size = 1000; static unsigned long window_memory_limit = 0; @@ -126,15 +156,21 @@ static void *get_delta(struct object_entry *entry) buf = read_object_file(&entry->idx.oid, &type, &size); if (!buf) - die("unable to read %s", oid_to_hex(&entry->idx.oid)); - base_buf = read_object_file(&entry->delta->idx.oid, &type, &base_size); + die(_("unable to read %s"), oid_to_hex(&entry->idx.oid)); + base_buf = read_object_file(&DELTA(entry)->idx.oid, &type, + &base_size); if (!base_buf) die("unable to read %s", - oid_to_hex(&entry->delta->idx.oid)); + oid_to_hex(&DELTA(entry)->idx.oid)); delta_buf = diff_delta(base_buf, base_size, buf, size, &delta_size, 0); - if (!delta_buf || delta_size != entry->delta_size) - die("delta size changed"); + /* + * We successfully computed this delta once but dropped it for + * memory reasons. Something is very wrong if this time we + * recompute and create a different delta. + */ + if (!delta_buf || delta_size != DELTA_SIZE(entry)) + BUG("delta size changed"); free(buf); free(base_buf); return delta_buf; @@ -264,11 +300,13 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent enum object_type type; void *buf; struct git_istream *st = NULL; + const unsigned hashsz = the_hash_algo->rawsz; if (!usable_delta) { - if (entry->type == OBJ_BLOB && - entry->size > big_file_threshold && - (st = open_istream(&entry->idx.oid, &type, &size, NULL)) != NULL) + if (oe_type(entry) == OBJ_BLOB && + oe_size_greater_than(&to_pack, entry, big_file_threshold) && + (st = open_istream(the_repository, &entry->idx.oid, &type, + &size, NULL)) != NULL) buf = NULL; else { buf = read_object_file(&entry->idx.oid, &type, &size); @@ -283,15 +321,15 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent FREE_AND_NULL(entry->delta_data); entry->z_delta_size = 0; } else if (entry->delta_data) { - size = entry->delta_size; + size = DELTA_SIZE(entry); buf = entry->delta_data; entry->delta_data = NULL; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + type = (allow_ofs_delta && DELTA(entry)->idx.offset) ? OBJ_OFS_DELTA : OBJ_REF_DELTA; } else { buf = get_delta(entry); - size = entry->delta_size; - type = (allow_ofs_delta && entry->delta->idx.offset) ? + size = DELTA_SIZE(entry); + type = (allow_ofs_delta && DELTA(entry)->idx.offset) ? OBJ_OFS_DELTA : OBJ_REF_DELTA; } @@ -315,12 +353,12 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent * encoding of the relative offset for the delta * base from this object's position in the pack. */ - off_t ofs = entry->idx.offset - entry->delta->idx.offset; + off_t ofs = entry->idx.offset - DELTA(entry)->idx.offset; unsigned pos = sizeof(dheader) - 1; dheader[pos] = ofs & 127; while (ofs >>= 7) dheader[--pos] = 128 | (--ofs & 127); - if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { + if (limit && hdrlen + sizeof(dheader) - pos + datalen + hashsz >= limit) { if (st) close_istream(st); free(buf); @@ -332,19 +370,19 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent } else if (type == OBJ_REF_DELTA) { /* * Deltas with a base reference contain - * an additional 20 bytes for the base sha1. + * additional bytes for the base object ID. */ - if (limit && hdrlen + 20 + datalen + 20 >= limit) { + if (limit && hdrlen + hashsz + datalen + hashsz >= limit) { if (st) close_istream(st); free(buf); return 0; } hashwrite(f, header, hdrlen); - hashwrite(f, entry->delta->idx.oid.hash, 20); - hdrlen += 20; + hashwrite(f, DELTA(entry)->idx.oid.hash, hashsz); + hdrlen += hashsz; } else { - if (limit && hdrlen + datalen + 20 >= limit) { + if (limit && hdrlen + datalen + hashsz >= limit) { if (st) close_istream(st); free(buf); @@ -367,28 +405,30 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, unsigned long limit, int usable_delta) { - struct packed_git *p = entry->in_pack; + struct packed_git *p = IN_PACK(entry); struct pack_window *w_curs = NULL; struct revindex_entry *revidx; off_t offset; - enum object_type type = entry->type; + enum object_type type = oe_type(entry); off_t datalen; unsigned char header[MAX_PACK_OBJECT_HEADER], dheader[MAX_PACK_OBJECT_HEADER]; unsigned hdrlen; + const unsigned hashsz = the_hash_algo->rawsz; + unsigned long entry_size = SIZE(entry); - if (entry->delta) - type = (allow_ofs_delta && entry->delta->idx.offset) ? + if (DELTA(entry)) + type = (allow_ofs_delta && DELTA(entry)->idx.offset) ? OBJ_OFS_DELTA : OBJ_REF_DELTA; hdrlen = encode_in_pack_object_header(header, sizeof(header), - type, entry->size); + type, entry_size); offset = entry->in_pack_offset; revidx = find_pack_revindex(p, offset); datalen = revidx[1].offset - offset; if (!pack_to_stdout && p->index_version > 1 && check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) { - error("bad packed object CRC for %s", + error(_("bad packed object CRC for %s"), oid_to_hex(&entry->idx.oid)); unuse_pack(&w_curs); return write_no_reuse_object(f, entry, limit, usable_delta); @@ -398,20 +438,20 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, datalen -= entry->in_pack_header_size; if (!pack_to_stdout && p->index_version == 1 && - check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) { - error("corrupt packed object for %s", + check_pack_inflate(p, &w_curs, offset, datalen, entry_size)) { + error(_("corrupt packed object for %s"), oid_to_hex(&entry->idx.oid)); unuse_pack(&w_curs); return write_no_reuse_object(f, entry, limit, usable_delta); } if (type == OBJ_OFS_DELTA) { - off_t ofs = entry->idx.offset - entry->delta->idx.offset; + off_t ofs = entry->idx.offset - DELTA(entry)->idx.offset; unsigned pos = sizeof(dheader) - 1; dheader[pos] = ofs & 127; while (ofs >>= 7) dheader[--pos] = 128 | (--ofs & 127); - if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { + if (limit && hdrlen + sizeof(dheader) - pos + datalen + hashsz >= limit) { unuse_pack(&w_curs); return 0; } @@ -420,16 +460,16 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, hdrlen += sizeof(dheader) - pos; reused_delta++; } else if (type == OBJ_REF_DELTA) { - if (limit && hdrlen + 20 + datalen + 20 >= limit) { + if (limit && hdrlen + hashsz + datalen + hashsz >= limit) { unuse_pack(&w_curs); return 0; } hashwrite(f, header, hdrlen); - hashwrite(f, entry->delta->idx.oid.hash, 20); - hdrlen += 20; + hashwrite(f, DELTA(entry)->idx.oid.hash, hashsz); + hdrlen += hashsz; reused_delta++; } else { - if (limit && hdrlen + datalen + 20 >= limit) { + if (limit && hdrlen + datalen + hashsz >= limit) { unuse_pack(&w_curs); return 0; } @@ -465,28 +505,29 @@ static off_t write_object(struct hashfile *f, else limit = pack_size_limit - write_offset; - if (!entry->delta) + if (!DELTA(entry)) usable_delta = 0; /* no delta */ else if (!pack_size_limit) usable_delta = 1; /* unlimited packfile */ - else if (entry->delta->idx.offset == (off_t)-1) + else if (DELTA(entry)->idx.offset == (off_t)-1) usable_delta = 0; /* base was written to another pack */ - else if (entry->delta->idx.offset) + else if (DELTA(entry)->idx.offset) usable_delta = 1; /* base already exists in this pack */ else usable_delta = 0; /* base could end up in another pack */ if (!reuse_object) to_reuse = 0; /* explicit */ - else if (!entry->in_pack) + else if (!IN_PACK(entry)) to_reuse = 0; /* can't reuse what we don't have */ - else if (entry->type == OBJ_REF_DELTA || entry->type == OBJ_OFS_DELTA) + else if (oe_type(entry) == OBJ_REF_DELTA || + oe_type(entry) == OBJ_OFS_DELTA) /* check_object() decided it for us ... */ to_reuse = usable_delta; /* ... but pack split may override that */ - else if (entry->type != entry->in_pack_type) + else if (oe_type(entry) != entry->in_pack_type) to_reuse = 0; /* pack has delta which is unusable */ - else if (entry->delta) + else if (DELTA(entry)) to_reuse = 0; /* we want to pack afresh */ else to_reuse = 1; /* we have it in-pack undeltified, @@ -529,7 +570,7 @@ static enum write_one_status write_one(struct hashfile *f, */ recursing = (e->idx.offset == 1); if (recursing) { - warning("recursive delta detected for object %s", + warning(_("recursive delta detected for object %s"), oid_to_hex(&e->idx.oid)); return WRITE_ONE_RECURSIVE; } else if (e->idx.offset || e->preferred_base) { @@ -538,12 +579,12 @@ static enum write_one_status write_one(struct hashfile *f, } /* if we are deltified, write out base object first. */ - if (e->delta) { + if (DELTA(e)) { e->idx.offset = 1; /* now recurse */ - switch (write_one(f, e->delta, offset)) { + switch (write_one(f, DELTA(e), offset)) { case WRITE_ONE_RECURSIVE: /* we cannot depend on this one */ - e->delta = NULL; + SET_DELTA(e, NULL); break; default: break; @@ -563,7 +604,7 @@ static enum write_one_status write_one(struct hashfile *f, /* make sure off_t is sufficiently large not to wrap */ if (signed_add_overflows(*offset, size)) - die("pack too large for current definition of off_t"); + die(_("pack too large for current definition of off_t")); *offset += size; return WRITE_ONE_WRITTEN; } @@ -572,12 +613,12 @@ static int mark_tagged(const char *path, const struct object_id *oid, int flag, void *cb_data) { struct object_id peeled; - struct object_entry *entry = packlist_find(&to_pack, oid->hash, NULL); + struct object_entry *entry = packlist_find(&to_pack, oid); if (entry) entry->tagged = 1; if (!peel_ref(path, &peeled)) { - entry = packlist_find(&to_pack, peeled.hash, NULL); + entry = packlist_find(&to_pack, &peeled); if (entry) entry->tagged = 1; } @@ -588,7 +629,7 @@ static inline void add_to_write_order(struct object_entry **wo, unsigned int *endp, struct object_entry *e) { - if (e->filled) + if (e->filled || oe_layer(&to_pack, e) != write_layer) return; wo[(*endp)++] = e; e->filled = 1; @@ -605,34 +646,34 @@ static void add_descendants_to_write_order(struct object_entry **wo, /* add this node... */ add_to_write_order(wo, endp, e); /* all its siblings... */ - for (s = e->delta_sibling; s; s = s->delta_sibling) { + for (s = DELTA_SIBLING(e); s; s = DELTA_SIBLING(s)) { add_to_write_order(wo, endp, s); } } /* drop down a level to add left subtree nodes if possible */ - if (e->delta_child) { + if (DELTA_CHILD(e)) { add_to_order = 1; - e = e->delta_child; + e = DELTA_CHILD(e); } else { add_to_order = 0; /* our sibling might have some children, it is next */ - if (e->delta_sibling) { - e = e->delta_sibling; + if (DELTA_SIBLING(e)) { + e = DELTA_SIBLING(e); continue; } /* go back to our parent node */ - e = e->delta; - while (e && !e->delta_sibling) { + e = DELTA(e); + while (e && !DELTA_SIBLING(e)) { /* we're on the right side of a subtree, keep * going up until we can go right again */ - e = e->delta; + e = DELTA(e); } if (!e) { /* done- we hit our original root node */ return; } /* pass it off to sibling at this level */ - e = e->delta_sibling; + e = DELTA_SIBLING(e); } }; } @@ -643,53 +684,20 @@ static void add_family_to_write_order(struct object_entry **wo, { struct object_entry *root; - for (root = e; root->delta; root = root->delta) + for (root = e; DELTA(root); root = DELTA(root)) ; /* nothing */ add_descendants_to_write_order(wo, endp, root); } -static struct object_entry **compute_write_order(void) +static void compute_layer_order(struct object_entry **wo, unsigned int *wo_end) { - unsigned int i, wo_end, last_untagged; - - struct object_entry **wo; + unsigned int i, last_untagged; struct object_entry *objects = to_pack.objects; for (i = 0; i < to_pack.nr_objects; i++) { - objects[i].tagged = 0; - objects[i].filled = 0; - objects[i].delta_child = NULL; - objects[i].delta_sibling = NULL; - } - - /* - * Fully connect delta_child/delta_sibling network. - * Make sure delta_sibling is sorted in the original - * recency order. - */ - for (i = to_pack.nr_objects; i > 0;) { - struct object_entry *e = &objects[--i]; - if (!e->delta) - continue; - /* Mark me as the first child */ - e->delta_sibling = e->delta->delta_child; - e->delta->delta_child = e; - } - - /* - * Mark objects that are at the tip of tags. - */ - for_each_tag_ref(mark_tagged, NULL); - - /* - * Give the objects in the original recency order until - * we see a tagged tip. - */ - ALLOC_ARRAY(wo, to_pack.nr_objects); - for (i = wo_end = 0; i < to_pack.nr_objects; i++) { if (objects[i].tagged) break; - add_to_write_order(wo, &wo_end, &objects[i]); + add_to_write_order(wo, wo_end, &objects[i]); } last_untagged = i; @@ -698,93 +706,267 @@ static struct object_entry **compute_write_order(void) */ for (; i < to_pack.nr_objects; i++) { if (objects[i].tagged) - add_to_write_order(wo, &wo_end, &objects[i]); + add_to_write_order(wo, wo_end, &objects[i]); } /* * And then all remaining commits and tags. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { - if (objects[i].type != OBJ_COMMIT && - objects[i].type != OBJ_TAG) + if (oe_type(&objects[i]) != OBJ_COMMIT && + oe_type(&objects[i]) != OBJ_TAG) continue; - add_to_write_order(wo, &wo_end, &objects[i]); + add_to_write_order(wo, wo_end, &objects[i]); } /* * And then all the trees. */ for (i = last_untagged; i < to_pack.nr_objects; i++) { - if (objects[i].type != OBJ_TREE) + if (oe_type(&objects[i]) != OBJ_TREE) continue; - add_to_write_order(wo, &wo_end, &objects[i]); + add_to_write_order(wo, wo_end, &objects[i]); } /* * Finally all the rest in really tight order */ for (i = last_untagged; i < to_pack.nr_objects; i++) { - if (!objects[i].filled) - add_family_to_write_order(wo, &wo_end, &objects[i]); + if (!objects[i].filled && oe_layer(&to_pack, &objects[i]) == write_layer) + add_family_to_write_order(wo, wo_end, &objects[i]); + } +} + +static struct object_entry **compute_write_order(void) +{ + uint32_t max_layers = 1; + unsigned int i, wo_end; + + struct object_entry **wo; + struct object_entry *objects = to_pack.objects; + + for (i = 0; i < to_pack.nr_objects; i++) { + objects[i].tagged = 0; + objects[i].filled = 0; + SET_DELTA_CHILD(&objects[i], NULL); + SET_DELTA_SIBLING(&objects[i], NULL); + } + + /* + * Fully connect delta_child/delta_sibling network. + * Make sure delta_sibling is sorted in the original + * recency order. + */ + for (i = to_pack.nr_objects; i > 0;) { + struct object_entry *e = &objects[--i]; + if (!DELTA(e)) + continue; + /* Mark me as the first child */ + e->delta_sibling_idx = DELTA(e)->delta_child_idx; + SET_DELTA_CHILD(DELTA(e), e); } + /* + * Mark objects that are at the tip of tags. + */ + for_each_tag_ref(mark_tagged, NULL); + + if (use_delta_islands) + max_layers = compute_pack_layers(&to_pack); + + ALLOC_ARRAY(wo, to_pack.nr_objects); + wo_end = 0; + + for (; write_layer < max_layers; ++write_layer) + compute_layer_order(wo, &wo_end); + if (wo_end != to_pack.nr_objects) - die("ordered %u objects, expected %"PRIu32, wo_end, to_pack.nr_objects); + die(_("ordered %u objects, expected %"PRIu32), + wo_end, to_pack.nr_objects); return wo; } -static off_t write_reused_pack(struct hashfile *f) + +/* + * A reused set of objects. All objects in a chunk have the same + * relative position in the original packfile and the generated + * packfile. + */ + +static struct reused_chunk { + /* The offset of the first object of this chunk in the original + * packfile. */ + off_t original; + /* The offset of the first object of this chunk in the generated + * packfile minus "original". */ + off_t difference; +} *reused_chunks; +static int reused_chunks_nr; +static int reused_chunks_alloc; + +static void record_reused_object(off_t where, off_t offset) +{ + if (reused_chunks_nr && reused_chunks[reused_chunks_nr-1].difference == offset) + return; + + ALLOC_GROW(reused_chunks, reused_chunks_nr + 1, + reused_chunks_alloc); + reused_chunks[reused_chunks_nr].original = where; + reused_chunks[reused_chunks_nr].difference = offset; + reused_chunks_nr++; +} + +/* + * Binary search to find the chunk that "where" is in. Note + * that we're not looking for an exact match, just the first + * chunk that contains it (which implicitly ends at the start + * of the next chunk. + */ +static off_t find_reused_offset(off_t where) +{ + int lo = 0, hi = reused_chunks_nr; + while (lo < hi) { + int mi = lo + ((hi - lo) / 2); + if (where == reused_chunks[mi].original) + return reused_chunks[mi].difference; + if (where < reused_chunks[mi].original) + hi = mi; + else + lo = mi + 1; + } + + /* + * The first chunk starts at zero, so we can't have gone below + * there. + */ + assert(lo); + return reused_chunks[lo-1].difference; +} + +static void write_reused_pack_one(size_t pos, struct hashfile *out, + struct pack_window **w_curs) { - unsigned char buffer[8192]; - off_t to_write, total; - int fd; + off_t offset, next, cur; + enum object_type type; + unsigned long size; + + offset = reuse_packfile->revindex[pos].offset; + next = reuse_packfile->revindex[pos + 1].offset; + + record_reused_object(offset, offset - hashfile_total(out)); - if (!is_pack_valid(reuse_packfile)) - die("packfile is invalid: %s", reuse_packfile->pack_name); + cur = offset; + type = unpack_object_header(reuse_packfile, w_curs, &cur, &size); + assert(type >= 0); - fd = git_open(reuse_packfile->pack_name); - if (fd < 0) - die_errno("unable to open packfile for reuse: %s", - reuse_packfile->pack_name); + if (type == OBJ_OFS_DELTA) { + off_t base_offset; + off_t fixup; - if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1) - die_errno("unable to seek in reused packfile"); + unsigned char header[MAX_PACK_OBJECT_HEADER]; + unsigned len; - if (reuse_packfile_offset < 0) - reuse_packfile_offset = reuse_packfile->pack_size - 20; + base_offset = get_delta_base(reuse_packfile, w_curs, &cur, type, offset); + assert(base_offset != 0); - total = to_write = reuse_packfile_offset - sizeof(struct pack_header); + /* Convert to REF_DELTA if we must... */ + if (!allow_ofs_delta) { + int base_pos = find_revindex_position(reuse_packfile, base_offset); + struct object_id base_oid; - while (to_write) { - int read_pack = xread(fd, buffer, sizeof(buffer)); + nth_packed_object_id(&base_oid, reuse_packfile, + reuse_packfile->revindex[base_pos].nr); - if (read_pack <= 0) - die_errno("unable to read from reused packfile"); + len = encode_in_pack_object_header(header, sizeof(header), + OBJ_REF_DELTA, size); + hashwrite(out, header, len); + hashwrite(out, base_oid.hash, the_hash_algo->rawsz); + copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur); + return; + } - if (read_pack > to_write) - read_pack = to_write; + /* Otherwise see if we need to rewrite the offset... */ + fixup = find_reused_offset(offset) - + find_reused_offset(base_offset); + if (fixup) { + unsigned char ofs_header[10]; + unsigned i, ofs_len; + off_t ofs = offset - base_offset - fixup; - hashwrite(f, buffer, read_pack); - to_write -= read_pack; + len = encode_in_pack_object_header(header, sizeof(header), + OBJ_OFS_DELTA, size); + + i = sizeof(ofs_header) - 1; + ofs_header[i] = ofs & 127; + while (ofs >>= 7) + ofs_header[--i] = 128 | (--ofs & 127); + + ofs_len = sizeof(ofs_header) - i; + + hashwrite(out, header, len); + hashwrite(out, ofs_header + sizeof(ofs_header) - ofs_len, ofs_len); + copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur); + return; + } + + /* ...otherwise we have no fixup, and can write it verbatim */ + } + + copy_pack_data(out, reuse_packfile, w_curs, offset, next - offset); +} + +static size_t write_reused_pack_verbatim(struct hashfile *out, + struct pack_window **w_curs) +{ + size_t pos = 0; + + while (pos < reuse_packfile_bitmap->word_alloc && + reuse_packfile_bitmap->words[pos] == (eword_t)~0) + pos++; + + if (pos) { + off_t to_write; + + written = (pos * BITS_IN_EWORD); + to_write = reuse_packfile->revindex[written].offset + - sizeof(struct pack_header); + + /* We're recording one chunk, not one object. */ + record_reused_object(sizeof(struct pack_header), 0); + hashflush(out); + copy_pack_data(out, reuse_packfile, w_curs, + sizeof(struct pack_header), to_write); - /* - * We don't know the actual number of objects written, - * only how many bytes written, how many bytes total, and - * how many objects total. So we can fake it by pretending all - * objects we are writing are the same size. This gives us a - * smooth progress meter, and at the end it matches the true - * answer. - */ - written = reuse_packfile_objects * - (((double)(total - to_write)) / total); display_progress(progress_state, written); } + return pos; +} + +static void write_reused_pack(struct hashfile *f) +{ + size_t i = 0; + uint32_t offset; + struct pack_window *w_curs = NULL; + + if (allow_ofs_delta) + i = write_reused_pack_verbatim(f, &w_curs); + + for (; i < reuse_packfile_bitmap->word_alloc; ++i) { + eword_t word = reuse_packfile_bitmap->words[i]; + size_t pos = (i * BITS_IN_EWORD); + + for (offset = 0; offset < BITS_IN_EWORD; ++offset) { + if ((word >> offset) == 0) + break; + + offset += ewah_bit_ctz64(word >> offset); + write_reused_pack_one(pos + offset, f, &w_curs); + display_progress(progress_state, ++written); + } + } - close(fd); - written = reuse_packfile_objects; - display_progress(progress_state, written); - return reuse_packfile_offset - sizeof(struct pack_header); + unuse_pack(&w_curs); } static const char no_split_warning[] = N_( @@ -817,11 +999,9 @@ static void write_pack_file(void) offset = write_pack_header(f, nr_remaining); if (reuse_packfile) { - off_t packfile_size; assert(pack_to_stdout); - - packfile_size = write_reused_pack(f); - offset += packfile_size; + write_reused_pack(f); + offset = hashfile_total(f); } nr_written = 0; @@ -837,16 +1017,17 @@ static void write_pack_file(void) * If so, rewrite it like in fast-import */ if (pack_to_stdout) { - hashclose(f, oid.hash, CSUM_CLOSE); + finalize_hashfile(f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_CLOSE); } else if (nr_written == nr_remaining) { - hashclose(f, oid.hash, CSUM_FSYNC); + finalize_hashfile(f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE); } else { - int fd = hashclose(f, oid.hash, 0); + int fd = finalize_hashfile(f, oid.hash, 0); fixup_pack_header_footer(fd, oid.hash, pack_tmp_name, nr_written, oid.hash, offset); close(fd); if (write_bitmap_index) { - warning(_(no_split_warning)); + if (write_bitmap_index != WRITE_BITMAP_QUIET) + warning(_(no_split_warning)); write_bitmap_index = 0; } } @@ -863,7 +1044,7 @@ static void write_pack_file(void) * to preserve this property. */ if (stat(pack_tmp_name, &st) < 0) { - warning_errno("failed to stat %s", pack_tmp_name); + warning_errno(_("failed to stat %s"), pack_tmp_name); } else if (!last_mtime) { last_mtime = st.st_mtime; } else { @@ -871,14 +1052,15 @@ static void write_pack_file(void) utb.actime = st.st_atime; utb.modtime = --last_mtime; if (utime(pack_tmp_name, &utb) < 0) - warning_errno("failed utime() on %s", pack_tmp_name); + warning_errno(_("failed utime() on %s"), pack_tmp_name); } strbuf_addf(&tmpname, "%s-", base_name); if (write_bitmap_index) { bitmap_writer_set_checksum(oid.hash); - bitmap_writer_build_type_index(written_list, nr_written); + bitmap_writer_build_type_index( + &to_pack, written_list, nr_written); } finish_tmp_packfile(&tmpname, pack_tmp_name, @@ -915,8 +1097,10 @@ static void write_pack_file(void) free(write_order); stop_progress(&progress_state); if (written != nr_result) - die("wrote %"PRIu32" objects while expecting %"PRIu32, - written, nr_result); + die(_("wrote %"PRIu32" objects while expecting %"PRIu32), + written, nr_result); + trace2_data_intmax("pack-objects", the_repository, + "write_pack_file/wrote", nr_result); } static int no_try_delta(const char *path) @@ -925,8 +1109,7 @@ static int no_try_delta(const char *path) if (!check) check = attr_check_initl("delta", NULL); - if (git_check_attr(path, check)) - return 0; + git_check_attr(the_repository->index, path, check); if (ATTR_FALSE(check->items[0].value)) return 1; return 0; @@ -943,12 +1126,15 @@ static int no_try_delta(const char *path) * few lines later when we want to add the new entry. */ static int have_duplicate_entry(const struct object_id *oid, - int exclude, - uint32_t *index_pos) + int exclude) { struct object_entry *entry; - entry = packlist_find(&to_pack, oid->hash, index_pos); + if (reuse_packfile_bitmap && + bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid)) + return 1; + + entry = packlist_find(&to_pack, oid); if (!entry) return 0; @@ -982,13 +1168,16 @@ static int want_found_object(int exclude, struct packed_git *p) * Otherwise, we signal "-1" at the end to tell the caller that we do * not know either way, and it needs to check more packs. */ - if (!ignore_packed_keep && + if (!ignore_packed_keep_on_disk && + !ignore_packed_keep_in_core && (!local || !have_non_local_packs)) return 1; if (local && !p->pack_local) return 0; - if (ignore_packed_keep && p->pack_local && p->pack_keep) + if (p->pack_local && + ((ignore_packed_keep_on_disk && p->pack_keep) || + (ignore_packed_keep_in_core && p->pack_keep_in_core))) return 0; /* we don't know yet; keep looking for more packs */ @@ -1011,8 +1200,9 @@ static int want_object_in_pack(const struct object_id *oid, { int want; struct list_head *pos; + struct multi_pack_index *m; - if (!exclude && local && has_loose_object_nonlocal(oid->hash)) + if (!exclude && local && has_loose_object_nonlocal(oid)) return 0; /* @@ -1025,6 +1215,32 @@ static int want_object_in_pack(const struct object_id *oid, if (want != -1) return want; } + + for (m = get_multi_pack_index(the_repository); m; m = m->next) { + struct pack_entry e; + if (fill_midx_entry(the_repository, oid, &e, m)) { + struct packed_git *p = e.p; + off_t offset; + + if (p == *found_pack) + offset = *found_offset; + else + offset = find_pack_entry_one(oid->hash, p); + + if (offset) { + if (!*found_pack) { + if (!is_pack_valid(p)) + continue; + *found_offset = offset; + *found_pack = p; + } + want = want_found_object(exclude, p); + if (want != -1) + return want; + } + } + } + list_for_each(pos, get_packed_git_mru(the_repository)) { struct packed_git *p = list_entry(pos, struct packed_git, mru); off_t offset; @@ -1058,22 +1274,20 @@ static void create_object_entry(const struct object_id *oid, uint32_t hash, int exclude, int no_try_delta, - uint32_t index_pos, struct packed_git *found_pack, off_t found_offset) { struct object_entry *entry; - entry = packlist_alloc(&to_pack, oid->hash, index_pos); + entry = packlist_alloc(&to_pack, oid); entry->hash = hash; - if (type) - entry->type = type; + oe_set_type(entry, type); if (exclude) entry->preferred_base = 1; else nr_result++; if (found_pack) { - entry->in_pack = found_pack; + oe_set_in_pack(&to_pack, entry, found_pack); entry->in_pack_offset = found_offset; } @@ -1089,15 +1303,17 @@ static int add_object_entry(const struct object_id *oid, enum object_type type, { struct packed_git *found_pack = NULL; off_t found_offset = 0; - uint32_t index_pos; - if (have_duplicate_entry(oid, exclude, &index_pos)) + display_progress(progress_state, ++nr_seen); + + if (have_duplicate_entry(oid, exclude)) return 0; if (!want_object_in_pack(oid, exclude, &found_pack, &found_offset)) { /* The pack is missing an object, so it will not have closure */ if (write_bitmap_index) { - warning(_(no_closure_warning)); + if (write_bitmap_index != WRITE_BITMAP_QUIET) + warning(_(no_closure_warning)); write_bitmap_index = 0; } return 0; @@ -1105,9 +1321,7 @@ static int add_object_entry(const struct object_id *oid, enum object_type type, create_object_entry(oid, type, pack_name_hash(name), exclude, name && no_try_delta(name), - index_pos, found_pack, found_offset); - - display_progress(progress_state, nr_result); + found_pack, found_offset); return 1; } @@ -1116,17 +1330,15 @@ static int add_object_entry_from_bitmap(const struct object_id *oid, int flags, uint32_t name_hash, struct packed_git *pack, off_t offset) { - uint32_t index_pos; + display_progress(progress_state, ++nr_seen); - if (have_duplicate_entry(oid, 0, &index_pos)) + if (have_duplicate_entry(oid, 0)) return 0; if (!want_object_in_pack(oid, 0, &pack, &offset)) return 0; - create_object_entry(oid, type, name_hash, 0, 0, index_pos, pack, offset); - - display_progress(progress_state, nr_result); + create_object_entry(oid, type, name_hash, 0, 0, pack, offset); return 1; } @@ -1174,7 +1386,7 @@ static struct pbase_tree_cache *pbase_tree_get(const struct object_id *oid) */ for (neigh = 0; neigh < 8; neigh++) { ent = pbase_tree_cache[my_ix]; - if (ent && !oidcmp(&ent->oid, oid)) { + if (ent && oideq(&ent->oid, oid)) { ent->ref++; return ent; } @@ -1261,7 +1473,7 @@ static void add_pbase_object(struct tree_desc *tree, if (cmp < 0) return; if (name[cmplen] != '/') { - add_object_entry(entry.oid, + add_object_entry(&entry.oid, object_type(entry.mode), fullname, 1); return; @@ -1272,7 +1484,7 @@ static void add_pbase_object(struct tree_desc *tree, const char *down = name+cmplen+1; int downlen = name_cmp_len(down); - tree = pbase_tree_get(entry.oid); + tree = pbase_tree_get(&entry.oid); if (!tree) return; init_tree_desc(&sub, tree->tree_data, tree->tree_size); @@ -1351,12 +1563,13 @@ static void add_preferred_base(struct object_id *oid) if (window <= num_preferred_base++) return; - data = read_object_with_reference(oid, tree_type, &size, &tree_oid); + data = read_object_with_reference(the_repository, oid, + tree_type, &size, &tree_oid); if (!data) return; for (it = pbase_tree; it; it = it->next) { - if (!oidcmp(&it->pcache.oid, &tree_oid)) { + if (oideq(&it->pcache.oid, &tree_oid)) { free(data); return; } @@ -1396,17 +1609,68 @@ static void cleanup_preferred_base(void) done_pbase_paths_num = done_pbase_paths_alloc = 0; } +/* + * Return 1 iff the object specified by "delta" can be sent + * literally as a delta against the base in "base_sha1". If + * so, then *base_out will point to the entry in our packing + * list, or NULL if we must use the external-base list. + * + * Depth value does not matter - find_deltas() will + * never consider reused delta as the base object to + * deltify other objects against, in order to avoid + * circular deltas. + */ +static int can_reuse_delta(const struct object_id *base_oid, + struct object_entry *delta, + struct object_entry **base_out) +{ + struct object_entry *base; + + /* + * First see if we're already sending the base (or it's explicitly in + * our "excluded" list). + */ + base = packlist_find(&to_pack, base_oid); + if (base) { + if (!in_same_island(&delta->idx.oid, &base->idx.oid)) + return 0; + *base_out = base; + return 1; + } + + /* + * Otherwise, reachability bitmaps may tell us if the receiver has it, + * even if it was buried too deep in history to make it into the + * packing list. + */ + if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, base_oid)) { + if (use_delta_islands) { + if (!in_same_island(&delta->idx.oid, base_oid)) + return 0; + } + *base_out = NULL; + return 1; + } + + return 0; +} + static void check_object(struct object_entry *entry) { - if (entry->in_pack) { - struct packed_git *p = entry->in_pack; + unsigned long canonical_size; + + if (IN_PACK(entry)) { + struct packed_git *p = IN_PACK(entry); struct pack_window *w_curs = NULL; - const unsigned char *base_ref = NULL; + int have_base = 0; + struct object_id base_ref; struct object_entry *base_entry; unsigned long used, used_0; unsigned long avail; off_t ofs; unsigned char *buf, c; + enum object_type type; + unsigned long in_pack_size; buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail); @@ -1415,11 +1679,15 @@ static void check_object(struct object_entry *entry) * since non-delta representations could still be reused. */ used = unpack_object_header_buffer(buf, avail, - &entry->in_pack_type, - &entry->size); + &type, + &in_pack_size); if (used == 0) goto give_up; + if (type < 0) + BUG("invalid type %d", type); + entry->in_pack_type = type; + /* * Determine if this is a delta and if so whether we can * reuse it or not. Otherwise let's find out as cheaply as @@ -1428,17 +1696,22 @@ static void check_object(struct object_entry *entry) switch (entry->in_pack_type) { default: /* Not a delta hence we've already got all we need. */ - entry->type = entry->in_pack_type; + oe_set_type(entry, entry->in_pack_type); + SET_SIZE(entry, in_pack_size); entry->in_pack_header_size = used; - if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB) + if (oe_type(entry) < OBJ_COMMIT || oe_type(entry) > OBJ_BLOB) goto give_up; unuse_pack(&w_curs); return; case OBJ_REF_DELTA: - if (reuse_delta && !entry->preferred_base) - base_ref = use_pack(p, &w_curs, - entry->in_pack_offset + used, NULL); - entry->in_pack_header_size = used + 20; + if (reuse_delta && !entry->preferred_base) { + oidread(&base_ref, + use_pack(p, &w_curs, + entry->in_pack_offset + used, + NULL)); + have_base = 1; + } + entry->in_pack_header_size = used + the_hash_algo->rawsz; break; case OBJ_OFS_DELTA: buf = use_pack(p, &w_curs, @@ -1449,7 +1722,7 @@ static void check_object(struct object_entry *entry) while (c & 128) { ofs += 1; if (!ofs || MSB(ofs, 7)) { - error("delta base offset overflow in pack for %s", + error(_("delta base offset overflow in pack for %s"), oid_to_hex(&entry->idx.oid)); goto give_up; } @@ -1458,7 +1731,7 @@ static void check_object(struct object_entry *entry) } ofs = entry->in_pack_offset - ofs; if (ofs <= 0 || ofs >= entry->in_pack_offset) { - error("delta base offset out of bound for %s", + error(_("delta base offset out of bound for %s"), oid_to_hex(&entry->idx.oid)); goto give_up; } @@ -1467,76 +1740,85 @@ static void check_object(struct object_entry *entry) revidx = find_pack_revindex(p, ofs); if (!revidx) goto give_up; - base_ref = nth_packed_object_sha1(p, revidx->nr); + if (!nth_packed_object_id(&base_ref, p, revidx->nr)) + have_base = 1; } entry->in_pack_header_size = used + used_0; break; } - if (base_ref && (base_entry = packlist_find(&to_pack, base_ref, NULL))) { - /* - * If base_ref was set above that means we wish to - * reuse delta data, and we even found that base - * in the list of objects we want to pack. Goodie! - * - * Depth value does not matter - find_deltas() will - * never consider reused delta as the base object to - * deltify other objects against, in order to avoid - * circular deltas. - */ - entry->type = entry->in_pack_type; - entry->delta = base_entry; - entry->delta_size = entry->size; - entry->delta_sibling = base_entry->delta_child; - base_entry->delta_child = entry; + if (have_base && + can_reuse_delta(&base_ref, entry, &base_entry)) { + oe_set_type(entry, entry->in_pack_type); + SET_SIZE(entry, in_pack_size); /* delta size */ + SET_DELTA_SIZE(entry, in_pack_size); + + if (base_entry) { + SET_DELTA(entry, base_entry); + entry->delta_sibling_idx = base_entry->delta_child_idx; + SET_DELTA_CHILD(base_entry, entry); + } else { + SET_DELTA_EXT(entry, &base_ref); + } + unuse_pack(&w_curs); return; } - if (entry->type) { + if (oe_type(entry)) { + off_t delta_pos; + /* * This must be a delta and we already know what the * final object type is. Let's extract the actual * object size from the delta header. */ - entry->size = get_size_from_delta(p, &w_curs, - entry->in_pack_offset + entry->in_pack_header_size); - if (entry->size == 0) + delta_pos = entry->in_pack_offset + entry->in_pack_header_size; + canonical_size = get_size_from_delta(p, &w_curs, delta_pos); + if (canonical_size == 0) goto give_up; + SET_SIZE(entry, canonical_size); unuse_pack(&w_curs); return; } /* * No choice but to fall back to the recursive delta walk - * with sha1_object_info() to find about the object type + * with oid_object_info() to find about the object type * at this point... */ give_up: unuse_pack(&w_curs); } - entry->type = oid_object_info(&entry->idx.oid, &entry->size); - /* - * The error condition is checked in prepare_pack(). This is - * to permit a missing preferred base object to be ignored - * as a preferred base. Doing so can result in a larger - * pack file, but the transfer will still take place. - */ + oe_set_type(entry, + oid_object_info(the_repository, &entry->idx.oid, &canonical_size)); + if (entry->type_valid) { + SET_SIZE(entry, canonical_size); + } else { + /* + * Bad object type is checked in prepare_pack(). This is + * to permit a missing preferred base object to be ignored + * as a preferred base. Doing so can result in a larger + * pack file, but the transfer will still take place. + */ + } } static int pack_offset_sort(const void *_a, const void *_b) { const struct object_entry *a = *(struct object_entry **)_a; const struct object_entry *b = *(struct object_entry **)_b; + const struct packed_git *a_in_pack = IN_PACK(a); + const struct packed_git *b_in_pack = IN_PACK(b); /* avoid filesystem trashing with loose objects */ - if (!a->in_pack && !b->in_pack) + if (!a_in_pack && !b_in_pack) return oidcmp(&a->idx.oid, &b->idx.oid); - if (a->in_pack < b->in_pack) + if (a_in_pack < b_in_pack) return -1; - if (a->in_pack > b->in_pack) + if (a_in_pack > b_in_pack) return 1; return a->in_pack_offset < b->in_pack_offset ? -1 : (a->in_pack_offset > b->in_pack_offset); @@ -1557,29 +1839,37 @@ static int pack_offset_sort(const void *_a, const void *_b) */ static void drop_reused_delta(struct object_entry *entry) { - struct object_entry **p = &entry->delta->delta_child; + unsigned *idx = &to_pack.objects[entry->delta_idx - 1].delta_child_idx; struct object_info oi = OBJECT_INFO_INIT; + enum object_type type; + unsigned long size; - while (*p) { - if (*p == entry) - *p = (*p)->delta_sibling; + while (*idx) { + struct object_entry *oe = &to_pack.objects[*idx - 1]; + + if (oe == entry) + *idx = oe->delta_sibling_idx; else - p = &(*p)->delta_sibling; + idx = &oe->delta_sibling_idx; } - entry->delta = NULL; + SET_DELTA(entry, NULL); entry->depth = 0; - oi.sizep = &entry->size; - oi.typep = &entry->type; - if (packed_object_info(entry->in_pack, entry->in_pack_offset, &oi) < 0) { + oi.sizep = &size; + oi.typep = &type; + if (packed_object_info(the_repository, IN_PACK(entry), entry->in_pack_offset, &oi) < 0) { /* * We failed to get the info from this pack for some reason; - * fall back to sha1_object_info, which may find another copy. - * And if that fails, the error will be recorded in entry->type + * fall back to oid_object_info, which may find another copy. + * And if that fails, the error will be recorded in oe_type(entry) * and dealt with in prepare_pack(). */ - entry->type = oid_object_info(&entry->idx.oid, &entry->size); + oe_set_type(entry, + oid_object_info(the_repository, &entry->idx.oid, &size)); + } else { + oe_set_type(entry, type); } + SET_SIZE(entry, size); } /* @@ -1603,7 +1893,7 @@ static void break_delta_chains(struct object_entry *entry) for (cur = entry, total_depth = 0; cur; - cur = cur->delta, total_depth++) { + cur = DELTA(cur), total_depth++) { if (cur->dfs_state == DFS_DONE) { /* * We've already seen this object and know it isn't @@ -1620,7 +1910,7 @@ static void break_delta_chains(struct object_entry *entry) * is a bug. */ if (cur->dfs_state != DFS_NONE) - die("BUG: confusing delta dfs state in first pass: %d", + BUG("confusing delta dfs state in first pass: %d", cur->dfs_state); /* @@ -1628,7 +1918,7 @@ static void break_delta_chains(struct object_entry *entry) * it's not a delta, we're done traversing, but we'll mark it * done to save time on future traversals. */ - if (!cur->delta) { + if (!DELTA(cur)) { cur->dfs_state = DFS_DONE; break; } @@ -1651,7 +1941,7 @@ static void break_delta_chains(struct object_entry *entry) * We keep all commits in the chain that we examined. */ cur->dfs_state = DFS_ACTIVE; - if (cur->delta->dfs_state == DFS_ACTIVE) { + if (DELTA(cur)->dfs_state == DFS_ACTIVE) { drop_reused_delta(cur); cur->dfs_state = DFS_DONE; break; @@ -1666,7 +1956,7 @@ static void break_delta_chains(struct object_entry *entry) * an extra "next" pointer to keep going after we reset cur->delta. */ for (cur = entry; cur; cur = next) { - next = cur->delta; + next = DELTA(cur); /* * We should have a chain of zero or more ACTIVE states down to @@ -1677,7 +1967,7 @@ static void break_delta_chains(struct object_entry *entry) if (cur->dfs_state == DFS_DONE) break; else if (cur->dfs_state != DFS_ACTIVE) - die("BUG: confusing delta dfs state in second pass: %d", + BUG("confusing delta dfs state in second pass: %d", cur->dfs_state); /* @@ -1711,6 +2001,10 @@ static void get_object_details(void) uint32_t i; struct object_entry **sorted_by_offset; + if (progress) + progress_state = start_progress(_("Counting objects"), + to_pack.nr_objects); + sorted_by_offset = xcalloc(to_pack.nr_objects, sizeof(struct object_entry *)); for (i = 0; i < to_pack.nr_objects; i++) sorted_by_offset[i] = to_pack.objects + i; @@ -1719,9 +2013,12 @@ static void get_object_details(void) for (i = 0; i < to_pack.nr_objects; i++) { struct object_entry *entry = sorted_by_offset[i]; check_object(entry); - if (big_file_threshold < entry->size) + if (entry->type_valid && + oe_size_greater_than(&to_pack, entry, big_file_threshold)) entry->no_try_delta = 1; + display_progress(progress_state, i + 1); } + stop_progress(&progress_state); /* * This must happen in a second pass, since we rely on the delta @@ -1746,10 +2043,14 @@ static int type_size_sort(const void *_a, const void *_b) { const struct object_entry *a = *(struct object_entry **)_a; const struct object_entry *b = *(struct object_entry **)_b; + const enum object_type a_type = oe_type(a); + const enum object_type b_type = oe_type(b); + const unsigned long a_size = SIZE(a); + const unsigned long b_size = SIZE(b); - if (a->type > b->type) + if (a_type > b_type) return -1; - if (a->type < b->type) + if (a_type < b_type) return 1; if (a->hash > b->hash) return -1; @@ -1759,9 +2060,14 @@ static int type_size_sort(const void *_a, const void *_b) return -1; if (a->preferred_base < b->preferred_base) return 1; - if (a->size > b->size) + if (use_delta_islands) { + const int island_cmp = island_delta_cmp(&a->idx.oid, &b->idx.oid); + if (island_cmp) + return island_cmp; + } + if (a_size > b_size) return -1; - if (a->size < b->size) + if (a_size < b_size) return 1; return a < b ? -1 : (a > b); /* newest first */ } @@ -1789,30 +2095,65 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size, return 0; } -#ifndef NO_PTHREADS - -static pthread_mutex_t read_mutex; -#define read_lock() pthread_mutex_lock(&read_mutex) -#define read_unlock() pthread_mutex_unlock(&read_mutex) - +/* Protect delta_cache_size */ static pthread_mutex_t cache_mutex; #define cache_lock() pthread_mutex_lock(&cache_mutex) #define cache_unlock() pthread_mutex_unlock(&cache_mutex) +/* + * Protect object list partitioning (e.g. struct thread_param) and + * progress_state + */ static pthread_mutex_t progress_mutex; #define progress_lock() pthread_mutex_lock(&progress_mutex) #define progress_unlock() pthread_mutex_unlock(&progress_mutex) -#else +/* + * Access to struct object_entry is unprotected since each thread owns + * a portion of the main object list. Just don't access object entries + * ahead in the list because they can be stolen and would need + * progress_mutex for protection. + */ -#define read_lock() (void)0 -#define read_unlock() (void)0 -#define cache_lock() (void)0 -#define cache_unlock() (void)0 -#define progress_lock() (void)0 -#define progress_unlock() (void)0 +/* + * Return the size of the object without doing any delta + * reconstruction (so non-deltas are true object sizes, but deltas + * return the size of the delta data). + */ +unsigned long oe_get_size_slow(struct packing_data *pack, + const struct object_entry *e) +{ + struct packed_git *p; + struct pack_window *w_curs; + unsigned char *buf; + enum object_type type; + unsigned long used, avail, size; + + if (e->type_ != OBJ_OFS_DELTA && e->type_ != OBJ_REF_DELTA) { + packing_data_lock(&to_pack); + if (oid_object_info(the_repository, &e->idx.oid, &size) < 0) + die(_("unable to get size of %s"), + oid_to_hex(&e->idx.oid)); + packing_data_unlock(&to_pack); + return size; + } + + p = oe_in_pack(pack, e); + if (!p) + BUG("when e->type is a delta, it must belong to a pack"); -#endif + packing_data_lock(&to_pack); + w_curs = NULL; + buf = use_pack(p, &w_curs, e->in_pack_offset, &avail); + used = unpack_object_header_buffer(buf, avail, &type, &size); + if (used == 0) + die(_("unable to parse object header of %s"), + oid_to_hex(&e->idx.oid)); + + unuse_pack(&w_curs); + packing_data_unlock(&to_pack); + return size; +} static int try_delta(struct unpacked *trg, struct unpacked *src, unsigned max_depth, unsigned long *mem_usage) @@ -1825,7 +2166,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, void *delta_buf; /* Don't bother doing diffs between different types */ - if (trg_entry->type != src_entry->type) + if (oe_type(trg_entry) != oe_type(src_entry)) return -1; /* @@ -1836,8 +2177,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, * it, we will still save the transfer cost, as we already know * the other side has it and we won't send src_entry at all. */ - if (reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack && + if (reuse_delta && IN_PACK(trg_entry) && + IN_PACK(trg_entry) == IN_PACK(src_entry) && !src_entry->preferred_base && trg_entry->in_pack_type != OBJ_REF_DELTA && trg_entry->in_pack_type != OBJ_OFS_DELTA) @@ -1848,48 +2189,51 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, return 0; /* Now some size filtering heuristics. */ - trg_size = trg_entry->size; - if (!trg_entry->delta) { - max_size = trg_size/2 - 20; + trg_size = SIZE(trg_entry); + if (!DELTA(trg_entry)) { + max_size = trg_size/2 - the_hash_algo->rawsz; ref_depth = 1; } else { - max_size = trg_entry->delta_size; + max_size = DELTA_SIZE(trg_entry); ref_depth = trg->depth; } max_size = (uint64_t)max_size * (max_depth - src->depth) / (max_depth - ref_depth + 1); if (max_size == 0) return 0; - src_size = src_entry->size; + src_size = SIZE(src_entry); sizediff = src_size < trg_size ? trg_size - src_size : 0; if (sizediff >= max_size) return 0; if (trg_size < src_size / 32) return 0; + if (!in_same_island(&trg->entry->idx.oid, &src->entry->idx.oid)) + return 0; + /* Load data if not already done */ if (!trg->data) { - read_lock(); + packing_data_lock(&to_pack); trg->data = read_object_file(&trg_entry->idx.oid, &type, &sz); - read_unlock(); + packing_data_unlock(&to_pack); if (!trg->data) - die("object %s cannot be read", + die(_("object %s cannot be read"), oid_to_hex(&trg_entry->idx.oid)); if (sz != trg_size) - die("object %s inconsistent object length (%lu vs %lu)", - oid_to_hex(&trg_entry->idx.oid), sz, - trg_size); + die(_("object %s inconsistent object length (%"PRIuMAX" vs %"PRIuMAX")"), + oid_to_hex(&trg_entry->idx.oid), (uintmax_t)sz, + (uintmax_t)trg_size); *mem_usage += sz; } if (!src->data) { - read_lock(); + packing_data_lock(&to_pack); src->data = read_object_file(&src_entry->idx.oid, &type, &sz); - read_unlock(); + packing_data_unlock(&to_pack); if (!src->data) { if (src_entry->preferred_base) { static int warned = 0; if (!warned++) - warning("object %s cannot be read", + warning(_("object %s cannot be read"), oid_to_hex(&src_entry->idx.oid)); /* * Those objects are not included in the @@ -1899,13 +2243,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, */ return 0; } - die("object %s cannot be read", + die(_("object %s cannot be read"), oid_to_hex(&src_entry->idx.oid)); } if (sz != src_size) - die("object %s inconsistent object length (%lu vs %lu)", - oid_to_hex(&src_entry->idx.oid), sz, - src_size); + die(_("object %s inconsistent object length (%"PRIuMAX" vs %"PRIuMAX")"), + oid_to_hex(&src_entry->idx.oid), (uintmax_t)sz, + (uintmax_t)src_size); *mem_usage += sz; } if (!src->index) { @@ -1913,7 +2257,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, if (!src->index) { static int warned = 0; if (!warned++) - warning("suboptimal pack - out of memory"); + warning(_("suboptimal pack - out of memory")); return 0; } *mem_usage += sizeof_delta_index(src->index); @@ -1923,9 +2267,9 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, if (!delta_buf) return 0; - if (trg_entry->delta) { + if (DELTA(trg_entry)) { /* Prefer only shallower same-sized deltas. */ - if (delta_size == trg_entry->delta_size && + if (delta_size == DELTA_SIZE(trg_entry) && src->depth + 1 >= trg->depth) { free(delta_buf); return 0; @@ -1940,7 +2284,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, free(trg_entry->delta_data); cache_lock(); if (trg_entry->delta_data) { - delta_cache_size -= trg_entry->delta_size; + delta_cache_size -= DELTA_SIZE(trg_entry); trg_entry->delta_data = NULL; } if (delta_cacheable(src_size, trg_size, delta_size)) { @@ -1952,8 +2296,8 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, free(delta_buf); } - trg_entry->delta = src_entry; - trg_entry->delta_size = delta_size; + SET_DELTA(trg_entry, src_entry); + SET_DELTA_SIZE(trg_entry, delta_size); trg->depth = src->depth + 1; return 1; @@ -1961,13 +2305,13 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, static unsigned int check_delta_limit(struct object_entry *me, unsigned int n) { - struct object_entry *child = me->delta_child; + struct object_entry *child = DELTA_CHILD(me); unsigned int m = n; while (child) { - unsigned int c = check_delta_limit(child, n + 1); + const unsigned int c = check_delta_limit(child, n + 1); if (m < c) m = c; - child = child->delta_sibling; + child = DELTA_SIBLING(child); } return m; } @@ -1978,7 +2322,7 @@ static unsigned long free_unpacked(struct unpacked *n) free_delta_index(n->index); n->index = NULL; if (n->data) { - freed_mem += n->entry->size; + freed_mem += SIZE(n->entry); FREE_AND_NULL(n->data); } n->entry = NULL; @@ -2019,7 +2363,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, while (window_memory_limit && mem_usage > window_memory_limit && count > 1) { - uint32_t tail = (idx + window - count) % window; + const uint32_t tail = (idx + window - count) % window; mem_usage -= free_unpacked(array + tail); count--; } @@ -2036,7 +2380,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, * otherwise they would become too deep. */ max_depth = depth; - if (entry->delta_child) { + if (DELTA_CHILD(entry)) { max_depth -= check_delta_limit(entry, 0); if (max_depth <= 0) goto next; @@ -2074,19 +2418,26 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, * between writes at that moment. */ if (entry->delta_data && !pack_to_stdout) { - entry->z_delta_size = do_compress(&entry->delta_data, - entry->delta_size); - cache_lock(); - delta_cache_size -= entry->delta_size; - delta_cache_size += entry->z_delta_size; - cache_unlock(); + unsigned long size; + + size = do_compress(&entry->delta_data, DELTA_SIZE(entry)); + if (size < (1U << OE_Z_DELTA_BITS)) { + entry->z_delta_size = size; + cache_lock(); + delta_cache_size -= DELTA_SIZE(entry); + delta_cache_size += entry->z_delta_size; + cache_unlock(); + } else { + FREE_AND_NULL(entry->delta_data); + entry->z_delta_size = 0; + } } /* if we made n a delta, and if n is already at max * depth, leaving it in the window is pointless. we * should evict it first. */ - if (entry->delta && max_depth <= n->depth) + if (DELTA(entry) && max_depth <= n->depth) continue; /* @@ -2094,7 +2445,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, * currently deltified object, to keep it longer. It will * be the first base object to be attempted next. */ - if (entry->delta) { + if (DELTA(entry)) { struct unpacked swap = array[best_base]; int dist = (window + idx - best_base) % window; int dst = best_base; @@ -2121,24 +2472,20 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, free(array); } -#ifndef NO_PTHREADS - -static void try_to_free_from_threads(size_t size) -{ - read_lock(); - release_pack_memory(size); - read_unlock(); -} - -static try_to_free_t old_try_to_free_routine; - /* + * The main object list is split into smaller lists, each is handed to + * one worker. + * * The main thread waits on the condition that (at least) one of the workers * has stopped working (which is indicated in the .working member of * struct thread_params). + * * When a work thread has completed its work, it sets .working to 0 and * signals the main thread and waits on the condition that .data_ready * becomes 1. + * + * The main thread steals half of the work from the worker that has + * most work left to hand it to the idle worker. */ struct thread_params { @@ -2162,18 +2509,14 @@ static pthread_cond_t progress_cond; */ static void init_threaded_search(void) { - init_recursive_mutex(&read_mutex); pthread_mutex_init(&cache_mutex, NULL); pthread_mutex_init(&progress_mutex, NULL); pthread_cond_init(&progress_cond, NULL); - old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads); } static void cleanup_threaded_search(void) { - set_try_to_free_routine(old_try_to_free_routine); pthread_cond_destroy(&progress_cond); - pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&cache_mutex); pthread_mutex_destroy(&progress_mutex); } @@ -2229,8 +2572,8 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, return; } if (progress > pack_to_stdout) - fprintf(stderr, "Delta compression using up to %d threads.\n", - delta_search_threads); + fprintf_ln(stderr, _("Delta compression using up to %d threads"), + delta_search_threads); p = xcalloc(delta_search_threads, sizeof(*p)); /* Partition the work amongst work threads. */ @@ -2270,7 +2613,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, ret = pthread_create(&p[i].thread, NULL, threaded_find_deltas, &p[i]); if (ret) - die("unable to create thread: %s", strerror(ret)); + die(_("unable to create thread: %s"), strerror(ret)); active_threads++; } @@ -2344,9 +2687,12 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, free(p); } -#else -#define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p) -#endif +static int obj_is_packed(const struct object_id *oid) +{ + return packlist_find(&to_pack, oid) || + (reuse_packfile_bitmap && + bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid)); +} static void add_tag_chain(const struct object_id *oid) { @@ -2359,13 +2705,13 @@ static void add_tag_chain(const struct object_id *oid) * it was included via bitmaps, we would not have parsed it * previously). */ - if (packlist_find(&to_pack, oid->hash, NULL)) + if (obj_is_packed(oid)) return; - tag = lookup_tag(oid); + tag = lookup_tag(the_repository, oid); while (1) { if (!tag || parse_tag(tag) || !tag->tagged) - die("unable to pack objects reachable from tag %s", + die(_("unable to pack objects reachable from tag %s"), oid_to_hex(oid)); add_object_entry(&tag->object.oid, OBJ_TAG, NULL, 0); @@ -2383,7 +2729,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag, if (starts_with(path, "refs/tags/") && /* is a tag? */ !peel_ref(path, &peeled) && /* peelable? */ - packlist_find(&to_pack, peeled.hash, NULL)) /* object packed? */ + obj_is_packed(&peeled)) /* object packed? */ add_tag_chain(oid); return 0; } @@ -2394,6 +2740,9 @@ static void prepare_pack(int window, int depth) uint32_t i, nr_deltas; unsigned n; + if (use_delta_islands) + resolve_tree_islands(the_repository, progress, &to_pack); + get_object_details(); /* @@ -2415,13 +2764,14 @@ static void prepare_pack(int window, int depth) for (i = 0; i < to_pack.nr_objects; i++) { struct object_entry *entry = to_pack.objects + i; - if (entry->delta) + if (DELTA(entry)) /* This happens if we decided to reuse existing * delta from a pack. "reuse_delta &&" is implied. */ continue; - if (entry->size < 50) + if (!entry->type_valid || + oe_size_less_than(&to_pack, entry, 50)) continue; if (entry->no_try_delta) @@ -2429,11 +2779,11 @@ static void prepare_pack(int window, int depth) if (!entry->preferred_base) { nr_deltas++; - if (entry->type < 0) - die("unable to get type of object %s", + if (oe_type(entry) < 0) + die(_("unable to get type of object %s"), oid_to_hex(&entry->idx.oid)); } else { - if (entry->type < 0) { + if (oe_type(entry) < 0) { /* * This object is not found, but we * don't have to include it anyway. @@ -2447,6 +2797,7 @@ static void prepare_pack(int window, int depth) if (nr_deltas && n > 1) { unsigned nr_done = 0; + if (progress) progress_state = start_progress(_("Compressing objects"), nr_deltas); @@ -2454,7 +2805,7 @@ static void prepare_pack(int window, int depth) ll_find_deltas(delta_list, n, window+1, depth, &nr_done); stop_progress(&progress_state); if (nr_done != nr_deltas) - die("inconsistency with delta count"); + die(_("inconsistency with delta count")); } free(delta_list); } @@ -2491,23 +2842,25 @@ static int git_pack_config(const char *k, const char *v, void *cb) use_bitmap_index_default = git_config_bool(k, v); return 0; } + if (!strcmp(k, "pack.allowpackreuse")) { + allow_pack_reuse = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "pack.threads")) { delta_search_threads = git_config_int(k, v); if (delta_search_threads < 0) - die("invalid number of threads specified (%d)", + die(_("invalid number of threads specified (%d)"), delta_search_threads); -#ifdef NO_PTHREADS - if (delta_search_threads != 1) { - warning("no threads support, ignoring %s", k); + if (!HAVE_THREADS && delta_search_threads != 1) { + warning(_("no threads support, ignoring %s"), k); delta_search_threads = 0; } -#endif return 0; } if (!strcmp(k, "pack.indexversion")) { pack_idx_opts.version = git_config_int(k, v); if (pack_idx_opts.version > 2) - die("bad pack.indexversion=%"PRIu32, + die(_("bad pack.indexversion=%"PRIu32), pack_idx_opts.version); return 0; } @@ -2525,7 +2878,7 @@ static void read_object_list_from_stdin(void) if (feof(stdin)) break; if (!ferror(stdin)) - die("fgets returned NULL, not EOF, not error!"); + die("BUG: fgets returned NULL, not EOF, not error!"); if (errno != EINTR) die_errno("fgets"); clearerr(stdin); @@ -2533,16 +2886,16 @@ static void read_object_list_from_stdin(void) } if (line[0] == '-') { if (get_oid_hex(line+1, &oid)) - die("expected edge object ID, got garbage:\n %s", + die(_("expected edge object ID, got garbage:\n %s"), line); add_preferred_base(&oid); continue; } if (parse_oid_hex(line, &oid, &p)) - die("expected object ID, got garbage:\n %s", line); + die(_("expected object ID, got garbage:\n %s"), line); add_preferred_base_object(p + 1); - add_object_entry(&oid, 0, p + 1, 0); + add_object_entry(&oid, OBJ_NONE, p + 1, 0); } } @@ -2556,6 +2909,9 @@ static void show_commit(struct commit *commit, void *data) if (write_bitmap_index) index_commit_for_bitmap(commit); + + if (use_delta_islands) + propagate_island_marks(commit); } static void show_object(struct object *obj, const char *name, void *data) @@ -2563,6 +2919,21 @@ static void show_object(struct object *obj, const char *name, void *data) add_preferred_base_object(name); add_object_entry(&obj->oid, obj->type, name, 0); obj->flags |= OBJECT_ADDED; + + if (use_delta_islands) { + const char *p; + unsigned depth; + struct object_entry *ent; + + /* the empty string is a root tree, which is depth 0 */ + depth = *name ? 1 : 0; + for (p = strchr(name, '/'); p; p = strchr(p + 1, '/')) + depth++; + + ent = packlist_find(&to_pack, &obj->oid); + if (ent && depth > oe_tree_depth(&to_pack, ent)) + oe_set_tree_depth(&to_pack, ent, depth); + } } static void show_object__ma_allow_any(struct object *obj, const char *name, void *data) @@ -2663,7 +3034,7 @@ static int ofscmp(const void *a_, const void *b_) return oidcmp(&a->object->oid, &b->object->oid); } -static void add_objects_in_unpacked_packs(struct rev_info *revs) +static void add_objects_in_unpacked_packs(void) { struct packed_git *p; struct in_pack in_pack; @@ -2671,22 +3042,22 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs) memset(&in_pack, 0, sizeof(in_pack)); - for (p = get_packed_git(the_repository); p; p = p->next) { + for (p = get_all_packs(the_repository); p; p = p->next) { struct object_id oid; struct object *o; - if (!p->pack_local || p->pack_keep) + if (!p->pack_local || p->pack_keep || p->pack_keep_in_core) continue; if (open_pack_index(p)) - die("cannot open pack index"); + die(_("cannot open pack index")); ALLOC_GROW(in_pack.array, in_pack.nr + p->num_objects, in_pack.alloc); for (i = 0; i < p->num_objects; i++) { - nth_packed_object_oid(&oid, p, i); - o = lookup_unknown_object(oid.hash); + nth_packed_object_id(&oid, p, i); + o = lookup_unknown_object(&oid); if (!(o->flags & OBJECT_ADDED)) mark_in_pack_object(o, p, &in_pack); o->flags |= OBJECT_ADDED; @@ -2706,10 +3077,10 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs) static int add_loose_object(const struct object_id *oid, const char *path, void *data) { - enum object_type type = oid_object_info(oid, NULL); + enum object_type type = oid_object_info(the_repository, oid, NULL); if (type < 0) { - warning("loose object at %s could not be examined", path); + warning(_("loose object at %s could not be examined"), path); return 0; } @@ -2735,16 +3106,17 @@ static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid) struct packed_git *p; p = (last_found != (void *)1) ? last_found : - get_packed_git(the_repository); + get_all_packs(the_repository); while (p) { - if ((!p->pack_local || p->pack_keep) && + if ((!p->pack_local || p->pack_keep || + p->pack_keep_in_core) && find_pack_entry_one(oid->hash, p)) { last_found = p; return 1; } if (p == last_found) - p = get_packed_git(the_repository); + p = get_all_packs(the_repository); else p = p->next; if (p == last_found) @@ -2774,26 +3146,26 @@ static int loosened_object_can_be_discarded(const struct object_id *oid, return 1; } -static void loosen_unused_packed_objects(struct rev_info *revs) +static void loosen_unused_packed_objects(void) { struct packed_git *p; uint32_t i; struct object_id oid; - for (p = get_packed_git(the_repository); p; p = p->next) { - if (!p->pack_local || p->pack_keep) + for (p = get_all_packs(the_repository); p; p = p->next) { + if (!p->pack_local || p->pack_keep || p->pack_keep_in_core) continue; if (open_pack_index(p)) - die("cannot open pack index"); + die(_("cannot open pack index")); for (i = 0; i < p->num_objects; i++) { - nth_packed_object_oid(&oid, p, i); - if (!packlist_find(&to_pack, oid.hash, NULL) && + nth_packed_object_id(&oid, p, i); + if (!packlist_find(&to_pack, &oid) && !has_sha1_pack_kept_or_nonlocal(&oid) && !loosened_object_can_be_discarded(&oid, p->mtime)) if (force_object_loose(&oid, p->mtime)) - die("unable to force loose object"); + die(_("unable to force loose object")); } } } @@ -2805,29 +3177,32 @@ static void loosen_unused_packed_objects(struct rev_info *revs) */ static int pack_options_allow_reuse(void) { - return pack_to_stdout && - allow_ofs_delta && - !ignore_packed_keep && + return allow_pack_reuse && + pack_to_stdout && + !ignore_packed_keep_on_disk && + !ignore_packed_keep_in_core && (!local || !have_non_local_packs) && !incremental; } static int get_object_list_from_bitmap(struct rev_info *revs) { - if (prepare_bitmap_walk(revs) < 0) + if (!(bitmap_git = prepare_bitmap_walk(revs, &filter_options))) return -1; if (pack_options_allow_reuse() && !reuse_partial_packfile_from_bitmap( + bitmap_git, &reuse_packfile, &reuse_packfile_objects, - &reuse_packfile_offset)) { + &reuse_packfile_bitmap)) { assert(reuse_packfile_objects); nr_result += reuse_packfile_objects; display_progress(progress_state, nr_result); } - traverse_bitmap_commit_list(&add_object_entry_from_bitmap); + traverse_bitmap_commit_list(bitmap_git, revs, + &add_object_entry_from_bitmap); return 0; } @@ -2846,15 +3221,22 @@ static void record_recent_commit(struct commit *commit, void *data) static void get_object_list(int ac, const char **av) { struct rev_info revs; + struct setup_revision_opt s_r_opt = { + .allow_exclude_promisor_objects = 1, + }; char line[1000]; int flags = 0; + int save_warning; - init_revisions(&revs, NULL); + repo_init_revisions(the_repository, &revs, NULL); save_commit_buffer = 0; - setup_revisions(ac, av, &revs, NULL); + setup_revisions(ac, av, &revs, &s_r_opt); /* make sure shallows are read */ - is_repository_shallow(); + is_repository_shallow(the_repository); + + save_warning = warn_on_object_refname_ambiguity; + warn_on_object_refname_ambiguity = 0; while (fgets(line, sizeof(line), stdin) != NULL) { int len = strlen(line); @@ -2872,22 +3254,27 @@ static void get_object_list(int ac, const char **av) struct object_id oid; if (get_oid_hex(line + 10, &oid)) die("not an SHA-1 '%s'", line + 10); - register_shallow(&oid); + register_shallow(the_repository, &oid); use_bitmap_index = 0; continue; } - die("not a rev '%s'", line); + die(_("not a rev '%s'"), line); } if (handle_revision_arg(line, &revs, flags, REVARG_CANNOT_BE_FILENAME)) - die("bad revision '%s'", line); + die(_("bad revision '%s'"), line); } + warn_on_object_refname_ambiguity = save_warning; + if (use_bitmap_index && !get_object_list_from_bitmap(&revs)) return; + if (use_delta_islands) + load_delta_islands(the_repository, progress); + if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - mark_edges_uninteresting(&revs, show_edge); + die(_("revision walk setup failed")); + mark_edges_uninteresting(&revs, show_edge, sparse); if (!fn_show_object) fn_show_object = show_object; @@ -2899,28 +3286,57 @@ static void get_object_list(int ac, const char **av) revs.ignore_missing_links = 1; if (add_unseen_recent_objects_to_traversal(&revs, unpack_unreachable_expiration)) - die("unable to add recent objects"); + die(_("unable to add recent objects")); if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); traverse_commit_list(&revs, record_recent_commit, record_recent_object, NULL); } if (keep_unreachable) - add_objects_in_unpacked_packs(&revs); + add_objects_in_unpacked_packs(); if (pack_loose_unreachable) add_unreachable_loose_objects(); if (unpack_unreachable) - loosen_unused_packed_objects(&revs); + loosen_unused_packed_objects(); oid_array_clear(&recent_objects); } +static void add_extra_kept_packs(const struct string_list *names) +{ + struct packed_git *p; + + if (!names->nr) + return; + + for (p = get_all_packs(the_repository); p; p = p->next) { + const char *name = basename(p->pack_name); + int i; + + if (!p->pack_local) + continue; + + for (i = 0; i < names->nr; i++) + if (!fspathcmp(name, names->items[i].string)) + break; + + if (i < names->nr) { + p->pack_keep_in_core = 1; + ignore_packed_keep_in_core = 1; + continue; + } + } +} + static int option_parse_index_version(const struct option *opt, const char *arg, int unset) { char *c; const char *val = arg; + + BUG_ON_OPT_NEG(unset); + pack_idx_opts.version = strtoul(val, &c, 10); if (pack_idx_opts.version > 2) die(_("unsupported index version %s"), val); @@ -2949,12 +3365,12 @@ static int option_parse_unpack_unreachable(const struct option *opt, int cmd_pack_objects(int argc, const char **argv, const char *prefix) { int use_internal_rev_list = 0; - int thin = 0; int shallow = 0; int all_progress_implied = 0; struct argv_array rp = ARGV_ARRAY_INIT; int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0; int rev_list_index = 0; + struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; struct option pack_objects_options[] = { OPT_SET_INT('q', "quiet", &progress, N_("do not show progress meter"), 0), @@ -2965,9 +3381,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "all-progress-implied", &all_progress_implied, N_("similar to --all-progress when progress meter is shown")), - { OPTION_CALLBACK, 0, "index-version", NULL, N_("version[,offset]"), + OPT_CALLBACK_F(0, "index-version", NULL, N_("<version>[,<offset>]"), N_("write the pack index file in the specified idx format version"), - 0, option_parse_index_version }, + PARSE_OPT_NONEG, option_parse_index_version), OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit, N_("maximum size of each output pack file")), OPT_BOOL(0, "local", &local, @@ -2992,18 +3408,18 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) N_("do not create an empty pack output")), OPT_BOOL(0, "revs", &use_internal_rev_list, N_("read revision arguments from standard input")), - { OPTION_SET_INT, 0, "unpacked", &rev_list_unpacked, NULL, - N_("limit the objects to those that are not yet packed"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, - { OPTION_SET_INT, 0, "all", &rev_list_all, NULL, - N_("include objects reachable from any reference"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, - { OPTION_SET_INT, 0, "reflog", &rev_list_reflog, NULL, - N_("include objects referred by reflog entries"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, - { OPTION_SET_INT, 0, "indexed-objects", &rev_list_index, NULL, - N_("include objects referred to by the index"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + OPT_SET_INT_F(0, "unpacked", &rev_list_unpacked, + N_("limit the objects to those that are not yet packed"), + 1, PARSE_OPT_NONEG), + OPT_SET_INT_F(0, "all", &rev_list_all, + N_("include objects reachable from any reference"), + 1, PARSE_OPT_NONEG), + OPT_SET_INT_F(0, "reflog", &rev_list_reflog, + N_("include objects referred by reflog entries"), + 1, PARSE_OPT_NONEG), + OPT_SET_INT_F(0, "indexed-objects", &rev_list_index, + N_("include objects referred to by the index"), + 1, PARSE_OPT_NONEG), OPT_BOOL(0, "stdout", &pack_to_stdout, N_("output pack to stdout")), OPT_BOOL(0, "include-tag", &include_tag, @@ -3012,33 +3428,52 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) N_("keep unreachable objects")), OPT_BOOL(0, "pack-loose-unreachable", &pack_loose_unreachable, N_("pack loose unreachable objects")), - { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, N_("time"), + OPT_CALLBACK_F(0, "unpack-unreachable", NULL, N_("time"), N_("unpack unreachable objects newer than <time>"), - PARSE_OPT_OPTARG, option_parse_unpack_unreachable }, + PARSE_OPT_OPTARG, option_parse_unpack_unreachable), + OPT_BOOL(0, "sparse", &sparse, + N_("use the sparse reachability algorithm")), OPT_BOOL(0, "thin", &thin, N_("create thin packs")), OPT_BOOL(0, "shallow", &shallow, N_("create packs suitable for shallow fetches")), - OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep, + OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep_on_disk, N_("ignore packs that have companion .keep file")), + OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"), + N_("ignore this pack")), OPT_INTEGER(0, "compression", &pack_compression_level, N_("pack compression level")), OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents, N_("do not hide commits by grafts"), 0), OPT_BOOL(0, "use-bitmap-index", &use_bitmap_index, N_("use a bitmap index if available to speed up counting objects")), - OPT_BOOL(0, "write-bitmap-index", &write_bitmap_index, - N_("write a bitmap index together with the pack index")), + OPT_SET_INT(0, "write-bitmap-index", &write_bitmap_index, + N_("write a bitmap index together with the pack index"), + WRITE_BITMAP_TRUE), + OPT_SET_INT_F(0, "write-bitmap-index-quiet", + &write_bitmap_index, + N_("write a bitmap index if possible"), + WRITE_BITMAP_QUIET, PARSE_OPT_HIDDEN), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), - { OPTION_CALLBACK, 0, "missing", NULL, N_("action"), + OPT_CALLBACK_F(0, "missing", NULL, N_("action"), N_("handling for missing objects"), PARSE_OPT_NONEG, - option_parse_missing_action }, + option_parse_missing_action), OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects, N_("do not pack objects in promisor packfiles")), + OPT_BOOL(0, "delta-islands", &use_delta_islands, + N_("respect islands during delta compression")), OPT_END(), }; - check_replace_refs = 0; + if (DFS_NUM_STATES > (1 << OE_DFS_STATE_BITS)) + BUG("too many dfs states, increase OE_DFS_STATE_BITS"); + + read_replace_refs = 0; + + sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1); + prepare_repo_settings(the_repository); + if (sparse < 0) + sparse = the_repository->settings.pack_use_sparse; reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); @@ -3054,6 +3489,17 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (pack_to_stdout != !base_name || argc) usage_with_options(pack_usage, pack_objects_options); + if (depth >= (1 << OE_DEPTH_BITS)) { + warning(_("delta chain depth %d is too deep, forcing %d"), + depth, (1 << OE_DEPTH_BITS) - 1); + depth = (1 << OE_DEPTH_BITS) - 1; + } + if (cache_max_small_delta_size >= (1U << OE_Z_DELTA_BITS)) { + warning(_("pack.deltaCacheLimit is too high, forcing %d"), + (1U << OE_Z_DELTA_BITS) - 1); + cache_max_small_delta_size = (1U << OE_Z_DELTA_BITS) - 1; + } + argv_array_push(&rp, "pack-objects"); if (thin) { use_internal_rev_list = 1; @@ -3085,42 +3531,41 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) fetch_if_missing = 0; argv_array_push(&rp, "--exclude-promisor-objects"); } + if (unpack_unreachable || keep_unreachable || pack_loose_unreachable) + use_internal_rev_list = 1; if (!reuse_object) reuse_delta = 0; if (pack_compression_level == -1) pack_compression_level = Z_DEFAULT_COMPRESSION; else if (pack_compression_level < 0 || pack_compression_level > Z_BEST_COMPRESSION) - die("bad pack compression level %d", pack_compression_level); + die(_("bad pack compression level %d"), pack_compression_level); if (!delta_search_threads) /* --threads=0 means autodetect */ delta_search_threads = online_cpus(); -#ifdef NO_PTHREADS - if (delta_search_threads != 1) - warning("no threads support, ignoring --threads"); -#endif + if (!HAVE_THREADS && delta_search_threads != 1) + warning(_("no threads support, ignoring --threads")); if (!pack_to_stdout && !pack_size_limit) pack_size_limit = pack_size_limit_cfg; if (pack_to_stdout && pack_size_limit) - die("--max-pack-size cannot be used to build a pack for transfer."); + die(_("--max-pack-size cannot be used to build a pack for transfer")); if (pack_size_limit && pack_size_limit < 1024*1024) { - warning("minimum pack size limit is 1 MiB"); + warning(_("minimum pack size limit is 1 MiB")); pack_size_limit = 1024*1024; } if (!pack_to_stdout && thin) - die("--thin cannot be used to build an indexable pack."); + die(_("--thin cannot be used to build an indexable pack")); if (keep_unreachable && unpack_unreachable) - die("--keep-unreachable and --unpack-unreachable are incompatible."); + die(_("--keep-unreachable and --unpack-unreachable are incompatible")); if (!rev_list_all || !rev_list_reflog || !rev_list_index) unpack_unreachable_expiration = 0; if (filter_options.choice) { if (!pack_to_stdout) - die("cannot use --filter without --stdout."); - use_bitmap_index = 0; + die(_("cannot use --filter without --stdout")); } /* @@ -3139,31 +3584,35 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) use_bitmap_index = use_bitmap_index_default; /* "hard" reasons not to use bitmaps; these just won't work at all */ - if (!use_internal_rev_list || (!pack_to_stdout && write_bitmap_index) || is_repository_shallow()) + if (!use_internal_rev_list || (!pack_to_stdout && write_bitmap_index) || is_repository_shallow(the_repository)) use_bitmap_index = 0; if (pack_to_stdout || !rev_list_all) write_bitmap_index = 0; + if (use_delta_islands) + argv_array_push(&rp, "--topo-order"); + if (progress && all_progress_implied) progress = 2; - if (ignore_packed_keep) { + add_extra_kept_packs(&keep_pack_list); + if (ignore_packed_keep_on_disk) { struct packed_git *p; - for (p = get_packed_git(the_repository); p; p = p->next) + for (p = get_all_packs(the_repository); p; p = p->next) if (p->pack_local && p->pack_keep) break; if (!p) /* no keep-able packs found */ - ignore_packed_keep = 0; + ignore_packed_keep_on_disk = 0; } if (local) { /* - * unlike ignore_packed_keep above, we do not want to - * unset "local" based on looking at packs, as it - * also covers non-local objects + * unlike ignore_packed_keep_on_disk above, we do not + * want to unset "local" based on looking at packs, as + * it also covers non-local objects */ struct packed_git *p; - for (p = get_packed_git(the_repository); p; p = p->next) { + for (p = get_all_packs(the_repository); p; p = p->next) { if (!p->pack_local) { have_non_local_packs = 1; break; @@ -3171,8 +3620,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } } + trace2_region_enter("pack-objects", "enumerate-objects", + the_repository); + prepare_packing_data(the_repository, &to_pack); + if (progress) - progress_state = start_progress(_("Counting objects"), 0); + progress_state = start_progress(_("Enumerating objects"), 0); if (!use_internal_rev_list) read_object_list_from_stdin(); else { @@ -3183,15 +3636,29 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (include_tag && nr_result) for_each_ref(add_ref_tag, NULL); stop_progress(&progress_state); + trace2_region_leave("pack-objects", "enumerate-objects", + the_repository); if (non_empty && !nr_result) return 0; - if (nr_result) + if (nr_result) { + trace2_region_enter("pack-objects", "prepare-pack", + the_repository); prepare_pack(window, depth); + trace2_region_leave("pack-objects", "prepare-pack", + the_repository); + } + + trace2_region_enter("pack-objects", "write-pack-file", the_repository); write_pack_file(); + trace2_region_leave("pack-objects", "write-pack-file", the_repository); + if (progress) - fprintf(stderr, "Total %"PRIu32" (delta %"PRIu32")," - " reused %"PRIu32" (delta %"PRIu32")\n", - written, written_delta, reused, reused_delta); + fprintf_ln(stderr, + _("Total %"PRIu32" (delta %"PRIu32")," + " reused %"PRIu32" (delta %"PRIu32")," + " pack-reused %"PRIu32), + written, written_delta, reused, reused_delta, + reuse_packfile_objects); return 0; } diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 354478a127..178e3409b7 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -20,7 +20,7 @@ static int load_all_packs, verbose, alt_odb; struct llist_item { struct llist_item *next; - const unsigned char *sha1; + const struct object_id *oid; }; static struct llist { struct llist_item *front; @@ -32,14 +32,10 @@ static struct pack_list { struct pack_list *next; struct packed_git *pack; struct llist *unique_objects; - struct llist *all_objects; + struct llist *remaining_objects; + size_t all_objects_size; } *local_packs = NULL, *altodb_packs = NULL; -struct pll { - struct pll *next; - struct pack_list *pl; -}; - static struct llist_item *free_nodes; static inline void llist_item_put(struct llist_item *item) @@ -63,15 +59,6 @@ static inline struct llist_item *llist_item_get(void) return new_item; } -static void llist_free(struct llist *list) -{ - while ((list->back = list->front)) { - list->front = list->front->next; - llist_item_put(list->back); - } - free(list); -} - static inline void llist_init(struct llist **list) { *list = xmalloc(sizeof(struct llist)); @@ -90,14 +77,14 @@ static struct llist * llist_copy(struct llist *list) return ret; new_item = ret->front = llist_item_get(); - new_item->sha1 = list->front->sha1; + new_item->oid = list->front->oid; old_item = list->front->next; while (old_item) { prev = new_item; new_item = llist_item_get(); prev->next = new_item; - new_item->sha1 = old_item->sha1; + new_item->oid = old_item->oid; old_item = old_item->next; } new_item->next = NULL; @@ -108,10 +95,10 @@ static struct llist * llist_copy(struct llist *list) static inline struct llist_item *llist_insert(struct llist *list, struct llist_item *after, - const unsigned char *sha1) + const struct object_id *oid) { struct llist_item *new_item = llist_item_get(); - new_item->sha1 = sha1; + new_item->oid = oid; new_item->next = NULL; if (after != NULL) { @@ -131,21 +118,21 @@ static inline struct llist_item *llist_insert(struct llist *list, } static inline struct llist_item *llist_insert_back(struct llist *list, - const unsigned char *sha1) + const struct object_id *oid) { - return llist_insert(list, list->back, sha1); + return llist_insert(list, list->back, oid); } static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, - const unsigned char *sha1, struct llist_item *hint) + const struct object_id *oid, struct llist_item *hint) { struct llist_item *prev = NULL, *l; l = (hint == NULL) ? list->front : hint; while (l) { - int cmp = hashcmp(l->sha1, sha1); + int cmp = oidcmp(l->oid, oid); if (cmp > 0) { /* we insert before this entry */ - return llist_insert(list, prev, sha1); + return llist_insert(list, prev, oid); } if (!cmp) { /* already exists */ return l; @@ -154,11 +141,11 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, l = l->next; } /* insert at the end */ - return llist_insert_back(list, sha1); + return llist_insert_back(list, oid); } /* returns a pointer to an item in front of sha1 */ -static inline struct llist_item * llist_sorted_remove(struct llist *list, const unsigned char *sha1, struct llist_item *hint) +static inline struct llist_item * llist_sorted_remove(struct llist *list, const struct object_id *oid, struct llist_item *hint) { struct llist_item *prev, *l; @@ -166,7 +153,7 @@ redo_from_start: l = (hint == NULL) ? list->front : hint; prev = NULL; while (l) { - int cmp = hashcmp(l->sha1, sha1); + const int cmp = oidcmp(l->oid, oid); if (cmp > 0) /* not in list, since sorted */ return prev; if (!cmp) { /* found */ @@ -201,7 +188,7 @@ static void llist_sorted_difference_inplace(struct llist *A, b = B->front; while (b) { - hint = llist_sorted_remove(A, b->sha1, hint); + hint = llist_sorted_remove(A, b->oid, hint); b = b->next; } } @@ -252,24 +239,32 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; const unsigned char *p1_base, *p2_base; struct llist_item *p1_hint = NULL, *p2_hint = NULL; + const unsigned int hashsz = the_hash_algo->rawsz; + + if (!p1->unique_objects) + p1->unique_objects = llist_copy(p1->remaining_objects); + if (!p2->unique_objects) + p2->unique_objects = llist_copy(p2->remaining_objects); p1_base = p1->pack->index_data; p2_base = p2->pack->index_data; p1_base += 256 * 4 + ((p1->pack->index_version < 2) ? 4 : 8); p2_base += 256 * 4 + ((p2->pack->index_version < 2) ? 4 : 8); - p1_step = (p1->pack->index_version < 2) ? 24 : 20; - p2_step = (p2->pack->index_version < 2) ? 24 : 20; + p1_step = hashsz + ((p1->pack->index_version < 2) ? 4 : 0); + p2_step = hashsz + ((p2->pack->index_version < 2) ? 4 : 0); while (p1_off < p1->pack->num_objects * p1_step && p2_off < p2->pack->num_objects * p2_step) { - int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); + const int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); /* cmp ~ p1 - p2 */ if (cmp == 0) { p1_hint = llist_sorted_remove(p1->unique_objects, - p1_base + p1_off, p1_hint); + (const struct object_id *)(p1_base + p1_off), + p1_hint); p2_hint = llist_sorted_remove(p2->unique_objects, - p1_base + p1_off, p2_hint); + (const struct object_id *)(p1_base + p1_off), + p2_hint); p1_off += p1_step; p2_off += p2_step; continue; @@ -282,90 +277,19 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) } } -static void pll_free(struct pll *l) -{ - struct pll *old; - struct pack_list *opl; - - while (l) { - old = l; - while (l->pl) { - opl = l->pl; - l->pl = opl->next; - free(opl); - } - l = l->next; - free(old); - } -} - -/* all the permutations have to be free()d at the same time, - * since they refer to each other - */ -static struct pll * get_permutations(struct pack_list *list, int n) -{ - struct pll *subset, *ret = NULL, *new_pll = NULL; - - if (list == NULL || pack_list_size(list) < n || n == 0) - return NULL; - - if (n == 1) { - while (list) { - new_pll = xmalloc(sizeof(*new_pll)); - new_pll->pl = NULL; - pack_list_insert(&new_pll->pl, list); - new_pll->next = ret; - ret = new_pll; - list = list->next; - } - return ret; - } - - while (list->next) { - subset = get_permutations(list->next, n - 1); - while (subset) { - new_pll = xmalloc(sizeof(*new_pll)); - new_pll->pl = subset->pl; - pack_list_insert(&new_pll->pl, list); - new_pll->next = ret; - ret = new_pll; - subset = subset->next; - } - list = list->next; - } - return ret; -} - -static int is_superset(struct pack_list *pl, struct llist *list) -{ - struct llist *diff; - - diff = llist_copy(list); - - while (pl) { - llist_sorted_difference_inplace(diff, pl->all_objects); - if (diff->size == 0) { /* we're done */ - llist_free(diff); - return 1; - } - pl = pl->next; - } - llist_free(diff); - return 0; -} - static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; const unsigned char *p1_base, *p2_base; + const unsigned int hashsz = the_hash_algo->rawsz; p1_base = p1->index_data; p2_base = p2->index_data; p1_base += 256 * 4 + ((p1->index_version < 2) ? 4 : 8); p2_base += 256 * 4 + ((p2->index_version < 2) ? 4 : 8); - p1_step = (p1->index_version < 2) ? 24 : 20; - p2_step = (p2->index_version < 2) ? 24 : 20; + p1_step = hashsz + ((p1->index_version < 2) ? 4 : 0); + p2_step = hashsz + ((p2->index_version < 2) ? 4 : 0); while (p1_off < p1->num_objects * p1_step && p2_off < p2->num_objects * p2_step) @@ -417,14 +341,58 @@ static inline off_t pack_set_bytecount(struct pack_list *pl) return ret; } +static int cmp_remaining_objects(const void *a, const void *b) +{ + struct pack_list *pl_a = *((struct pack_list **)a); + struct pack_list *pl_b = *((struct pack_list **)b); + + if (pl_a->remaining_objects->size == pl_b->remaining_objects->size) { + /* have the same remaining_objects, big pack first */ + if (pl_a->all_objects_size == pl_b->all_objects_size) + return 0; + else if (pl_a->all_objects_size < pl_b->all_objects_size) + return 1; + else + return -1; + } else if (pl_a->remaining_objects->size < pl_b->remaining_objects->size) { + /* sort by remaining objects, more objects first */ + return 1; + } else { + return -1; + } +} + +/* Sort pack_list, greater size of remaining_objects first */ +static void sort_pack_list(struct pack_list **pl) +{ + struct pack_list **ary, *p; + int i; + size_t n = pack_list_size(*pl); + + if (n < 2) + return; + + /* prepare an array of packed_list for easier sorting */ + ary = xcalloc(n, sizeof(struct pack_list *)); + for (n = 0, p = *pl; p; p = p->next) + ary[n++] = p; + + QSORT(ary, n, cmp_remaining_objects); + + /* link them back again */ + for (i = 0; i < n - 1; i++) + ary[i]->next = ary[i + 1]; + ary[n - 1]->next = NULL; + *pl = ary[0]; + + free(ary); +} + + static void minimize(struct pack_list **min) { - struct pack_list *pl, *unique = NULL, - *non_unique = NULL, *min_perm = NULL; - struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm; - struct llist *missing; - off_t min_perm_size = 0, perm_size; - int n; + struct pack_list *pl, *unique = NULL, *non_unique = NULL; + struct llist *missing, *unique_pack_objects; pl = local_packs; while (pl) { @@ -438,53 +406,41 @@ static void minimize(struct pack_list **min) missing = llist_copy(all_objects); pl = unique; while (pl) { - llist_sorted_difference_inplace(missing, pl->all_objects); + llist_sorted_difference_inplace(missing, pl->remaining_objects); pl = pl->next; } + *min = unique; + /* return if there are no objects missing from the unique set */ if (missing->size == 0) { - *min = unique; free(missing); return; } - /* find the permutations which contain all missing objects */ - for (n = 1; n <= pack_list_size(non_unique) && !perm_ok; n++) { - perm_all = perm = get_permutations(non_unique, n); - while (perm) { - if (is_superset(perm->pl, missing)) { - new_perm = xmalloc(sizeof(struct pll)); - memcpy(new_perm, perm, sizeof(struct pll)); - new_perm->next = perm_ok; - perm_ok = new_perm; - } - perm = perm->next; - } - if (perm_ok) - break; - pll_free(perm_all); - } - if (perm_ok == NULL) - die("Internal error: No complete sets found!"); - - /* find the permutation with the smallest size */ - perm = perm_ok; - while (perm) { - perm_size = pack_set_bytecount(perm->pl); - if (!min_perm_size || min_perm_size > perm_size) { - min_perm_size = perm_size; - min_perm = perm->pl; - } - perm = perm->next; - } - *min = min_perm; - /* add the unique packs to the list */ - pl = unique; + unique_pack_objects = llist_copy(all_objects); + llist_sorted_difference_inplace(unique_pack_objects, missing); + + /* remove unique pack objects from the non_unique packs */ + pl = non_unique; while (pl) { - pack_list_insert(min, pl); + llist_sorted_difference_inplace(pl->remaining_objects, unique_pack_objects); pl = pl->next; } + + while (non_unique) { + /* sort the non_unique packs, greater size of remaining_objects first */ + sort_pack_list(&non_unique); + if (non_unique->remaining_objects->size == 0) + break; + + pack_list_insert(min, non_unique); + + for (pl = non_unique->next; pl && pl->remaining_objects->size > 0; pl = pl->next) + llist_sorted_difference_inplace(pl->remaining_objects, non_unique->remaining_objects); + + non_unique = non_unique->next; + } } static void load_all_objects(void) @@ -496,10 +452,10 @@ static void load_all_objects(void) while (pl) { hint = NULL; - l = pl->all_objects->front; + l = pl->remaining_objects->front; while (l) { hint = llist_insert_sorted_unique(all_objects, - l->sha1, hint); + l->oid, hint); l = l->next; } pl = pl->next; @@ -507,7 +463,7 @@ static void load_all_objects(void) /* remove objects present in remote packs */ pl = altodb_packs; while (pl) { - llist_sorted_difference_inplace(all_objects, pl->all_objects); + llist_sorted_difference_inplace(all_objects, pl->remaining_objects); pl = pl->next; } } @@ -532,11 +488,10 @@ static void scan_alt_odb_packs(void) while (alt) { local = local_packs; while (local) { - llist_sorted_difference_inplace(local->unique_objects, - alt->all_objects); + llist_sorted_difference_inplace(local->remaining_objects, + alt->remaining_objects); local = local->next; } - llist_sorted_difference_inplace(all_objects, alt->all_objects); alt = alt->next; } } @@ -551,20 +506,20 @@ static struct pack_list * add_pack(struct packed_git *p) return NULL; l.pack = p; - llist_init(&l.all_objects); + llist_init(&l.remaining_objects); if (open_pack_index(p)) return NULL; base = p->index_data; base += 256 * 4 + ((p->index_version < 2) ? 4 : 8); - step = (p->index_version < 2) ? 24 : 20; + step = the_hash_algo->rawsz + ((p->index_version < 2) ? 4 : 0); while (off < p->num_objects * step) { - llist_insert_back(l.all_objects, base + off); + llist_insert_back(l.remaining_objects, (const struct object_id *)(base + off)); off += step; } - /* this list will be pruned in cmp_two_packs later */ - l.unique_objects = llist_copy(l.all_objects); + l.all_objects_size = l.remaining_objects->size; + l.unique_objects = NULL; if (p->pack_local) return pack_list_insert(&local_packs, &l); else @@ -573,7 +528,7 @@ static struct pack_list * add_pack(struct packed_git *p) static struct pack_list * add_pack_file(const char *filename) { - struct packed_git *p = get_packed_git(the_repository); + struct packed_git *p = get_all_packs(the_repository); if (strlen(filename) < 40) die("Bad pack filename: %s", filename); @@ -588,7 +543,7 @@ static struct pack_list * add_pack_file(const char *filename) static void load_all(void) { - struct packed_git *p = get_packed_git(the_repository); + struct packed_git *p = get_all_packs(the_repository); while (p) { add_pack(p); @@ -599,10 +554,10 @@ static void load_all(void) int cmd_pack_redundant(int argc, const char **argv, const char *prefix) { int i; - struct pack_list *min, *red, *pl; + struct pack_list *min = NULL, *red, *pl; struct llist *ignore; - unsigned char *sha1; - char buf[42]; /* 40 byte sha1 + \n + \0 */ + struct object_id *oid; + char buf[GIT_MAX_HEXSZ + 2]; /* hex hash + \n + \0 */ if (argc == 2 && !strcmp(argv[1], "-h")) usage(pack_redundant_usage); @@ -642,7 +597,6 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) load_all_objects(); - cmp_local_packs(); if (alt_odb) scan_alt_odb_packs(); @@ -650,19 +604,21 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) llist_init(&ignore); if (!isatty(0)) { while (fgets(buf, sizeof(buf), stdin)) { - sha1 = xmalloc(20); - if (get_sha1_hex(buf, sha1)) - die("Bad sha1 on stdin: %s", buf); - llist_insert_sorted_unique(ignore, sha1, NULL); + oid = xmalloc(sizeof(*oid)); + if (get_oid_hex(buf, oid)) + die("Bad object ID on stdin: %s", buf); + llist_insert_sorted_unique(ignore, oid, NULL); } } llist_sorted_difference_inplace(all_objects, ignore); pl = local_packs; while (pl) { - llist_sorted_difference_inplace(pl->unique_objects, ignore); + llist_sorted_difference_inplace(pl->remaining_objects, ignore); pl = pl->next; } + cmp_local_packs(); + minimize(&min); if (verbose) { @@ -685,7 +641,7 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) pl = red = pack_list_difference(local_packs, min); while (pl) { printf("%s\n%s\n", - sha1_pack_index_name(pl->pack->sha1), + sha1_pack_index_name(pl->pack->hash), pl->pack->pack_name); pl = pl->next; } diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c index b106a392a4..cfbd5c36c7 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -1,6 +1,8 @@ #include "builtin.h" +#include "config.h" #include "parse-options.h" #include "refs.h" +#include "repository.h" static char const * const pack_refs_usage[] = { N_("git pack-refs [<options>]"), @@ -15,7 +17,8 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) OPT_BIT(0, "prune", &flags, N_("prune loose refs (default)"), PACK_REFS_PRUNE), OPT_END(), }; + git_config(git_default_config, NULL); if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0)) usage_with_options(pack_refs_usage, opts); - return refs_pack_refs(get_main_ref_store(), flags); + return refs_pack_refs(get_main_ref_store(the_repository), flags); } diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 970d0d30b4..822ffff51f 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,15 +1,12 @@ +#include "cache.h" #include "builtin.h" #include "config.h" +#include "diff.h" static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result) { - char name[50]; - - if (!patchlen) - return; - - memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1); - printf("%s %s\n", oid_to_hex(result), name); + if (patchlen) + printf("%s %s\n", oid_to_hex(result), oid_to_hex(id)); } static int remove_space(char *line) @@ -54,30 +51,14 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -static void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx) -{ - unsigned char hash[GIT_MAX_RAWSZ]; - unsigned short carry = 0; - int i; - - git_SHA1_Final(hash, ctx); - git_SHA1_Init(ctx); - /* 20-byte sum, with carry */ - for (i = 0; i < GIT_SHA1_RAWSZ; ++i) { - carry += result->hash[i] + hash[i]; - result->hash[i] = carry; - carry >>= 8; - } -} - static int get_one_patchid(struct object_id *next_oid, struct object_id *result, struct strbuf *line_buf, int stable) { int patchlen = 0, found_next = 0; int before = -1, after = -1; - git_SHA_CTX ctx; + git_hash_ctx ctx; - git_SHA1_Init(&ctx); + the_hash_algo->init_fn(&ctx); oidclr(result); while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { @@ -137,7 +118,7 @@ static int get_one_patchid(struct object_id *next_oid, struct object_id *result, /* Compute the sha without whitespace */ len = remove_space(line); patchlen += len; - git_SHA1_Update(&ctx, line, len); + the_hash_algo->update_fn(&ctx, line, len); } if (!found_next) diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index 419238171d..b7b9281a8c 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -1,53 +1,12 @@ #include "builtin.h" -#include "cache.h" -#include "progress.h" #include "parse-options.h" -#include "packfile.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_sha1_pack(oid->hash)) - 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; @@ -62,6 +21,11 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, prune_packed_options, prune_packed_usage, 0); + if (argc > 0) + usage_msg_opt(_("too many arguments"), + prune_packed_usage, + prune_packed_options); + prune_packed_objects(opts); return 0; } diff --git a/builtin/prune.c b/builtin/prune.c index 38ced18dad..02c6ab7cba 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -6,6 +6,9 @@ #include "reachable.h" #include "parse-options.h" #include "progress.h" +#include "prune-packed.h" +#include "object-store.h" +#include "shallow.h" static const char * const prune_usage[] = { N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"), @@ -30,16 +33,39 @@ static int prune_tmp_file(const char *fullpath) return 0; } +static void perform_reachability_traversal(struct rev_info *revs) +{ + static int initialized; + struct progress *progress = NULL; + + if (initialized) + return; + + if (show_progress) + progress = start_delayed_progress(_("Checking connectivity"), 0); + mark_reachable_objects(revs, 1, expire, progress); + stop_progress(&progress); + initialized = 1; +} + +static int is_object_reachable(const struct object_id *oid, + struct rev_info *revs) +{ + struct object *obj; + + perform_reachability_traversal(revs); + + obj = lookup_object(the_repository, oid); + return obj && (obj->flags & SEEN); +} + static int prune_object(const struct object_id *oid, const char *fullpath, void *data) { + struct rev_info *revs = data; struct stat st; - /* - * Do we know about this object? - * It must have been reachable - */ - if (lookup_object(oid->hash)) + if (is_object_reachable(oid, revs)) return 0; if (lstat(fullpath, &st)) { @@ -50,7 +76,8 @@ static int prune_object(const struct object_id *oid, const char *fullpath, if (st.st_mtime > expire) return 0; if (show_only || verbose) { - enum object_type type = oid_object_info(oid, NULL); + enum object_type type = oid_object_info(the_repository, oid, + NULL); printf("%s %s\n", oid_to_hex(oid), (type > 0) ? type_name(type) : "unknown"); } @@ -100,7 +127,6 @@ static void remove_temporary_files(const char *path) int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; - struct progress *progress = NULL; int exclude_promisor_objects = 0; const struct option options[] = { OPT__DRY_RUN(&show_only, N_("do not remove, show only")), @@ -116,9 +142,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix) expire = TIME_MAX; save_commit_buffer = 0; - check_replace_refs = 0; + read_replace_refs = 0; ref_paranoia = 1; - init_revisions(&revs, prefix); + repo_init_revisions(the_repository, &revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); @@ -140,17 +166,13 @@ int cmd_prune(int argc, const char **argv, const char *prefix) if (show_progress == -1) show_progress = isatty(2); - if (show_progress) - progress = start_delayed_progress(_("Checking connectivity"), 0); if (exclude_promisor_objects) { fetch_if_missing = 0; revs.exclude_promisor_objects = 1; } - mark_reachable_objects(&revs, 1, expire, progress); - stop_progress(&progress); for_each_loose_file_in_objdir(get_object_directory(), prune_object, - prune_cruft, prune_subdir, NULL); + prune_cruft, prune_subdir, &revs); prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0); remove_temporary_files(get_object_directory()); @@ -158,8 +180,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix) remove_temporary_files(s); free(s); - if (is_repository_shallow()) - prune_shallow(show_only); + if (is_repository_shallow(the_repository)) { + perform_reachability_traversal(&revs); + prune_shallow(show_only ? PRUNE_SHOW_ONLY : 0); + } return 0; } diff --git a/builtin/pull.c b/builtin/pull.c index e32d6cd5b4..00e5857a8d 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -5,50 +5,41 @@ * * Fetch one or more remote refs and merge it/them into the current HEAD. */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "builtin.h" #include "parse-options.h" -#include "exec_cmd.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" #include "refs.h" +#include "refspec.h" #include "revision.h" #include "submodule.h" #include "submodule-config.h" #include "tempfile.h" #include "lockfile.h" #include "wt-status.h" - -enum rebase_type { - REBASE_INVALID = -1, - REBASE_FALSE = 0, - REBASE_TRUE, - REBASE_PRESERVE, - REBASE_INTERACTIVE -}; +#include "commit-reach.h" +#include "sequencer.h" /** * Parses the value of --rebase. If value is a false value, returns * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is - * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with - * a fatal error if fatal is true, otherwise returns REBASE_INVALID. + * "merges", returns REBASE_MERGES. If value is "preserve", returns + * REBASE_PRESERVE. If value is a invalid value, dies with a fatal error if + * fatal is true, otherwise returns REBASE_INVALID. */ static enum rebase_type parse_config_rebase(const char *key, const char *value, int fatal) { - int v = git_parse_maybe_bool(value); - - if (!v) - return REBASE_FALSE; - else if (v > 0) - return REBASE_TRUE; - else if (!strcmp(value, "preserve")) - return REBASE_PRESERVE; - else if (!strcmp(value, "interactive")) - return REBASE_INTERACTIVE; + enum rebase_type v = rebase_parse_value(value); + if (v != REBASE_INVALID) + return v; if (fatal) die(_("Invalid value for %s: %s"), key, value); @@ -90,10 +81,12 @@ static char *opt_signoff; static char *opt_squash; static char *opt_commit; static char *opt_edit; +static char *cleanup_arg; static char *opt_ff; static char *opt_verify_signatures; static int opt_autostash = -1; static int config_autostash; +static int check_trust_level = 1; static struct argv_array opt_strategies = ARGV_ARRAY_INIT; static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT; static char *opt_gpg_sign; @@ -115,6 +108,9 @@ static char *opt_update_shallow; static char *opt_refmap; 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 */ @@ -122,17 +118,17 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "progress", &opt_progress, NULL, N_("force progress reporting"), PARSE_OPT_NOARG), - { OPTION_CALLBACK, 0, "recurse-submodules", + OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"), N_("control for recursive fetching of submodules"), - PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules }, + PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), /* Options passed to git-merge or git-rebase */ OPT_GROUP(N_("Options related to merging")), - { OPTION_CALLBACK, 'r', "rebase", &opt_rebase, - "false|true|preserve|interactive", + OPT_CALLBACK_F('r', "rebase", &opt_rebase, + "(false|true|merges|preserve|interactive)", N_("incorporate changes by rebasing rather than merging"), - PARSE_OPT_OPTARG, parse_opt_rebase }, + PARSE_OPT_OPTARG, parse_opt_rebase), OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, N_("do not show a diffstat at the end of the merge"), PARSE_OPT_NOARG | PARSE_OPT_NONEG), @@ -157,6 +153,7 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "edit", &opt_edit, NULL, N_("edit message before committing"), PARSE_OPT_NOARG), + OPT_CLEANUP(&cleanup_arg), OPT_PASSTHRU(0, "ff", &opt_ff, NULL, N_("allow fast-forward"), PARSE_OPT_NOARG), @@ -167,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), @@ -211,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), @@ -220,12 +226,24 @@ 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, + N_("set upstream for git pull/fetch"), + PARSE_OPT_NOARG), OPT_END() }; @@ -326,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; } @@ -334,6 +368,8 @@ static enum rebase_type config_get_rebase(void) */ static int git_pull_config(const char *var, const char *value, void *cb) { + int status; + if (!strcmp(var, "rebase.autostash")) { config_autostash = git_config_bool(var, value); return 0; @@ -341,7 +377,14 @@ static int git_pull_config(const char *var, const char *value, void *cb) recurse_submodules = git_config_bool(var, value) ? RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; return 0; + } else if (!strcmp(var, "gpg.mintrustlevel")) { + check_trust_level = 0; } + + status = git_gpg_config(var, value, cb); + if (status) + return status; + return git_default_config(var, value, cb); } @@ -351,16 +394,17 @@ static int git_pull_config(const char *var, const char *value, void *cb) */ static void get_merge_heads(struct oid_array *merge_heads) { - const char *filename = git_path_fetch_head(); + const char *filename = git_path_fetch_head(the_repository); FILE *fp; struct strbuf sb = STRBUF_INIT; struct object_id oid; fp = xfopen(filename, "r"); while (strbuf_getline_lf(&sb, fp) != EOF) { - if (get_oid_hex(sb.buf, &oid)) - continue; /* invalid line: does not start with SHA1 */ - if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t")) + const char *p; + if (parse_oid_hex(sb.buf, &oid, &p)) + continue; /* invalid line: does not start with object ID */ + if (starts_with(p, "\tnot-for-merge\t")) continue; /* ref is not-for-merge */ oid_array_append(merge_heads, &oid); } @@ -534,12 +578,19 @@ static int run_fetch(const char *repo, const char **refspecs) argv_array_push(&args, opt_ipv4); if (opt_ipv6) argv_array_push(&args, opt_ipv6); + if (opt_show_forced_updates > 0) + argv_array_push(&args, "--show-forced-updates"); + else if (opt_show_forced_updates == 0) + 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); argv_array_pushv(&args, refspecs); } else if (*refspecs) - die("BUG: refspecs without repo?"); + BUG("refspecs without repo?"); ret = run_command_v_opt(args.argv, RUN_GIT_CMD); argv_array_clear(&args); return ret; @@ -551,13 +602,27 @@ static int run_fetch(const char *repo, const char **refspecs) static int pull_into_void(const struct object_id *merge_head, const struct object_id *curr_head) { + if (opt_verify_signatures) { + struct commit *commit; + + commit = lookup_commit(the_repository, merge_head); + if (!commit) + die(_("unable to access commit %s"), + oid_to_hex(merge_head)); + + verify_merge_signature(commit, opt_verbosity, + check_trust_level); + } + /* * Two-way merge: we treat the index as based on an empty tree, * and try to fast-forward to HEAD. This ensures we will not lose * index/worktree changes that the user already made on the unborn * branch. */ - if (checkout_fast_forward(the_hash_algo->empty_tree, merge_head, 0)) + if (checkout_fast_forward(the_repository, + the_hash_algo->empty_tree, + merge_head, 0)) return 1; if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR)) @@ -620,6 +685,8 @@ static int run_merge(void) argv_array_push(&args, opt_commit); if (opt_edit) argv_array_push(&args, opt_edit); + if (cleanup_arg) + argv_array_pushf(&args, "--cleanup=%s", cleanup_arg); if (opt_ff) argv_array_push(&args, opt_ff); if (opt_verify_signatures) @@ -628,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"); @@ -668,19 +739,19 @@ static const char *get_upstream_branch(const char *remote) } /** - * Derives the remote tracking branch from the remote and refspec. + * Derives the remote-tracking branch from the remote and refspec. * * FIXME: The current implementation assumes the default mapping of * refs/heads/<branch_name> to refs/remotes/<remote_name>/<branch_name>. */ static const char *get_tracking_branch(const char *remote, const char *refspec) { - struct refspec *spec; + struct refspec_item spec; const char *spec_src; const char *merge_branch; - spec = parse_fetch_refspec(1, &refspec); - spec_src = spec->src; + refspec_item_init_or_die(&spec, refspec, REFSPEC_FETCH); + spec_src = spec.src; if (!*spec_src || !strcmp(spec_src, "HEAD")) spec_src = "HEAD"; else if (skip_prefix(spec_src, "heads/", &spec_src)) @@ -700,13 +771,13 @@ static const char *get_tracking_branch(const char *remote, const char *refspec) } else merge_branch = NULL; - free_refspec(1, spec); + refspec_item_clear(&spec); return merge_branch; } /** * Given the repo and refspecs, sets fork_point to the point at which the - * current branch forked from its remote tracking branch. Returns 0 on success, + * current branch forked from its remote-tracking branch. Returns 0 on success, * -1 on failure. */ static int get_rebase_fork_point(struct object_id *fork_point, const char *repo, @@ -736,7 +807,7 @@ static int get_rebase_fork_point(struct object_id *fork_point, const char *repo, cp.no_stderr = 1; cp.git_cmd = 1; - ret = capture_command(&cp, &sb, GIT_SHA1_HEXSZ); + ret = capture_command(&cp, &sb, GIT_MAX_HEXSZ); if (ret) goto cleanup; @@ -760,10 +831,13 @@ static int get_octopus_merge_base(struct object_id *merge_base, { struct commit_list *revs = NULL, *result; - commit_list_insert(lookup_commit_reference(curr_head), &revs); - commit_list_insert(lookup_commit_reference(merge_head), &revs); + commit_list_insert(lookup_commit_reference(the_repository, curr_head), + &revs); + commit_list_insert(lookup_commit_reference(the_repository, merge_head), + &revs); if (!is_null_oid(fork_point)) - commit_list_insert(lookup_commit_reference(fork_point), &revs); + commit_list_insert(lookup_commit_reference(the_repository, fork_point), + &revs); result = get_octopus_merge_bases(revs); free_commit_list(revs); @@ -778,7 +852,7 @@ static int get_octopus_merge_base(struct object_id *merge_base, } /** - * Given the current HEAD SHA1, the merge head returned from git-fetch and the + * Given the current HEAD oid, the merge head returned from git-fetch and the * fork point calculated by get_rebase_fork_point(), runs git-rebase with the * appropriate arguments and returns its exit status. */ @@ -791,7 +865,7 @@ static int run_rebase(const struct object_id *curr_head, struct argv_array args = ARGV_ARRAY_INIT; if (!get_octopus_merge_base(&oct_merge_base, curr_head, merge_head, fork_point)) - if (!is_null_oid(fork_point) && !oidcmp(&oct_merge_base, fork_point)) + if (!is_null_oid(fork_point) && oideq(&oct_merge_base, fork_point)) fork_point = NULL; argv_array_push(&args, "rebase"); @@ -800,7 +874,9 @@ static int run_rebase(const struct object_id *curr_head, argv_push_verbosity(&args); /* Options passed to git-rebase */ - if (opt_rebase == REBASE_PRESERVE) + if (opt_rebase == REBASE_MERGES) + argv_array_push(&args, "--rebase-merges"); + else if (opt_rebase == REBASE_PRESERVE) argv_array_push(&args, "--preserve-merges"); else if (opt_rebase == REBASE_INTERACTIVE) argv_array_push(&args, "--interactive"); @@ -846,6 +922,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0); + if (cleanup_arg) + /* + * this only checks the validity of cleanup_arg; we don't need + * a valid value for use_editor + */ + get_cleanup_mode(cleanup_arg, 0); + parse_repo_refspecs(argc, argv, &repo, &refspecs); if (!opt_ff) @@ -857,15 +940,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (read_cache_unmerged()) die_resolve_conflict("pull"); - if (file_exists(git_path_merge_head())) + if (file_exists(git_path_merge_head(the_repository))) die_conclude_merge(); 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) @@ -875,7 +955,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) die(_("Updating an unborn branch with changes added to the index.")); if (!autostash) - require_clean_work_tree(N_("pull with rebase"), + require_clean_work_tree(the_repository, + N_("pull with rebase"), _("please commit or stash them."), 1, 0); if (get_rebase_fork_point(&rebase_fork_point, repo, *refspecs)) @@ -892,7 +973,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) oidclr(&curr_head); if (!is_null_oid(&orig_head) && !is_null_oid(&curr_head) && - oidcmp(&orig_head, &curr_head)) { + !oideq(&orig_head, &curr_head)) { /* * The fetch involved updating the current branch. * @@ -905,7 +986,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) "fast-forwarding your working tree from\n" "commit %s."), oid_to_hex(&orig_head)); - if (checkout_fast_forward(&orig_head, &curr_head, 0)) + if (checkout_fast_forward(the_repository, &orig_head, + &curr_head, 0)) die(_("Cannot fast-forward your working tree.\n" "After making sure that you saved anything precious from\n" "$ git diff %s\n" @@ -929,24 +1011,29 @@ 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(&rebase_fork_point, &curr_head)) + submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head)) die(_("cannot rebase with locally recorded submodule modifications")); if (!autostash) { struct commit_list *list = NULL; struct commit *merge_head, *head; - head = lookup_commit_reference(&orig_head); + head = lookup_commit_reference(the_repository, + &orig_head); commit_list_insert(head, &list); - merge_head = lookup_commit_reference(&merge_heads.oid[0]); + merge_head = lookup_commit_reference(the_repository, + &merge_heads.oid[0]); 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/push.c b/builtin/push.c index 013c20d616..bc94078e72 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -4,6 +4,7 @@ #include "cache.h" #include "config.h" #include "refs.h" +#include "refspec.h" #include "run-command.h" #include "builtin.h" #include "remote.h" @@ -12,12 +13,40 @@ #include "submodule.h" #include "submodule-config.h" #include "send-pack.h" +#include "color.h" static const char * const push_usage[] = { N_("git push [<options>] [<repository> [<refspec>...]]"), NULL, }; +static int push_use_color = -1; +static char push_colors[][COLOR_MAXLEN] = { + GIT_COLOR_RESET, + GIT_COLOR_RED, /* ERROR */ +}; + +enum color_push { + PUSH_COLOR_RESET = 0, + PUSH_COLOR_ERROR = 1 +}; + +static int parse_push_color_slot(const char *slot) +{ + if (!strcasecmp(slot, "reset")) + return PUSH_COLOR_RESET; + if (!strcasecmp(slot, "error")) + return PUSH_COLOR_ERROR; + return -1; +} + +static const char *push_get_color(enum color_push ix) +{ + if (want_color_stderr(push_use_color)) + return push_colors[ix]; + return ""; +} + static int thin = 1; static int deleterefs; static const char *receivepack; @@ -28,34 +57,25 @@ static enum transport_family family; static struct push_cas_option cas; -static const char **refspec; -static int refspec_nr; -static int refspec_alloc; +static struct refspec rs = REFSPEC_INIT_PUSH; static struct string_list push_options_config = STRING_LIST_INIT_DUP; -static void add_refspec(const char *ref) -{ - refspec_nr++; - ALLOC_GROW(refspec, refspec_nr, refspec_alloc); - refspec[refspec_nr-1] = ref; -} - static const char *map_refspec(const char *ref, struct remote *remote, struct ref *local_refs) { + const char *branch_name; struct ref *matched = NULL; /* Does "ref" uniquely name our ref? */ if (count_refspec_match(ref, local_refs, &matched) != 1) return ref; - if (remote->push) { - struct refspec query; - memset(&query, 0, sizeof(struct refspec)); + if (remote->push.nr) { + struct refspec_item query; + memset(&query, 0, sizeof(struct refspec_item)); query.src = matched->name; - if (!query_refspecs(remote->push, remote->push_refspec_nr, &query) && - query.dst) { + if (!query_refspecs(&remote->push, &query) && query.dst) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%s%s:%s", query.force ? "+" : "", @@ -65,8 +85,8 @@ static const char *map_refspec(const char *ref, } if (push_default == PUSH_DEFAULT_UPSTREAM && - starts_with(matched->name, "refs/heads/")) { - struct branch *branch = branch_get(matched->name + 11); + skip_prefix(matched->name, "refs/heads/", &branch_name)) { + struct branch *branch = branch_get(branch_name); if (branch->merge_nr == 1 && branch->merge[0]->src) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%s:%s", @@ -110,7 +130,7 @@ static void set_refspecs(const char **refs, int nr, const char *repo) } ref = map_refspec(ref, remote, local_refs); } - add_refspec(ref); + refspec_append(&rs, ref); } } @@ -124,7 +144,9 @@ static int push_url_of_remote(struct remote *remote, const char ***url_p) return remote->url_nr; } -static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) { +static NORETURN void die_push_simple(struct branch *branch, + struct remote *remote) +{ /* * There's no point in using shorten_unambiguous_ref here, * as the ambiguity would be on the remote side, not what @@ -154,10 +176,10 @@ static NORETURN int die_push_simple(struct branch *branch, struct remote *remote "\n" "To push to the branch of the same name on the remote, use\n" "\n" - " git push %s %s\n" + " git push %s HEAD\n" "%s"), remote->name, short_upstream, - remote->name, branch->name, advice_maybe); + remote->name, advice_maybe); } static const char message_detached_head_die[] = @@ -198,7 +220,7 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch, } strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src); - add_refspec(refspec.buf); + refspec_append(&rs, refspec.buf); } static void setup_push_current(struct remote *remote, struct branch *branch) @@ -208,7 +230,7 @@ static void setup_push_current(struct remote *remote, struct branch *branch) if (!branch) die(_(message_detached_head_die), remote->name); strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname); - add_refspec(refspec.buf); + refspec_append(&rs, refspec.buf); } static int is_workflow_triangular(struct remote *remote) @@ -225,7 +247,7 @@ static void setup_default_push_refspecs(struct remote *remote) switch (push_default) { default: case PUSH_DEFAULT_MATCHING: - add_refspec(":"); + refspec_append(&rs, ":"); break; case PUSH_DEFAULT_UNSPECIFIED: @@ -313,10 +335,12 @@ static void advise_ref_needs_force(void) advise(_(message_advice_ref_needs_force)); } -static int push_with_options(struct transport *transport, int flags) +static int push_with_options(struct transport *transport, struct refspec *rs, + int flags) { int err; unsigned int reject_reasons; + char *anon_url = transport_anonymize_url(transport->url); transport_set_verbosity(transport, verbosity, progress); transport->family = family; @@ -334,13 +358,19 @@ static int push_with_options(struct transport *transport, int flags) } if (verbosity > 0) - fprintf(stderr, _("Pushing to %s\n"), transport->url); - err = transport_push(transport, refspec_nr, refspec, flags, - &reject_reasons); - if (err != 0) - error(_("failed to push some refs to '%s'"), transport->url); + fprintf(stderr, _("Pushing to %s\n"), anon_url); + trace2_region_enter("push", "transport_push", the_repository); + err = transport_push(the_repository, transport, + rs, flags, &reject_reasons); + trace2_region_leave("push", "transport_push", the_repository); + if (err != 0) { + fprintf(stderr, "%s", push_get_color(PUSH_COLOR_ERROR)); + error(_("failed to push some refs to '%s'"), anon_url); + fprintf(stderr, "%s", push_get_color(PUSH_COLOR_RESET)); + } err |= transport_disconnect(transport); + free(anon_url); if (!err) return 0; @@ -360,53 +390,20 @@ static int push_with_options(struct transport *transport, int flags) } static int do_push(const char *repo, int flags, - const struct string_list *push_options) + const struct string_list *push_options, + struct remote *remote) { int i, errs; - struct remote *remote = pushremote_get(repo); const char **url; int url_nr; - - if (!remote) { - if (repo) - die(_("bad repository '%s'"), repo); - die(_("No configured push destination.\n" - "Either specify the URL from the command-line or configure a remote repository using\n" - "\n" - " git remote add <name> <url>\n" - "\n" - "and then push using the remote name\n" - "\n" - " git push <name>\n")); - } - - if (remote->mirror) - flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + struct refspec *push_refspec = &rs; if (push_options->nr) flags |= TRANSPORT_PUSH_OPTIONS; - if ((flags & TRANSPORT_PUSH_ALL) && refspec) { - if (!strcmp(*refspec, "refs/tags/*")) - return error(_("--all and --tags are incompatible")); - return error(_("--all can't be combined with refspecs")); - } - - if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) { - if (!strcmp(*refspec, "refs/tags/*")) - return error(_("--mirror and --tags are incompatible")); - return error(_("--mirror can't be combined with refspecs")); - } - - if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == - (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { - return error(_("--all and --mirror are incompatible")); - } - - if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) { - if (remote->push_refspec_nr) { - refspec = remote->push_refspec; - refspec_nr = remote->push_refspec_nr; + if (!push_refspec->nr && !(flags & TRANSPORT_PUSH_ALL)) { + if (remote->push.nr) { + push_refspec = &remote->push; } else if (!(flags & TRANSPORT_PUSH_MIRROR)) setup_default_push_refspecs(remote); } @@ -418,7 +415,7 @@ static int do_push(const char *repo, int flags, transport_get(remote, url[i]); if (flags & TRANSPORT_PUSH_OPTIONS) transport->push_options = push_options; - if (push_with_options(transport, flags)) + if (push_with_options(transport, push_refspec, flags)) errs++; } } else { @@ -426,7 +423,7 @@ static int do_push(const char *repo, int flags, transport_get(remote, NULL); if (flags & TRANSPORT_PUSH_OPTIONS) transport->push_options = push_options; - if (push_with_options(transport, flags)) + if (push_with_options(transport, push_refspec, flags)) errs++; } return !!errs; @@ -439,10 +436,8 @@ static int option_parse_recurse_submodules(const struct option *opt, if (unset) *recurse_submodules = RECURSE_SUBMODULES_OFF; - else if (arg) - *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg); else - die("%s missing parameter", opt->long_name); + *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg); return 0; } @@ -467,6 +462,7 @@ static void set_push_cert_flags(int *flags, int v) static int git_push_config(const char *k, const char *v, void *cb) { + const char *slot_name; int *flags = cb; int status; @@ -514,6 +510,16 @@ static int git_push_config(const char *k, const char *v, void *cb) else string_list_append(&push_options_config, v); return 0; + } else if (!strcmp(k, "color.push")) { + push_use_color = git_config_colorbool(k, v); + return 0; + } else if (skip_prefix(k, "color.push.", &slot_name)) { + int slot = parse_push_color_slot(slot_name); + if (slot < 0) + return 0; + if (!v) + return config_error_nonbool(k); + return color_parse(v, push_colors[slot]); } return git_default_config(k, v, NULL); @@ -529,6 +535,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) struct string_list push_options_cmdline = STRING_LIST_INIT_DUP; struct string_list *push_options; const struct string_list_item *item; + struct remote *remote; struct option options[] = { OPT__VERBOSITY(&verbosity), @@ -541,13 +548,11 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE), - { OPTION_CALLBACK, - 0, CAS_OPT_NAME, &cas, N_("refname>:<expect"), - N_("require old value of ref to be at this value"), - PARSE_OPT_OPTARG, parseopt_push_cas_option }, - { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, "check|on-demand|no", - N_("control recursive pushing of submodules"), - PARSE_OPT_OPTARG, option_parse_recurse_submodules }, + OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"), + N_("require old value of ref to be at this value"), + PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option), + OPT_CALLBACK(0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)", + N_("control recursive pushing of submodules"), option_parse_recurse_submodules), OPT_BOOL_F( 0 , "thin", &thin, N_("use thin pack"), PARSE_OPT_NOCOMPLETE), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", N_("receive pack program")), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", N_("receive pack program")), @@ -559,9 +564,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK), OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), TRANSPORT_PUSH_FOLLOW_TAGS), - { OPTION_CALLBACK, - 0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"), - PARSE_OPT_OPTARG, option_parse_push_signed }, + OPT_CALLBACK_F(0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"), + PARSE_OPT_OPTARG, option_parse_push_signed), OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC), OPT_STRING_LIST('o', "push-option", &push_options_cmdline, N_("server-specific"), N_("option to transmit")), OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"), @@ -592,18 +596,50 @@ int cmd_push(int argc, const char **argv, const char *prefix) flags |= TRANSPORT_RECURSE_SUBMODULES_ONLY; if (tags) - add_refspec("refs/tags/*"); + refspec_append(&rs, "refs/tags/*"); if (argc > 0) { repo = argv[0]; set_refspecs(argv + 1, argc - 1, repo); } + remote = pushremote_get(repo); + if (!remote) { + if (repo) + die(_("bad repository '%s'"), repo); + die(_("No configured push destination.\n" + "Either specify the URL from the command-line or configure a remote repository using\n" + "\n" + " git remote add <name> <url>\n" + "\n" + "and then push using the remote name\n" + "\n" + " git push <name>\n")); + } + + if (remote->mirror) + flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + + if (flags & TRANSPORT_PUSH_ALL) { + if (tags) + die(_("--all and --tags are incompatible")); + if (argc >= 2) + die(_("--all can't be combined with refspecs")); + } + if (flags & TRANSPORT_PUSH_MIRROR) { + if (tags) + die(_("--mirror and --tags are incompatible")); + if (argc >= 2) + die(_("--mirror can't be combined with refspecs")); + } + if ((flags & TRANSPORT_PUSH_ALL) && (flags & TRANSPORT_PUSH_MIRROR)) + die(_("--all and --mirror are incompatible")); + for_each_string_list_item(item, push_options) if (strchr(item->string, '\n')) die(_("push options must not have new line characters")); - rc = do_push(repo, flags, push_options); + rc = do_push(repo, flags, push_options, remote); string_list_clear(&push_options_cmdline, 0); string_list_clear(&push_options_config, 0); if (rc == -1) diff --git a/builtin/range-diff.c b/builtin/range-diff.c new file mode 100644 index 0000000000..d8a4670629 --- /dev/null +++ b/builtin/range-diff.c @@ -0,0 +1,92 @@ +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" +#include "range-diff.h" +#include "config.h" + +static const char * const builtin_range_diff_usage[] = { +N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"), +N_("git range-diff [<options>] <old-tip>...<new-tip>"), +N_("git range-diff [<options>] <base> <old-tip> <new-tip>"), +NULL +}; + +int cmd_range_diff(int argc, const char **argv, const char *prefix) +{ + int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; + struct diff_options diffopt = { NULL }; + struct argv_array other_arg = ARGV_ARRAY_INIT; + int simple_color = -1; + struct option range_diff_options[] = { + OPT_INTEGER(0, "creation-factor", &creation_factor, + N_("Percentage by which creation is weighted")), + OPT_BOOL(0, "no-dual-color", &simple_color, + N_("use simple diff colors")), + OPT_PASSTHRU_ARGV(0, "notes", &other_arg, + N_("notes"), N_("passed to 'git log'"), + PARSE_OPT_OPTARG), + OPT_END() + }; + struct option *options; + int res = 0; + struct strbuf range1 = STRBUF_INIT, range2 = STRBUF_INIT; + + git_config(git_diff_ui_config, NULL); + + repo_diff_setup(the_repository, &diffopt); + + options = parse_options_concat(range_diff_options, diffopt.parseopts); + argc = parse_options(argc, argv, prefix, options, + builtin_range_diff_usage, 0); + + diff_setup_done(&diffopt); + + /* force color when --dual-color was used */ + if (!simple_color) + diffopt.use_color = 1; + + if (argc == 2) { + if (!strstr(argv[0], "..")) + die(_("no .. in range: '%s'"), argv[0]); + strbuf_addstr(&range1, argv[0]); + + if (!strstr(argv[1], "..")) + die(_("no .. in range: '%s'"), argv[1]); + strbuf_addstr(&range2, argv[1]); + } else if (argc == 3) { + strbuf_addf(&range1, "%s..%s", argv[0], argv[1]); + strbuf_addf(&range2, "%s..%s", argv[0], argv[2]); + } else if (argc == 1) { + const char *b = strstr(argv[0], "..."), *a = argv[0]; + int a_len; + + if (!b) { + error(_("single arg format must be symmetric range")); + usage_with_options(builtin_range_diff_usage, options); + } + + a_len = (int)(b - a); + if (!a_len) { + a = "HEAD"; + a_len = strlen(a); + } + b += 3; + if (!*b) + b = "HEAD"; + strbuf_addf(&range1, "%s..%.*s", b, a_len, a); + strbuf_addf(&range2, "%.*s..%s", a_len, a, b); + } else { + error(_("need two commit ranges")); + usage_with_options(builtin_range_diff_usage, options); + } + FREE_AND_NULL(options); + + res = show_range_diff(range1.buf, range2.buf, creation_factor, + simple_color < 1, &diffopt, &other_arg); + + argv_array_clear(&other_arg); + strbuf_release(&range1); + strbuf_release(&range2); + + return res; +} diff --git a/builtin/read-tree.c b/builtin/read-tree.c index bf87a2710b..485e7b0479 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "lockfile.h" @@ -44,6 +45,7 @@ static const char * const read_tree_usage[] = { static int index_output_cb(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); set_alternate_index_output(arg); return 0; } @@ -54,6 +56,8 @@ static int exclude_per_directory_cb(const struct option *opt, const char *arg, struct dir_struct *dir; struct unpack_trees_options *opts; + BUG_ON_OPT_NEG(unset); + opts = (struct unpack_trees_options *)opt->value; if (opts->dir) @@ -107,19 +111,18 @@ static int git_read_tree_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -static struct lock_file lock_file; - -int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) +int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) { int i, stage = 0; struct object_id oid; struct tree_desc t[MAX_UNPACK_TREES]; struct unpack_trees_options opts; int prefix_set = 0; + struct lock_file lock_file = LOCK_INIT; const struct option read_tree_options[] = { - { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"), + OPT_CALLBACK_F(0, "index-output", NULL, N_("file"), N_("write resulting index to <file>"), - PARSE_OPT_NONEG, index_output_cb }, + PARSE_OPT_NONEG, index_output_cb), OPT_BOOL(0, "empty", &read_empty, N_("only empty the index")), OPT__VERBOSE(&opts.verbose_update, N_("be verbose")), @@ -134,13 +137,13 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) N_("same as -m, but discard unmerged entries")), { OPTION_STRING, 0, "prefix", &opts.prefix, N_("<subdirectory>/"), N_("read the tree into the index under <subdirectory>/"), - PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP }, + PARSE_OPT_NONEG }, OPT_BOOL('u', NULL, &opts.update, N_("update working tree with merge result")), - { OPTION_CALLBACK, 0, "exclude-per-directory", &opts, + OPT_CALLBACK_F(0, "exclude-per-directory", &opts, N_("gitignore"), N_("allow explicitly ignored files to be overwritten"), - PARSE_OPT_NONEG, exclude_per_directory_cb }, + PARSE_OPT_NONEG, exclude_per_directory_cb), OPT_BOOL('i', NULL, &opts.index_only, N_("don't check the working tree after merging")), OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")), @@ -148,9 +151,10 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) N_("skip applying sparse checkout filter")), OPT_BOOL(0, "debug-unpack", &opts.debug_unpack, N_("debug unpack-trees")), - { OPTION_CALLBACK, 0, "recurse-submodules", NULL, + OPT_CALLBACK_F(0, "recurse-submodules", NULL, "checkout", "control recursive updating of submodules", - PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, + PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater), + OPT__QUIET(&opts.quiet, N_("suppress feedback messages")), OPT_END() }; @@ -161,7 +165,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) git_config(git_read_tree_config, NULL); - argc = parse_options(argc, argv, unused_prefix, read_tree_options, + argc = parse_options(argc, argv, cmd_prefix, read_tree_options, read_tree_usage, 0); hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); @@ -181,7 +185,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) if (opts.reset || opts.merge || opts.prefix) { if (read_cache_unmerged() && (opts.prefix || opts.merge)) - die("You need to resolve your current index first"); + die(_("You need to resolve your current index first")); stage = opts.merge = 1; } resolve_undo_clear(); @@ -256,7 +260,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * what came from the tree. */ if (nr_trees == 1 && !opts.prefix) - prime_cache_tree(&the_index, trees[0]); + prime_cache_tree(the_repository, + the_repository->index, + trees[0]); if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die("unable to write new index file"); diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c deleted file mode 100644 index ad074705bb..0000000000 --- a/builtin/rebase--helper.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "config.h" -#include "parse-options.h" -#include "sequencer.h" - -static const char * const builtin_rebase_helper_usage[] = { - N_("git rebase--helper [<options>]"), - NULL -}; - -int cmd_rebase__helper(int argc, const char **argv, const char *prefix) -{ - struct replay_opts opts = REPLAY_OPTS_INIT; - unsigned flags = 0, keep_empty = 0; - int abbreviate_commands = 0; - enum { - CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS, - CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH, - ADD_EXEC - } command = 0; - struct option options[] = { - OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), - OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")), - OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message, - N_("allow commits with empty messages")), - OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), - CONTINUE), - OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), - ABORT), - OPT_CMDMODE(0, "make-script", &command, - N_("make rebase script"), MAKE_SCRIPT), - OPT_CMDMODE(0, "shorten-ids", &command, - N_("shorten commit ids in the todo list"), SHORTEN_OIDS), - OPT_CMDMODE(0, "expand-ids", &command, - N_("expand commit ids in the todo list"), EXPAND_OIDS), - OPT_CMDMODE(0, "check-todo-list", &command, - N_("check the todo list"), CHECK_TODO_LIST), - OPT_CMDMODE(0, "skip-unnecessary-picks", &command, - N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS), - OPT_CMDMODE(0, "rearrange-squash", &command, - N_("rearrange fixup/squash lines"), REARRANGE_SQUASH), - OPT_CMDMODE(0, "add-exec-commands", &command, - N_("insert exec commands in todo list"), ADD_EXEC), - OPT_END() - }; - - sequencer_init_config(&opts); - git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands); - - opts.action = REPLAY_INTERACTIVE_REBASE; - opts.allow_ff = 1; - opts.allow_empty = 1; - - argc = parse_options(argc, argv, NULL, options, - builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0); - - flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0; - flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; - flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; - - if (command == CONTINUE && argc == 1) - return !!sequencer_continue(&opts); - if (command == ABORT && argc == 1) - return !!sequencer_remove_state(&opts); - if (command == MAKE_SCRIPT && argc > 1) - return !!sequencer_make_script(stdout, argc, argv, flags); - if ((command == SHORTEN_OIDS || command == EXPAND_OIDS) && argc == 1) - return !!transform_todos(flags); - if (command == CHECK_TODO_LIST && argc == 1) - return !!check_todo_list(); - if (command == SKIP_UNNECESSARY_PICKS && argc == 1) - return !!skip_unnecessary_picks(); - if (command == REARRANGE_SQUASH && argc == 1) - return !!rearrange_squash(); - if (command == ADD_EXEC && argc == 2) - return !!sequencer_add_exec_commands(argv[1]); - usage_with_options(builtin_rebase_helper_usage, options); -} diff --git a/builtin/rebase.c b/builtin/rebase.c new file mode 100644 index 0000000000..37ba76ac3d --- /dev/null +++ b/builtin/rebase.c @@ -0,0 +1,2080 @@ +/* + * "git rebase" builtin command + * + * Copyright (c) 2018 Pratik Karki + */ + +#define USE_THE_INDEX_COMPATIBILITY_MACROS +#include "builtin.h" +#include "run-command.h" +#include "exec-cmd.h" +#include "argv-array.h" +#include "dir.h" +#include "packfile.h" +#include "refs.h" +#include "quote.h" +#include "config.h" +#include "cache-tree.h" +#include "unpack-trees.h" +#include "lockfile.h" +#include "parse-options.h" +#include "commit.h" +#include "diff.h" +#include "wt-status.h" +#include "revision.h" +#include "commit-reach.h" +#include "rerere.h" +#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>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), + N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " + "--root [<branch>]"), + N_("git rebase --continue | --abort | --skip | --edit-todo"), + NULL +}; + +static GIT_PATH_FUNC(path_squash_onto, "rebase-merge/squash-onto") +static GIT_PATH_FUNC(path_interactive, "rebase-merge/interactive") +static GIT_PATH_FUNC(apply_dir, "rebase-apply") +static GIT_PATH_FUNC(merge_dir, "rebase-merge") + +enum rebase_type { + REBASE_UNSPECIFIED = -1, + REBASE_APPLY, + REBASE_MERGE, + REBASE_PRESERVE_MERGES +}; + +enum empty_type { + EMPTY_UNSPECIFIED = -1, + EMPTY_DROP, + EMPTY_KEEP, + EMPTY_ASK +}; + +struct rebase_options { + enum rebase_type type; + enum empty_type empty; + const char *default_backend; + const char *state_dir; + struct commit *upstream; + const char *upstream_name; + const char *upstream_arg; + char *head_name; + struct object_id orig_head; + struct commit *onto; + const char *onto_name; + const char *revisions; + const char *switch_to; + int root, root_with_onto; + struct object_id *squash_onto; + struct commit *restrict_revision; + int dont_finish_rebase; + enum { + REBASE_NO_QUIET = 1<<0, + REBASE_VERBOSE = 1<<1, + REBASE_DIFFSTAT = 1<<2, + REBASE_FORCE = 1<<3, + REBASE_INTERACTIVE_EXPLICIT = 1<<4, + } flags; + struct argv_array git_am_opts; + const char *action; + int signoff; + int allow_rerere_autoupdate; + int keep_empty; + int autosquash; + char *gpg_sign_opt; + int autostash; + char *cmd; + int allow_empty_message; + int rebase_merges, rebase_cousins; + char *strategy, *strategy_opts; + 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, \ + .git_format_patch_opt = STRBUF_INIT \ + } + +static struct replay_opts get_replay_opts(const struct rebase_options *opts) +{ + struct replay_opts replay = REPLAY_OPTS_INIT; + + replay.action = REPLAY_INTERACTIVE_REBASE; + sequencer_init_config(&replay); + + replay.signoff = opts->signoff; + replay.allow_ff = !(opts->flags & REBASE_FORCE); + if (opts->allow_rerere_autoupdate) + replay.allow_rerere_auto = opts->allow_rerere_autoupdate; + replay.allow_empty = 1; + replay.allow_empty_message = opts->allow_empty_message; + replay.drop_redundant_commits = (opts->empty == EMPTY_DROP); + replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP); + replay.quiet = !(opts->flags & REBASE_NO_QUIET); + replay.verbose = opts->flags & REBASE_VERBOSE; + replay.reschedule_failed_exec = opts->reschedule_failed_exec; + replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt); + replay.strategy = opts->strategy; + if (opts->strategy_opts) + parse_strategy_opts(&replay, opts->strategy_opts); + + if (opts->squash_onto) { + oidcpy(&replay.squash_onto, opts->squash_onto); + replay.have_squash_onto = 1; + } + + return replay; +} + +enum action { + ACTION_NONE = 0, + ACTION_CONTINUE, + ACTION_SKIP, + ACTION_ABORT, + ACTION_QUIT, + ACTION_EDIT_TODO, + ACTION_SHOW_CURRENT_PATCH, + ACTION_SHORTEN_OIDS, + ACTION_EXPAND_OIDS, + ACTION_CHECK_TODO_LIST, + ACTION_REARRANGE_SQUASH, + ACTION_ADD_EXEC +}; + +static const char *action_names[] = { "undefined", + "continue", + "skip", + "abort", + "quit", + "edit_todo", + "show_current_patch" }; + +static int add_exec_commands(struct string_list *commands) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + todo_list_add_exec_commands(&todo_list, commands); + res = todo_list_write_to_file(the_repository, &todo_list, + todo_file, NULL, NULL, -1, 0); + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; +} + +static int rearrange_squash_in_todo_file(void) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res = 0; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + res = todo_list_rearrange_squash(&todo_list); + if (!res) + res = todo_list_write_to_file(the_repository, &todo_list, + todo_file, NULL, NULL, -1, 0); + + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; +} + +static int transform_todo_file(unsigned flags) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int res; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + res = todo_list_write_to_file(the_repository, &todo_list, todo_file, + NULL, NULL, -1, flags); + todo_list_release(&todo_list); + + if (res) + return error_errno(_("could not write '%s'."), todo_file); + return 0; +} + +static int edit_todo_file(unsigned flags) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT, + new_todo = TODO_LIST_INIT; + int res = 0; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + strbuf_stripspace(&todo_list.buf, 1); + res = edit_todo_list(the_repository, &todo_list, &new_todo, NULL, NULL, flags); + if (!res && todo_list_write_to_file(the_repository, &new_todo, todo_file, + NULL, NULL, -1, flags & ~(TODO_LIST_SHORTEN_IDS))) + res = error_errno(_("could not write '%s'"), todo_file); + + todo_list_release(&todo_list); + todo_list_release(&new_todo); + + return res; +} + +static int get_revision_ranges(struct commit *upstream, struct commit *onto, + struct object_id *orig_head, const char **head_hash, + char **revisions, char **shortrevisions) +{ + struct commit *base_rev = upstream ? upstream : onto; + const char *shorthead; + + *head_hash = find_unique_abbrev(orig_head, GIT_MAX_HEXSZ); + *revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid), + *head_hash); + + shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV); + + if (upstream) { + const char *shortrev; + + shortrev = find_unique_abbrev(&base_rev->object.oid, + DEFAULT_ABBREV); + + *shortrevisions = xstrfmt("%s..%s", shortrev, shorthead); + } else + *shortrevisions = xstrdup(shorthead); + + return 0; +} + +static int init_basic_state(struct replay_opts *opts, const char *head_name, + struct commit *onto, const char *orig_head) +{ + FILE *interactive; + + if (!is_directory(merge_dir()) && mkdir_in_gitdir(merge_dir())) + return error_errno(_("could not create temporary %s"), merge_dir()); + + delete_reflog("REBASE_HEAD"); + + interactive = fopen(path_interactive(), "w"); + if (!interactive) + return error_errno(_("could not mark as interactive")); + fclose(interactive); + + return write_basic_state(opts, head_name, onto, orig_head); +} + +static void split_exec_commands(const char *cmd, struct string_list *commands) +{ + if (cmd && *cmd) { + string_list_split(commands, cmd, '\n', -1); + + /* rebase.c adds a new line to cmd after every command, + * so here the last command is always empty */ + string_list_remove_empty_items(commands, 0); + } +} + +static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) +{ + int ret; + const char *head_hash = NULL; + char *revisions = NULL, *shortrevisions = NULL; + struct argv_array make_script_args = ARGV_ARRAY_INIT; + struct todo_list todo_list = TODO_LIST_INIT; + struct replay_opts replay = get_replay_opts(opts); + struct string_list commands = STRING_LIST_INIT_DUP; + + if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head, + &head_hash, &revisions, &shortrevisions)) + return -1; + + if (init_basic_state(&replay, + opts->head_name ? opts->head_name : "detached HEAD", + opts->onto, head_hash)) { + free(revisions); + free(shortrevisions); + + return -1; + } + + if (!opts->upstream && opts->squash_onto) + write_file(path_squash_onto(), "%s\n", + oid_to_hex(opts->squash_onto)); + + argv_array_pushl(&make_script_args, "", revisions, NULL); + if (opts->restrict_revision) + argv_array_pushf(&make_script_args, "^%s", + oid_to_hex(&opts->restrict_revision->object.oid)); + + ret = sequencer_make_script(the_repository, &todo_list.buf, + make_script_args.argc, make_script_args.argv, + flags); + + if (ret) + error(_("could not generate todo list")); + else { + discard_cache(); + if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, + &todo_list)) + BUG("unusable todo list"); + + split_exec_commands(opts->cmd, &commands); + ret = complete_action(the_repository, &replay, flags, + shortrevisions, opts->onto_name, opts->onto, head_hash, + &commands, opts->autosquash, &todo_list); + } + + string_list_clear(&commands, 0); + free(revisions); + free(shortrevisions); + todo_list_release(&todo_list); + argv_array_clear(&make_script_args); + + return ret; +} + +static int run_sequencer_rebase(struct rebase_options *opts, + enum action command) +{ + unsigned flags = 0; + int abbreviate_commands = 0, ret = 0; + + git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands); + + flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0; + flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; + flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0; + flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; + flags |= 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: { + if (!opts->onto && !opts->upstream) + die(_("a base commit must be provided with --upstream or --onto")); + + ret = do_interactive_rebase(opts, flags); + break; + } + case ACTION_SKIP: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + + rerere_clear(the_repository, &merge_rr); + } + /* fallthrough */ + case ACTION_CONTINUE: { + struct replay_opts replay_opts = get_replay_opts(opts); + + ret = sequencer_continue(the_repository, &replay_opts); + break; + } + case ACTION_EDIT_TODO: + ret = edit_todo_file(flags); + break; + case ACTION_SHOW_CURRENT_PATCH: { + struct child_process cmd = CHILD_PROCESS_INIT; + + cmd.git_cmd = 1; + argv_array_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL); + ret = run_command(&cmd); + + break; + } + case ACTION_SHORTEN_OIDS: + case ACTION_EXPAND_OIDS: + ret = transform_todo_file(flags); + break; + case ACTION_CHECK_TODO_LIST: + ret = check_todo_list_from_file(the_repository); + break; + case ACTION_REARRANGE_SQUASH: + ret = rearrange_squash_in_todo_file(); + break; + case ACTION_ADD_EXEC: { + struct string_list commands = STRING_LIST_INIT_DUP; + + split_exec_commands(opts->cmd, &commands); + ret = add_exec_commands(&commands); + string_list_clear(&commands, 0); + break; + } + default: + BUG("invalid command '%d'", command); + } + + return ret; +} + +static 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) +{ + struct rebase_options *opts = opt->value; + + BUG_ON_OPT_ARG(arg); + + imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty"); + opts->keep_empty = !unset; + opts->type = REBASE_MERGE; + return 0; +} + +static const char * const builtin_rebase_interactive_usage[] = { + N_("git rebase--interactive [<options>]"), + NULL +}; + +int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) +{ + struct rebase_options opts = REBASE_OPTIONS_INIT; + struct object_id squash_onto = null_oid; + enum action command = ACTION_NONE; + struct option options[] = { + OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"), + REBASE_FORCE), + OPT_CALLBACK_F('k', "keep-empty", &options, NULL, + 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, + N_("allow commits with empty messages"), + PARSE_OPT_HIDDEN), + OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")), + OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins, + N_("keep original branch points of cousins")), + OPT_BOOL(0, "autosquash", &opts.autosquash, + N_("move commits that begin with squash!/fixup!")), + OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")), + OPT_BIT('v', "verbose", &opts.flags, + N_("display a diffstat of what changed upstream"), + REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), + OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), + ACTION_CONTINUE), + OPT_CMDMODE(0, "skip", &command, N_("skip commit"), ACTION_SKIP), + OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"), + ACTION_EDIT_TODO), + OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"), + ACTION_SHOW_CURRENT_PATCH), + OPT_CMDMODE(0, "shorten-ids", &command, + N_("shorten commit ids in the todo list"), ACTION_SHORTEN_OIDS), + OPT_CMDMODE(0, "expand-ids", &command, + N_("expand commit ids in the todo list"), ACTION_EXPAND_OIDS), + OPT_CMDMODE(0, "check-todo-list", &command, + N_("check the todo list"), ACTION_CHECK_TODO_LIST), + OPT_CMDMODE(0, "rearrange-squash", &command, + N_("rearrange fixup/squash lines"), ACTION_REARRANGE_SQUASH), + OPT_CMDMODE(0, "add-exec-commands", &command, + N_("insert exec commands in todo list"), ACTION_ADD_EXEC), + { OPTION_CALLBACK, 0, "onto", &opts.onto, N_("onto"), N_("onto"), + PARSE_OPT_NONEG, parse_opt_commit, 0 }, + { OPTION_CALLBACK, 0, "restrict-revision", &opts.restrict_revision, + N_("restrict-revision"), N_("restrict revision"), + PARSE_OPT_NONEG, parse_opt_commit, 0 }, + { OPTION_CALLBACK, 0, "squash-onto", &squash_onto, N_("squash-onto"), + N_("squash onto"), PARSE_OPT_NONEG, parse_opt_object_id, 0 }, + { OPTION_CALLBACK, 0, "upstream", &opts.upstream, N_("upstream"), + N_("the upstream commit"), PARSE_OPT_NONEG, parse_opt_commit, + 0 }, + OPT_STRING(0, "head-name", &opts.head_name, N_("head-name"), N_("head name")), + { OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign_opt, N_("key-id"), + N_("GPG-sign commits"), + PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"), + N_("rebase strategy")), + OPT_STRING(0, "strategy-opts", &opts.strategy_opts, N_("strategy-opts"), + N_("strategy options")), + OPT_STRING(0, "switch-to", &opts.switch_to, N_("switch-to"), + N_("the branch or commit to checkout")), + OPT_STRING(0, "onto-name", &opts.onto_name, N_("onto-name"), N_("onto name")), + OPT_STRING(0, "cmd", &opts.cmd, N_("cmd"), N_("the command to run")), + OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_autoupdate), + OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec, + N_("automatically re-schedule any `exec` that fails")), + OPT_END() + }; + + opts.rebase_cousins = -1; + + if (argc == 1) + usage_with_options(builtin_rebase_interactive_usage, options); + + argc = parse_options(argc, argv, prefix, options, + builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0); + + if (!is_null_oid(&squash_onto)) + opts.squash_onto = &squash_onto; + + if (opts.rebase_cousins >= 0 && !opts.rebase_merges) + warning(_("--[no-]rebase-cousins has no effect without " + "--rebase-merges")); + + return !!run_sequencer_rebase(&opts, command); +} + +static int is_merge(struct rebase_options *opts) +{ + return opts->type == REBASE_MERGE || + opts->type == REBASE_PRESERVE_MERGES; +} + +static void imply_merge(struct rebase_options *opts, const char *option) +{ + switch (opts->type) { + case REBASE_APPLY: + die(_("%s requires the merge backend"), option); + break; + case REBASE_MERGE: + case REBASE_PRESERVE_MERGES: + break; + default: + opts->type = REBASE_MERGE; /* implied */ + break; + } +} + +/* Returns the filename prefixed by the state_dir */ +static const char *state_dir_path(const char *filename, struct rebase_options *opts) +{ + static struct strbuf path = STRBUF_INIT; + static size_t prefix_len; + + if (!prefix_len) { + strbuf_addf(&path, "%s/", opts->state_dir); + prefix_len = path.len; + } + + strbuf_setlen(&path, prefix_len); + strbuf_addstr(&path, filename); + return path.buf; +} + +/* Initialize the rebase options from the state directory. */ +static int read_basic_state(struct rebase_options *opts) +{ + struct strbuf head_name = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + 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; + strbuf_release(&head_name); + if (get_oid(buf.buf, &oid)) + return error(_("could not get 'onto': '%s'"), buf.buf); + opts->onto = lookup_commit_or_die(&oid, buf.buf); + + /* + * We always write to orig-head, but interactive rebase used to write to + * head. Fall back to reading from head to cover for the case that the + * user upgraded git with an ongoing interactive rebase. + */ + strbuf_reset(&buf); + if (file_exists(state_dir_path("orig-head", opts))) { + if (!read_oneliner(&buf, state_dir_path("orig-head", opts), + READ_ONELINER_WARN_MISSING)) + return -1; + } 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); + + if (file_exists(state_dir_path("quiet", opts))) + opts->flags &= ~REBASE_NO_QUIET; + else + opts->flags |= REBASE_NO_QUIET; + + if (file_exists(state_dir_path("verbose", opts))) + opts->flags |= REBASE_VERBOSE; + + if (file_exists(state_dir_path("signoff", opts))) { + opts->signoff = 1; + opts->flags |= REBASE_FORCE; + } + + if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) { + strbuf_reset(&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; + else if (!strcmp(buf.buf, "--no-rerere-autoupdate")) + opts->allow_rerere_autoupdate = RERERE_NOAUTOUPDATE; + else + warning(_("ignoring invalid allow_rerere_autoupdate: " + "'%s'"), buf.buf); + } + + if (file_exists(state_dir_path("gpg_sign_opt", opts))) { + strbuf_reset(&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); + } + + if (file_exists(state_dir_path("strategy", opts))) { + strbuf_reset(&buf); + if (!read_oneliner(&buf, state_dir_path("strategy", opts), + READ_ONELINER_WARN_MISSING)) + return -1; + free(opts->strategy); + opts->strategy = xstrdup(buf.buf); + } + + if (file_exists(state_dir_path("strategy_opts", opts))) { + strbuf_reset(&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); + } + + strbuf_release(&buf); + + return 0; +} + +static int rebase_write_basic_state(struct rebase_options *opts) +{ + write_file(state_dir_path("head-name", opts), "%s", + opts->head_name ? opts->head_name : "detached HEAD"); + write_file(state_dir_path("onto", opts), "%s", + opts->onto ? oid_to_hex(&opts->onto->object.oid) : ""); + write_file(state_dir_path("orig-head", opts), "%s", + oid_to_hex(&opts->orig_head)); + if (!(opts->flags & REBASE_NO_QUIET)) + write_file(state_dir_path("quiet", opts), "%s", ""); + if (opts->flags & REBASE_VERBOSE) + write_file(state_dir_path("verbose", opts), "%s", ""); + if (opts->strategy) + write_file(state_dir_path("strategy", opts), "%s", + opts->strategy); + if (opts->strategy_opts) + write_file(state_dir_path("strategy_opts", opts), "%s", + opts->strategy_opts); + if (opts->allow_rerere_autoupdate > 0) + write_file(state_dir_path("allow_rerere_autoupdate", opts), + "-%s-rerere-autoupdate", + opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ? + "" : "-no"); + if (opts->gpg_sign_opt) + write_file(state_dir_path("gpg_sign_opt", opts), "%s", + opts->gpg_sign_opt); + if (opts->signoff) + write_file(state_dir_path("signoff", opts), "--signoff"); + + return 0; +} + +static int finish_rebase(struct rebase_options *opts) +{ + struct strbuf dir = STRBUF_INIT; + int ret = 0; + + delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); + apply_autostash(state_dir_path("autostash", opts)); + close_object_store(the_repository->objects); + /* + * We ignore errors in 'gc --auto', since the + * user should see them. + */ + run_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); + if (opts->type == REBASE_MERGE) { + struct replay_opts replay = REPLAY_OPTS_INIT; + + replay.action = REPLAY_INTERACTIVE_REBASE; + ret = sequencer_remove_state(&replay); + } else { + strbuf_addstr(&dir, opts->state_dir); + if (remove_dir_recursively(&dir, 0)) + ret = error(_("could not remove '%s'"), + opts->state_dir); + strbuf_release(&dir); + } + + return ret; +} + +static struct commit *peel_committish(const char *name) +{ + struct object *obj; + struct object_id oid; + + if (get_oid(name, &oid)) + return NULL; + obj = parse_object(the_repository, &oid); + return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); +} + +static void add_var(struct strbuf *buf, const char *name, const char *value) +{ + if (!value) + strbuf_addf(buf, "unset %s; ", name); + else { + strbuf_addf(buf, "%s=", name); + sq_quote_buf(buf, value); + strbuf_addstr(buf, "; "); + } +} + +static int move_to_original_branch(struct rebase_options *opts) +{ + struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; + int ret; + + if (!opts->head_name) + return 0; /* nothing to move back to */ + + if (!opts->onto) + BUG("move_to_original_branch without onto"); + + strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s", + 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(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); + return ret; +} + +static const char *resolvemsg = +N_("Resolve all conflicts manually, mark them as resolved with\n" +"\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n" +"You can instead skip this commit: run \"git rebase --skip\".\n" +"To abort and get back to the state before \"git rebase\", run " +"\"git rebase --abort\"."); + +static int run_am(struct rebase_options *opts) +{ + struct child_process am = CHILD_PROCESS_INIT; + struct child_process format_patch = CHILD_PROCESS_INIT; + struct strbuf revisions = STRBUF_INIT; + int status; + char *rebased_patches; + + am.git_cmd = 1; + argv_array_push(&am.args, "am"); + + if (opts->action && !strcmp("continue", opts->action)) { + argv_array_push(&am.args, "--resolved"); + argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); + if (opts->gpg_sign_opt) + argv_array_push(&am.args, opts->gpg_sign_opt); + status = run_command(&am); + if (status) + return status; + + return move_to_original_branch(opts); + } + if (opts->action && !strcmp("skip", opts->action)) { + argv_array_push(&am.args, "--skip"); + argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); + status = run_command(&am); + if (status) + return status; + + return move_to_original_branch(opts); + } + if (opts->action && !strcmp("show-current-patch", opts->action)) { + argv_array_push(&am.args, "--show-current-patch"); + return run_command(&am); + } + + strbuf_addf(&revisions, "%s...%s", + oid_to_hex(opts->root ? + /* this is now equivalent to !opts->upstream */ + &opts->onto->object.oid : + &opts->upstream->object.oid), + oid_to_hex(&opts->orig_head)); + + rebased_patches = xstrdup(git_path("rebased-patches")); + format_patch.out = open(rebased_patches, + O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (format_patch.out < 0) { + status = error_errno(_("could not open '%s' for writing"), + rebased_patches); + free(rebased_patches); + argv_array_clear(&am.args); + return status; + } + + format_patch.git_cmd = 1; + argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout", + "--full-index", "--cherry-pick", "--right-only", + "--src-prefix=a/", "--dst-prefix=b/", "--no-renames", + "--no-cover-letter", "--pretty=mboxrd", "--topo-order", + "--no-base", NULL); + if (opts->git_format_patch_opt.len) + argv_array_split(&format_patch.args, + opts->git_format_patch_opt.buf); + argv_array_push(&format_patch.args, revisions.buf); + if (opts->restrict_revision) + argv_array_pushf(&format_patch.args, "^%s", + oid_to_hex(&opts->restrict_revision->object.oid)); + + status = run_command(&format_patch); + if (status) { + unlink(rebased_patches); + free(rebased_patches); + argv_array_clear(&am.args); + + 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" + "\n %s\n\n" + "As a result, git cannot rebase them."), + opts->revisions); + + strbuf_release(&revisions); + return status; + } + strbuf_release(&revisions); + + am.in = open(rebased_patches, O_RDONLY); + if (am.in < 0) { + status = error_errno(_("could not open '%s' for reading"), + rebased_patches); + free(rebased_patches); + argv_array_clear(&am.args); + return status; + } + + argv_array_pushv(&am.args, opts->git_am_opts.argv); + argv_array_push(&am.args, "--rebasing"); + argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); + argv_array_push(&am.args, "--patch-format=mboxrd"); + if (opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE) + argv_array_push(&am.args, "--rerere-autoupdate"); + else if (opts->allow_rerere_autoupdate == RERERE_NOAUTOUPDATE) + argv_array_push(&am.args, "--no-rerere-autoupdate"); + if (opts->gpg_sign_opt) + argv_array_push(&am.args, opts->gpg_sign_opt); + status = run_command(&am); + unlink(rebased_patches); + free(rebased_patches); + + if (!status) { + return move_to_original_branch(opts); + } + + if (is_directory(opts->state_dir)) + rebase_write_basic_state(opts); + + return status; +} + +static int run_specific_rebase(struct rebase_options *opts, enum action action) +{ + const char *argv[] = { NULL, NULL }; + struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT; + int status; + const char *backend, *backend_func; + + if (opts->type == REBASE_MERGE) { + /* Run sequencer-based rebase */ + setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1); + if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { + setenv("GIT_SEQUENCE_EDITOR", ":", 1); + opts->autosquash = 0; + } + if (opts->gpg_sign_opt) { + /* remove the leading "-S" */ + char *tmp = xstrdup(opts->gpg_sign_opt + 2); + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = tmp; + } + + status = run_sequencer_rebase(opts, action); + goto finished_rebase; + } + + if (opts->type == REBASE_APPLY) { + status = run_am(opts); + goto finished_rebase; + } + + add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir())); + add_var(&script_snippet, "state_dir", opts->state_dir); + + add_var(&script_snippet, "upstream_name", opts->upstream_name); + add_var(&script_snippet, "upstream", opts->upstream ? + oid_to_hex(&opts->upstream->object.oid) : NULL); + add_var(&script_snippet, "head_name", + opts->head_name ? opts->head_name : "detached HEAD"); + add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head)); + add_var(&script_snippet, "onto", opts->onto ? + oid_to_hex(&opts->onto->object.oid) : NULL); + add_var(&script_snippet, "onto_name", opts->onto_name); + add_var(&script_snippet, "revisions", opts->revisions); + add_var(&script_snippet, "restrict_revision", opts->restrict_revision ? + oid_to_hex(&opts->restrict_revision->object.oid) : NULL); + sq_quote_argv_pretty(&buf, opts->git_am_opts.argv); + add_var(&script_snippet, "git_am_opt", buf.buf); + strbuf_release(&buf); + add_var(&script_snippet, "verbose", + opts->flags & REBASE_VERBOSE ? "t" : ""); + add_var(&script_snippet, "diffstat", + opts->flags & REBASE_DIFFSTAT ? "t" : ""); + add_var(&script_snippet, "force_rebase", + opts->flags & REBASE_FORCE ? "t" : ""); + if (opts->switch_to) + add_var(&script_snippet, "switch_to", opts->switch_to); + add_var(&script_snippet, "action", opts->action ? opts->action : ""); + add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); + add_var(&script_snippet, "allow_rerere_autoupdate", + opts->allow_rerere_autoupdate ? + 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); + add_var(&script_snippet, "allow_empty_message", + opts->allow_empty_message ? "--allow-empty-message" : ""); + add_var(&script_snippet, "rebase_merges", + opts->rebase_merges ? "t" : ""); + add_var(&script_snippet, "rebase_cousins", + opts->rebase_cousins ? "t" : ""); + add_var(&script_snippet, "strategy", opts->strategy); + add_var(&script_snippet, "strategy_opts", opts->strategy_opts); + add_var(&script_snippet, "rebase_root", opts->root ? "t" : ""); + add_var(&script_snippet, "squash_onto", + opts->squash_onto ? oid_to_hex(opts->squash_onto) : ""); + add_var(&script_snippet, "git_format_patch_opt", + opts->git_format_patch_opt.buf); + + if (is_merge(opts) && + !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { + strbuf_addstr(&script_snippet, + "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; "); + opts->autosquash = 0; + } + + switch (opts->type) { + case REBASE_PRESERVE_MERGES: + backend = "git-rebase--preserve-merges"; + backend_func = "git_rebase__preserve_merges"; + break; + default: + BUG("Unhandled rebase type %d", opts->type); + break; + } + + strbuf_addf(&script_snippet, + ". git-sh-setup && . %s && %s", backend, backend_func); + argv[0] = script_snippet.buf; + + status = run_command_v_opt(argv, RUN_USING_SHELL); +finished_rebase: + if (opts->dont_finish_rebase) + ; /* do nothing */ + else if (opts->type == REBASE_MERGE) + ; /* merge backend cleans up after itself */ + else if (status == 0) { + if (!file_exists(state_dir_path("stopped-sha", opts))) + finish_rebase(opts); + } else if (status == 2) { + struct strbuf dir = STRBUF_INIT; + + apply_autostash(state_dir_path("autostash", opts)); + strbuf_addstr(&dir, opts->state_dir); + remove_dir_recursively(&dir, 0); + strbuf_release(&dir); + die("Nothing to do"); + } + + strbuf_release(&script_snippet); + + return status ? -1 : 0; +} + +static int rebase_config(const char *var, const char *value, void *data) +{ + struct rebase_options *opts = data; + + if (!strcmp(var, "rebase.stat")) { + if (git_config_bool(var, value)) + opts->flags |= REBASE_DIFFSTAT; + else + opts->flags &= ~REBASE_DIFFSTAT; + return 0; + } + + if (!strcmp(var, "rebase.autosquash")) { + opts->autosquash = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "commit.gpgsign")) { + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = git_config_bool(var, value) ? + xstrdup("-S") : NULL; + return 0; + } + + if (!strcmp(var, "rebase.autostash")) { + opts->autostash = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "rebase.reschedulefailedexec")) { + opts->reschedule_failed_exec = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "rebase.usebuiltin")) { + opts->use_legacy_rebase = !git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "rebase.backend")) { + return git_config_string(&opts->default_backend, var, value); + } + + return git_default_config(var, value, data); +} + +/* + * Determines whether the commits in from..to are linear, i.e. contain + * no merge commits. This function *expects* `from` to be an ancestor of + * `to`. + */ +static int is_linear_history(struct commit *from, struct commit *to) +{ + while (to && to != from) { + parse_commit(to); + if (!to->parents) + return 1; + if (to->parents->next) + return 0; + to = to->parents->item; + } + return 1; +} + +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct commit *restrict_revision, + struct object_id *head_oid, struct object_id *merge_base) +{ + struct commit *head = lookup_commit(the_repository, head_oid); + struct commit_list *merge_bases = NULL; + int res = 0; + + if (!head) + goto done; + + merge_bases = get_merge_bases(onto, head); + if (!merge_bases || merge_bases->next) { + oidcpy(merge_base, &null_oid); + goto done; + } + + oidcpy(merge_base, &merge_bases->item->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; + + if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base)) + goto done; + + if (!upstream) + goto done; + + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (!merge_bases || merge_bases->next) + goto done; + + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + + res = 1; + +done: + free_commit_list(merge_bases); + return res && is_linear_history(onto, head); +} + +static int parse_opt_am(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *opts = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + opts->type = REBASE_APPLY; + + return 0; +} + +/* -i followed by -m is still -i */ +static int parse_opt_merge(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *opts = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + if (!is_merge(opts)) + opts->type = REBASE_MERGE; + + return 0; +} + +/* -i followed by -p is still explicitly interactive, but -p alone is not */ +static int parse_opt_interactive(const struct option *opt, const char *arg, + int unset) +{ + struct rebase_options *opts = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + opts->type = REBASE_MERGE; + opts->flags |= REBASE_INTERACTIVE_EXPLICIT; + + return 0; +} + +static enum empty_type parse_empty_value(const char *value) +{ + if (!strcasecmp(value, "drop")) + return EMPTY_DROP; + else if (!strcasecmp(value, "keep")) + return EMPTY_KEEP; + else if (!strcasecmp(value, "ask")) + return EMPTY_ASK; + + die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value); +} + +static int parse_opt_empty(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *options = opt->value; + enum empty_type value = parse_empty_value(arg); + + BUG_ON_OPT_NEG(unset); + + options->empty = value; + return 0; +} + +static void NORETURN error_on_missing_default_upstream(void) +{ + struct branch *current_branch = branch_get(NULL); + + printf(_("%s\n" + "Please specify which branch you want to rebase against.\n" + "See git-rebase(1) for details.\n" + "\n" + " git rebase '<branch>'\n" + "\n"), + current_branch ? _("There is no tracking information for " + "the current branch.") : + _("You are not currently on a branch.")); + + if (current_branch) { + const char *remote = current_branch->remote_name; + + if (!remote) + remote = _("<remote>"); + + printf(_("If you wish to set tracking information for this " + "branch you can do so with:\n" + "\n" + " git branch --set-upstream-to=%s/<branch> %s\n" + "\n"), + remote, current_branch->name); + } + exit(1); +} + +static void set_reflog_action(struct rebase_options *options) +{ + const char *env; + struct strbuf buf = STRBUF_INIT; + + if (!is_merge(options)) + return; + + env = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); + if (env && strcmp("rebase", env)) + return; /* only override it if it is "rebase" */ + + strbuf_addf(&buf, "rebase (%s)", options->action); + setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1); + strbuf_release(&buf); +} + +static int check_exec_cmd(const char *cmd) +{ + if (strchr(cmd, '\n')) + return error(_("exec commands cannot contain newlines")); + + /* Does the command consist purely of whitespace? */ + if (!cmd[strspn(cmd, " \t\r\f\v")]) + return error(_("empty exec command")); + + return 0; +} + +int cmd_rebase(int argc, const char **argv, const char *prefix) +{ + struct rebase_options options = REBASE_OPTIONS_INIT; + const char *branch_name; + int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; + int ok_to_skip_pre_rebase = 0; + struct strbuf msg = STRBUF_INIT; + struct strbuf revisions = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct object_id merge_base; + enum action action = ACTION_NONE; + const char *gpg_sign = NULL; + struct string_list exec = STRING_LIST_INIT_NODUP; + const char *rebase_merges = NULL; + int fork_point = -1; + struct string_list strategy_options = STRING_LIST_INIT_NODUP; + struct object_id squash_onto; + char *squash_onto_name = NULL; + int reschedule_failed_exec = -1; + int allow_preemptive_ff = 1; + struct option builtin_rebase_options[] = { + OPT_STRING(0, "onto", &options.onto_name, + N_("revision"), + N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), + OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, + N_("allow pre-rebase hook to run")), + OPT_NEGBIT('q', "quiet", &options.flags, + N_("be quiet. implies --no-stat"), + REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), + OPT_BIT('v', "verbose", &options.flags, + N_("display a diffstat of what changed upstream"), + REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), + {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, + N_("do not show diffstat of what changed upstream"), + PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "signoff", &options.signoff, + N_("add a Signed-off-by: line to each commit")), + OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &options.git_am_opts, + NULL, N_("passed to 'git am'"), + PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "committer-date-is-author-date", + &options.git_am_opts, NULL, + N_("passed to 'git am'"), PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "ignore-date", &options.git_am_opts, NULL, + N_("passed to 'git am'"), PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV('C', NULL, &options.git_am_opts, N_("n"), + N_("passed to 'git apply'"), 0), + OPT_PASSTHRU_ARGV(0, "whitespace", &options.git_am_opts, + N_("action"), N_("passed to 'git apply'"), 0), + OPT_BIT('f', "force-rebase", &options.flags, + N_("cherry-pick all commits, even if unchanged"), + REBASE_FORCE), + OPT_BIT(0, "no-ff", &options.flags, + N_("cherry-pick all commits, even if unchanged"), + REBASE_FORCE), + OPT_CMDMODE(0, "continue", &action, N_("continue"), + ACTION_CONTINUE), + OPT_CMDMODE(0, "skip", &action, + N_("skip current patch and continue"), ACTION_SKIP), + OPT_CMDMODE(0, "abort", &action, + N_("abort and check out the original branch"), + ACTION_ABORT), + OPT_CMDMODE(0, "quit", &action, + N_("abort but keep HEAD where it is"), ACTION_QUIT), + OPT_CMDMODE(0, "edit-todo", &action, N_("edit the todo list " + "during an interactive rebase"), ACTION_EDIT_TODO), + OPT_CMDMODE(0, "show-current-patch", &action, + N_("show the patch file being applied or merged"), + ACTION_SHOW_CURRENT_PATCH), + OPT_CALLBACK_F(0, "apply", &options, NULL, + N_("use apply strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_am), + OPT_CALLBACK_F('m', "merge", &options, NULL, + N_("use merging strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_merge), + OPT_CALLBACK_F('i', "interactive", &options, NULL, + N_("let the user edit the list of commits to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_interactive), + OPT_SET_INT_F('p', "preserve-merges", &options.type, + N_("(DEPRECATED) try to recreate merges instead of " + "ignoring them"), + REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), + OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), + OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}", + N_("how to handle commits that become empty"), + PARSE_OPT_NONEG, parse_opt_empty), + OPT_CALLBACK_F('k', "keep-empty", &options, NULL, + N_("keep commits which start empty"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, + parse_opt_keep_empty), + OPT_BOOL(0, "autosquash", &options.autosquash, + N_("move commits that begin with " + "squash!/fixup! under -i")), + { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), + N_("GPG-sign commits"), + PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_AUTOSTASH(&options.autostash), + OPT_STRING_LIST('x', "exec", &exec, N_("exec"), + N_("add exec lines after each commit of the " + "editable list")), + OPT_BOOL_F(0, "allow-empty-message", + &options.allow_empty_message, + N_("allow rebasing commits with empty messages"), + PARSE_OPT_HIDDEN), + {OPTION_STRING, 'r', "rebase-merges", &rebase_merges, + N_("mode"), + N_("try to rebase merges instead of skipping them"), + PARSE_OPT_OPTARG, NULL, (intptr_t)""}, + OPT_BOOL(0, "fork-point", &fork_point, + N_("use 'merge-base --fork-point' to refine upstream")), + OPT_STRING('s', "strategy", &options.strategy, + N_("strategy"), N_("use the given merge strategy")), + OPT_STRING_LIST('X', "strategy-option", &strategy_options, + N_("option"), + N_("pass the argument through to the merge " + "strategy")), + OPT_BOOL(0, "root", &options.root, + N_("rebase all reachable commits up to the root(s)")), + 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; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + + 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)) + warning(_("the rebase.useBuiltin support has been removed!\n" + "See its entry in 'git help config' for details.")); + + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/applying", apply_dir()); + if(file_exists(buf.buf)) + die(_("It looks like 'git am' is in progress. Cannot rebase.")); + + if (is_directory(apply_dir())) { + options.type = REBASE_APPLY; + options.state_dir = apply_dir(); + } else if (is_directory(merge_dir())) { + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/rewritten", merge_dir()); + if (is_directory(buf.buf)) { + options.type = REBASE_PRESERVE_MERGES; + options.flags |= REBASE_INTERACTIVE_EXPLICIT; + } else { + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/interactive", merge_dir()); + if(file_exists(buf.buf)) { + options.type = REBASE_MERGE; + options.flags |= REBASE_INTERACTIVE_EXPLICIT; + } else + options.type = REBASE_MERGE; + } + options.state_dir = merge_dir(); + } + + if (options.type != REBASE_UNSPECIFIED) + in_progress = 1; + + total_argc = argc; + argc = parse_options(argc, argv, prefix, + builtin_rebase_options, + builtin_rebase_usage, 0); + + if (action != ACTION_NONE && total_argc != 2) { + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + } + + if (argc > 2) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + + if (options.type == REBASE_PRESERVE_MERGES) + warning(_("git rebase --preserve-merges is deprecated. " + "Use --rebase-merges instead.")); + + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + + if (options.root && fork_point > 0) + die(_("cannot combine '--root' with '--fork-point'")); + + if (action != ACTION_NONE && !in_progress) + die(_("No rebase in progress?")); + setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); + + if (action == ACTION_EDIT_TODO && !is_merge(&options)) + die(_("The --edit-todo action can only be used during " + "interactive rebase.")); + + if (trace2_is_enabled()) { + if (is_merge(&options)) + trace2_cmd_mode("interactive"); + else if (exec.nr) + trace2_cmd_mode("interactive-exec"); + else + trace2_cmd_mode(action_names[action]); + } + + switch (action) { + case ACTION_CONTINUE: { + struct object_id head; + struct lock_file lock_file = LOCK_INIT; + int fd; + + options.action = "continue"; + set_reflog_action(&options); + + /* Sanity check */ + if (get_oid("HEAD", &head)) + die(_("Cannot read HEAD")); + + fd = hold_locked_index(&lock_file, 0); + if (repo_read_index(the_repository) < 0) + die(_("could not read index")); + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, + NULL); + if (0 <= fd) + repo_update_index_if_able(the_repository, &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(the_repository, 1)) { + puts(_("You must edit all merge conflicts and then\n" + "mark them as resolved using git add")); + exit(1); + } + if (read_basic_state(&options)) + exit(1); + goto run_rebase; + } + case ACTION_SKIP: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + + options.action = "skip"; + set_reflog_action(&options); + + rerere_clear(the_repository, &merge_rr); + string_list_clear(&merge_rr, 1); + + 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)) + exit(1); + goto run_rebase; + } + case ACTION_ABORT: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + options.action = "abort"; + set_reflog_action(&options); + + rerere_clear(the_repository, &merge_rr); + string_list_clear(&merge_rr, 1); + + if (read_basic_state(&options)) + exit(1); + if (reset_head(the_repository, &options.orig_head, "reset", + options.head_name, RESET_HEAD_HARD, + 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); + ret = !!finish_rebase(&options); + goto cleanup; + } + case ACTION_QUIT: { + save_autostash(state_dir_path("autostash", &options)); + if (options.type == REBASE_MERGE) { + struct replay_opts replay = REPLAY_OPTS_INIT; + + replay.action = REPLAY_INTERACTIVE_REBASE; + ret = !!sequencer_remove_state(&replay); + } else { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.state_dir); + ret = !!remove_dir_recursively(&buf, 0); + if (ret) + error(_("could not remove '%s'"), + options.state_dir); + } + goto cleanup; + } + case ACTION_EDIT_TODO: + options.action = "edit-todo"; + options.dont_finish_rebase = 1; + goto run_rebase; + case ACTION_SHOW_CURRENT_PATCH: + options.action = "show-current-patch"; + options.dont_finish_rebase = 1; + goto run_rebase; + case ACTION_NONE: + break; + default: + BUG("action: %d", action); + } + + /* Make sure no rebase is in progress */ + if (in_progress) { + const char *last_slash = strrchr(options.state_dir, '/'); + const char *state_dir_base = + last_slash ? last_slash + 1 : options.state_dir; + const char *cmd_live_rebase = + "git rebase (--continue | --abort | --skip)"; + strbuf_reset(&buf); + strbuf_addf(&buf, "rm -fr \"%s\"", options.state_dir); + die(_("It seems that there is already a %s directory, and\n" + "I wonder if you are in the middle of another rebase. " + "If that is the\n" + "case, please try\n\t%s\n" + "If that is not the case, please\n\t%s\n" + "and run me again. I am stopping in case you still " + "have something\n" + "valuable there.\n"), + state_dir_base, cmd_live_rebase, buf.buf); + } + + if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) || + (action != ACTION_NONE) || + (exec.nr > 0) || + options.autosquash) { + allow_preemptive_ff = 0; + } + + for (i = 0; i < options.git_am_opts.argc; i++) { + const char *option = options.git_am_opts.argv[i], *p; + if (!strcmp(option, "--committer-date-is-author-date") || + !strcmp(option, "--ignore-date") || + !strcmp(option, "--whitespace=fix") || + !strcmp(option, "--whitespace=strip")) + allow_preemptive_ff = 0; + else if (skip_prefix(option, "-C", &p)) { + while (*p) + if (!isdigit(*(p++))) + die(_("switch `C' expects a " + "numerical value")); + } else if (skip_prefix(option, "--whitespace=", &p)) { + if (*p && strcmp(p, "warn") && strcmp(p, "nowarn") && + strcmp(p, "error") && strcmp(p, "error-all")) + die("Invalid whitespace option: '%s'", p); + } + } + + for (i = 0; i < exec.nr; i++) + if (check_exec_cmd(exec.items[i].string)) + exit(1); + + if (!(options.flags & REBASE_NO_QUIET)) + argv_array_push(&options.git_am_opts, "-q"); + + if (options.empty != EMPTY_UNSPECIFIED) + imply_merge(&options, "--empty"); + + 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; + + imply_merge(&options, "--exec"); + + strbuf_reset(&buf); + for (i = 0; i < exec.nr; i++) + strbuf_addf(&buf, "exec %s\n", exec.items[i].string); + options.cmd = xstrdup(buf.buf); + } + + if (rebase_merges) { + if (!*rebase_merges) + ; /* default mode; do nothing */ + else if (!strcmp("rebase-cousins", rebase_merges)) + options.rebase_cousins = 1; + else if (strcmp("no-rebase-cousins", rebase_merges)) + die(_("Unknown mode: %s"), rebase_merges); + options.rebase_merges = 1; + imply_merge(&options, "--rebase-merges"); + } + + if (strategy_options.nr) { + int i; + + if (!options.strategy) + options.strategy = "recursive"; + + strbuf_reset(&buf); + for (i = 0; i < strategy_options.nr; i++) + strbuf_addf(&buf, " --%s", + strategy_options.items[i].string); + options.strategy_opts = xstrdup(buf.buf); + } + + if (options.strategy) { + options.strategy = xstrdup(options.strategy); + switch (options.type) { + case REBASE_APPLY: + die(_("--strategy requires --merge or --interactive")); + case REBASE_MERGE: + case REBASE_PRESERVE_MERGES: + /* compatible */ + break; + case REBASE_UNSPECIFIED: + options.type = REBASE_MERGE; + break; + default: + BUG("unhandled rebase type (%d)", options.type); + } + } + + if (options.type == REBASE_MERGE) + imply_merge(&options, "--merge"); + + if (options.root && !options.onto_name) + imply_merge(&options, "--root without --onto"); + + if (isatty(2) && options.flags & REBASE_NO_QUIET) + strbuf_addstr(&options.git_format_patch_opt, " --progress"); + + if (options.git_am_opts.argc || options.type == REBASE_APPLY) { + /* all am options except -q are compatible only with --apply */ + for (i = options.git_am_opts.argc - 1; i >= 0; i--) + if (strcmp(options.git_am_opts.argv[i], "-q")) + break; + + if (i >= 0) { + if (is_merge(&options)) + die(_("cannot combine apply options with " + "merge options")); + else + options.type = REBASE_APPLY; + } + } + + if (options.type == REBASE_UNSPECIFIED) { + if (!strcmp(options.default_backend, "merge")) + imply_merge(&options, "--merge"); + else if (!strcmp(options.default_backend, "apply")) + options.type = REBASE_APPLY; + else + die(_("Unknown rebase backend: %s"), + options.default_backend); + } + + switch (options.type) { + case REBASE_MERGE: + case REBASE_PRESERVE_MERGES: + options.state_dir = merge_dir(); + break; + case REBASE_APPLY: + options.state_dir = apply_dir(); + break; + default: + BUG("options.type was just set above; should be unreachable."); + } + + if (options.empty == EMPTY_UNSPECIFIED) { + if (options.flags & REBASE_INTERACTIVE_EXPLICIT) + options.empty = EMPTY_ASK; + else if (exec.nr > 0) + options.empty = EMPTY_KEEP; + else + options.empty = EMPTY_DROP; + } + if (reschedule_failed_exec > 0 && !is_merge(&options)) + die(_("--reschedule-failed-exec requires " + "--exec or --interactive")); + if (reschedule_failed_exec >= 0) + options.reschedule_failed_exec = reschedule_failed_exec; + + if (options.signoff) { + if (options.type == REBASE_PRESERVE_MERGES) + die("cannot combine '--signoff' with " + "'--preserve-merges'"); + argv_array_push(&options.git_am_opts, "--signoff"); + options.flags |= REBASE_FORCE; + } + + if (options.type == REBASE_PRESERVE_MERGES) { + /* + * Note: incompatibility with --signoff handled in signoff block above + * Note: incompatibility with --interactive is just a strong warning; + * git-rebase.txt caveats with "unless you know what you are doing" + */ + if (options.rebase_merges) + die(_("cannot combine '--preserve-merges' with " + "'--rebase-merges'")); + + if (options.reschedule_failed_exec) + die(_("error: cannot combine '--preserve-merges' with " + "'--reschedule-failed-exec'")); + } + + if (!options.root) { + if (argc < 1) { + struct branch *branch; + + branch = branch_get(NULL); + options.upstream_name = branch_get_upstream(branch, + NULL); + if (!options.upstream_name) + error_on_missing_default_upstream(); + if (fork_point < 0) + fork_point = 1; + } else { + options.upstream_name = argv[0]; + argc--; + argv++; + if (!strcmp(options.upstream_name, "-")) + options.upstream_name = "@{-1}"; + } + options.upstream = peel_committish(options.upstream_name); + if (!options.upstream) + die(_("invalid upstream '%s'"), options.upstream_name); + options.upstream_arg = options.upstream_name; + } else { + if (!options.onto_name) { + if (commit_tree("", 0, the_hash_algo->empty_tree, NULL, + &squash_onto, NULL, NULL) < 0) + die(_("Could not create new root commit")); + options.squash_onto = &squash_onto; + options.onto_name = squash_onto_name = + xstrdup(oid_to_hex(&squash_onto)); + } else + options.root_with_onto = 1; + + options.upstream_name = NULL; + options.upstream = NULL; + if (argc > 1) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + options.upstream_arg = "--root"; + } + + /* Make sure the branch to rebase onto is valid. */ + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) + options.onto_name = options.upstream_name; + if (strstr(options.onto_name, "...")) { + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } + options.onto = lookup_commit_or_die(&merge_base, + options.onto_name); + } else { + options.onto = peel_committish(options.onto_name); + if (!options.onto) + die(_("Does not point to a valid commit '%s'"), + options.onto_name); + } + + /* + * If the branch to rebase is given, that is the branch we will rebase + * branch_name -- branch/commit being rebased, or + * HEAD (already detached) + * orig_head -- commit object name of tip of the branch before rebasing + * head_name -- refs/heads/<that-branch> or NULL (detached HEAD) + */ + if (argc == 1) { + /* Is it "rebase other branchname" or "rebase other commit"? */ + branch_name = argv[0]; + options.switch_to = argv[0]; + + /* Is it a local branch? */ + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/heads/%s", branch_name); + if (!read_ref(buf.buf, &options.orig_head)) { + die_if_checked_out(buf.buf, 1); + options.head_name = xstrdup(buf.buf); + /* If not is it a valid ref (branch or commit)? */ + } else if (!get_oid(branch_name, &options.orig_head)) + options.head_name = NULL; + else + die(_("fatal: no such branch/commit '%s'"), + branch_name); + } else if (argc == 0) { + /* Do not need to switch branches, we are already on it. */ + options.head_name = + xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL, + &flags)); + if (!options.head_name) + die(_("No such ref: %s"), "HEAD"); + if (flags & REF_ISSYMREF) { + if (!skip_prefix(options.head_name, + "refs/heads/", &branch_name)) + branch_name = options.head_name; + + } else { + FREE_AND_NULL(options.head_name); + branch_name = "HEAD"; + } + if (get_oid("HEAD", &options.orig_head)) + die(_("Could not resolve HEAD to a revision")); + } else + BUG("unexpected number of arguments left to parse"); + + if (fork_point > 0) { + struct commit *head = + lookup_commit_reference(the_repository, + &options.orig_head); + options.restrict_revision = + get_fork_point(options.upstream_name, head); + } + + if (repo_read_index(the_repository) < 0) + die(_("could not read index")); + + if (options.autostash) { + create_autostash(the_repository, state_dir_path("autostash", &options), + DEFAULT_REFLOG_ACTION); + } + + if (require_clean_work_tree(the_repository, "rebase", + _("Please commit or stash them."), 1, 1)) { + ret = 1; + goto cleanup; + } + + /* + * Now we are rebasing commits upstream..orig_head (or with --root, + * everything leading up to orig_head) on top of onto. + */ + + /* + * Check if we are already based on onto with linear history, + * in which case we could fast-forward without replacing the commits + * with new commits recreated by replaying their changes. + * + * Note that can_fast_forward() initializes merge_base, so we have to + * call it before checking allow_preemptive_ff. + */ + if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, + &options.orig_head, &merge_base) && + allow_preemptive_ff) { + int flag; + + if (!(options.flags & REBASE_FORCE)) { + /* Lazily switch to the target branch if needed... */ + if (options.switch_to) { + strbuf_reset(&buf); + strbuf_addf(&buf, "%s: checkout %s", + getenv(GIT_REFLOG_ACTION_ENVIRONMENT), + options.switch_to); + if (reset_head(the_repository, + &options.orig_head, "checkout", + options.head_name, + RESET_HEAD_RUN_POST_CHECKOUT_HOOK, + NULL, buf.buf, + DEFAULT_REFLOG_ACTION) < 0) { + ret = !!error(_("could not switch to " + "%s"), + options.switch_to); + goto cleanup; + } + } + + if (!(options.flags & REBASE_NO_QUIET)) + ; /* be quiet */ + else if (!strcmp(branch_name, "HEAD") && + resolve_ref_unsafe("HEAD", 0, NULL, &flag)) + puts(_("HEAD is up to date.")); + else + printf(_("Current branch %s is up to date.\n"), + branch_name); + ret = !!finish_rebase(&options); + goto cleanup; + } else if (!(options.flags & REBASE_NO_QUIET)) + ; /* be quiet */ + else if (!strcmp(branch_name, "HEAD") && + resolve_ref_unsafe("HEAD", 0, NULL, &flag)) + puts(_("HEAD is up to date, rebase forced.")); + else + printf(_("Current branch %s is up to date, rebase " + "forced.\n"), branch_name); + } + + /* If a hook exists, give it a chance to interrupt*/ + if (!ok_to_skip_pre_rebase && + run_hook_le(NULL, "pre-rebase", options.upstream_arg, + argc ? argv[0] : NULL, NULL)) + die(_("The pre-rebase hook refused to rebase.")); + + if (options.flags & REBASE_DIFFSTAT) { + struct diff_options opts; + + if (options.flags & REBASE_VERBOSE) { + if (is_null_oid(&merge_base)) + printf(_("Changes to %s:\n"), + oid_to_hex(&options.onto->object.oid)); + else + printf(_("Changes from %s to %s:\n"), + oid_to_hex(&merge_base), + oid_to_hex(&options.onto->object.oid)); + } + + /* We want color (if set), but no pager */ + diff_setup(&opts); + opts.stat_width = -1; /* use full terminal width */ + opts.stat_graph_width = -1; /* respect statGraphWidth config */ + opts.output_format |= + DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; + opts.detect_rename = DIFF_DETECT_RENAME; + diff_setup_done(&opts); + diff_tree_oid(is_null_oid(&merge_base) ? + the_hash_algo->empty_tree : &merge_base, + &options.onto->object.oid, "", &opts); + diffcore_std(&opts); + diff_flush(&opts); + } + + if (is_merge(&options)) + goto run_rebase; + + /* Detach HEAD and reset the tree */ + if (options.flags & REBASE_NO_QUIET) + printf(_("First, rewinding head to replay your work on top of " + "it...\n")); + + strbuf_addf(&msg, "%s: checkout %s", + getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); + 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, DEFAULT_REFLOG_ACTION)) + die(_("Could not detach HEAD")); + strbuf_release(&msg); + + /* + * If the onto is a proper descendant of the tip of the branch, then + * we just fast-forwarded. + */ + strbuf_reset(&msg); + if (oideq(&merge_base, &options.orig_head)) { + printf(_("Fast-forwarded %s to %s.\n"), + branch_name, options.onto_name); + strbuf_addf(&msg, "rebase finished: %s onto %s", + options.head_name ? options.head_name : "detached HEAD", + oid_to_hex(&options.onto->object.oid)); + 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; + } + + strbuf_addf(&revisions, "%s..%s", + options.root ? oid_to_hex(&options.onto->object.oid) : + (options.restrict_revision ? + oid_to_hex(&options.restrict_revision->object.oid) : + oid_to_hex(&options.upstream->object.oid)), + oid_to_hex(&options.orig_head)); + + options.revisions = revisions.buf; + +run_rebase: + ret = !!run_specific_rebase(&options, action); + +cleanup: + strbuf_release(&buf); + strbuf_release(&revisions); + free(options.head_name); + free(options.gpg_sign_opt); + free(options.cmd); + free(squash_onto_name); + return ret; +} diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c4272fbc96..ea3d0f01af 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -7,14 +7,13 @@ #include "pkt-line.h" #include "sideband.h" #include "run-command.h" -#include "exec_cmd.h" +#include "exec-cmd.h" #include "commit.h" #include "object.h" #include "remote.h" #include "connect.h" -#include "transport.h" #include "string-list.h" -#include "sha1-array.h" +#include "oid-array.h" #include "connected.h" #include "argv-array.h" #include "version.h" @@ -25,7 +24,11 @@ #include "tmp-objdir.h" #include "oidset.h" #include "packfile.h" +#include "object-store.h" #include "protocol.h" +#include "commit-reach.h" +#include "worktree.h" +#include "shallow.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -279,8 +282,7 @@ static int show_ref_cb(const char *path_full, const struct object_id *oid, return 0; } -static void show_one_alternate_ref(const char *refname, - const struct object_id *oid, +static void show_one_alternate_ref(const struct object_id *oid, void *data) { struct oidset *seen = data; @@ -417,24 +419,22 @@ static int copy_to_sideband(int in, int out, void *arg) return 0; } -#define HMAC_BLOCK_SIZE 64 - -static void hmac_sha1(unsigned char *out, +static void hmac_hash(unsigned char *out, const char *key_in, size_t key_len, const char *text, size_t text_len) { - unsigned char key[HMAC_BLOCK_SIZE]; - unsigned char k_ipad[HMAC_BLOCK_SIZE]; - unsigned char k_opad[HMAC_BLOCK_SIZE]; + unsigned char key[GIT_MAX_BLKSZ]; + unsigned char k_ipad[GIT_MAX_BLKSZ]; + unsigned char k_opad[GIT_MAX_BLKSZ]; int i; - git_SHA_CTX ctx; + git_hash_ctx ctx; /* RFC 2104 2. (1) */ - memset(key, '\0', HMAC_BLOCK_SIZE); - if (HMAC_BLOCK_SIZE < key_len) { - git_SHA1_Init(&ctx); - git_SHA1_Update(&ctx, key_in, key_len); - git_SHA1_Final(key, &ctx); + memset(key, '\0', GIT_MAX_BLKSZ); + if (the_hash_algo->blksz < key_len) { + the_hash_algo->init_fn(&ctx); + the_hash_algo->update_fn(&ctx, key_in, key_len); + the_hash_algo->final_fn(key, &ctx); } else { memcpy(key, key_in, key_len); } @@ -446,29 +446,29 @@ static void hmac_sha1(unsigned char *out, } /* RFC 2104 2. (3) & (4) */ - git_SHA1_Init(&ctx); - git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad)); - git_SHA1_Update(&ctx, text, text_len); - git_SHA1_Final(out, &ctx); + the_hash_algo->init_fn(&ctx); + the_hash_algo->update_fn(&ctx, k_ipad, sizeof(k_ipad)); + the_hash_algo->update_fn(&ctx, text, text_len); + the_hash_algo->final_fn(out, &ctx); /* RFC 2104 2. (6) & (7) */ - git_SHA1_Init(&ctx); - git_SHA1_Update(&ctx, k_opad, sizeof(k_opad)); - git_SHA1_Update(&ctx, out, 20); - git_SHA1_Final(out, &ctx); + the_hash_algo->init_fn(&ctx); + the_hash_algo->update_fn(&ctx, k_opad, sizeof(k_opad)); + the_hash_algo->update_fn(&ctx, out, the_hash_algo->rawsz); + the_hash_algo->final_fn(out, &ctx); } static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp) { struct strbuf buf = STRBUF_INIT; - unsigned char sha1[20]; + unsigned char hash[GIT_MAX_RAWSZ]; strbuf_addf(&buf, "%s:%"PRItime, path, stamp); - hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));; + hmac_hash(hash, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed)); strbuf_release(&buf); - /* RFC 2104 5. HMAC-SHA1-80 */ - strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, 20, sha1_to_hex(sha1)); + /* RFC 2104 5. HMAC-SHA1 or HMAC-SHA256 */ + strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, (int)the_hash_algo->hexsz, hash_to_hex(hash)); return strbuf_detach(&buf, NULL); } @@ -500,12 +500,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; @@ -547,8 +562,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; @@ -629,8 +650,6 @@ static void prepare_push_cert_sha1(struct child_process *proc) return; if (!already_done) { - struct strbuf gpg_output = STRBUF_INIT; - struct strbuf gpg_status = STRBUF_INIT; int bogs /* beginning_of_gpg_sig */; already_done = 1; @@ -639,22 +658,11 @@ static void prepare_push_cert_sha1(struct child_process *proc) oidclr(&push_cert_oid); memset(&sigcheck, '\0', sizeof(sigcheck)); - sigcheck.result = 'N'; bogs = parse_signature(push_cert.buf, push_cert.len); - if (verify_signed_buffer(push_cert.buf, bogs, - push_cert.buf + bogs, push_cert.len - bogs, - &gpg_output, &gpg_status) < 0) { - ; /* error running gpg */ - } else { - sigcheck.payload = push_cert.buf; - sigcheck.gpg_output = gpg_output.buf; - sigcheck.gpg_status = gpg_status.buf; - parse_gpg_output(&sigcheck); - } + check_signature(push_cert.buf, bogs, push_cert.buf + bogs, + push_cert.len - bogs, &sigcheck); - strbuf_release(&gpg_output); - strbuf_release(&gpg_status); nonce_status = check_nonce(push_cert.buf, bogs); } if (!is_null_oid(&push_cert_oid)) { @@ -706,6 +714,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; + proc.trace2_hook_name = hook_name; + if (feed_state->push_options) { int i; for (i = 0; i < feed_state->push_options->nr; i++) @@ -819,6 +829,7 @@ static int run_update_hook(struct command *cmd) proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; proc.argv = argv; + proc.trace2_hook_name = "update"; code = start_command(&proc); if (code) @@ -828,16 +839,6 @@ static int run_update_hook(struct command *cmd) return finish_command(&proc); } -static int is_ref_checked_out(const char *ref) -{ - if (is_bare_repository()) - return 0; - - if (!head_name) - return 0; - return !strcmp(head_name, ref); -} - static char *refuse_unconfigured_deny_msg = N_("By default, updating the current branch in a non-bare repository\n" "is denied, because it will make the index and work tree inconsistent\n" @@ -876,7 +877,7 @@ static void refuse_unconfigured_deny_delete_current(void) static int command_singleton_iterator(void *cb_data, struct object_id *oid); static int update_shallow_ref(struct command *cmd, struct shallow_info *si) { - static struct lock_file shallow_lock; + struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT; struct oid_array extra = OID_ARRAY_INIT; struct check_connected_options opt = CHECK_CONNECTED_INIT; uint32_t mask = 1 << (cmd->index % 32); @@ -893,19 +894,19 @@ 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 * not lose these new roots.. */ for (i = 0; i < extra.nr; i++) - register_shallow(&extra.oid[i]); + register_shallow(the_repository, &extra.oid[i]); si->shallow_ref[cmd->index] = 0; oid_array_clear(&extra); @@ -968,7 +969,7 @@ static const char *push_to_deploy(unsigned char *sha1, return "Working directory has unstaged changes"; /* diff-index with either HEAD or an empty tree */ - diff_index[4] = head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX; + diff_index[4] = head_has_history() ? "HEAD" : empty_tree_oid_hex(); child_process_init(&child); child.argv = diff_index; @@ -980,7 +981,7 @@ static const char *push_to_deploy(unsigned char *sha1, if (run_command(&child)) return "Working directory has staged changes"; - read_tree[3] = sha1_to_hex(sha1); + read_tree[3] = hash_to_hex(sha1); child_process_init(&child); child.argv = read_tree; child.env = env->argv; @@ -997,28 +998,38 @@ static const char *push_to_deploy(unsigned char *sha1, static const char *push_to_checkout_hook = "push-to-checkout"; -static const char *push_to_checkout(unsigned char *sha1, +static const char *push_to_checkout(unsigned char *hash, struct argv_array *env, const char *work_tree) { argv_array_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); if (run_hook_le(env->argv, push_to_checkout_hook, - sha1_to_hex(sha1), NULL)) + hash_to_hex(hash), NULL)) return "push-to-checkout hook declined"; else return NULL; } -static const char *update_worktree(unsigned char *sha1) +static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree) { - const char *retval; - const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : ".."; + const char *retval, *work_tree, *git_dir = NULL; struct argv_array env = ARGV_ARRAY_INIT; + if (worktree && worktree->path) + work_tree = worktree->path; + else if (git_work_tree_cfg) + work_tree = git_work_tree_cfg; + else + work_tree = ".."; + if (is_bare_repository()) return "denyCurrentBranch = updateInstead needs a worktree"; + if (worktree) + git_dir = get_worktree_git_dir(worktree); + if (!git_dir) + git_dir = get_git_dir(); - argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir())); + argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir)); if (!find_hook(push_to_checkout_hook)) retval = push_to_deploy(sha1, &env, work_tree); @@ -1037,6 +1048,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) const char *ret; struct object_id *old_oid = &cmd->old_oid; struct object_id *new_oid = &cmd->new_oid; + int do_update_worktree = 0; + const struct worktree *worktree = is_bare_repository() ? NULL : find_shared_symref("HEAD", name); /* only refs/... are allowed */ if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { @@ -1048,7 +1061,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) free(namespaced_name); namespaced_name = strbuf_detach(&namespaced_name_buf, NULL); - if (is_ref_checked_out(namespaced_name)) { + if (worktree) { switch (deny_current_branch) { case DENY_IGNORE: break; @@ -1062,9 +1075,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) refuse_unconfigured_deny(); return "branch is currently checked out"; case DENY_UPDATE_INSTEAD: - ret = update_worktree(new_oid->hash); - if (ret) - return ret; + /* pass -- let other checks intervene first */ + do_update_worktree = 1; break; } } @@ -1081,7 +1093,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) return "deletion prohibited"; } - if (head_name && !strcmp(namespaced_name, head_name)) { + if (worktree || (head_name && !strcmp(namespaced_name, head_name))) { switch (deny_delete_current) { case DENY_IGNORE: break; @@ -1107,8 +1119,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) struct object *old_object, *new_object; struct commit *old_commit, *new_commit; - old_object = parse_object(old_oid); - new_object = parse_object(new_oid); + old_object = parse_object(the_repository, old_oid); + new_object = parse_object(the_repository, new_oid); if (!old_object || !new_object || old_object->type != OBJ_COMMIT || @@ -1129,9 +1141,15 @@ static const char *update(struct command *cmd, struct shallow_info *si) return "hook declined"; } + if (do_update_worktree) { + ret = update_worktree(new_oid->hash, find_shared_symref("HEAD", name)); + if (ret) + return ret; + } + if (is_null_oid(new_oid)) { struct strbuf err = STRBUF_INIT; - if (!parse_object(old_oid)) { + if (!parse_object(the_repository, old_oid)) { old_oid = NULL; if (ref_exists(name)) { rp_warning("Allowing deletion of corrupt ref."); @@ -1196,6 +1214,7 @@ static void run_update_post_hook(struct command *commands) proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; + proc.trace2_hook_name = "post-update"; if (!start_command(&proc)) { if (use_sideband) @@ -1204,17 +1223,12 @@ static void run_update_post_hook(struct command *commands) } } -static void check_aliased_update(struct command *cmd, struct string_list *list) +static void check_aliased_update_internal(struct command *cmd, + struct string_list *list, + const char *dst_name, int flag) { - struct strbuf buf = STRBUF_INIT; - const char *dst_name; struct string_list_item *item; struct command *dst_cmd; - int flag; - - strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); - dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag); - strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) return; @@ -1234,8 +1248,8 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) dst_cmd = (struct command *) item->util; - if (!oidcmp(&cmd->old_oid, &dst_cmd->old_oid) && - !oidcmp(&cmd->new_oid, &dst_cmd->new_oid)) + if (oideq(&cmd->old_oid, &dst_cmd->old_oid) && + oideq(&cmd->new_oid, &dst_cmd->new_oid)) return; dst_cmd->skip_update = 1; @@ -1253,6 +1267,18 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) "inconsistent aliased update"; } +static void check_aliased_update(struct command *cmd, struct string_list *list) +{ + struct strbuf buf = STRBUF_INIT; + const char *dst_name; + int flag; + + strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); + dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag); + check_aliased_update_internal(cmd, list, dst_name, flag); + strbuf_release(&buf); +} + static void check_aliased_updates(struct command *commands) { struct command *cmd; @@ -1378,7 +1404,7 @@ static void warn_if_skipped_connectivity_check(struct command *commands, } } if (!checked_connectivity) - die("BUG: connectivity check skipped???"); + BUG("connectivity check skipped???"); } static void execute_commands_non_atomic(struct command *commands, @@ -1575,30 +1601,29 @@ static void queue_commands_from_cert(struct command **tail, } } -static struct command *read_head_info(struct oid_array *shallow) +static struct command *read_head_info(struct packet_reader *reader, + struct oid_array *shallow) { struct command *commands = NULL; struct command **p = &commands; for (;;) { - char *line; - int len, linelen; + int linelen; - line = packet_read_line(0, &len); - if (!line) + if (packet_reader_read(reader) != PACKET_READ_NORMAL) break; - if (len > 8 && starts_with(line, "shallow ")) { + if (reader->pktlen > 8 && starts_with(reader->line, "shallow ")) { struct object_id oid; - if (get_oid_hex(line + 8, &oid)) + if (get_oid_hex(reader->line + 8, &oid)) die("protocol error: expected shallow sha, got '%s'", - line + 8); + reader->line + 8); oid_array_append(shallow, &oid); continue; } - linelen = strlen(line); - if (linelen < len) { - const char *feature_list = line + linelen + 1; + linelen = strlen(reader->line); + if (linelen < reader->pktlen) { + const char *feature_list = reader->line + linelen + 1; if (parse_feature_request(feature_list, "report-status")) report_status = 1; if (parse_feature_request(feature_list, "side-band-64k")) @@ -1613,28 +1638,32 @@ static struct command *read_head_info(struct oid_array *shallow) use_push_options = 1; } - if (!strcmp(line, "push-cert")) { + if (!strcmp(reader->line, "push-cert")) { int true_flush = 0; - char certbuf[1024]; + int saved_options = reader->options; + reader->options &= ~PACKET_READ_CHOMP_NEWLINE; for (;;) { - len = packet_read(0, NULL, NULL, - certbuf, sizeof(certbuf), 0); - if (!len) { + packet_reader_read(reader); + if (reader->status == PACKET_READ_FLUSH) { true_flush = 1; break; } - if (!strcmp(certbuf, "push-cert-end\n")) + if (reader->status != PACKET_READ_NORMAL) { + die("protocol error: got an unexpected packet"); + } + if (!strcmp(reader->line, "push-cert-end\n")) break; /* end of cert */ - strbuf_addstr(&push_cert, certbuf); + strbuf_addstr(&push_cert, reader->line); } + reader->options = saved_options; if (true_flush) break; continue; } - p = queue_command(p, line, linelen); + p = queue_command(p, reader->line, linelen); } if (push_cert.len) @@ -1643,18 +1672,14 @@ static struct command *read_head_info(struct oid_array *shallow) return commands; } -static void read_push_options(struct string_list *options) +static void read_push_options(struct packet_reader *reader, + struct string_list *options) { while (1) { - char *line; - int len; - - line = packet_read_line(0, &len); - - if (!line) + if (packet_reader_read(reader) != PACKET_READ_NORMAL) break; - string_list_append(options, line); + string_list_append(options, reader->line); } } @@ -1805,8 +1830,7 @@ static const char *unpack_with_sideband(struct shallow_info *si) return ret; } -static void prepare_shallow_update(struct command *commands, - struct shallow_info *si) +static void prepare_shallow_update(struct shallow_info *si) { int i, j, k, bitmap_size = DIV_ROUND_UP(si->ref->nr, 32); @@ -1845,7 +1869,7 @@ static void prepare_shallow_update(struct command *commands, /* * keep hooks happy by forcing a temporary shallow file via * env variable because we can't add --shallow-file to every - * command. check_everything_connected() will be done with + * command. check_connected() will be done with * true .git/shallow though. */ setenv(GIT_SHALLOW_FILE_ENVIRONMENT, alt_shallow_file, 1); @@ -1872,7 +1896,7 @@ static void update_shallow_info(struct command *commands, si->ref = ref; if (shallow_update) { - prepare_shallow_update(commands, si); + prepare_shallow_update(si); return; } @@ -1930,6 +1954,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) struct oid_array shallow = OID_ARRAY_INIT; struct oid_array ref = OID_ARRAY_INIT; struct shallow_info si; + struct packet_reader reader; struct option options[] = { OPT__QUIET(&quiet, N_("quiet")), @@ -1965,6 +1990,12 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) unpack_limit = receive_unpack_limit; switch (determine_protocol_version_server()) { + case protocol_v2: + /* + * push support for protocol v2 has not been implemented yet, + * so ignore the request to use v2 and fallback to using v0. + */ + break; case protocol_v1: /* * v1 is just the original protocol with a version string, @@ -1986,12 +2017,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) if (advertise_refs) return 0; - if ((commands = read_head_info(&shallow)) != NULL) { + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_DIE_ON_ERR_PACKET); + + if ((commands = read_head_info(&reader, &shallow)) != NULL) { const char *unpack_status = NULL; struct string_list push_options = STRING_LIST_INIT_DUP; if (use_push_options) - read_push_options(&push_options); + read_push_options(&reader, &push_options); if (!check_cert_push_options(&push_options)) { struct command *cmd; for (cmd = commands; cmd; cmd = cmd->next) @@ -2028,7 +2063,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) proc.git_cmd = 1; proc.argv = argv_gc_auto; - close_all_packs(the_repository->objects); + close_object_store(the_repository->objects); if (!start_command(&proc)) { if (use_sideband) copy_to_sideband(proc.err, -1, NULL); diff --git a/builtin/reflog.c b/builtin/reflog.c index a89bd1dd25..52ecf6d43c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -1,6 +1,8 @@ #include "builtin.h" #include "config.h" #include "lockfile.h" +#include "object-store.h" +#include "repository.h" #include "commit.h" #include "refs.h" #include "dir.h" @@ -8,14 +10,19 @@ #include "diff.h" #include "revision.h" #include "reachable.h" +#include "worktree.h" /* NEEDSWORK: switch to using parse_options */ static const char reflog_expire_usage[] = -"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>..."; +N_("git reflog expire [--expire=<time>] " + "[--expire-unreachable=<time>] " + "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] " + "[--verbose] [--all] <refs>..."); static const char reflog_delete_usage[] = -"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>..."; +N_("git reflog delete [--rewrite] [--updateref] " + "[--dry-run | -n] [--verbose] <refs>..."); static const char reflog_exists_usage[] = -"git reflog exists <ref>"; +N_("git reflog exists <ref>"); static timestamp_t default_reflog_expire; static timestamp_t default_reflog_expire_unreachable; @@ -50,6 +57,7 @@ struct collect_reflog_cb { struct collected_reflog **e; int alloc; int nr; + struct worktree *wt; }; /* Remember to update object flag allocation in object.h */ @@ -64,7 +72,7 @@ static int tree_is_complete(const struct object_id *oid) int complete; struct tree *tree; - tree = lookup_tree(oid); + tree = lookup_tree(the_repository, oid); if (!tree) return 0; if (tree->object.flags & SEEN) @@ -86,8 +94,8 @@ static int tree_is_complete(const struct object_id *oid) init_tree_desc(&desc, tree->buffer, tree->size); complete = 1; while (tree_entry(&desc, &entry)) { - if (!has_sha1_file(entry.oid->hash) || - (S_ISDIR(entry.mode) && !tree_is_complete(entry.oid))) { + if (!has_object_file(&entry.oid) || + (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) { tree->object.flags |= INCOMPLETE; complete = 0; } @@ -128,7 +136,7 @@ static int commit_is_complete(struct commit *commit) struct commit_list *parent; c = (struct commit *)object_array_pop(&study); - if (!c->object.parsed && !parse_object(&c->object.oid)) + if (!c->object.parsed && !parse_object(the_repository, &c->object.oid)) c->object.flags |= INCOMPLETE; if (c->object.flags & INCOMPLETE) { @@ -154,7 +162,7 @@ static int commit_is_complete(struct commit *commit) for (i = 0; i < found.nr; i++) { struct commit *c = (struct commit *)found.objects[i].item; - if (!tree_is_complete(&c->tree->object.oid)) { + if (!tree_is_complete(get_commit_tree_oid(c))) { is_incomplete = 1; c->object.flags |= INCOMPLETE; } @@ -194,7 +202,7 @@ static int keep_entry(struct commit **it, struct object_id *oid) if (is_null_oid(oid)) return 1; - commit = lookup_commit_reference_gently(oid, 1); + commit = lookup_commit_reference_gently(the_repository, oid, 1); if (!commit) return 0; @@ -263,7 +271,8 @@ static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit if (is_null_oid(oid)) return 0; - commit = lookup_commit_reference_gently(oid, 1); + commit = lookup_commit_reference_gently(the_repository, oid, + 1); /* Not a commit -- keep it */ if (!commit) @@ -320,24 +329,39 @@ static int push_tip_to_list(const char *refname, const struct object_id *oid, struct commit *tip_commit; if (flags & REF_ISSYMREF) return 0; - tip_commit = lookup_commit_reference_gently(oid, 1); + tip_commit = lookup_commit_reference_gently(the_repository, oid, 1); if (!tip_commit) return 0; commit_list_insert(tip_commit, list); return 0; } +static int is_head(const char *refname) +{ + switch (ref_type(refname)) { + case REF_TYPE_OTHER_PSEUDOREF: + case REF_TYPE_MAIN_PSEUDOREF: + if (parse_worktree_ref(refname, NULL, NULL, &refname)) + BUG("not a worktree ref: %s", refname); + break; + default: + break; + } + return !strcmp(refname, "HEAD"); +} + static void reflog_expiry_prepare(const char *refname, const struct object_id *oid, void *cb_data) { struct expire_reflog_policy_cb *cb = cb_data; - if (!cb->cmd.expire_unreachable || !strcmp(refname, "HEAD")) { + if (!cb->cmd.expire_unreachable || is_head(refname)) { cb->tip_commit = NULL; cb->unreachable_expire_kind = UE_HEAD; } else { - cb->tip_commit = lookup_commit_reference_gently(oid, 1); + cb->tip_commit = lookup_commit_reference_gently(the_repository, + oid, 1); if (!cb->tip_commit) cb->unreachable_expire_kind = UE_ALWAYS; else @@ -384,8 +408,19 @@ static int collect_reflog(const char *ref, const struct object_id *oid, int unus { struct collected_reflog *e; struct collect_reflog_cb *cb = cb_data; + struct strbuf newref = STRBUF_INIT; + + /* + * Avoid collecting the same shared ref multiple times because + * they are available via all worktrees. + */ + if (!cb->wt->is_current && ref_type(ref) == REF_TYPE_NORMAL) + return 0; + + strbuf_worktree_ref(cb->wt, &newref, ref); + FLEX_ALLOC_STR(e, reflog, newref.buf); + strbuf_release(&newref); - FLEX_ALLOC_STR(e, reflog, ref); oidcpy(&e->oid, oid); ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc); cb->e[cb->nr++] = e; @@ -424,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; @@ -508,7 +543,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) { struct expire_reflog_policy_cb cb; timestamp_t now = time(NULL); - int i, status, do_all; + int i, status, do_all, all_worktrees = 1; int explicit_expiry = 0; unsigned int flags = 0; @@ -525,15 +560,16 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; + if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) flags |= EXPIRE_REFLOGS_DRY_RUN; - else if (starts_with(arg, "--expire=")) { - if (parse_expiry_date(arg + 9, &cb.cmd.expire_total)) + else if (skip_prefix(arg, "--expire=", &arg)) { + if (parse_expiry_date(arg, &cb.cmd.expire_total)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_TOTAL; } - else if (starts_with(arg, "--expire-unreachable=")) { - if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable)) + else if (skip_prefix(arg, "--expire-unreachable=", &arg)) { + if (parse_expiry_date(arg, &cb.cmd.expire_unreachable)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_UNREACH; } @@ -545,6 +581,8 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) flags |= EXPIRE_REFLOGS_UPDATE_REF; else if (!strcmp(arg, "--all")) do_all = 1; + else if (!strcmp(arg, "--single-worktree")) + all_worktrees = 0; else if (!strcmp(arg, "--verbose")) flags |= EXPIRE_REFLOGS_VERBOSE; else if (!strcmp(arg, "--")) { @@ -552,7 +590,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) break; } else if (arg[0] == '-') - usage(reflog_expire_usage); + usage(_(reflog_expire_usage)); else break; } @@ -563,9 +601,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) * from reflog if the repository was pruned with older git. */ if (cb.cmd.stalefix) { - init_revisions(&cb.cmd.revs, prefix); + repo_init_revisions(the_repository, &cb.cmd.revs, prefix); if (flags & EXPIRE_REFLOGS_VERBOSE) - printf("Marking reachable objects..."); + printf(_("Marking reachable objects...")); mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL); if (flags & EXPIRE_REFLOGS_VERBOSE) putchar('\n'); @@ -573,10 +611,19 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) if (do_all) { struct collect_reflog_cb collected; + struct worktree **worktrees, **p; int i; memset(&collected, 0, sizeof(collected)); - for_each_reflog(collect_reflog, &collected); + worktrees = get_worktrees(0); + for (p = worktrees; *p; p++) { + if (!all_worktrees && !(*p)->is_current) + continue; + collected.wt = *p; + refs_for_each_reflog(get_worktree_ref_store(*p), + collect_reflog, &collected); + } + free_worktrees(worktrees); for (i = 0; i < collected.nr; i++) { struct collected_reflog *e = collected.e[i]; set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog); @@ -594,7 +641,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) char *ref; struct object_id oid; if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) { - status |= error("%s points nowhere!", argv[i]); + status |= error(_("%s points nowhere!"), argv[i]); continue; } set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); @@ -640,13 +687,13 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) break; } else if (arg[0] == '-') - usage(reflog_delete_usage); + usage(_(reflog_delete_usage)); else break; } if (argc - i < 1) - return error("Nothing to delete?"); + return error(_("no reflog specified to delete")); for ( ; i < argc; i++) { const char *spec = strstr(argv[i], "@{"); @@ -655,12 +702,12 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) int recno; if (!spec) { - status |= error("Not a reflog: %s", argv[i]); + status |= error(_("not a reflog: %s"), argv[i]); continue; } if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) { - status |= error("no reflog for '%s'", argv[i]); + status |= error(_("no reflog for '%s'"), argv[i]); continue; } @@ -695,7 +742,7 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) break; } else if (arg[0] == '-') - usage(reflog_exists_usage); + usage(_(reflog_exists_usage)); else break; } @@ -703,10 +750,10 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) start = i; if (argc - start != 1) - usage(reflog_exists_usage); + usage(_(reflog_exists_usage)); if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL)) - die("invalid ref format: %s", argv[start]); + die(_("invalid ref format: %s"), argv[start]); return !reflog_exists(argv[start]); } @@ -715,12 +762,12 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) */ static const char reflog_usage[] = -"git reflog [ show | expire | delete | exists ]"; +N_("git reflog [ show | expire | delete | exists ]"); int cmd_reflog(int argc, const char **argv, const char *prefix) { if (argc > 1 && !strcmp(argv[1], "-h")) - usage(reflog_usage); + usage(_(reflog_usage)); /* With no command, we default to showing it. */ if (argc < 2 || *argv[1] == '-') diff --git a/builtin/remote.c b/builtin/remote.c index 805ffc05cd..e8377994e5 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -6,8 +6,12 @@ #include "string-list.h" #include "strbuf.h" #include "run-command.h" +#include "rebase.h" #include "refs.h" +#include "refspec.h" +#include "object-store.h" #include "argv-array.h" +#include "commit-reach.h" static const char * const builtin_remote_usage[] = { N_("git remote [-v | --verbose]"), @@ -166,9 +170,9 @@ static int add(int argc, const char **argv) OPT_STRING_LIST('t', "track", &track, N_("branch"), N_("branch(es) to track")), OPT_STRING('m', "master", &master, N_("branch"), N_("master branch")), - { OPTION_CALLBACK, 0, "mirror", &mirror, N_("push|fetch"), + OPT_CALLBACK_F(0, "mirror", &mirror, "(push|fetch)", N_("set up remote as a mirror to push to or fetch from"), - PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt }, + PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt), OPT_END() }; @@ -245,7 +249,8 @@ static int add(int argc, const char **argv) struct branch_info { char *remote_name; struct string_list merge; - enum { NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE } rebase; + enum rebase_type rebase; + char *push_remote_name; }; static struct string_list branch_list = STRING_LIST_INIT_NODUP; @@ -259,57 +264,69 @@ static const char *abbrev_ref(const char *name, const char *prefix) static int config_read_branches(const char *key, const char *value, void *cb) { - if (starts_with(key, "branch.")) { - const char *orig_key = key; - char *name; - struct string_list_item *item; - struct branch_info *info; - enum { REMOTE, MERGE, REBASE } type; - size_t key_len; - - key += 7; - if (strip_suffix(key, ".remote", &key_len)) { - name = xmemdupz(key, key_len); - type = REMOTE; - } else if (strip_suffix(key, ".merge", &key_len)) { - name = xmemdupz(key, key_len); - type = MERGE; - } else if (strip_suffix(key, ".rebase", &key_len)) { - name = xmemdupz(key, key_len); - type = REBASE; - } else - return 0; + const char *orig_key = key; + char *name; + struct string_list_item *item; + struct branch_info *info; + enum { REMOTE, MERGE, REBASE, PUSH_REMOTE } type; + size_t key_len; - item = string_list_insert(&branch_list, name); + if (!starts_with(key, "branch.")) + return 0; - if (!item->util) - item->util = xcalloc(1, sizeof(struct branch_info)); - info = item->util; - if (type == REMOTE) { - if (info->remote_name) - warning(_("more than one %s"), orig_key); - info->remote_name = xstrdup(value); - } else if (type == MERGE) { - char *space = strchr(value, ' '); - value = abbrev_branch(value); - while (space) { - char *merge; - merge = xstrndup(value, space - value); - string_list_append(&info->merge, merge); - value = abbrev_branch(space + 1); - space = strchr(value, ' '); - } - string_list_append(&info->merge, xstrdup(value)); - } else { - int v = git_parse_maybe_bool(value); - if (v >= 0) - info->rebase = v; - else if (!strcmp(value, "preserve")) - info->rebase = NORMAL_REBASE; - else if (!strcmp(value, "interactive")) - info->rebase = INTERACTIVE_REBASE; + key += strlen("branch."); + if (strip_suffix(key, ".remote", &key_len)) + type = REMOTE; + else if (strip_suffix(key, ".merge", &key_len)) + type = MERGE; + else if (strip_suffix(key, ".rebase", &key_len)) + type = REBASE; + else if (strip_suffix(key, ".pushremote", &key_len)) + type = PUSH_REMOTE; + else + return 0; + name = xmemdupz(key, key_len); + + item = string_list_insert(&branch_list, name); + + if (!item->util) + item->util = xcalloc(1, sizeof(struct branch_info)); + info = item->util; + switch (type) { + case REMOTE: + if (info->remote_name) + warning(_("more than one %s"), orig_key); + info->remote_name = xstrdup(value); + break; + case MERGE: { + char *space = strchr(value, ' '); + value = abbrev_branch(value); + while (space) { + char *merge; + merge = xstrndup(value, space - value); + string_list_append(&info->merge, merge); + value = abbrev_branch(space + 1); + space = strchr(value, ' '); } + string_list_append(&info->merge, xstrdup(value)); + break; + } + case REBASE: + /* + * Consider invalid values as false and check the + * truth value with >= REBASE_TRUE. + */ + info->rebase = rebase_parse_value(value); + break; + case PUSH_REMOTE: + if (info->push_remote_name) + warning(_("more than one %s"), orig_key); + info->push_remote_name = xstrdup(value); + break; + default: + BUG("unexpected type=%d", type); } + return 0; } @@ -332,10 +349,10 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat struct ref *ref, *stale_refs; int i; - for (i = 0; i < states->remote->fetch_refspec_nr; i++) - if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1)) + for (i = 0; i < states->remote->fetch.nr; i++) + if (get_fetch_map(remote_refs, &states->remote->fetch.items[i], &tail, 1)) die(_("Could not get fetch map for refspec %s"), - states->remote->fetch_refspec[i]); + states->remote->fetch.raw[i]); states->new_refs.strdup_strings = 1; states->tracked.strdup_strings = 1; @@ -346,8 +363,7 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat else string_list_append(&states->tracked, abbrev_branch(ref->name)); } - stale_refs = get_stale_heads(states->remote->fetch, - states->remote->fetch_refspec_nr, fetch_map); + stale_refs = get_stale_heads(&states->remote->fetch, fetch_map); for (ref = stale_refs; ref; ref = ref->next) { struct string_list_item *item = string_list_append(&states->stale, abbrev_branch(ref->name)); @@ -387,8 +403,7 @@ static int get_push_ref_states(const struct ref *remote_refs, local_refs = get_local_heads(); push_map = copy_ref_list(remote_refs); - match_push_refs(local_refs, &push_map, remote->push_refspec_nr, - remote->push_refspec, MATCH_REFS_NONE); + match_push_refs(local_refs, &push_map, &remote->push, MATCH_REFS_NONE); states->push.strdup_strings = 1; for (ref = push_map; ref; ref = ref->next) { @@ -408,7 +423,7 @@ static int get_push_ref_states(const struct ref *remote_refs, if (is_null_oid(&ref->new_oid)) { info->status = PUSH_STATUS_DELETE; - } else if (!oidcmp(&ref->old_oid, &ref->new_oid)) + } else if (oideq(&ref->old_oid, &ref->new_oid)) info->status = PUSH_STATUS_UPTODATE; else if (is_null_oid(&ref->old_oid)) info->status = PUSH_STATUS_CREATE; @@ -434,14 +449,14 @@ static int get_push_ref_states_noquery(struct ref_states *states) return 0; states->push.strdup_strings = 1; - if (!remote->push_refspec_nr) { + if (!remote->push.nr) { item = string_list_append(&states->push, _("(matching)")); info = item->util = xcalloc(1, sizeof(struct push_info)); info->status = PUSH_STATUS_NOTQUERIED; info->dest = xstrdup(item->string); } - for (i = 0; i < remote->push_refspec_nr; i++) { - struct refspec *spec = remote->push + i; + for (i = 0; i < remote->push.nr; i++) { + const struct refspec_item *spec = &remote->push.items[i]; if (spec->matching) item = string_list_append(&states->push, _("(matching)")); else if (strlen(spec->src)) @@ -461,7 +476,7 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat { struct ref *ref, *matches; struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map; - struct refspec refspec; + struct refspec_item refspec; refspec.force = 0; refspec.pattern = 1; @@ -514,7 +529,7 @@ static int add_branch_for_removal(const char *refname, const struct object_id *oid, int flags, void *cb_data) { struct branches_for_remote *branches = cb_data; - struct refspec refspec; + struct refspec_item refspec; struct known_remote *kr; memset(&refspec, 0, sizeof(refspec)); @@ -562,7 +577,7 @@ static int read_remote_branches(const char *refname, strbuf_addf(&buf, "refs/remotes/%s/", rename->old_name); if (starts_with(refname, buf.buf)) { - item = string_list_append(rename->remote_branches, xstrdup(refname)); + item = string_list_append(rename->remote_branches, refname); symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, &flag); if (symref && (flag & REF_ISSYMREF)) @@ -585,12 +600,12 @@ static int migrate_file(struct remote *remote) git_config_set_multivar(buf.buf, remote->url[i], "^$", 0); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.push", remote->name); - for (i = 0; i < remote->push_refspec_nr; i++) - git_config_set_multivar(buf.buf, remote->push_refspec[i], "^$", 0); + for (i = 0; i < remote->push.raw_nr; i++) + git_config_set_multivar(buf.buf, remote->push.raw[i], "^$", 0); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", remote->name); - for (i = 0; i < remote->fetch_refspec_nr; i++) - git_config_set_multivar(buf.buf, remote->fetch_refspec[i], "^$", 0); + for (i = 0; i < remote->fetch.raw_nr; i++) + git_config_set_multivar(buf.buf, remote->fetch.raw[i], "^$", 0); if (remote->origin == REMOTE_REMOTES) unlink_or_warn(git_path("remotes/%s", remote->name)); else if (remote->origin == REMOTE_BRANCHES) @@ -600,6 +615,56 @@ static int migrate_file(struct remote *remote) return 0; } +struct push_default_info +{ + const char *old_name; + enum config_scope scope; + struct strbuf origin; + int linenr; +}; + +static int config_read_push_default(const char *key, const char *value, + void *cb) +{ + struct push_default_info* info = cb; + if (strcmp(key, "remote.pushdefault") || + !value || strcmp(value, info->old_name)) + return 0; + + info->scope = current_config_scope(); + strbuf_reset(&info->origin); + strbuf_addstr(&info->origin, current_config_name()); + info->linenr = current_config_line(); + + return 0; +} + +static void handle_push_default(const char* old_name, const char* new_name) +{ + struct push_default_info push_default = { + old_name, CONFIG_SCOPE_UNKNOWN, STRBUF_INIT, -1 }; + git_config(config_read_push_default, &push_default); + if (push_default.scope >= CONFIG_SCOPE_COMMAND) + ; /* pass */ + else if (push_default.scope >= CONFIG_SCOPE_LOCAL) { + int result = git_config_set_gently("remote.pushDefault", + new_name); + if (new_name && result && result != CONFIG_NOTHING_SET) + die(_("could not set '%s'"), "remote.pushDefault"); + else if (!new_name && result && result != CONFIG_NOTHING_SET) + die(_("could not unset '%s'"), "remote.pushDefault"); + } else if (push_default.scope >= CONFIG_SCOPE_SYSTEM) { + /* warn */ + warning(_("The %s configuration remote.pushDefault in:\n" + "\t%s:%d\n" + "now names the non-existent remote '%s'"), + config_scope_name(push_default.scope), + push_default.origin.buf, push_default.linenr, + old_name); + } +} + + static int mv(int argc, const char **argv) { struct option options[] = { @@ -608,7 +673,7 @@ static int mv(int argc, const char **argv) struct remote *oldremote, *newremote; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT, old_remote_context = STRBUF_INIT; - struct string_list remote_branches = STRING_LIST_INIT_NODUP; + struct string_list remote_branches = STRING_LIST_INIT_DUP; struct rename_info rename; int i, refspec_updated = 0; @@ -621,7 +686,7 @@ static int mv(int argc, const char **argv) oldremote = remote_get(rename.old_name); if (!remote_is_configured(oldremote, 1)) - die(_("No such remote: %s"), rename.old_name); + die(_("No such remote: '%s'"), rename.old_name); if (!strcmp(rename.old_name, rename.new_name) && oldremote->origin != REMOTE_CONFIG) return migrate_file(oldremote); @@ -645,11 +710,11 @@ static int mv(int argc, const char **argv) strbuf_addf(&buf, "remote.%s.fetch", rename.new_name); git_config_set_multivar(buf.buf, NULL, NULL, 1); strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name); - for (i = 0; i < oldremote->fetch_refspec_nr; i++) { + for (i = 0; i < oldremote->fetch.raw_nr; i++) { char *ptr; strbuf_reset(&buf2); - strbuf_addstr(&buf2, oldremote->fetch_refspec[i]); + strbuf_addstr(&buf2, oldremote->fetch.raw[i]); ptr = strstr(buf2.buf, old_remote_context.buf); if (ptr) { refspec_updated = 1; @@ -675,6 +740,11 @@ static int mv(int argc, const char **argv) strbuf_addf(&buf, "branch.%s.remote", item->string); git_config_set(buf.buf, rename.new_name); } + if (info->push_remote_name && !strcmp(info->push_remote_name, rename.old_name)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.pushremote", item->string); + git_config_set(buf.buf, rename.new_name); + } } if (!refspec_updated) @@ -688,9 +758,8 @@ static int mv(int argc, const char **argv) for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; int flag = 0; - struct object_id oid; - read_ref_full(item->string, RESOLVE_REF_READING, &oid, &flag); + read_ref_full(item->string, RESOLVE_REF_READING, NULL, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF)) @@ -730,6 +799,10 @@ static int mv(int argc, const char **argv) if (create_symref(buf.buf, buf2.buf, buf3.buf)) die(_("creating '%s' failed"), buf.buf); } + string_list_clear(&remote_branches, 1); + + handle_push_default(rename.old_name, rename.new_name); + return 0; } @@ -756,7 +829,7 @@ static int rm(int argc, const char **argv) remote = remote_get(argv[1]); if (!remote_is_configured(remote, 1)) - die(_("No such remote: %s"), argv[1]); + die(_("No such remote: '%s'"), argv[1]); known_remotes.to_delete = remote; for_each_remote(add_known_remote, &known_remotes); @@ -776,6 +849,13 @@ static int rm(int argc, const char **argv) die(_("could not unset '%s'"), buf.buf); } } + if (info->push_remote_name && !strcmp(info->push_remote_name, remote->name)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.pushremote", item->string); + result = git_config_set_gently(buf.buf, NULL); + if (result && result != CONFIG_NOTHING_SET) + die(_("could not unset '%s'"), buf.buf); + } } /* @@ -808,6 +888,8 @@ static int rm(int argc, const char **argv) strbuf_addf(&buf, "remote.%s", remote->name); if (git_config_rename_section(buf.buf, NULL) < 1) return error(_("Could not remove config section '%s'"), buf.buf); + + handle_push_default(remote->name, NULL); } return result; @@ -833,7 +915,7 @@ static int append_ref_to_tracked_list(const char *refname, const struct object_id *oid, int flags, void *cb_data) { struct ref_states *states = cb_data; - struct refspec refspec; + struct refspec_item refspec; if (flags & REF_ISSYMREF) return 0; @@ -855,14 +937,14 @@ static int get_remote_ref_states(const char *name, states->remote = remote_get(name); if (!states->remote) - return error(_("No such remote: %s"), name); + return error(_("No such remote: '%s'"), name); read_branches(); if (query) { transport = transport_get(states->remote, states->remote->url_nr > 0 ? states->remote->url[0] : NULL); - remote_refs = transport_get_remote_refs(transport); + remote_refs = transport_get_remote_refs(transport, NULL); transport_disconnect(transport); states->queried = 1; @@ -938,7 +1020,7 @@ static int add_local_to_show_info(struct string_list_item *branch_item, void *cb return 0; if ((n = strlen(branch_item->string)) > show_info->width) show_info->width = n; - if (branch_info->rebase) + if (branch_info->rebase >= REBASE_TRUE) show_info->any_rebase = 1; item = string_list_insert(show_info->list, branch_item->string); @@ -955,17 +1037,23 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) int width = show_info->width + 4; int i; - if (branch_info->rebase && branch_info->merge.nr > 1) { + if (branch_info->rebase >= REBASE_TRUE && branch_info->merge.nr > 1) { error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"), item->string); return 0; } printf(" %-*s ", show_info->width, item->string); - if (branch_info->rebase) { - printf_ln(branch_info->rebase == INTERACTIVE_REBASE - ? _("rebases interactively onto remote %s") - : _("rebases onto remote %s"), merge->items[0].string); + if (branch_info->rebase >= REBASE_TRUE) { + const char *msg; + if (branch_info->rebase == REBASE_INTERACTIVE) + msg = _("rebases interactively onto remote %s"); + else if (branch_info->rebase == REBASE_MERGES) + msg = _("rebases interactively (with merges) onto " + "remote %s"); + else + msg = _("rebases onto remote %s"); + printf_ln(msg, merge->items[0].string); return 0; } else if (show_info->any_rebase) { printf_ln(_(" merges with remote %s"), merge->items[0].string); @@ -1395,7 +1483,7 @@ static int update(int argc, const char **argv) return retval; } -static int remove_all_fetch_refspecs(const char *remote, const char *key) +static int remove_all_fetch_refspecs(const char *key) { return git_config_set_multivar_gently(key, NULL, NULL, 1); } @@ -1425,7 +1513,7 @@ static int set_remote_branches(const char *remotename, const char **branches, if (!remote_is_configured(remote, 1)) die(_("No such remote '%s'"), remotename); - if (!add_mode && remove_all_fetch_refspecs(remotename, key.buf)) { + if (!add_mode && remove_all_fetch_refspecs(key.buf)) { strbuf_release(&key); return 1; } diff --git a/builtin/repack.c b/builtin/repack.c index 7bdb40142f..df287739d9 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -8,10 +8,17 @@ #include "strbuf.h" #include "string-list.h" #include "argv-array.h" +#include "midx.h" +#include "packfile.h" +#include "prune-packed.h" +#include "object-store.h" +#include "promisor-remote.h" +#include "shallow.h" static int delta_base_offset = 1; static int pack_kept_objects = -1; -static int write_bitmaps; +static int write_bitmaps = -1; +static int use_delta_islands; static char *packdir, *packtmp; static const char *const git_repack_usage[] = { @@ -40,6 +47,10 @@ static int repack_config(const char *var, const char *value, void *cb) write_bitmaps = git_config_bool(var, value); return 0; } + if (!strcmp(var, "repack.usedeltaislands")) { + use_delta_islands = git_config_bool(var, value); + return 0; + } return git_default_config(var, value, cb); } @@ -83,10 +94,11 @@ static void remove_pack_on_signal(int signo) /* * Adds all packs hex strings to the fname list, which do not - * have a corresponding .keep or .promisor file. These packs are not to + * have a corresponding .keep file. These packs are not to * be kept if we are going to pack everything into one file. */ -static void get_non_kept_pack_filenames(struct string_list *fname_list) +static void get_non_kept_pack_filenames(struct string_list *fname_list, + const struct string_list *extra_keep) { DIR *dir; struct dirent *e; @@ -97,13 +109,20 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list) while ((e = readdir(dir)) != NULL) { size_t len; + int i; + + for (i = 0; i < extra_keep->nr; i++) + if (!fspathcmp(e->d_name, extra_keep->items[i].string)) + break; + if (extra_keep->nr > 0 && i < extra_keep->nr) + continue; + if (!strip_suffix(e->d_name, ".pack", &len)) continue; fname = xmemdupz(e->d_name, len); - if (!file_exists(mkpath("%s/%s.keep", packdir, fname)) && - !file_exists(mkpath("%s/%s.promisor", packdir, fname))) + if (!file_exists(mkpath("%s/%s.keep", packdir, fname))) string_list_append_nodup(fname_list, fname); else free(fname); @@ -113,20 +132,128 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list) static void remove_redundant_pack(const char *dir_name, const char *base_name) { - const char *exts[] = {".pack", ".idx", ".keep", ".bitmap"}; - int i; struct strbuf buf = STRBUF_INIT; - size_t plen; + strbuf_addf(&buf, "%s/%s.pack", dir_name, base_name); + unlink_pack_path(buf.buf, 1); + strbuf_release(&buf); +} - strbuf_addf(&buf, "%s/%s", dir_name, base_name); - plen = buf.len; +struct pack_objects_args { + const char *window; + const char *window_memory; + const char *depth; + const char *threads; + const char *max_pack_size; + int no_reuse_delta; + int no_reuse_object; + int quiet; + int local; +}; - for (i = 0; i < ARRAY_SIZE(exts); i++) { - strbuf_setlen(&buf, plen); - strbuf_addstr(&buf, exts[i]); - unlink(buf.buf); +static void prepare_pack_objects(struct child_process *cmd, + const struct pack_objects_args *args) +{ + argv_array_push(&cmd->args, "pack-objects"); + if (args->window) + argv_array_pushf(&cmd->args, "--window=%s", args->window); + if (args->window_memory) + argv_array_pushf(&cmd->args, "--window-memory=%s", args->window_memory); + if (args->depth) + argv_array_pushf(&cmd->args, "--depth=%s", args->depth); + if (args->threads) + argv_array_pushf(&cmd->args, "--threads=%s", args->threads); + if (args->max_pack_size) + argv_array_pushf(&cmd->args, "--max-pack-size=%s", args->max_pack_size); + if (args->no_reuse_delta) + argv_array_pushf(&cmd->args, "--no-reuse-delta"); + if (args->no_reuse_object) + argv_array_pushf(&cmd->args, "--no-reuse-object"); + if (args->local) + argv_array_push(&cmd->args, "--local"); + if (args->quiet) + argv_array_push(&cmd->args, "--quiet"); + if (delta_base_offset) + argv_array_push(&cmd->args, "--delta-base-offset"); + argv_array_push(&cmd->args, packtmp); + cmd->git_cmd = 1; + cmd->out = -1; +} + +/* + * Write oid to the given struct child_process's stdin, starting it first if + * necessary. + */ +static int write_oid(const struct object_id *oid, struct packed_git *pack, + uint32_t pos, void *data) +{ + struct child_process *cmd = data; + + if (cmd->in == -1) { + if (start_command(cmd)) + die(_("could not start pack-objects to repack promisor objects")); } - strbuf_release(&buf); + + xwrite(cmd->in, oid_to_hex(oid), the_hash_algo->hexsz); + xwrite(cmd->in, "\n", 1); + return 0; +} + +static void repack_promisor_objects(const struct pack_objects_args *args, + struct string_list *names) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + FILE *out; + struct strbuf line = STRBUF_INIT; + + prepare_pack_objects(&cmd, args); + cmd.in = -1; + + /* + * NEEDSWORK: Giving pack-objects only the OIDs without any ordering + * hints may result in suboptimal deltas in the resulting pack. See if + * the OIDs can be sent with fake paths such that pack-objects can use a + * {type -> existing pack order} ordering when computing deltas instead + * of a {type -> size} ordering, which may produce better deltas. + */ + for_each_packed_object(write_oid, &cmd, + FOR_EACH_OBJECT_PROMISOR_ONLY); + + if (cmd.in == -1) + /* No packed objects; cmd was never started */ + return; + + close(cmd.in); + + out = xfdopen(cmd.out, "r"); + while (strbuf_getline_lf(&line, out) != EOF) { + char *promisor_name; + int fd; + if (line.len != the_hash_algo->hexsz) + die(_("repack: Expecting full hex object ID lines only from pack-objects.")); + string_list_append(names, line.buf); + + /* + * pack-objects creates the .pack and .idx files, but not the + * .promisor file. Create the .promisor file, which is empty. + * + * NEEDSWORK: fetch-pack sometimes generates non-empty + * .promisor files containing the ref names and associated + * hashes at the point of generation of the corresponding + * packfile, but this would not preserve their contents. Maybe + * concatenate the contents of all .promisor files instead of + * just creating a new empty file. + */ + promisor_name = mkpathdup("%s-%s.promisor", packtmp, + line.buf); + fd = open(promisor_name, O_CREAT|O_EXCL|O_WRONLY, 0600); + if (fd < 0) + die_errno(_("unable to create '%s'"), promisor_name); + close(fd); + free(promisor_name); + } + fclose(out); + if (finish_command(&cmd)) + die(_("could not finish pack-objects to repack promisor objects")); } #define ALL_INTO_ONE 1 @@ -141,6 +268,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) {".pack"}, {".idx"}, {".bitmap", 1}, + {".promisor", 1}, }; struct child_process cmd = CHILD_PROCESS_INIT; struct string_list_item *item; @@ -148,7 +276,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) struct string_list rollback = STRING_LIST_INIT_NODUP; struct string_list existing_packs = STRING_LIST_INIT_DUP; struct strbuf line = STRBUF_INIT; - int ext, ret, failed; + int i, ext, ret, failed; FILE *out; /* variables to be filled by option parsing */ @@ -156,14 +284,10 @@ int cmd_repack(int argc, const char **argv, const char *prefix) int delete_redundant = 0; const char *unpack_unreachable = NULL; int keep_unreachable = 0; - const char *window = NULL, *window_memory = NULL; - const char *depth = NULL; - const char *threads = NULL; - const char *max_pack_size = NULL; - int no_reuse_delta = 0, no_reuse_object = 0; + struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; int no_update_server_info = 0; - int quiet = 0; - int local = 0; + int midx_cleared = 0; + struct pack_objects_args po_args = {NULL}; struct option builtin_repack_options[] = { OPT_BIT('a', NULL, &pack_everything, @@ -173,33 +297,37 @@ int cmd_repack(int argc, const char **argv, const char *prefix) LOOSEN_UNREACHABLE | ALL_INTO_ONE), OPT_BOOL('d', NULL, &delete_redundant, N_("remove redundant packs, and run git-prune-packed")), - OPT_BOOL('f', NULL, &no_reuse_delta, + OPT_BOOL('f', NULL, &po_args.no_reuse_delta, N_("pass --no-reuse-delta to git-pack-objects")), - OPT_BOOL('F', NULL, &no_reuse_object, + OPT_BOOL('F', NULL, &po_args.no_reuse_object, N_("pass --no-reuse-object to git-pack-objects")), OPT_BOOL('n', NULL, &no_update_server_info, N_("do not run git-update-server-info")), - OPT__QUIET(&quiet, N_("be quiet")), - OPT_BOOL('l', "local", &local, + OPT__QUIET(&po_args.quiet, N_("be quiet")), + OPT_BOOL('l', "local", &po_args.local, N_("pass --local to git-pack-objects")), OPT_BOOL('b', "write-bitmap-index", &write_bitmaps, N_("write bitmap index")), + OPT_BOOL('i', "delta-islands", &use_delta_islands, + N_("pass --delta-islands to git-pack-objects")), OPT_STRING(0, "unpack-unreachable", &unpack_unreachable, N_("approxidate"), N_("with -A, do not loosen objects older than this")), OPT_BOOL('k', "keep-unreachable", &keep_unreachable, N_("with -a, repack unreachable objects")), - OPT_STRING(0, "window", &window, N_("n"), + OPT_STRING(0, "window", &po_args.window, N_("n"), N_("size of the window used for delta compression")), - OPT_STRING(0, "window-memory", &window_memory, N_("bytes"), + OPT_STRING(0, "window-memory", &po_args.window_memory, N_("bytes"), N_("same as the above, but limit memory size instead of entries count")), - OPT_STRING(0, "depth", &depth, N_("n"), + OPT_STRING(0, "depth", &po_args.depth, N_("n"), N_("limits the maximum delta depth")), - OPT_STRING(0, "threads", &threads, N_("n"), + OPT_STRING(0, "threads", &po_args.threads, N_("n"), N_("limits the maximum number of threads")), - OPT_STRING(0, "max-pack-size", &max_pack_size, N_("bytes"), + OPT_STRING(0, "max-pack-size", &po_args.max_pack_size, N_("bytes"), N_("maximum size of each packfile")), OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects, N_("repack objects in packs marked with .keep")), + OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"), + N_("do not repack this pack")), OPT_END() }; @@ -215,8 +343,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix) (unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE))) die(_("--keep-unreachable and -A are incompatible")); + if (write_bitmaps < 0) { + if (!(pack_everything & ALL_INTO_ONE) || + !is_bare_repository()) + write_bitmaps = 0; + } if (pack_kept_objects < 0) - pack_kept_objects = write_bitmaps; + pack_kept_objects = write_bitmaps > 0; if (write_bitmaps && !(pack_everything & ALL_INTO_ONE)) die(_(incremental_bitmap_conflict_error)); @@ -226,35 +359,31 @@ int cmd_repack(int argc, const char **argv, const char *prefix) sigchain_push_common(remove_pack_on_signal); - argv_array_push(&cmd.args, "pack-objects"); + prepare_pack_objects(&cmd, &po_args); + argv_array_push(&cmd.args, "--keep-true-parents"); if (!pack_kept_objects) argv_array_push(&cmd.args, "--honor-pack-keep"); + for (i = 0; i < keep_pack_list.nr; i++) + argv_array_pushf(&cmd.args, "--keep-pack=%s", + keep_pack_list.items[i].string); argv_array_push(&cmd.args, "--non-empty"); argv_array_push(&cmd.args, "--all"); argv_array_push(&cmd.args, "--reflog"); argv_array_push(&cmd.args, "--indexed-objects"); - if (repository_format_partial_clone) + if (has_promisor_remote()) argv_array_push(&cmd.args, "--exclude-promisor-objects"); - if (window) - argv_array_pushf(&cmd.args, "--window=%s", window); - if (window_memory) - argv_array_pushf(&cmd.args, "--window-memory=%s", window_memory); - if (depth) - argv_array_pushf(&cmd.args, "--depth=%s", depth); - if (threads) - argv_array_pushf(&cmd.args, "--threads=%s", threads); - if (max_pack_size) - argv_array_pushf(&cmd.args, "--max-pack-size=%s", max_pack_size); - if (no_reuse_delta) - argv_array_pushf(&cmd.args, "--no-reuse-delta"); - if (no_reuse_object) - argv_array_pushf(&cmd.args, "--no-reuse-object"); - if (write_bitmaps) + if (write_bitmaps > 0) argv_array_push(&cmd.args, "--write-bitmap-index"); + else if (write_bitmaps < 0) + argv_array_push(&cmd.args, "--write-bitmap-index-quiet"); + if (use_delta_islands) + argv_array_push(&cmd.args, "--delta-islands"); if (pack_everything & ALL_INTO_ONE) { - get_non_kept_pack_filenames(&existing_packs); + get_non_kept_pack_filenames(&existing_packs, &keep_pack_list); + + repack_promisor_objects(&po_args, &names); if (existing_packs.nr && delete_redundant) { if (unpack_unreachable) { @@ -277,17 +406,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argv_array_push(&cmd.args, "--incremental"); } - if (local) - argv_array_push(&cmd.args, "--local"); - if (quiet) - argv_array_push(&cmd.args, "--quiet"); - if (delta_base_offset) - argv_array_push(&cmd.args, "--delta-base-offset"); - - argv_array_push(&cmd.args, packtmp); - - cmd.git_cmd = 1; - cmd.out = -1; cmd.no_stdin = 1; ret = start_command(&cmd); @@ -296,8 +414,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) out = xfdopen(cmd.out, "r"); while (strbuf_getline_lf(&line, out) != EOF) { - if (line.len != 40) - die("repack: Expecting 40 character sha1 lines only from pack-objects."); + if (line.len != the_hash_algo->hexsz) + die(_("repack: Expecting full hex object ID lines only from pack-objects.")); string_list_append(&names, line.buf); } fclose(out); @@ -305,8 +423,10 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (ret) return ret; - if (!names.nr && !quiet) - printf("Nothing new to pack.\n"); + if (!names.nr && !po_args.quiet) + printf_ln(_("Nothing new to pack.")); + + close_object_store(the_repository->objects); /* * Ok we have prepared all new packfiles. @@ -318,6 +438,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix) for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { char *fname, *fname_old; + + if (!midx_cleared) { + clear_midx_file(the_repository); + midx_cleared = 1; + } + fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); if (!file_exists(fname)) { @@ -359,13 +485,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (rollback_failure.nr) { int i; fprintf(stderr, - "WARNING: Some packs in use have been renamed by\n" - "WARNING: prefixing old- to their name, in order to\n" - "WARNING: replace them with the new version of the\n" - "WARNING: file. But the operation failed, and the\n" - "WARNING: attempt to rename them back to their\n" - "WARNING: original names also failed.\n" - "WARNING: Please rename them in %s manually:\n", packdir); + _("WARNING: Some packs in use have been renamed by\n" + "WARNING: prefixing old- to their name, in order to\n" + "WARNING: replace them with the new version of the\n" + "WARNING: file. But the operation failed, and the\n" + "WARNING: attempt to rename them back to their\n" + "WARNING: original names also failed.\n" + "WARNING: Please rename them in %s manually:\n"), packdir); for (i = 0; i < rollback_failure.nr; i++) fprintf(stderr, "WARNING: old-%s -> %s\n", rollback_failure.items[i].string, @@ -414,26 +540,39 @@ int cmd_repack(int argc, const char **argv, const char *prefix) /* End of pack replacement. */ + reprepare_packed_git(the_repository); + if (delete_redundant) { + const int hexsz = the_hash_algo->hexsz; int opts = 0; string_list_sort(&names); for_each_string_list_item(item, &existing_packs) { char *sha1; size_t len = strlen(item->string); - if (len < 40) + if (len < hexsz) continue; - sha1 = item->string + len - 40; + sha1 = item->string + len - hexsz; if (!string_list_has_string(&names, sha1)) remove_redundant_pack(packdir, item->string); } - if (!quiet && isatty(2)) + if (!po_args.quiet && isatty(2)) opts |= PRUNE_PACKED_VERBOSE; prune_packed_objects(opts); + + if (!keep_unreachable && + (!(pack_everything & LOOSEN_UNREACHABLE) || + unpack_unreachable) && + is_repository_shallow(the_repository)) + prune_shallow(PRUNE_QUICK); } if (!no_update_server_info) update_server_info(0); remove_temporary_files(); + + if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) + write_midx_file(get_object_directory(), 0); + string_list_clear(&names, 0); string_list_clear(&rollback, 0); string_list_clear(&existing_packs, 0); diff --git a/builtin/replace.c b/builtin/replace.c index 935647be6b..b36d17a657 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -14,12 +14,15 @@ #include "refs.h" #include "parse-options.h" #include "run-command.h" +#include "object-store.h" +#include "repository.h" #include "tag.h" static const char * const git_replace_usage[] = { N_("git replace [-f] <object> <replacement>"), N_("git replace [-f] --edit <object>"), N_("git replace [-f] --graft <commit> [<parent>...]"), + N_("git replace [-f] --convert-graft-file"), N_("git replace -d <object>..."), N_("git replace [--format=<format>] [-l [<pattern>]]"), NULL @@ -36,7 +39,8 @@ struct show_data { enum replace_format format; }; -static int show_reference(const char *refname, const struct object_id *oid, +static int show_reference(struct repository *r, const char *refname, + const struct object_id *oid, int flag, void *cb_data) { struct show_data *data = cb_data; @@ -51,10 +55,10 @@ static int show_reference(const char *refname, const struct object_id *oid, enum object_type obj_type, repl_type; if (get_oid(refname, &object)) - return error("Failed to resolve '%s' as a valid ref.", refname); + return error(_("failed to resolve '%s' as a valid ref"), refname); - obj_type = oid_object_info(&object, NULL); - repl_type = oid_object_info(oid, NULL); + obj_type = oid_object_info(r, &object, NULL); + repl_type = oid_object_info(r, oid, NULL); printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type), oid_to_hex(oid), type_name(repl_type)); @@ -78,12 +82,16 @@ static int list_replace_refs(const char *pattern, const char *format) data.format = REPLACE_FORMAT_MEDIUM; else if (!strcmp(format, "long")) data.format = REPLACE_FORMAT_LONG; + /* + * Please update _git_replace() in git-completion.bash when + * you add new format + */ else - die("invalid replace format '%s'\n" - "valid formats are 'short', 'medium' and 'long'\n", - format); + return error(_("invalid replace format '%s'\n" + "valid formats are 'short', 'medium' and 'long'"), + format); - for_each_replace_ref(show_reference, (void *)&data); + for_each_replace_ref(the_repository, show_reference, (void *)&data); return 0; } @@ -104,7 +112,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) for (p = argv; *p; p++) { if (get_oid(*p, &oid)) { - error("Failed to resolve '%s' as a valid ref.", *p); + error("failed to resolve '%s' as a valid ref", *p); had_error = 1; continue; } @@ -114,7 +122,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) full_hex = ref.buf + base_len; if (read_ref(ref.buf, &oid)) { - error("replace ref '%s' not found.", full_hex); + error(_("replace ref '%s' not found"), full_hex); had_error = 1; continue; } @@ -130,11 +138,11 @@ static int delete_replace_ref(const char *name, const char *ref, { if (delete_ref(NULL, ref, oid, 0)) return 1; - printf("Deleted replace ref '%s'\n", name); + printf_ln(_("Deleted replace ref '%s'"), name); return 0; } -static void check_ref_valid(struct object_id *object, +static int check_ref_valid(struct object_id *object, struct object_id *prev, struct strbuf *ref, int force) @@ -142,12 +150,13 @@ static void check_ref_valid(struct object_id *object, strbuf_reset(ref); strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object)); if (check_refname_format(ref->buf, 0)) - die("'%s' is not a valid ref name.", ref->buf); + return error(_("'%s' is not a valid ref name"), ref->buf); if (read_ref(ref->buf, prev)) oidclr(prev); else if (!force) - die("replace ref '%s' already exists", ref->buf); + return error(_("replace ref '%s' already exists"), ref->buf); + return 0; } static int replace_object_oid(const char *object_ref, @@ -161,28 +170,33 @@ static int replace_object_oid(const char *object_ref, struct strbuf ref = STRBUF_INIT; struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; + int res = 0; - obj_type = oid_object_info(object, NULL); - repl_type = oid_object_info(repl, NULL); + obj_type = oid_object_info(the_repository, object, NULL); + repl_type = oid_object_info(the_repository, repl, NULL); if (!force && obj_type != repl_type) - die("Objects must be of the same type.\n" - "'%s' points to a replaced object of type '%s'\n" - "while '%s' points to a replacement object of type '%s'.", - object_ref, type_name(obj_type), - replace_ref, type_name(repl_type)); - - check_ref_valid(object, &prev, &ref, force); + return error(_("Objects must be of the same type.\n" + "'%s' points to a replaced object of type '%s'\n" + "while '%s' points to a replacement object of " + "type '%s'."), + object_ref, type_name(obj_type), + replace_ref, type_name(repl_type)); + + if (check_ref_valid(object, &prev, &ref, force)) { + strbuf_release(&ref); + return -1; + } transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, repl, &prev, 0, NULL, &err) || ref_transaction_commit(transaction, &err)) - die("%s", err.buf); + res = error("%s", err.buf); ref_transaction_free(transaction); strbuf_release(&ref); - return 0; + return res; } static int replace_object(const char *object_ref, const char *replace_ref, int force) @@ -190,9 +204,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f struct object_id object, repl; if (get_oid(object_ref, &object)) - die("Failed to resolve '%s' as a valid ref.", object_ref); + return error(_("failed to resolve '%s' as a valid ref"), + object_ref); if (get_oid(replace_ref, &repl)) - die("Failed to resolve '%s' as a valid ref.", replace_ref); + return error(_("failed to resolve '%s' as a valid ref"), + replace_ref); return replace_object_oid(object_ref, &object, replace_ref, &repl, force); } @@ -202,7 +218,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f * If "raw" is true, then the object's raw contents are printed according to * "type". Otherwise, we pretty-print the contents for human editing. */ -static void export_object(const struct object_id *oid, enum object_type type, +static int export_object(const struct object_id *oid, enum object_type type, int raw, const char *filename) { struct child_process cmd = CHILD_PROCESS_INIT; @@ -210,7 +226,7 @@ static void export_object(const struct object_id *oid, enum object_type type, fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0) - die_errno("unable to open %s for writing", filename); + return error_errno(_("unable to open %s for writing"), filename); argv_array_push(&cmd.args, "--no-replace-objects"); argv_array_push(&cmd.args, "cat-file"); @@ -223,7 +239,8 @@ static void export_object(const struct object_id *oid, enum object_type type, cmd.out = fd; if (run_command(&cmd)) - die("cat-file reported failure"); + return error(_("cat-file reported failure")); + return 0; } /* @@ -231,14 +248,14 @@ static void export_object(const struct object_id *oid, enum object_type type, * interpreting it as "type", and writing the result to the object database. * The sha1 of the written object is returned via sha1. */ -static void import_object(struct object_id *oid, enum object_type type, +static int import_object(struct object_id *oid, enum object_type type, int raw, const char *filename) { int fd; fd = open(filename, O_RDONLY); if (fd < 0) - die_errno("unable to open %s for reading", filename); + return error_errno(_("unable to open %s for reading"), filename); if (!raw && type == OBJ_TREE) { const char *argv[] = { "mktree", NULL }; @@ -250,27 +267,40 @@ static void import_object(struct object_id *oid, enum object_type type, cmd.in = fd; cmd.out = -1; - if (start_command(&cmd)) - die("unable to spawn mktree"); + if (start_command(&cmd)) { + close(fd); + return error(_("unable to spawn mktree")); + } - if (strbuf_read(&result, cmd.out, 41) < 0) - die_errno("unable to read from mktree"); + if (strbuf_read(&result, cmd.out, the_hash_algo->hexsz + 1) < 0) { + error_errno(_("unable to read from mktree")); + close(fd); + close(cmd.out); + return -1; + } close(cmd.out); - if (finish_command(&cmd)) - die("mktree reported failure"); - if (get_oid_hex(result.buf, oid) < 0) - die("mktree did not return an object name"); + if (finish_command(&cmd)) { + strbuf_release(&result); + return error(_("mktree reported failure")); + } + if (get_oid_hex(result.buf, oid) < 0) { + strbuf_release(&result); + return error(_("mktree did not return an object name")); + } strbuf_release(&result); } else { struct stat st; int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT; - if (fstat(fd, &st) < 0) - die_errno("unable to fstat %s", filename); - if (index_fd(oid, fd, &st, type, NULL, flags) < 0) - die("unable to write object to database"); + if (fstat(fd, &st) < 0) { + error_errno(_("unable to fstat %s"), filename); + close(fd); + return -1; + } + if (index_fd(the_repository->index, oid, fd, &st, type, NULL, flags) < 0) + return error(_("unable to write object to database")); /* index_fd close()s fd for us */ } @@ -278,59 +308,82 @@ static void import_object(struct object_id *oid, enum object_type type, * No need to close(fd) here; both run-command and index-fd * will have done it for us. */ + return 0; } static int edit_and_replace(const char *object_ref, int force, int raw) { - char *tmpfile = git_pathdup("REPLACE_EDITOBJ"); + char *tmpfile; enum object_type type; struct object_id old_oid, new_oid, prev; struct strbuf ref = STRBUF_INIT; if (get_oid(object_ref, &old_oid) < 0) - die("Not a valid object name: '%s'", object_ref); + return error(_("not a valid object name: '%s'"), object_ref); - type = oid_object_info(&old_oid, NULL); + type = oid_object_info(the_repository, &old_oid, NULL); if (type < 0) - die("unable to get object type for %s", oid_to_hex(&old_oid)); + return error(_("unable to get object type for %s"), + oid_to_hex(&old_oid)); - check_ref_valid(&old_oid, &prev, &ref, force); + if (check_ref_valid(&old_oid, &prev, &ref, force)) { + strbuf_release(&ref); + return -1; + } strbuf_release(&ref); - export_object(&old_oid, type, raw, tmpfile); - if (launch_editor(tmpfile, NULL, NULL) < 0) - die("editing object file failed"); - import_object(&new_oid, type, raw, tmpfile); - + tmpfile = git_pathdup("REPLACE_EDITOBJ"); + if (export_object(&old_oid, type, raw, tmpfile)) { + free(tmpfile); + return -1; + } + if (launch_editor(tmpfile, NULL, NULL) < 0) { + free(tmpfile); + return error(_("editing object file failed")); + } + if (import_object(&new_oid, type, raw, tmpfile)) { + free(tmpfile); + return -1; + } free(tmpfile); - if (!oidcmp(&old_oid, &new_oid)) - return error("new object is the same as the old one: '%s'", oid_to_hex(&old_oid)); + if (oideq(&old_oid, &new_oid)) + return error(_("new object is the same as the old one: '%s'"), oid_to_hex(&old_oid)); return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force); } -static void replace_parents(struct strbuf *buf, int argc, const char **argv) +static int replace_parents(struct strbuf *buf, int argc, const char **argv) { struct strbuf new_parents = STRBUF_INIT; const char *parent_start, *parent_end; int i; + const unsigned hexsz = the_hash_algo->hexsz; /* find existing parents */ parent_start = buf->buf; - parent_start += GIT_SHA1_HEXSZ + 6; /* "tree " + "hex sha1" + "\n" */ + parent_start += hexsz + 6; /* "tree " + "hex sha1" + "\n" */ parent_end = parent_start; while (starts_with(parent_end, "parent ")) - parent_end += 48; /* "parent " + "hex sha1" + "\n" */ + parent_end += hexsz + 8; /* "parent " + "hex sha1" + "\n" */ /* prepare new parents */ for (i = 0; i < argc; i++) { struct object_id oid; - if (get_oid(argv[i], &oid) < 0) - die(_("Not a valid object name: '%s'"), argv[i]); - lookup_commit_or_die(&oid, argv[i]); - strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&oid)); + struct commit *commit; + + if (get_oid(argv[i], &oid) < 0) { + strbuf_release(&new_parents); + return error(_("not a valid object name: '%s'"), + argv[i]); + } + commit = lookup_commit_reference(the_repository, &oid); + if (!commit) { + strbuf_release(&new_parents); + return error(_("could not parse %s as a commit"), argv[i]); + } + strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&commit->object.oid)); } /* replace existing parents with new ones */ @@ -338,6 +391,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv) new_parents.buf, new_parents.len); strbuf_release(&new_parents); + return 0; } struct check_mergetag_data { @@ -345,7 +399,7 @@ struct check_mergetag_data { const char **argv; }; -static void check_one_mergetag(struct commit *commit, +static int check_one_mergetag(struct commit *commit, struct commit_extra_header *extra, void *data) { @@ -355,36 +409,39 @@ static void check_one_mergetag(struct commit *commit, struct tag *tag; int i; - hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid); - tag = lookup_tag(&tag_oid); + hash_object_file(the_hash_algo, extra->value, extra->len, + type_name(OBJ_TAG), &tag_oid); + tag = lookup_tag(the_repository, &tag_oid); if (!tag) - die(_("bad mergetag in commit '%s'"), ref); - if (parse_tag_buffer(tag, extra->value, extra->len)) - die(_("malformed mergetag in commit '%s'"), ref); + return error(_("bad mergetag in commit '%s'"), ref); + if (parse_tag_buffer(the_repository, tag, extra->value, extra->len)) + return error(_("malformed mergetag in commit '%s'"), ref); /* iterate over new parents */ for (i = 1; i < mergetag_data->argc; i++) { struct object_id oid; if (get_oid(mergetag_data->argv[i], &oid) < 0) - die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]); - if (!oidcmp(&tag->tagged->oid, &oid)) - return; /* found */ + return error(_("not a valid object name: '%s'"), + mergetag_data->argv[i]); + if (oideq(get_tagged_oid(tag), &oid)) + return 0; /* found */ } - die(_("original commit '%s' contains mergetag '%s' that is discarded; " - "use --edit instead of --graft"), ref, oid_to_hex(&tag_oid)); + return error(_("original commit '%s' contains mergetag '%s' that is " + "discarded; use --edit instead of --graft"), ref, + oid_to_hex(&tag_oid)); } -static void check_mergetags(struct commit *commit, int argc, const char **argv) +static int check_mergetags(struct commit *commit, int argc, const char **argv) { struct check_mergetag_data mergetag_data; mergetag_data.argc = argc; mergetag_data.argv = argv; - for_each_mergetag(check_one_mergetag, commit, &mergetag_data); + return for_each_mergetag(check_one_mergetag, commit, &mergetag_data); } -static int create_graft(int argc, const char **argv, int force) +static int create_graft(int argc, const char **argv, int force, int gentle) { struct object_id old_oid, new_oid; const char *old_ref = argv[0]; @@ -394,31 +451,83 @@ static int create_graft(int argc, const char **argv, int force) unsigned long size; if (get_oid(old_ref, &old_oid) < 0) - die(_("Not a valid object name: '%s'"), old_ref); - commit = lookup_commit_or_die(&old_oid, old_ref); + return error(_("not a valid object name: '%s'"), old_ref); + commit = lookup_commit_reference(the_repository, &old_oid); + if (!commit) + return error(_("could not parse %s"), old_ref); buffer = get_commit_buffer(commit, &size); strbuf_add(&buf, buffer, size); unuse_commit_buffer(commit, buffer); - replace_parents(&buf, argc - 1, &argv[1]); + if (replace_parents(&buf, argc - 1, &argv[1]) < 0) { + strbuf_release(&buf); + return -1; + } if (remove_signature(&buf)) { - warning(_("the original commit '%s' has a gpg signature."), old_ref); + warning(_("the original commit '%s' has a gpg signature"), old_ref); warning(_("the signature will be removed in the replacement commit!")); } - check_mergetags(commit, argc, argv); + if (check_mergetags(commit, argc, argv)) { + strbuf_release(&buf); + return -1; + } - if (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) - die(_("could not write replacement commit for: '%s'"), old_ref); + if (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) { + strbuf_release(&buf); + return error(_("could not write replacement commit for: '%s'"), + old_ref); + } strbuf_release(&buf); - if (!oidcmp(&old_oid, &new_oid)) - return error("new commit is the same as the old one: '%s'", oid_to_hex(&old_oid)); + if (oideq(&commit->object.oid, &new_oid)) { + if (gentle) { + warning(_("graft for '%s' unnecessary"), + oid_to_hex(&commit->object.oid)); + return 0; + } + return error(_("new commit is the same as the old one: '%s'"), + oid_to_hex(&commit->object.oid)); + } - return replace_object_oid(old_ref, &old_oid, "replacement", &new_oid, force); + return replace_object_oid(old_ref, &commit->object.oid, + "replacement", &new_oid, force); +} + +static int convert_graft_file(int force) +{ + const char *graft_file = get_graft_file(the_repository); + FILE *fp = fopen_or_warn(graft_file, "r"); + struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT; + struct argv_array args = ARGV_ARRAY_INIT; + + if (!fp) + return -1; + + advice_graft_file_deprecated = 0; + while (strbuf_getline(&buf, fp) != EOF) { + if (*buf.buf == '#') + continue; + + argv_array_split(&args, buf.buf); + if (args.argc && create_graft(args.argc, args.argv, force, 1)) + strbuf_addf(&err, "\n\t%s", buf.buf); + argv_array_clear(&args); + } + fclose(fp); + + strbuf_release(&buf); + + if (!err.len) + return unlink_or_warn(graft_file); + + warning(_("could not convert the following graft(s):\n%s"), err.buf); + strbuf_release(&err); + + return -1; } int cmd_replace(int argc, const char **argv, const char *prefix) @@ -432,6 +541,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix) MODE_DELETE, MODE_EDIT, MODE_GRAFT, + MODE_CONVERT_GRAFT_FILE, MODE_REPLACE } cmdmode = MODE_UNSPECIFIED; struct option options[] = { @@ -439,6 +549,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix) OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE), OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT), OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT), + OPT_CMDMODE(0, "convert-graft-file", &cmdmode, N_("convert existing graft file"), MODE_CONVERT_GRAFT_FILE), OPT_BOOL_F('f', "force", &force, N_("replace the ref if it exists"), PARSE_OPT_NOCOMPLETE), OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")), @@ -446,7 +557,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix) OPT_END() }; - check_replace_refs = 0; + read_replace_refs = 0; git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0); @@ -455,52 +566,59 @@ int cmd_replace(int argc, const char **argv, const char *prefix) cmdmode = argc ? MODE_REPLACE : MODE_LIST; if (format && cmdmode != MODE_LIST) - usage_msg_opt("--format cannot be used when not listing", + usage_msg_opt(_("--format cannot be used when not listing"), git_replace_usage, options); if (force && cmdmode != MODE_REPLACE && cmdmode != MODE_EDIT && - cmdmode != MODE_GRAFT) - usage_msg_opt("-f only makes sense when writing a replacement", + cmdmode != MODE_GRAFT && + cmdmode != MODE_CONVERT_GRAFT_FILE) + usage_msg_opt(_("-f only makes sense when writing a replacement"), git_replace_usage, options); if (raw && cmdmode != MODE_EDIT) - usage_msg_opt("--raw only makes sense with --edit", + usage_msg_opt(_("--raw only makes sense with --edit"), git_replace_usage, options); switch (cmdmode) { case MODE_DELETE: if (argc < 1) - usage_msg_opt("-d needs at least one argument", + usage_msg_opt(_("-d needs at least one argument"), git_replace_usage, options); return for_each_replace_name(argv, delete_replace_ref); case MODE_REPLACE: if (argc != 2) - usage_msg_opt("bad number of arguments", + usage_msg_opt(_("bad number of arguments"), git_replace_usage, options); return replace_object(argv[0], argv[1], force); case MODE_EDIT: if (argc != 1) - usage_msg_opt("-e needs exactly one argument", + usage_msg_opt(_("-e needs exactly one argument"), git_replace_usage, options); return edit_and_replace(argv[0], force, raw); case MODE_GRAFT: if (argc < 1) - usage_msg_opt("-g needs at least one argument", + usage_msg_opt(_("-g needs at least one argument"), + git_replace_usage, options); + return create_graft(argc, argv, force, 0); + + case MODE_CONVERT_GRAFT_FILE: + if (argc != 0) + usage_msg_opt(_("--convert-graft-file takes no argument"), git_replace_usage, options); - return create_graft(argc, argv, force); + return !!convert_graft_file(force); case MODE_LIST: if (argc > 1) - usage_msg_opt("only one pattern can be given with -l", + usage_msg_opt(_("only one pattern can be given with -l"), git_replace_usage, options); return list_replace_refs(argv[0], format); default: - die("BUG: invalid cmdmode %d", (int)cmdmode); + BUG("invalid cmdmode %d", (int)cmdmode); } } diff --git a/builtin/rerere.c b/builtin/rerere.c index 0bc40298c2..fd3be17b97 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -41,7 +41,8 @@ static int diff_two(const char *file1, const char *label1, xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; - ecb.outf = outf; + ecb.out_hunk = NULL; + ecb.out_line = outf; ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb); free(minus.ptr); @@ -70,28 +71,29 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) flags = RERERE_NOAUTOUPDATE; if (argc < 1) - return rerere(flags); + return repo_rerere(the_repository, flags); if (!strcmp(argv[0], "forget")) { struct pathspec pathspec; if (argc < 2) - warning("'git rerere forget' without paths is deprecated"); + warning(_("'git rerere forget' without paths is deprecated")); parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, prefix, argv + 1); - return rerere_forget(&pathspec); + return rerere_forget(the_repository, &pathspec); } if (!strcmp(argv[0], "clear")) { - rerere_clear(&merge_rr); + rerere_clear(the_repository, &merge_rr); } else if (!strcmp(argv[0], "gc")) - rerere_gc(&merge_rr); + rerere_gc(the_repository, &merge_rr); else if (!strcmp(argv[0], "status")) { - if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0) + if (setup_rerere(the_repository, &merge_rr, + flags | RERERE_READONLY) < 0) return 0; for (i = 0; i < merge_rr.nr; i++) printf("%s\n", merge_rr.items[i].string); } else if (!strcmp(argv[0], "remaining")) { - rerere_remaining(&merge_rr); + rerere_remaining(the_repository, &merge_rr); for (i = 0; i < merge_rr.nr; i++) { if (merge_rr.items[i].util != RERERE_RESOLVED) printf("%s\n", merge_rr.items[i].string); @@ -101,13 +103,14 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) merge_rr.items[i].util = NULL; } } else if (!strcmp(argv[0], "diff")) { - if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0) + if (setup_rerere(the_repository, &merge_rr, + flags | RERERE_READONLY) < 0) return 0; for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; const struct rerere_id *id = merge_rr.items[i].util; if (diff_two(rerere_path(id, "preimage"), path, path, path)) - die("unable to generate diff for %s", rerere_path(id, NULL)); + die(_("unable to generate diff for '%s'"), rerere_path(id, NULL)); } } else usage_with_options(rerere_usage, options); diff --git a/builtin/reset.c b/builtin/reset.c index 7f1c3f02a3..8ae69d6f2b 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -7,6 +7,7 @@ * * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "config.h" #include "lockfile.h" @@ -25,10 +26,13 @@ #include "submodule.h" #include "submodule-config.h" +#define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000) + static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"), - N_("git reset [-q] [<tree-ish>] [--] <paths>..."), - N_("git reset --patch [<tree-ish>] [--] [<paths>...]"), + N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."), + N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"), + N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"), NULL }; @@ -39,10 +43,10 @@ static const char *reset_type_names[] = { static inline int is_merge(void) { - return !access(git_path_merge_head(), F_OK); + 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]; @@ -56,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) { @@ -76,13 +81,13 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet) struct object_id head_oid; if (get_oid("HEAD", &head_oid)) return error(_("You do not have a valid HEAD.")); - if (!fill_tree_descriptor(desc + nr, &head_oid)) + if (!fill_tree_descriptor(the_repository, desc + nr, &head_oid)) return error(_("Failed to find tree of HEAD.")); nr++; opts.fn = twoway_merge; } - if (!fill_tree_descriptor(desc + nr, oid)) { + if (!fill_tree_descriptor(the_repository, desc + nr, oid)) { error(_("Failed to find tree of %s."), oid_to_hex(oid)); goto out; } @@ -93,7 +98,7 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet) if (reset_type == MIXED || reset_type == HARD) { tree = parse_tree_indirect(oid); - prime_cache_tree(&the_index, tree); + prime_cache_tree(the_repository, the_repository->index, tree); } ret = 0; @@ -134,7 +139,7 @@ static void update_index_from_diff(struct diff_queue_struct *q, continue; } - ce = make_cache_entry(one->mode, one->oid.hash, one->path, + ce = make_cache_entry(&the_index, one->mode, &one->oid, one->path, 0, 0); if (!ce) die(_("make_cache_entry failed for path '%s'"), @@ -159,6 +164,7 @@ static int read_from_tree(const struct pathspec *pathspec, opt.format_callback = update_index_from_diff; opt.format_callback_data = &intent_to_add; opt.flags.override_submodule_config = 1; + opt.repo = the_repository; if (do_diff_cache(tree_oid, &opt)) return 1; @@ -280,8 +286,8 @@ static int git_reset_config(const char *var, const char *value, void *cb) int cmd_reset(int argc, const char **argv, const char *prefix) { int reset_type = NONE, update_ref_status = 0, quiet = 0; - int patch_mode = 0, unborn; - const char *rev; + int patch_mode = 0, pathspec_file_nul = 0, unborn; + const char *rev, *pathspec_from_file = NULL; struct object_id oid; struct pathspec pathspec; int intent_to_add = 0; @@ -296,30 +302,47 @@ int cmd_reset(int argc, const char **argv, const char *prefix) N_("reset HEAD, index and working tree"), MERGE), OPT_SET_INT(0, "keep", &reset_type, N_("reset HEAD but keep local changes"), KEEP), - { OPTION_CALLBACK, 0, "recurse-submodules", NULL, + OPT_CALLBACK_F(0, "recurse-submodules", NULL, "reset", "control recursive updating of submodules", - PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, + PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater), OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that removed paths will be added later")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END() }; git_config(git_reset_config, NULL); + git_config_get_bool("reset.quiet", &quiet); argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); parse_args(&pathspec, argv, prefix, patch_mode, &rev); + if (pathspec_from_file) { + if (patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ - hashcpy(oid.hash, EMPTY_TREE_SHA1_BIN); - } else if (!pathspec.nr) { + oidcpy(&oid, the_hash_algo->empty_tree); + } else if (!pathspec.nr && !patch_mode) { struct commit *commit; if (get_oid_committish(rev, &oid)) die(_("Failed to resolve '%s' as a valid revision."), rev); - commit = lookup_commit_reference(&oid); + commit = lookup_commit_reference(the_repository, &oid); if (!commit) die(_("Could not parse object '%s'."), rev); oidcpy(&oid, &commit->object.oid); @@ -336,6 +359,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (patch_mode) { if (reset_type != NONE) die(_("--patch is incompatible with --{hard,mixed,soft}")); + trace2_cmd_mode("patch-interactive"); return run_add_interactive(rev, "--patch=reset", &pathspec); } @@ -352,6 +376,11 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (reset_type == NONE) reset_type = MIXED; /* by default */ + if (pathspec.nr) + trace2_cmd_mode("path"); + else + trace2_cmd_mode(reset_type_names[reset_type]); + if (reset_type != SOFT && (reset_type != MIXED || get_git_work_tree())) setup_work_tree(); @@ -375,15 +404,35 @@ int cmd_reset(int argc, const char **argv, const char *prefix) int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, &oid, intent_to_add)) return 1; - if (get_git_work_tree()) + the_index.updated_skipworktree = 1; + if (!quiet && get_git_work_tree()) { + uint64_t t_begin, t_delta_in_ms; + + t_begin = getnanotime(); refresh_index(&the_index, flags, NULL, NULL, _("Unstaged changes after reset:")); + t_delta_in_ms = (getnanotime() - t_begin) / 1000000; + if (advice_reset_quiet_warning && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) { + printf(_("\nIt took %.2f seconds to enumerate unstaged changes after reset. You can\n" + "use '--quiet' to avoid this. Set the config setting reset.quiet to true\n" + "to make this the default.\n"), t_delta_in_ms / 1000.0); + } + } } 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)) @@ -396,10 +445,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix) update_ref_status = reset_refs(rev, &oid); if (reset_type == HARD && !update_ref_status && !quiet) - print_new_head_line(lookup_commit_reference(&oid)); + print_new_head_line(lookup_commit_reference(the_repository, &oid)); } if (!pathspec.nr) - remove_branch_state(); + remove_branch_state(the_repository, 0); return update_ref_status; } diff --git a/builtin/rev-list.c b/builtin/rev-list.c index fadd3ec14c..f520111eda 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -6,6 +6,8 @@ #include "list-objects.h" #include "list-objects-filter.h" #include "list-objects-filter-options.h" +#include "object.h" +#include "object-store.h" #include "pack.h" #include "pack-bitmap.h" #include "builtin.h" @@ -46,6 +48,7 @@ static const char rev_list_usage[] = " --objects | --objects-edge\n" " --unpacked\n" " --header | --pretty\n" +" --[no-]object-names\n" " --abbrev=<n> | --no-abbrev\n" " --abbrev-commit\n" " --left-right\n" @@ -72,9 +75,12 @@ enum missing_action { }; static enum missing_action arg_missing_action; +/* display only the oid of each object encountered */ +static int arg_show_object_names = 1; + #define DEFAULT_OIDSET_SIZE (16*1024) -static void finish_commit(struct commit *commit, void *data); +static void finish_commit(struct commit *commit); static void show_commit(struct commit *commit, void *data) { struct rev_list_info *info = data; @@ -83,7 +89,7 @@ static void show_commit(struct commit *commit, void *data) display_progress(progress, ++progress_counter); if (info->flags & REV_LIST_QUIET) { - finish_commit(commit, data); + finish_commit(commit); return; } @@ -96,7 +102,7 @@ static void show_commit(struct commit *commit, void *data) revs->count_left++; else revs->count_right++; - finish_commit(commit, data); + finish_commit(commit); return; } @@ -185,16 +191,17 @@ static void show_commit(struct commit *commit, void *data) putchar('\n'); } maybe_flush_or_die(stdout, "stdout"); - finish_commit(commit, data); + finish_commit(commit); } -static void finish_commit(struct commit *commit, void *data) +static void finish_commit(struct commit *commit) { if (commit->parents) { free_commit_list(commit->parents); commit->parents = NULL; } - free_commit_buffer(commit); + free_commit_buffer(the_repository->parsed_objects, + commit); } static inline void finish_object__ma(struct object *obj) @@ -207,7 +214,8 @@ static inline void finish_object__ma(struct object *obj) */ switch (arg_missing_action) { case MA_ERROR: - die("missing blob object '%s'", oid_to_hex(&obj->oid)); + die("missing %s object '%s'", + type_name(obj->type), oid_to_hex(&obj->oid)); return; case MA_ALLOW_ANY: @@ -220,8 +228,8 @@ static inline void finish_object__ma(struct object *obj) case MA_ALLOW_PROMISOR: if (is_promisor_object(&obj->oid)) return; - die("unexpected missing blob object '%s'", - oid_to_hex(&obj->oid)); + die("unexpected missing %s object '%s'", + type_name(obj->type), oid_to_hex(&obj->oid)); return; default: @@ -233,24 +241,42 @@ static inline void finish_object__ma(struct object *obj) static int finish_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; - if (obj->type == OBJ_BLOB && !has_object_file(&obj->oid)) { + if (oid_object_info_extended(the_repository, &obj->oid, NULL, 0) < 0) { finish_object__ma(obj); return 1; } if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT) - parse_object(&obj->oid); + parse_object(the_repository, &obj->oid); return 0; } static void show_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; + struct rev_info *revs = info->revs; + if (finish_object(obj, name, cb_data)) return; display_progress(progress, ++progress_counter); if (info->flags & REV_LIST_QUIET) return; - show_object_with_name(stdout, obj, name); + + if (revs->count) { + /* + * The object count is always accumulated in the .count_right + * field for traversal that is not a left-right traversal, + * and cmd_rev_list() made sure that a .count request that + * wants to count non-commit objects, which is handled by + * the show_object() callback, does not ask for .left_right. + */ + revs->count_right++; + return; + } + + if (arg_show_object_names) + show_object_with_name(stdout, obj, name); + else + printf("%s\n", oid_to_hex(&obj->oid)); } static void show_edge(struct commit *commit) @@ -353,10 +379,86 @@ static inline int parse_missing_action_value(const char *value) return 0; } +static int try_bitmap_count(struct rev_info *revs, + struct list_objects_filter_options *filter) +{ + uint32_t commit_count = 0, + tag_count = 0, + tree_count = 0, + blob_count = 0; + int max_count; + struct bitmap_index *bitmap_git; + + /* This function only handles counting, not general traversal. */ + if (!revs->count) + return -1; + + /* + * A bitmap result can't know left/right, etc, because we don't + * actually traverse. + */ + if (revs->left_right || revs->cherry_mark) + return -1; + + /* + * If we're counting reachable objects, we can't handle a max count of + * commits to traverse, since we don't know which objects go with which + * commit. + */ + if (revs->max_count >= 0 && + (revs->tag_objects || revs->tree_objects || revs->blob_objects)) + return -1; + + /* + * This must be saved before doing any walking, since the revision + * machinery will count it down to zero while traversing. + */ + max_count = revs->max_count; + + bitmap_git = prepare_bitmap_walk(revs, filter); + if (!bitmap_git) + return -1; + + count_bitmap_commit_list(bitmap_git, &commit_count, + revs->tree_objects ? &tree_count : NULL, + revs->blob_objects ? &blob_count : NULL, + revs->tag_objects ? &tag_count : NULL); + if (max_count >= 0 && max_count < commit_count) + commit_count = max_count; + + printf("%d\n", commit_count + tree_count + blob_count + tag_count); + free_bitmap_index(bitmap_git); + return 0; +} + +static int try_bitmap_traversal(struct rev_info *revs, + struct list_objects_filter_options *filter) +{ + struct bitmap_index *bitmap_git; + + /* + * We can't use a bitmap result with a traversal limit, since the set + * of commits we'd get would be essentially random. + */ + if (revs->max_count >= 0) + return -1; + + bitmap_git = prepare_bitmap_walk(revs, filter); + if (!bitmap_git) + return -1; + + traverse_bitmap_commit_list(bitmap_git, revs, &show_object_fast); + free_bitmap_index(bitmap_git); + return 0; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct rev_list_info info; + struct setup_revision_opt s_r_opt = { + .allow_exclude_promisor_objects = 1, + }; int i; int bisect_list = 0; int bisect_show_vars = 0; @@ -368,7 +470,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) usage(rev_list_usage); git_config(git_default_config, NULL); - init_revisions(&revs, prefix); + repo_init_revisions(the_repository, &revs, prefix); revs.abbrev = DEFAULT_ABBREV; revs.commit_format = CMIT_FMT_UNSPECIFIED; @@ -400,7 +502,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } } - argc = setup_revisions(argc, argv, &revs, NULL); + if (arg_missing_action) + revs.do_not_die_on_missing_tree = 1; + + argc = setup_revisions(argc, argv, &revs, &s_r_opt); memset(&info, 0, sizeof(info)); info.revs = &revs; @@ -453,10 +558,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) parse_list_objects_filter(&filter_options, arg); if (filter_options.choice && !revs.blob_objects) die(_("object filtering requires --objects")); - if (filter_options.choice == LOFC_SPARSE_OID && - !filter_options.sparse_oid_value) - die(_("invalid sparse value '%s'"), - filter_options.filter_spec); continue; } if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { @@ -473,6 +574,16 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (skip_prefix(arg, "--missing=", &arg)) continue; /* already handled above */ + if (!strcmp(arg, ("--no-object-names"))) { + arg_show_object_names = 0; + continue; + } + + if (!strcmp(arg, ("--object-names"))) { + arg_show_object_names = 1; + continue; + } + usage(rev_list_usage); } @@ -491,15 +602,17 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if ((!revs.commits && reflog_walk_empty(revs.reflog_info) && (!(revs.tag_objects || revs.tree_objects || revs.blob_objects) && !revs.pending.nr) && - !revs.rev_input_given) || + !revs.rev_input_given && !revs.read_from_stdin) || revs.diff) usage(rev_list_usage); if (revs.show_notes) die(_("rev-list does not support display of notes")); - if (filter_options.choice && use_bitmap_index) - die(_("cannot combine --use-bitmap-index with object filtering")); + if (revs.count && + (revs.tag_objects || revs.tree_objects || revs.blob_objects) && + (revs.left_right || revs.cherry_mark)) + die(_("marked counting is incompatible with --objects")); save_commit_buffer = (revs.verbose_header || revs.grep_filter.pattern_list || @@ -510,30 +623,17 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (show_progress) progress = start_delayed_progress(show_progress, 0); - if (use_bitmap_index && !revs.prune) { - if (revs.count && !revs.left_right && !revs.cherry_mark) { - uint32_t commit_count; - int max_count = revs.max_count; - if (!prepare_bitmap_walk(&revs)) { - count_bitmap_commit_list(&commit_count, NULL, NULL, NULL); - if (max_count >= 0 && max_count < commit_count) - commit_count = max_count; - printf("%d\n", commit_count); - return 0; - } - } else if (revs.max_count < 0 && - revs.tag_objects && revs.tree_objects && revs.blob_objects) { - if (!prepare_bitmap_walk(&revs)) { - traverse_bitmap_commit_list(&show_object_fast); - return 0; - } - } + if (use_bitmap_index) { + if (!try_bitmap_count(&revs, &filter_options)) + return 0; + if (!try_bitmap_traversal(&revs, &filter_options)) + return 0; } if (prepare_revision_walk(&revs)) die("revision walk setup failed"); if (revs.tree_objects) - mark_edges_uninteresting(&revs, show_edge); + mark_edges_uninteresting(&revs, show_edge, 0); if (bisect_list) { int reaches, all; diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 36b2087782..669dd2fd6f 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -3,6 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "commit.h" @@ -14,6 +15,8 @@ #include "revision.h" #include "split-index.h" #include "submodule.h" +#include "commit-reach.h" +#include "shallow.h" #define DO_REVS 1 #define DO_NOREV 2 @@ -280,8 +283,12 @@ static int try_difference(const char *arg) if (symmetric) { struct commit_list *exclude; struct commit *a, *b; - a = lookup_commit_reference(&start_oid); - b = lookup_commit_reference(&end_oid); + a = lookup_commit_reference(the_repository, &start_oid); + b = lookup_commit_reference(the_repository, &end_oid); + if (!a || !b) { + *dotdot = '.'; + return 0; + } exclude = get_merge_bases(a, b); while (exclude) { struct commit *commit = pop_commit(&exclude); @@ -328,12 +335,12 @@ static int try_parent_shorthands(const char *arg) return 0; *dotdot = 0; - if (get_oid_committish(arg, &oid)) { + if (get_oid_committish(arg, &oid) || + !(commit = lookup_commit_reference(the_repository, &oid))) { *dotdot = '^'; return 0; } - commit = lookup_commit_reference(&oid); if (exclude_parent && exclude_parent > commit_list_count(commit->parents)) { *dotdot = '^'; @@ -587,6 +594,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *name = NULL; struct object_context unused; struct strbuf buf = STRBUF_INIT; + const int hexsz = the_hash_algo->hexsz; if (argc > 1 && !strcmp("--parseopt", argv[1])) return cmd_parseopt(argc - 1, argv + 1, prefix); @@ -724,8 +732,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) abbrev = strtoul(arg, NULL, 10); if (abbrev < MINIMUM_ABBREV) abbrev = MINIMUM_ABBREV; - else if (40 <= abbrev) - abbrev = 40; + else if (hexsz <= abbrev) + abbrev = hexsz; continue; } if (!strcmp(arg, "--sq")) { @@ -760,6 +768,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--all")) { for_each_ref(show_reference, NULL); + clear_ref_exclusion(&ref_excludes); continue; } if (skip_prefix(arg, "--disambiguate=", &arg)) { @@ -795,12 +804,15 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *work_tree = get_git_work_tree(); if (work_tree) puts(work_tree); + else + die("this operation must be run in a work tree"); 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")) { @@ -847,7 +859,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; } } @@ -879,7 +894,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--is-shallow-repository")) { - printf("%s\n", is_repository_shallow() ? "true" + printf("%s\n", + is_repository_shallow(the_repository) ? "true" : "false"); continue; } @@ -887,8 +903,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("Could not read the index")); if (the_index.split_index) { - const unsigned char *sha1 = the_index.split_index->base_sha1; - const char *path = git_path("sharedindex.%s", sha1_to_hex(sha1)); + const struct object_id *oid = &the_index.split_index->base_oid; + const char *path = git_path("sharedindex.%s", oid_to_hex(oid)); strbuf_reset(&buf); puts(relative_path(path, prefix, &buf)); } @@ -910,6 +926,17 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) show_datestring("--min-age=", arg); continue; } + if (opt_with_value(arg, "--show-object-format", &arg)) { + const char *val = arg ? arg : "storage"; + + if (strcmp(val, "storage") && + strcmp(val, "input") && + strcmp(val, "output")) + die("unknown mode for --show-object-format: %s", + arg); + puts(the_hash_algo->name); + continue; + } if (show_flag(arg) && verify) die_no_single_rev(quiet); continue; @@ -926,7 +953,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) name++; type = REVERSED; } - if (!get_oid_with_context(name, flags, &oid, &unused)) { + if (!get_oid_with_context(the_repository, name, + flags, &oid, &unused)) { if (verify) revs_count++; else diff --git a/builtin/revert.c b/builtin/revert.c index 76f0a35b07..f61cc5d82c 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -7,6 +7,7 @@ #include "rerere.h" #include "dir.h" #include "sequencer.h" +#include "branch.h" /* * This implements the builtins revert and cherry-pick. @@ -68,7 +69,8 @@ static int option_parse_m(const struct option *opt, replay->mainline = strtol(arg, &end, 10); if (*end || replay->mainline <= 0) - return opterror(opt, "expects a number greater than zero", 0); + return error(_("option `%s' expects a number greater than zero"), + opt->long_name); return 0; } @@ -94,11 +96,14 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) { const char * const * usage_str = revert_or_cherry_pick_usage(opts); const char *me = action_name(opts); + const char *cleanup_arg = NULL; int cmd = 0; struct option base_options[] = { OPT_CMDMODE(0, "quit", &cmd, N_("end revert or cherry-pick sequence"), 'q'), OPT_CMDMODE(0, "continue", &cmd, N_("resume revert or cherry-pick sequence"), 'c'), OPT_CMDMODE(0, "abort", &cmd, N_("cancel revert or cherry-pick sequence"), 'a'), + OPT_CMDMODE(0, "skip", &cmd, N_("skip current commit and continue"), 's'), + OPT_CLEANUP(&cleanup_arg), OPT_BOOL('n', "no-commit", &opts->no_commit, N_("don't automatically commit")), OPT_BOOL('e', "edit", &opts->edit, N_("edit the commit message")), OPT_NOOP_NOARG('r', NULL), @@ -135,6 +140,11 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) if (opts->keep_redundant_commits) opts->allow_empty = 1; + if (cleanup_arg) { + opts->default_msg_cleanup = get_cleanup_mode(cleanup_arg, 1); + opts->explicit_cleanup = 1; + } + /* Check for incompatible command line arguments */ if (cmd) { char *this_operation; @@ -142,6 +152,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) this_operation = "--quit"; else if (cmd == 'c') this_operation = "--continue"; + else if (cmd == 's') + this_operation = "--skip"; else { assert(cmd == 'a'); this_operation = "--abort"; @@ -173,7 +185,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) } else { struct setup_revision_opt s_r_opt; opts->revs = xmalloc(sizeof(*opts->revs)); - init_revisions(opts->revs, NULL); + repo_init_revisions(the_repository, opts->revs, NULL); opts->revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED; if (argc < 2) usage_with_options(usage_str, options); @@ -191,13 +203,19 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) opts->gpg_sign = xstrdup_or_null(opts->gpg_sign); opts->strategy = xstrdup_or_null(opts->strategy); - if (cmd == 'q') - return sequencer_remove_state(opts); + if (cmd == 'q') { + int ret = sequencer_remove_state(opts); + if (!ret) + remove_branch_state(the_repository, 0); + return ret; + } if (cmd == 'c') - return sequencer_continue(opts); + return sequencer_continue(the_repository, opts); if (cmd == 'a') - return sequencer_rollback(opts); - return sequencer_pick_revisions(opts); + return sequencer_rollback(the_repository, opts); + if (cmd == 's') + return sequencer_skip(the_repository, opts); + return sequencer_pick_revisions(the_repository, opts); } int cmd_revert(int argc, const char **argv, const char *prefix) diff --git a/builtin/rm.c b/builtin/rm.c index 5b6fc7ee81..4858631e0f 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -3,6 +3,7 @@ * * Copyright (C) Linus Torvalds 2006 */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "config.h" #include "lockfile.h" @@ -60,7 +61,7 @@ static void print_error_files(struct string_list *files_list, } } -static void submodules_absorb_gitdir_if_needed(const char *prefix) +static void submodules_absorb_gitdir_if_needed(void) { int i; for (i = 0; i < list.nr; i++) { @@ -82,7 +83,7 @@ static void submodules_absorb_gitdir_if_needed(const char *prefix) continue; if (!submodule_uses_gitfile(name)) - absorb_git_dir_into_superproject(prefix, name, + absorb_git_dir_into_superproject(name, ABSORB_GITDIR_RECURSE_SUBMODULES); } } @@ -109,7 +110,7 @@ static int check_local_mod(struct object_id *head, int index_only) const struct cache_entry *ce; const char *name = list.entry[i].name; struct object_id oid; - unsigned mode; + unsigned short mode; int local_changes = 0; int staged_changes = 0; @@ -178,9 +179,9 @@ static int check_local_mod(struct object_id *head, int index_only) * way as changed from the HEAD. */ if (no_head - || get_tree_entry(head, name, &oid, &mode) + || get_tree_entry(the_repository, head, name, &oid, &mode) || ce->ce_mode != create_ce_mode(mode) - || oidcmp(&ce->oid, &oid)) + || !oideq(&ce->oid, &oid)) staged_changes = 1; /* @@ -233,10 +234,9 @@ static int check_local_mod(struct object_id *head, int index_only) return errs; } -static struct lock_file lock_file; - static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; -static int ignore_unmatch = 0; +static int ignore_unmatch = 0, pathspec_file_nul; +static char *pathspec_from_file; static struct option builtin_rm_options[] = { OPT__DRY_RUN(&show_only, N_("dry run")), @@ -246,11 +246,14 @@ static struct option builtin_rm_options[] = { OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")), OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch, N_("exit with a zero status even if nothing matched")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END(), }; int cmd_rm(int argc, const char **argv, const char *prefix) { + struct lock_file lock_file = LOCK_INIT; int i; struct pathspec pathspec; char *seen; @@ -259,8 +262,24 @@ int cmd_rm(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_rm_options, builtin_rm_usage, 0); - if (!argc) - usage_with_options(builtin_rm_usage, builtin_rm_options); + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD, + prefix, argv); + + if (pathspec_from_file) { + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_CWD, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (!pathspec.nr) + die(_("No pathspec was given. Which files should I remove?")); if (!index_only) setup_work_tree(); @@ -270,23 +289,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); - parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD, - prefix, argv); - refresh_index(&the_index, REFRESH_QUIET, &pathspec, NULL, NULL); + refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL); seen = xcalloc(pathspec.nr, 1); for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; - if (!ce_path_match(ce, &pathspec, seen)) + if (!ce_path_match(&the_index, ce, &pathspec, seen)) continue; ALLOC_GROW(list.entry, list.nr + 1, list.alloc); list.entry[list.nr].name = xstrdup(ce->name); list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode); if (list.entry[list.nr++].is_submodule && !is_staging_gitmodules_ok(&the_index)) - die (_("Please stage your changes to .gitmodules or stash them to proceed")); + die(_("please stage your changes to .gitmodules or stash them to proceed")); } if (pathspec.nr) { @@ -313,7 +329,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) } if (!index_only) - submodules_absorb_gitdir_if_needed(prefix); + submodules_absorb_gitdir_if_needed(); /* * If not forced, the file, the index and the HEAD (if exists) diff --git a/builtin/send-pack.c b/builtin/send-pack.c index fc4f0bb5fb..2b9610f121 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -11,9 +11,10 @@ #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" static const char * const send_pack_usage[] = { N_("git send-pack [--all | --mirror] [--dry-run] [--force] " @@ -120,13 +121,12 @@ static int send_pack_config(const char *k, const char *v, void *cb) } } } - return 0; + return git_default_config(k, v, cb); } int cmd_send_pack(int argc, const char **argv, const char *prefix) { - int i, nr_refspecs = 0; - const char **refspecs = NULL; + struct refspec rs = REFSPEC_INIT_PUSH; const char *remote_name = NULL; struct remote *remote = NULL; const char *dest = NULL; @@ -154,6 +154,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int progress = -1; int from_stdin = 0; struct push_cas_option cas = {0}; + struct packet_reader reader; struct option options[] = { OPT__VERBOSITY(&verbose), @@ -164,9 +165,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) OPT_BOOL('n' , "dry-run", &dry_run, N_("dry run")), OPT_BOOL(0, "mirror", &send_mirror, N_("mirror all refs")), OPT_BOOL('f', "force", &force_update, N_("force updates")), - { OPTION_CALLBACK, - 0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"), - PARSE_OPT_OPTARG, option_parse_push_signed }, + OPT_CALLBACK_F(0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"), + PARSE_OPT_OPTARG, option_parse_push_signed), OPT_STRING_LIST(0, "push-option", &push_options, N_("server-specific"), N_("option to transmit")), @@ -176,10 +176,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "stateless-rpc", &stateless_rpc, N_("use stateless RPC protocol")), OPT_BOOL(0, "stdin", &from_stdin, N_("read refs from stdin")), OPT_BOOL(0, "helper-status", &helper_status, N_("print status from remote helper")), - { OPTION_CALLBACK, - 0, CAS_OPT_NAME, &cas, N_("refname>:<expect"), + OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"), N_("require old value of ref to be at this value"), - PARSE_OPT_OPTARG, parseopt_push_cas_option }, + PARSE_OPT_OPTARG, parseopt_push_cas_option), OPT_END() }; @@ -187,8 +186,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0); if (argc > 0) { dest = argv[0]; - refspecs = (const char **)(argv + 1); - nr_refspecs = argc - 1; + refspec_appendn(&rs, argv + 1, argc - 1); } if (!dest) @@ -207,31 +205,23 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.push_options = push_options.nr ? &push_options : NULL; if (from_stdin) { - struct argv_array all_refspecs = ARGV_ARRAY_INIT; - - for (i = 0; i < nr_refspecs; i++) - argv_array_push(&all_refspecs, refspecs[i]); - if (args.stateless_rpc) { const char *buf; while ((buf = packet_read_line(0, NULL))) - argv_array_push(&all_refspecs, buf); + refspec_append(&rs, buf); } else { struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, stdin) != EOF) - argv_array_push(&all_refspecs, line.buf); + refspec_append(&rs, line.buf); strbuf_release(&line); } - - refspecs = all_refspecs.argv; - nr_refspecs = all_refspecs.argc; } /* * --all and --mirror are incompatible; neither makes sense * with any refspecs. */ - if ((nr_refspecs > 0 && (send_all || args.send_mirror)) || + if ((rs.nr > 0 && (send_all || args.send_mirror)) || (send_all && args.send_mirror)) usage_with_options(send_pack_usage, options); @@ -256,10 +246,23 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.verbose ? CONNECT_VERBOSE : 0); } - get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL, - &extra_have, &shallow); - - transport_verify_remote_names(nr_refspecs, refspecs); + packet_reader_init(&reader, fd[0], NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_DIE_ON_ERR_PACKET); + + switch (discover_version(&reader)) { + case protocol_v2: + die("support for protocol v2 not implemented yet"); + break; + case protocol_v1: + case protocol_v0: + get_remote_heads(&reader, &remote_refs, REF_NORMAL, + &extra_have, &shallow); + break; + case protocol_unknown_version: + BUG("unknown protocol version"); + } local_refs = get_local_heads(); @@ -271,7 +274,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) flags |= MATCH_REFS_MIRROR; /* match them up */ - if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) + if (match_push_refs(local_refs, &remote_refs, &rs, flags)) return -1; if (!is_empty_cas(&cas)) diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 608d6ba77b..c856c58bb5 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -268,8 +268,9 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) N_("Suppress commit descriptions, only provides commit count")), OPT_BOOL('e', "email", &log.email, N_("Show the email address of each author")), - { OPTION_CALLBACK, 'w', NULL, &log, N_("w[,i1[,i2]]"), - N_("Linewrap output"), PARSE_OPT_OPTARG, &parse_wrap_args }, + OPT_CALLBACK_F('w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"), + N_("Linewrap output"), PARSE_OPT_OPTARG, + &parse_wrap_args), OPT_END(), }; @@ -277,7 +278,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); shortlog_init(&log); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); parse_options_start(&ctx, argc, argv, prefix, options, PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); @@ -286,6 +287,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) case PARSE_OPT_HELP: case PARSE_OPT_ERROR: exit(129); + case PARSE_OPT_COMPLETE: + exit(0); case PARSE_OPT_DONE: goto parse_done; } diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 6c2148b71d..7e52ee9126 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -7,6 +7,7 @@ #include "argv-array.h" #include "parse-options.h" #include "dir.h" +#include "commit-slab.h" static const char* show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" @@ -21,6 +22,11 @@ static int showbranch_use_color = -1; static struct argv_array default_args = ARGV_ARRAY_INIT; +/* + * TODO: convert this use of commit->object.flags to commit-slab + * instead to store a pointer to ref name directly. Then use the same + * UNINTERESTING definition from revision.h here. + */ #define UNINTERESTING 01 #define REV_SHIFT 2 @@ -59,15 +65,27 @@ struct commit_name { int generation; /* how many parents away from head_name */ }; +define_commit_slab(commit_name_slab, struct commit_name *); +static struct commit_name_slab name_slab; + +static struct commit_name *commit_to_name(struct commit *commit) +{ + return *commit_name_slab_at(&name_slab, commit); +} + + /* Name the commit as nth generation ancestor of head_name; * we count only the first-parent relationship for naming purposes. */ static void name_commit(struct commit *commit, const char *head_name, int nth) { struct commit_name *name; - if (!commit->util) - commit->util = xmalloc(sizeof(struct commit_name)); - name = commit->util; + + name = *commit_name_slab_at(&name_slab, commit); + if (!name) { + name = xmalloc(sizeof(*name)); + *commit_name_slab_at(&name_slab, commit) = name; + } name->head_name = head_name; name->generation = nth; } @@ -79,8 +97,8 @@ static void name_commit(struct commit *commit, const char *head_name, int nth) */ static void name_parent(struct commit *commit, struct commit *parent) { - struct commit_name *commit_name = commit->util; - struct commit_name *parent_name = parent->util; + struct commit_name *commit_name = commit_to_name(commit); + struct commit_name *parent_name = commit_to_name(parent); if (!commit_name) return; if (!parent_name || @@ -94,12 +112,12 @@ static int name_first_parent_chain(struct commit *c) int i = 0; while (c) { struct commit *p; - if (!c->util) + if (!commit_to_name(c)) break; if (!c->parents) break; p = c->parents->item; - if (!p->util) { + if (!commit_to_name(p)) { name_parent(c, p); i++; } @@ -122,7 +140,7 @@ static void name_commits(struct commit_list *list, /* First give names to the given heads */ for (cl = list; cl; cl = cl->next) { c = cl->item; - if (c->util) + if (commit_to_name(c)) continue; for (i = 0; i < num_rev; i++) { if (rev[i] == c) { @@ -148,9 +166,9 @@ static void name_commits(struct commit_list *list, struct commit_name *n; int nth; c = cl->item; - if (!c->util) + if (!commit_to_name(c)) continue; - n = c->util; + n = commit_to_name(c); parents = c->parents; nth = 0; while (parents) { @@ -158,7 +176,7 @@ static void name_commits(struct commit_list *list, struct strbuf newname = STRBUF_INIT; parents = parents->next; nth++; - if (p->util) + if (commit_to_name(p)) continue; switch (n->generation) { case 0: @@ -271,7 +289,7 @@ static void show_one_commit(struct commit *commit, int no_name) { struct strbuf pretty = STRBUF_INIT; const char *pretty_str = "(unavailable)"; - struct commit_name *name = commit->util; + struct commit_name *name = commit_to_name(commit); if (commit->object.parsed) { pp_commit_easy(CMIT_FMT_ONELINE, commit, &pretty); @@ -360,7 +378,8 @@ static void sort_ref_range(int bottom, int top) static int append_ref(const char *refname, const struct object_id *oid, int allow_dups) { - struct commit *commit = lookup_commit_reference_gently(oid, 1); + struct commit *commit = lookup_commit_reference_gently(the_repository, + oid, 1); int i; if (!commit) @@ -393,7 +412,7 @@ static int append_head_ref(const char *refname, const struct object_id *oid, /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. */ - if (get_oid(refname + ofs, &tmp) || oidcmp(&tmp, oid)) + if (get_oid(refname + ofs, &tmp) || !oideq(&tmp, oid)) ofs = 5; return append_ref(refname + ofs, oid, 0); } @@ -408,7 +427,7 @@ static int append_remote_ref(const char *refname, const struct object_id *oid, /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. */ - if (get_oid(refname + ofs, &tmp) || oidcmp(&tmp, oid)) + if (get_oid(refname + ofs, &tmp) || !oideq(&tmp, oid)) ofs = 5; return append_ref(refname + ofs, oid, 0); } @@ -466,7 +485,7 @@ static void snarf_refs(int head, int remotes) static int rev_is_head(const char *head, const char *name, unsigned char *head_sha1, unsigned char *sha1) { - if (!head || (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) + if (!head || (head_sha1 && sha1 && !hasheq(head_sha1, sha1))) return 0; skip_prefix(head, "refs/heads/", &head); if (!skip_prefix(name, "refs/heads/", &name)) @@ -495,7 +514,6 @@ static int show_merge_base(struct commit_list *seen, int num_rev) static int show_independent(struct commit **rev, int num_rev, - char **ref_name, unsigned int *rev_mask) { int i; @@ -518,7 +536,7 @@ static void append_one_rev(const char *av) append_ref(av, &revkey, 0); return; } - if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { + if (strpbrk(av, "*?[")) { /* glob style match */ int saved_matches = ref_name_cnt; @@ -585,6 +603,7 @@ static int parse_reflog_param(const struct option *opt, const char *arg, { char *ep; const char **base = (const char **)opt->value; + BUG_ON_OPT_NEG(unset); if (!arg) arg = ""; reflog = strtoul(arg, &ep, 10); @@ -652,14 +671,16 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) N_("topologically sort, maintaining date order " "where possible"), REV_SORT_BY_COMMIT_DATE), - { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"), + OPT_CALLBACK_F('g', "reflog", &reflog_base, N_("<n>[,<base>]"), N_("show <n> most recent ref-log entries starting at " "base"), - PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, - parse_reflog_param }, + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, + parse_reflog_param), OPT_END() }; + init_commit_name_slab(&name_slab); + git_config(git_show_branch_config, NULL); /* If nothing is specified, try the default first */ @@ -731,7 +752,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) /* Ah, that is a date spec... */ timestamp_t at; at = approxidate(reflog_base); - read_ref_at(ref, flags, at, -1, &oid, NULL, + read_ref_at(get_main_ref_store(the_repository), + ref, flags, at, -1, &oid, NULL, NULL, NULL, &base); } } @@ -743,7 +765,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) timestamp_t timestamp; int tz; - if (read_ref_at(ref, flags, 0, base + i, &oid, &logmsg, + if (read_ref_at(get_main_ref_store(the_repository), + ref, flags, 0, base + i, &oid, &logmsg, ×tamp, &tz, NULL)) { reflog = i; break; @@ -810,7 +833,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) MAX_REVS), MAX_REVS); if (get_oid(ref_name[num_rev], &revkey)) die(_("'%s' is not a valid ref."), ref_name[num_rev]); - commit = lookup_commit_reference(&revkey); + commit = lookup_commit_reference(the_repository, &revkey); if (!commit) die(_("cannot find commit %s (%s)"), ref_name[num_rev], oid_to_hex(&revkey)); @@ -838,7 +861,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) return show_merge_base(seen, num_rev); if (independent) - return show_independent(rev, num_rev, ref_name, rev_mask); + return show_independent(rev, num_rev, rev_mask); /* Show list; --more=-1 means list-only */ if (1 < num_rev || extra < 0) { diff --git a/builtin/show-index.c b/builtin/show-index.c new file mode 100644 index 0000000000..0826f6a5a2 --- /dev/null +++ b/builtin/show-index.c @@ -0,0 +1,87 @@ +#include "builtin.h" +#include "cache.h" +#include "pack.h" + +static const char show_index_usage[] = +"git show-index"; + +int cmd_show_index(int argc, const char **argv, const char *prefix) +{ + int i; + unsigned nr; + unsigned int version; + static unsigned int top_index[256]; + const unsigned hashsz = the_hash_algo->rawsz; + + if (argc != 1) + usage(show_index_usage); + if (fread(top_index, 2 * 4, 1, stdin) != 1) + die("unable to read header"); + if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) { + version = ntohl(top_index[1]); + if (version < 2 || version > 2) + die("unknown index version"); + if (fread(top_index, 256 * 4, 1, stdin) != 1) + die("unable to read index"); + } else { + version = 1; + if (fread(&top_index[2], 254 * 4, 1, stdin) != 1) + die("unable to read index"); + } + nr = 0; + for (i = 0; i < 256; i++) { + unsigned n = ntohl(top_index[i]); + if (n < nr) + die("corrupt index file"); + nr = n; + } + if (version == 1) { + for (i = 0; i < nr; i++) { + unsigned int offset, entry[(GIT_MAX_RAWSZ + 4) / sizeof(unsigned int)]; + + if (fread(entry, 4 + hashsz, 1, stdin) != 1) + die("unable to read entry %u/%u", i, nr); + offset = ntohl(entry[0]); + printf("%u %s\n", offset, hash_to_hex((void *)(entry+1))); + } + } else { + unsigned off64_nr = 0; + struct { + struct object_id oid; + uint32_t crc; + uint32_t off; + } *entries; + ALLOC_ARRAY(entries, nr); + for (i = 0; i < nr; i++) + if (fread(entries[i].oid.hash, hashsz, 1, stdin) != 1) + die("unable to read sha1 %u/%u", i, nr); + for (i = 0; i < nr; i++) + if (fread(&entries[i].crc, 4, 1, stdin) != 1) + die("unable to read crc %u/%u", i, nr); + for (i = 0; i < nr; i++) + if (fread(&entries[i].off, 4, 1, stdin) != 1) + die("unable to read 32b offset %u/%u", i, nr); + for (i = 0; i < nr; i++) { + uint64_t offset; + uint32_t off = ntohl(entries[i].off); + if (!(off & 0x80000000)) { + offset = off; + } else { + uint32_t off64[2]; + if ((off & 0x7fffffff) != off64_nr) + die("inconsistent 64b offset index"); + if (fread(off64, 8, 1, stdin) != 1) + die("unable to read 64b offset %u", off64_nr); + offset = (((uint64_t)ntohl(off64[0])) << 32) | + ntohl(off64[1]); + off64_nr++; + } + printf("%" PRIuMAX " %s (%08"PRIx32")\n", + (uintmax_t) offset, + oid_to_hex(&entries[i].oid), + ntohl(entries[i].crc)); + } + free(entries); + } + return 0; +} diff --git a/builtin/show-ref.c b/builtin/show-ref.c index f2eb1a7724..ae60b4acf2 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -1,6 +1,8 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "refs.h" +#include "object-store.h" #include "object.h" #include "tag.h" #include "string-list.h" @@ -22,7 +24,7 @@ static void show_one(const char *refname, const struct object_id *oid) const char *hex; struct object_id peeled; - if (!has_sha1_file(oid->hash)) + if (!has_object_file(oid)) die("git show-ref: bad ref %s (%s)", refname, oid_to_hex(oid)); @@ -150,6 +152,7 @@ static int hash_callback(const struct option *opt, const char *arg, int unset) static int exclude_existing_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); exclude_arg = 1; *(const char **)opt->value = arg; return 0; @@ -166,20 +169,22 @@ static const struct option show_ref_options[] = { N_("show the HEAD reference, even if it would be filtered out")), OPT_BOOL('d', "dereference", &deref_tags, N_("dereference tags into object IDs")), - { OPTION_CALLBACK, 's', "hash", &abbrev, N_("n"), - N_("only show SHA1 hash using <n> digits"), - PARSE_OPT_OPTARG, &hash_callback }, + OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"), + N_("only show SHA1 hash using <n> digits"), + PARSE_OPT_OPTARG, &hash_callback), OPT__ABBREV(&abbrev), OPT__QUIET(&quiet, N_("do not print results to stdout (useful with --verify)")), - { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg, - N_("pattern"), N_("show refs from stdin that aren't in local repository"), - PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback }, + OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg, + N_("pattern"), N_("show refs from stdin that aren't in local repository"), + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback), OPT_END() }; int cmd_show_ref(int argc, const char **argv, const char *prefix) { + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, show_ref_options, show_ref_usage, 0); diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c new file mode 100644 index 0000000000..95d0882417 --- /dev/null +++ b/builtin/sparse-checkout.c @@ -0,0 +1,620 @@ +#include "builtin.h" +#include "config.h" +#include "dir.h" +#include "parse-options.h" +#include "pathspec.h" +#include "repository.h" +#include "run-command.h" +#include "strbuf.h" +#include "string-list.h" +#include "cache.h" +#include "cache-tree.h" +#include "lockfile.h" +#include "resolve-undo.h" +#include "unpack-trees.h" +#include "wt-status.h" +#include "quote.h" + +static const char *empty_base = ""; + +static char const * const builtin_sparse_checkout_usage[] = { + N_("git sparse-checkout (init|list|set|add|reapply|disable) <options>"), + NULL +}; + +static char *get_sparse_checkout_filename(void) +{ + return git_pathdup("info/sparse-checkout"); +} + +static void write_patterns_to_file(FILE *fp, struct pattern_list *pl) +{ + int i; + + for (i = 0; i < pl->nr; i++) { + struct path_pattern *p = pl->patterns[i]; + + if (p->flags & PATTERN_FLAG_NEGATIVE) + fprintf(fp, "!"); + + fprintf(fp, "%s", p->pattern); + + if (p->flags & PATTERN_FLAG_MUSTBEDIR) + fprintf(fp, "/"); + + fprintf(fp, "\n"); + } +} + +static int sparse_checkout_list(int argc, const char **argv) +{ + struct pattern_list pl; + char *sparse_filename; + int res; + + memset(&pl, 0, sizeof(pl)); + + pl.use_cone_patterns = core_sparse_checkout_cone; + + sparse_filename = get_sparse_checkout_filename(); + res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL); + free(sparse_filename); + + if (res < 0) { + warning(_("this worktree is not sparse (sparse-checkout file may not exist)")); + return 0; + } + + if (pl.use_cone_patterns) { + int i; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct string_list sl = STRING_LIST_INIT_DUP; + + hashmap_for_each_entry(&pl.recursive_hashmap, &iter, pe, ent) { + /* pe->pattern starts with "/", skip it */ + string_list_insert(&sl, pe->pattern + 1); + } + + string_list_sort(&sl); + + for (i = 0; i < sl.nr; i++) { + quote_c_style(sl.items[i].string, NULL, stdout, 0); + printf("\n"); + } + + return 0; + } + + write_patterns_to_file(stdout, &pl); + clear_pattern_list(&pl); + + return 0; +} + +static int update_working_directory(struct pattern_list *pl) +{ + enum update_sparsity_result result; + struct unpack_trees_options o; + struct lock_file lock_file = LOCK_INIT; + struct repository *r = the_repository; + + memset(&o, 0, sizeof(o)); + o.verbose_update = isatty(2); + o.update = 1; + o.head_idx = -1; + o.src_index = r->index; + o.dst_index = r->index; + o.skip_sparse_checkout = 0; + o.pl = pl; + + setup_work_tree(); + + repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR); + + 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 + rollback_lock_file(&lock_file); + + return result; +} + +static char *escaped_pattern(char *pattern) +{ + char *p = pattern; + struct strbuf final = STRBUF_INIT; + + while (*p) { + if (is_glob_special(*p)) + strbuf_addch(&final, '\\'); + + strbuf_addch(&final, *p); + p++; + } + + return strbuf_detach(&final, NULL); +} + +static void write_cone_to_file(FILE *fp, struct pattern_list *pl) +{ + int i; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct string_list sl = STRING_LIST_INIT_DUP; + struct strbuf parent_pattern = STRBUF_INIT; + + hashmap_for_each_entry(&pl->parent_hashmap, &iter, pe, ent) { + if (hashmap_get_entry(&pl->recursive_hashmap, pe, ent, NULL)) + continue; + + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, + &parent_pattern)) + string_list_insert(&sl, pe->pattern); + } + + string_list_sort(&sl); + string_list_remove_duplicates(&sl, 0); + + fprintf(fp, "/*\n!/*/\n"); + + for (i = 0; i < sl.nr; i++) { + char *pattern = escaped_pattern(sl.items[i].string); + + if (strlen(pattern)) + fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern); + free(pattern); + } + + string_list_clear(&sl, 0); + + hashmap_for_each_entry(&pl->recursive_hashmap, &iter, pe, ent) { + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, + &parent_pattern)) + string_list_insert(&sl, pe->pattern); + } + + strbuf_release(&parent_pattern); + + string_list_sort(&sl); + string_list_remove_duplicates(&sl, 0); + + for (i = 0; i < sl.nr; i++) { + char *pattern = escaped_pattern(sl.items[i].string); + fprintf(fp, "%s/\n", pattern); + free(pattern); + } +} + +static int write_patterns_and_update(struct pattern_list *pl) +{ + char *sparse_filename; + FILE *fp; + int fd; + struct lock_file lk = LOCK_INIT; + int result; + + sparse_filename = get_sparse_checkout_filename(); + + if (safe_create_leading_directories(sparse_filename)) + die(_("failed to create directory for sparse-checkout file")); + + fd = hold_lock_file_for_update(&lk, sparse_filename, + LOCK_DIE_ON_ERROR); + + result = update_working_directory(pl); + if (result) { + rollback_lock_file(&lk); + free(sparse_filename); + clear_pattern_list(pl); + update_working_directory(NULL); + return result; + } + + fp = xfdopen(fd, "w"); + + if (core_sparse_checkout_cone) + write_cone_to_file(fp, pl); + else + write_patterns_to_file(fp, pl); + + fflush(fp); + commit_lock_file(&lk); + + free(sparse_filename); + clear_pattern_list(pl); + + return 0; +} + +enum sparse_checkout_mode { + MODE_NO_PATTERNS = 0, + MODE_ALL_PATTERNS = 1, + MODE_CONE_PATTERNS = 2, +}; + +static int set_config(enum sparse_checkout_mode mode) +{ + const char *config_path; + + if (git_config_set_gently("extensions.worktreeConfig", "true")) { + error(_("failed to set extensions.worktreeConfig setting")); + return 1; + } + + config_path = git_path("config.worktree"); + git_config_set_in_file_gently(config_path, + "core.sparseCheckout", + mode ? "true" : NULL); + + git_config_set_in_file_gently(config_path, + "core.sparseCheckoutCone", + mode == MODE_CONE_PATTERNS ? "true" : NULL); + + return 0; +} + +static char const * const builtin_sparse_checkout_init_usage[] = { + N_("git sparse-checkout init [--cone]"), + NULL +}; + +static struct sparse_checkout_init_opts { + int cone_mode; +} init_opts; + +static int sparse_checkout_init(int argc, const char **argv) +{ + struct pattern_list pl; + char *sparse_filename; + int res; + struct object_id oid; + int mode; + struct strbuf pattern = STRBUF_INIT; + + static struct option builtin_sparse_checkout_init_options[] = { + OPT_BOOL(0, "cone", &init_opts.cone_mode, + N_("initialize the sparse-checkout in cone mode")), + OPT_END(), + }; + + repo_read_index(the_repository); + + argc = parse_options(argc, argv, NULL, + builtin_sparse_checkout_init_options, + builtin_sparse_checkout_init_usage, 0); + + if (init_opts.cone_mode) { + mode = MODE_CONE_PATTERNS; + core_sparse_checkout_cone = 1; + } else + mode = MODE_ALL_PATTERNS; + + if (set_config(mode)) + return 1; + + memset(&pl, 0, sizeof(pl)); + + sparse_filename = get_sparse_checkout_filename(); + res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL); + + /* If we already have a sparse-checkout file, use it. */ + if (res >= 0) { + free(sparse_filename); + core_apply_sparse_checkout = 1; + return update_working_directory(NULL); + } + + if (get_oid("HEAD", &oid)) { + FILE *fp; + + /* assume we are in a fresh repo, but update the sparse-checkout file */ + fp = xfopen(sparse_filename, "w"); + if (!fp) + die(_("failed to open '%s'"), sparse_filename); + + free(sparse_filename); + fprintf(fp, "/*\n!/*/\n"); + fclose(fp); + return 0; + } + + strbuf_addstr(&pattern, "/*"); + add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0); + strbuf_addstr(&pattern, "!/*/"); + add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0); + + return write_patterns_and_update(&pl); +} + +static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path) +{ + struct pattern_entry *e = xmalloc(sizeof(*e)); + e->patternlen = path->len; + e->pattern = strbuf_detach(path, NULL); + hashmap_entry_init(&e->ent, + ignore_case ? + strihash(e->pattern) : + strhash(e->pattern)); + + hashmap_add(&pl->recursive_hashmap, &e->ent); + + while (e->patternlen) { + char *slash = strrchr(e->pattern, '/'); + char *oldpattern = e->pattern; + size_t newlen; + + if (slash == e->pattern) + break; + + newlen = slash - e->pattern; + e = xmalloc(sizeof(struct pattern_entry)); + e->patternlen = newlen; + e->pattern = xstrndup(oldpattern, newlen); + hashmap_entry_init(&e->ent, + ignore_case ? + strihash(e->pattern) : + strhash(e->pattern)); + + if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL)) + hashmap_add(&pl->parent_hashmap, &e->ent); + } +} + +static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) +{ + strbuf_trim(line); + + strbuf_trim_trailing_dir_sep(line); + + if (strbuf_normalize_path(line)) + die(_("could not normalize path %s"), line->buf); + + if (!line->len) + return; + + if (line->buf[0] != '/') + strbuf_insertstr(line, 0, "/"); + + insert_recursive_pattern(pl, line); +} + +static char const * const builtin_sparse_checkout_set_usage[] = { + N_("git sparse-checkout (set|add) (--stdin | <patterns>)"), + NULL +}; + +static struct sparse_checkout_set_opts { + int use_stdin; +} set_opts; + +static void add_patterns_from_input(struct pattern_list *pl, + int argc, const char **argv) +{ + int i; + if (core_sparse_checkout_cone) { + struct strbuf line = STRBUF_INIT; + + hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0); + hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0); + pl->use_cone_patterns = 1; + + if (set_opts.use_stdin) { + struct strbuf unquoted = STRBUF_INIT; + while (!strbuf_getline(&line, stdin)) { + if (line.buf[0] == '"') { + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, line.buf, NULL)) + die(_("unable to unquote C-style string '%s'"), + line.buf); + + strbuf_swap(&unquoted, &line); + } + + strbuf_to_cone_pattern(&line, pl); + } + + strbuf_release(&unquoted); + } else { + for (i = 0; i < argc; i++) { + strbuf_setlen(&line, 0); + strbuf_addstr(&line, argv[i]); + strbuf_to_cone_pattern(&line, pl); + } + } + } else { + if (set_opts.use_stdin) { + struct strbuf line = STRBUF_INIT; + + while (!strbuf_getline(&line, stdin)) { + size_t len; + char *buf = strbuf_detach(&line, &len); + add_pattern(buf, empty_base, 0, pl, 0); + } + } else { + for (i = 0; i < argc; i++) + add_pattern(argv[i], empty_base, 0, pl, 0); + } + } +} + +enum modify_type { + REPLACE, + ADD, +}; + +static void add_patterns_cone_mode(int argc, const char **argv, + struct pattern_list *pl) +{ + struct strbuf buffer = STRBUF_INIT; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct pattern_list existing; + char *sparse_filename = get_sparse_checkout_filename(); + + add_patterns_from_input(pl, argc, argv); + + memset(&existing, 0, sizeof(existing)); + existing.use_cone_patterns = core_sparse_checkout_cone; + + if (add_patterns_from_file_to_list(sparse_filename, "", 0, + &existing, NULL)) + die(_("unable to load existing sparse-checkout patterns")); + free(sparse_filename); + + hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) { + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, &buffer) || + !hashmap_contains_parent(&pl->parent_hashmap, + pe->pattern, &buffer)) { + strbuf_reset(&buffer); + strbuf_addstr(&buffer, pe->pattern); + insert_recursive_pattern(pl, &buffer); + } + } + + clear_pattern_list(&existing); + strbuf_release(&buffer); +} + +static void add_patterns_literal(int argc, const char **argv, + struct pattern_list *pl) +{ + char *sparse_filename = get_sparse_checkout_filename(); + if (add_patterns_from_file_to_list(sparse_filename, "", 0, + pl, NULL)) + die(_("unable to load existing sparse-checkout patterns")); + free(sparse_filename); + add_patterns_from_input(pl, argc, argv); +} + +static int modify_pattern_list(int argc, const char **argv, enum modify_type m) +{ + int result; + int changed_config = 0; + struct pattern_list pl; + memset(&pl, 0, sizeof(pl)); + + switch (m) { + case ADD: + if (core_sparse_checkout_cone) + add_patterns_cone_mode(argc, argv, &pl); + else + add_patterns_literal(argc, argv, &pl); + break; + + case REPLACE: + add_patterns_from_input(&pl, argc, argv); + break; + } + + if (!core_apply_sparse_checkout) { + set_config(MODE_ALL_PATTERNS); + core_apply_sparse_checkout = 1; + changed_config = 1; + } + + result = write_patterns_and_update(&pl); + + if (result && changed_config) + set_config(MODE_NO_PATTERNS); + + clear_pattern_list(&pl); + return result; +} + +static int sparse_checkout_set(int argc, const char **argv, const char *prefix, + enum modify_type m) +{ + static struct option builtin_sparse_checkout_set_options[] = { + OPT_BOOL(0, "stdin", &set_opts.use_stdin, + N_("read patterns from standard in")), + OPT_END(), + }; + + repo_read_index(the_repository); + + argc = parse_options(argc, argv, prefix, + builtin_sparse_checkout_set_options, + builtin_sparse_checkout_set_usage, + PARSE_OPT_KEEP_UNKNOWN); + + 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); + + memset(&pl, 0, sizeof(pl)); + hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0); + hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0); + pl.use_cone_patterns = 0; + core_apply_sparse_checkout = 1; + + strbuf_addstr(&match_all, "/*"); + add_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0); + + if (update_working_directory(&pl)) + die(_("error while refreshing working directory")); + + clear_pattern_list(&pl); + return set_config(MODE_NO_PATTERNS); +} + +int cmd_sparse_checkout(int argc, const char **argv, const char *prefix) +{ + static struct option builtin_sparse_checkout_options[] = { + OPT_END(), + }; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_sparse_checkout_usage, + builtin_sparse_checkout_options); + + argc = parse_options(argc, argv, prefix, + builtin_sparse_checkout_options, + builtin_sparse_checkout_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + git_config(git_default_config, NULL); + + if (argc > 0) { + if (!strcmp(argv[0], "list")) + return sparse_checkout_list(argc, argv); + if (!strcmp(argv[0], "init")) + return sparse_checkout_init(argc, argv); + if (!strcmp(argv[0], "set")) + 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); + } + + usage_with_options(builtin_sparse_checkout_usage, + builtin_sparse_checkout_options); +} diff --git a/builtin/stash.c b/builtin/stash.c new file mode 100644 index 0000000000..0c52a3b849 --- /dev/null +++ b/builtin/stash.c @@ -0,0 +1,1615 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS +#include "builtin.h" +#include "config.h" +#include "parse-options.h" +#include "refs.h" +#include "lockfile.h" +#include "cache-tree.h" +#include "unpack-trees.h" +#include "merge-recursive.h" +#include "argv-array.h" +#include "run-command.h" +#include "dir.h" +#include "rerere.h" +#include "revision.h" +#include "log-tree.h" +#include "diffcore.h" +#include "exec-cmd.h" + +#define INCLUDE_ALL_FILES 2 + +static const char * const git_stash_usage[] = { + N_("git stash list [<options>]"), + N_("git stash show [<options>] [<stash>]"), + N_("git stash drop [-q|--quiet] [<stash>]"), + N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"), + N_("git stash branch <branchname> [<stash>]"), + N_("git stash clear"), + N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" + " [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" + " [--] [<pathspec>...]]"), + N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + " [-u|--include-untracked] [-a|--all] [<message>]"), + NULL +}; + +static const char * const git_stash_list_usage[] = { + N_("git stash list [<options>]"), + NULL +}; + +static const char * const git_stash_show_usage[] = { + N_("git stash show [<options>] [<stash>]"), + NULL +}; + +static const char * const git_stash_drop_usage[] = { + N_("git stash drop [-q|--quiet] [<stash>]"), + NULL +}; + +static const char * const git_stash_pop_usage[] = { + N_("git stash pop [--index] [-q|--quiet] [<stash>]"), + NULL +}; + +static const char * const git_stash_apply_usage[] = { + N_("git stash apply [--index] [-q|--quiet] [<stash>]"), + NULL +}; + +static const char * const git_stash_branch_usage[] = { + N_("git stash branch <branchname> [<stash>]"), + NULL +}; + +static const char * const git_stash_clear_usage[] = { + N_("git stash clear"), + NULL +}; + +static const char * const git_stash_store_usage[] = { + N_("git stash store [-m|--message <message>] [-q|--quiet] <commit>"), + NULL +}; + +static const char * const git_stash_push_usage[] = { + N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" + " [--] [<pathspec>...]]"), + NULL +}; + +static const char * const git_stash_save_usage[] = { + N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + " [-u|--include-untracked] [-a|--all] [<message>]"), + NULL +}; + +static const char *ref_stash = "refs/stash"; +static struct strbuf stash_index_path = STRBUF_INIT; + +/* + * w_commit is set to the commit containing the working tree + * b_commit is set to the base commit + * i_commit is set to the commit containing the index tree + * u_commit is set to the commit containing the untracked files tree + * w_tree is set to the working tree + * b_tree is set to the base tree + * i_tree is set to the index tree + * u_tree is set to the untracked files tree + */ +struct stash_info { + struct object_id w_commit; + struct object_id b_commit; + struct object_id i_commit; + struct object_id u_commit; + struct object_id w_tree; + struct object_id b_tree; + struct object_id i_tree; + struct object_id u_tree; + struct strbuf revision; + int is_stash_ref; + int has_u; +}; + +static void free_stash_info(struct stash_info *info) +{ + strbuf_release(&info->revision); +} + +static void assert_stash_like(struct stash_info *info, const char *revision) +{ + if (get_oidf(&info->b_commit, "%s^1", revision) || + get_oidf(&info->w_tree, "%s:", revision) || + get_oidf(&info->b_tree, "%s^1:", revision) || + get_oidf(&info->i_tree, "%s^2:", revision)) + die(_("'%s' is not a stash-like commit"), revision); +} + +static int get_stash_info(struct stash_info *info, int argc, const char **argv) +{ + int ret; + char *end_of_rev; + char *expanded_ref; + const char *revision; + const char *commit = NULL; + struct object_id dummy; + struct strbuf symbolic = STRBUF_INIT; + + if (argc > 1) { + int i; + struct strbuf refs_msg = STRBUF_INIT; + + for (i = 0; i < argc; i++) + strbuf_addf(&refs_msg, " '%s'", argv[i]); + + fprintf_ln(stderr, _("Too many revisions specified:%s"), + refs_msg.buf); + strbuf_release(&refs_msg); + + return -1; + } + + if (argc == 1) + commit = argv[0]; + + strbuf_init(&info->revision, 0); + if (!commit) { + if (!ref_exists(ref_stash)) { + free_stash_info(info); + fprintf_ln(stderr, _("No stash entries found.")); + return -1; + } + + strbuf_addf(&info->revision, "%s@{0}", ref_stash); + } else if (strspn(commit, "0123456789") == strlen(commit)) { + strbuf_addf(&info->revision, "%s@{%s}", ref_stash, commit); + } else { + strbuf_addstr(&info->revision, commit); + } + + revision = info->revision.buf; + + if (get_oid(revision, &info->w_commit)) { + error(_("%s is not a valid reference"), revision); + free_stash_info(info); + return -1; + } + + assert_stash_like(info, revision); + + info->has_u = !get_oidf(&info->u_tree, "%s^3:", revision); + + end_of_rev = strchrnul(revision, '@'); + strbuf_add(&symbolic, revision, end_of_rev - revision); + + ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref); + strbuf_release(&symbolic); + switch (ret) { + case 0: /* Not found, but valid ref */ + info->is_stash_ref = 0; + break; + case 1: + info->is_stash_ref = !strcmp(expanded_ref, ref_stash); + break; + default: /* Invalid or ambiguous */ + free_stash_info(info); + } + + free(expanded_ref); + return !(ret == 0 || ret == 1); +} + +static int do_clear_stash(void) +{ + struct object_id obj; + if (get_oid(ref_stash, &obj)) + return 0; + + return delete_ref(NULL, ref_stash, &obj, 0); +} + +static int clear_stash(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_clear_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (argc) + return error(_("git stash clear with parameters is " + "unimplemented")); + + return do_clear_stash(); +} + +static int reset_tree(struct object_id *i_tree, int update, int reset) +{ + int nr_trees = 1; + struct unpack_trees_options opts; + struct tree_desc t[MAX_UNPACK_TREES]; + struct tree *tree; + struct lock_file lock_file = LOCK_INIT; + + read_cache_preload(NULL); + if (refresh_cache(REFRESH_QUIET)) + return -1; + + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + + memset(&opts, 0, sizeof(opts)); + + tree = parse_tree_indirect(i_tree); + if (parse_tree(tree)) + return -1; + + init_tree_desc(t, tree->buffer, tree->size); + + opts.head_idx = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.merge = 1; + opts.reset = reset; + opts.update = update; + opts.fn = oneway_merge; + + if (unpack_trees(nr_trees, t, &opts)) + return -1; + + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + return error(_("unable to write new index file")); + + return 0; +} + +static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) +{ + struct child_process cp = CHILD_PROCESS_INIT; + const char *w_commit_hex = oid_to_hex(w_commit); + + /* + * Diff-tree would not be very hard to replace with a native function, + * however it should be done together with apply_cached. + */ + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL); + argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex); + + return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); +} + +static int apply_cached(struct strbuf *out) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * Apply currently only reads either from stdin or a file, thus + * apply_all_patches would have to be updated to optionally take a + * buffer. + */ + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "apply", "--cached", NULL); + return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); +} + +static int reset_head(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * Reset is overall quite simple, however there is no current public + * API for resetting. + */ + cp.git_cmd = 1; + argv_array_push(&cp.args, "reset"); + + return run_command(&cp); +} + +static void add_diff_to_buf(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + + for (i = 0; i < q->nr; i++) { + strbuf_addstr(data, q->queue[i]->one->path); + + /* NUL-terminate: will be fed to update-index -z */ + strbuf_addch(data, '\0'); + } +} + +static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) +{ + struct child_process cp = CHILD_PROCESS_INIT; + const char *c_tree_hex = oid_to_hex(c_tree); + + /* + * diff-index is very similar to diff-tree above, and should be + * converted together with update_index. + */ + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only", + "--diff-filter=A", NULL); + argv_array_push(&cp.args, c_tree_hex); + return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); +} + +static int update_index(struct strbuf *out) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * Update-index is very complicated and may need to have a public + * function exposed in order to remove this forking. + */ + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL); + return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); +} + +static int restore_untracked(struct object_id *u_tree) +{ + int res; + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * We need to run restore files from a given index, but without + * affecting the current index, so we use GIT_INDEX_FILE with + * run_command to fork processes that will not interfere. + */ + cp.git_cmd = 1; + argv_array_push(&cp.args, "read-tree"); + argv_array_push(&cp.args, oid_to_hex(u_tree)); + argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + if (run_command(&cp)) { + remove_path(stash_index_path.buf); + return -1; + } + + child_process_init(&cp); + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "checkout-index", "--all", NULL); + argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + + res = run_command(&cp); + remove_path(stash_index_path.buf); + return res; +} + +static int do_apply_stash(const char *prefix, struct stash_info *info, + int index, int quiet) +{ + int ret; + int has_index = index; + struct merge_options o; + struct object_id c_tree; + struct object_id index_tree; + struct commit *result; + const struct object_id *bases[1]; + + read_cache_preload(NULL); + if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) + return -1; + + if (write_cache_as_tree(&c_tree, 0, NULL)) + return error(_("cannot apply a stash in the middle of a merge")); + + if (index) { + if (oideq(&info->b_tree, &info->i_tree) || + oideq(&c_tree, &info->i_tree)) { + has_index = 0; + } else { + struct strbuf out = STRBUF_INIT; + + if (diff_tree_binary(&out, &info->w_commit)) { + strbuf_release(&out); + return error(_("could not generate diff %s^!."), + oid_to_hex(&info->w_commit)); + } + + ret = apply_cached(&out); + strbuf_release(&out); + if (ret) + return error(_("conflicts in index." + "Try without --index.")); + + discard_cache(); + read_cache(); + if (write_cache_as_tree(&index_tree, 0, NULL)) + return error(_("could not save index tree")); + + reset_head(); + discard_cache(); + read_cache(); + } + } + + if (info->has_u && restore_untracked(&info->u_tree)) + return error(_("could not restore untracked files from stash")); + + init_merge_options(&o, the_repository); + + o.branch1 = "Updated upstream"; + o.branch2 = "Stashed changes"; + + if (oideq(&info->b_tree, &c_tree)) + o.branch1 = "Version stash was based on"; + + if (quiet) + o.verbosity = 0; + + if (o.verbosity >= 3) + printf_ln(_("Merging %s with %s"), o.branch1, o.branch2); + + bases[0] = &info->b_tree; + + ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases, + &result); + if (ret) { + rerere(0); + + if (index) + fprintf_ln(stderr, _("Index was not unstashed.")); + + return ret; + } + + if (has_index) { + if (reset_tree(&index_tree, 0, 0)) + return -1; + } else { + struct strbuf out = STRBUF_INIT; + + if (get_newly_staged(&out, &c_tree)) { + strbuf_release(&out); + return -1; + } + + if (reset_tree(&c_tree, 0, 1)) { + strbuf_release(&out); + return -1; + } + + ret = update_index(&out); + strbuf_release(&out); + if (ret) + return -1; + + /* read back the result of update_index() back from the disk */ + discard_cache(); + read_cache(); + } + + if (!quiet) { + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * Status is quite simple and could be replaced with calls to + * wt_status in the future, but it adds complexities which may + * require more tests. + */ + cp.git_cmd = 1; + cp.dir = prefix; + argv_array_pushf(&cp.env_array, GIT_WORK_TREE_ENVIRONMENT"=%s", + absolute_path(get_git_work_tree())); + argv_array_pushf(&cp.env_array, GIT_DIR_ENVIRONMENT"=%s", + absolute_path(get_git_dir())); + argv_array_push(&cp.args, "status"); + run_command(&cp); + } + + return 0; +} + +static int apply_stash(int argc, const char **argv, const char *prefix) +{ + int ret; + int quiet = 0; + int index = 0; + struct stash_info info; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_BOOL(0, "index", &index, + N_("attempt to recreate the index")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_apply_usage, 0); + + if (get_stash_info(&info, argc, argv)) + return -1; + + ret = do_apply_stash(prefix, &info, index, quiet); + free_stash_info(&info); + return ret; +} + +static int do_drop_stash(struct stash_info *info, int quiet) +{ + int ret; + struct child_process cp_reflog = CHILD_PROCESS_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * reflog does not provide a simple function for deleting refs. One will + * need to be added to avoid implementing too much reflog code here + */ + + cp_reflog.git_cmd = 1; + argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref", + "--rewrite", NULL); + argv_array_push(&cp_reflog.args, info->revision.buf); + ret = run_command(&cp_reflog); + if (!ret) { + if (!quiet) + printf_ln(_("Dropped %s (%s)"), info->revision.buf, + oid_to_hex(&info->w_commit)); + } else { + return error(_("%s: Could not drop stash entry"), + info->revision.buf); + } + + /* + * This could easily be replaced by get_oid, but currently it will throw + * a fatal error when a reflog is empty, which we can not recover from. + */ + cp.git_cmd = 1; + /* Even though --quiet is specified, rev-parse still outputs the hash */ + cp.no_stdout = 1; + argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL); + argv_array_pushf(&cp.args, "%s@{0}", ref_stash); + ret = run_command(&cp); + + /* do_clear_stash if we just dropped the last stash entry */ + if (ret) + do_clear_stash(); + + return 0; +} + +static void assert_stash_ref(struct stash_info *info) +{ + if (!info->is_stash_ref) { + error(_("'%s' is not a stash reference"), info->revision.buf); + free_stash_info(info); + exit(1); + } +} + +static int drop_stash(int argc, const char **argv, const char *prefix) +{ + int ret; + int quiet = 0; + struct stash_info info; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_drop_usage, 0); + + if (get_stash_info(&info, argc, argv)) + return -1; + + assert_stash_ref(&info); + + ret = do_drop_stash(&info, quiet); + free_stash_info(&info); + return ret; +} + +static int pop_stash(int argc, const char **argv, const char *prefix) +{ + int ret; + int index = 0; + int quiet = 0; + struct stash_info info; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_BOOL(0, "index", &index, + N_("attempt to recreate the index")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_pop_usage, 0); + + if (get_stash_info(&info, argc, argv)) + return -1; + + assert_stash_ref(&info); + if ((ret = do_apply_stash(prefix, &info, index, quiet))) + printf_ln(_("The stash entry is kept in case " + "you need it again.")); + else + ret = do_drop_stash(&info, quiet); + + free_stash_info(&info); + return ret; +} + +static int branch_stash(int argc, const char **argv, const char *prefix) +{ + int ret; + const char *branch = NULL; + struct stash_info info; + struct child_process cp = CHILD_PROCESS_INIT; + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_branch_usage, 0); + + if (!argc) { + fprintf_ln(stderr, _("No branch name specified")); + return -1; + } + + branch = argv[0]; + + if (get_stash_info(&info, argc - 1, argv + 1)) + return -1; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "checkout", "-b", NULL); + argv_array_push(&cp.args, branch); + argv_array_push(&cp.args, oid_to_hex(&info.b_commit)); + ret = run_command(&cp); + if (!ret) + ret = do_apply_stash(prefix, &info, 1, 0); + if (!ret && info.is_stash_ref) + ret = do_drop_stash(&info, 0); + + free_stash_info(&info); + + return ret; +} + +static int list_stash(int argc, const char **argv, const char *prefix) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_list_usage, + PARSE_OPT_KEEP_UNKNOWN); + + if (!ref_exists(ref_stash)) + return 0; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "log", "--format=%gd: %gs", "-g", + "--first-parent", "-m", NULL); + argv_array_pushv(&cp.args, argv); + argv_array_push(&cp.args, ref_stash); + argv_array_push(&cp.args, "--"); + return run_command(&cp); +} + +static int show_stat = 1; +static int show_patch; +static int use_legacy_stash; + +static int git_stash_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "stash.showstat")) { + show_stat = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "stash.showpatch")) { + show_patch = git_config_bool(var, value); + return 0; + } + 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) +{ + int i; + int ret = 0; + struct stash_info info; + struct rev_info rev; + struct argv_array stash_args = ARGV_ARRAY_INIT; + struct argv_array revision_args = ARGV_ARRAY_INIT; + struct option options[] = { + OPT_END() + }; + + init_diff_ui_defaults(); + git_config(git_diff_ui_config, NULL); + init_revisions(&rev, prefix); + + argv_array_push(&revision_args, argv[0]); + for (i = 1; i < argc; i++) { + if (argv[i][0] != '-') + argv_array_push(&stash_args, argv[i]); + else + argv_array_push(&revision_args, argv[i]); + } + + ret = get_stash_info(&info, stash_args.argc, stash_args.argv); + argv_array_clear(&stash_args); + if (ret) + return -1; + + /* + * The config settings are applied only if there are not passed + * any options. + */ + if (revision_args.argc == 1) { + if (show_stat) + rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT; + + if (show_patch) + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + + if (!show_stat && !show_patch) { + free_stash_info(&info); + return 0; + } + } + + argc = setup_revisions(revision_args.argc, revision_args.argv, &rev, NULL); + if (argc > 1) { + free_stash_info(&info); + usage_with_options(git_stash_show_usage, options); + } + if (!rev.diffopt.output_format) { + rev.diffopt.output_format = DIFF_FORMAT_PATCH; + diff_setup_done(&rev.diffopt); + } + + rev.diffopt.flags.recursive = 1; + setup_diff_pager(&rev.diffopt); + diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); + log_tree_diff_flush(&rev); + + free_stash_info(&info); + return diff_result_code(&rev.diffopt, 0); +} + +static int do_store_stash(const struct object_id *w_commit, const char *stash_msg, + int quiet) +{ + if (!stash_msg) + stash_msg = "Created via \"git stash store\"."; + + if (update_ref(stash_msg, ref_stash, w_commit, NULL, + REF_FORCE_CREATE_REFLOG, + quiet ? UPDATE_REFS_QUIET_ON_ERR : + UPDATE_REFS_MSG_ON_ERR)) { + if (!quiet) { + fprintf_ln(stderr, _("Cannot update %s with %s"), + ref_stash, oid_to_hex(w_commit)); + } + return -1; + } + + return 0; +} + +static int store_stash(int argc, const char **argv, const char *prefix) +{ + int quiet = 0; + const char *stash_msg = NULL; + struct object_id obj; + struct object_context dummy; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet")), + OPT_STRING('m', "message", &stash_msg, "message", + N_("stash message")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_store_usage, + PARSE_OPT_KEEP_UNKNOWN); + + if (argc != 1) { + if (!quiet) + fprintf_ln(stderr, _("\"git stash store\" requires one " + "<commit> argument")); + return -1; + } + + if (get_oid_with_context(the_repository, + argv[0], quiet ? GET_OID_QUIETLY : 0, &obj, + &dummy)) { + if (!quiet) + fprintf_ln(stderr, _("Cannot update %s with %s"), + ref_stash, argv[0]); + return -1; + } + + return do_store_stash(&obj, stash_msg, quiet); +} + +static void add_pathspecs(struct argv_array *args, + const struct pathspec *ps) { + int i; + + for (i = 0; i < ps->nr; i++) + argv_array_push(args, ps->items[i].original); +} + +/* + * `untracked_files` will be filled with the names of untracked files. + * The return value is: + * + * = 0 if there are not any untracked files + * > 0 if there are untracked files + */ +static int get_untracked_files(const struct pathspec *ps, int include_untracked, + struct strbuf *untracked_files) +{ + int i; + int found = 0; + struct dir_struct dir; + + memset(&dir, 0, sizeof(dir)); + if (include_untracked != INCLUDE_ALL_FILES) + setup_standard_excludes(&dir); + + fill_directory(&dir, the_repository->index, ps); + for (i = 0; i < dir.nr; i++) { + struct dir_entry *ent = dir.entries[i]; + found++; + strbuf_addstr(untracked_files, ent->name); + /* NUL-terminate: will be fed to update-index -z */ + strbuf_addch(untracked_files, '\0'); + free(ent); + } + + free(dir.entries); + free(dir.ignored); + clear_directory(&dir); + return found; +} + +/* + * The return value of `check_changes_tracked_files()` can be: + * + * < 0 if there was an error + * = 0 if there are no changes. + * > 0 if there are changes. + */ +static int check_changes_tracked_files(const struct pathspec *ps) +{ + int result; + struct rev_info rev; + struct object_id dummy; + int ret = 0; + + /* No initial commit. */ + if (get_oid("HEAD", &dummy)) + return -1; + + if (read_cache() < 0) + return -1; + + init_revisions(&rev, NULL); + copy_pathspec(&rev.prune_data, ps); + + rev.diffopt.flags.quick = 1; + rev.diffopt.flags.ignore_submodules = 1; + rev.abbrev = 0; + + add_head_to_pending(&rev); + diff_setup_done(&rev.diffopt); + + result = run_diff_index(&rev, 1); + if (diff_result_code(&rev.diffopt, result)) { + ret = 1; + goto done; + } + + object_array_clear(&rev.pending); + result = run_diff_files(&rev, 0); + if (diff_result_code(&rev.diffopt, result)) { + ret = 1; + goto done; + } + +done: + clear_pathspec(&rev.prune_data); + return ret; +} + +/* + * The function will fill `untracked_files` with the names of untracked files + * It will return 1 if there were any changes and 0 if there were not. + */ +static int check_changes(const struct pathspec *ps, int include_untracked, + struct strbuf *untracked_files) +{ + int ret = 0; + if (check_changes_tracked_files(ps)) + ret = 1; + + if (include_untracked && get_untracked_files(ps, include_untracked, + untracked_files)) + ret = 1; + + return ret; +} + +static int save_untracked_files(struct stash_info *info, struct strbuf *msg, + struct strbuf files) +{ + int ret = 0; + struct strbuf untracked_msg = STRBUF_INIT; + struct child_process cp_upd_index = CHILD_PROCESS_INIT; + struct index_state istate = { NULL }; + + cp_upd_index.git_cmd = 1; + argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", + "--remove", "--stdin", NULL); + argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + + strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); + if (pipe_command(&cp_upd_index, files.buf, files.len, NULL, 0, + NULL, 0)) { + ret = -1; + goto done; + } + + if (write_index_as_tree(&info->u_tree, &istate, stash_index_path.buf, 0, + NULL)) { + ret = -1; + goto done; + } + + if (commit_tree(untracked_msg.buf, untracked_msg.len, + &info->u_tree, NULL, &info->u_commit, NULL, NULL)) { + ret = -1; + goto done; + } + +done: + discard_index(&istate); + strbuf_release(&untracked_msg); + remove_path(stash_index_path.buf); + return ret; +} + +static int stash_patch(struct stash_info *info, const struct pathspec *ps, + struct strbuf *out_patch, int quiet) +{ + int ret = 0; + struct child_process cp_read_tree = CHILD_PROCESS_INIT; + struct child_process cp_diff_tree = CHILD_PROCESS_INIT; + struct index_state istate = { NULL }; + char *old_index_env = NULL, *old_repo_index_file; + + remove_path(stash_index_path.buf); + + cp_read_tree.git_cmd = 1; + argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL); + argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + if (run_command(&cp_read_tree)) { + ret = -1; + goto done; + } + + /* Find out what the user wants. */ + old_repo_index_file = the_repository->index_file; + the_repository->index_file = stash_index_path.buf; + old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); + setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); + + ret = run_add_interactive(NULL, "--patch=stash", ps); + + the_repository->index_file = old_repo_index_file; + if (old_index_env && *old_index_env) + setenv(INDEX_ENVIRONMENT, old_index_env, 1); + else + unsetenv(INDEX_ENVIRONMENT); + FREE_AND_NULL(old_index_env); + + /* State of the working tree. */ + if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, + NULL)) { + ret = -1; + goto done; + } + + cp_diff_tree.git_cmd = 1; + argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD", + oid_to_hex(&info->w_tree), "--", NULL); + if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { + ret = -1; + goto done; + } + + if (!out_patch->len) { + if (!quiet) + fprintf_ln(stderr, _("No changes selected")); + ret = 1; + } + +done: + discard_index(&istate); + remove_path(stash_index_path.buf); + return ret; +} + +static int stash_working_tree(struct stash_info *info, const struct pathspec *ps) +{ + int ret = 0; + struct rev_info rev; + struct child_process cp_upd_index = CHILD_PROCESS_INIT; + struct strbuf diff_output = STRBUF_INIT; + struct index_state istate = { NULL }; + + init_revisions(&rev, NULL); + copy_pathspec(&rev.prune_data, ps); + + set_alternate_index_output(stash_index_path.buf); + if (reset_tree(&info->i_tree, 0, 0)) { + ret = -1; + goto done; + } + set_alternate_index_output(NULL); + + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = add_diff_to_buf; + rev.diffopt.format_callback_data = &diff_output; + + if (read_cache_preload(&rev.diffopt.pathspec) < 0) { + ret = -1; + goto done; + } + + add_pending_object(&rev, parse_object(the_repository, &info->b_commit), + ""); + if (run_diff_index(&rev, 0)) { + ret = -1; + goto done; + } + + cp_upd_index.git_cmd = 1; + argv_array_pushl(&cp_upd_index.args, "update-index", + "--ignore-skip-worktree-entries", + "-z", "--add", "--remove", "--stdin", NULL); + argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + + if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len, + NULL, 0, NULL, 0)) { + ret = -1; + goto done; + } + + if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, + NULL)) { + ret = -1; + goto done; + } + +done: + discard_index(&istate); + UNLEAK(rev); + object_array_clear(&rev.pending); + clear_pathspec(&rev.prune_data); + strbuf_release(&diff_output); + remove_path(stash_index_path.buf); + return ret; +} + +static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf, + int include_untracked, int patch_mode, + struct stash_info *info, struct strbuf *patch, + int quiet) +{ + int ret = 0; + int flags = 0; + int untracked_commit_option = 0; + const char *head_short_sha1 = NULL; + const char *branch_ref = NULL; + const char *branch_name = "(no branch)"; + struct commit *head_commit = NULL; + struct commit_list *parents = NULL; + struct strbuf msg = STRBUF_INIT; + struct strbuf commit_tree_label = STRBUF_INIT; + struct strbuf untracked_files = STRBUF_INIT; + + prepare_fallback_ident("git stash", "git@stash"); + + read_cache_preload(NULL); + if (refresh_and_write_cache(REFRESH_QUIET, 0, 0) < 0) { + ret = -1; + goto done; + } + + if (get_oid("HEAD", &info->b_commit)) { + if (!quiet) + fprintf_ln(stderr, _("You do not have " + "the initial commit yet")); + ret = -1; + goto done; + } else { + head_commit = lookup_commit(the_repository, &info->b_commit); + } + + if (!check_changes(ps, include_untracked, &untracked_files)) { + ret = 1; + goto done; + } + + branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags); + if (flags & REF_ISSYMREF) + branch_name = strrchr(branch_ref, '/') + 1; + head_short_sha1 = find_unique_abbrev(&head_commit->object.oid, + DEFAULT_ABBREV); + strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1); + pp_commit_easy(CMIT_FMT_ONELINE, head_commit, &msg); + + strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf); + commit_list_insert(head_commit, &parents); + if (write_cache_as_tree(&info->i_tree, 0, NULL) || + commit_tree(commit_tree_label.buf, commit_tree_label.len, + &info->i_tree, parents, &info->i_commit, NULL, NULL)) { + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current " + "index state")); + ret = -1; + goto done; + } + + if (include_untracked) { + if (save_untracked_files(info, &msg, untracked_files)) { + if (!quiet) + fprintf_ln(stderr, _("Cannot save " + "the untracked files")); + ret = -1; + goto done; + } + untracked_commit_option = 1; + } + if (patch_mode) { + ret = stash_patch(info, ps, patch, quiet); + if (ret < 0) { + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current " + "worktree state")); + goto done; + } else if (ret > 0) { + goto done; + } + } else { + if (stash_working_tree(info, ps)) { + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current " + "worktree state")); + ret = -1; + goto done; + } + } + + if (!stash_msg_buf->len) + strbuf_addf(stash_msg_buf, "WIP on %s", msg.buf); + else + strbuf_insertf(stash_msg_buf, 0, "On %s: ", branch_name); + + /* + * `parents` will be empty after calling `commit_tree()`, so there is + * no need to call `free_commit_list()` + */ + parents = NULL; + if (untracked_commit_option) + commit_list_insert(lookup_commit(the_repository, + &info->u_commit), + &parents); + commit_list_insert(lookup_commit(the_repository, &info->i_commit), + &parents); + commit_list_insert(head_commit, &parents); + + if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &info->w_tree, + parents, &info->w_commit, NULL, NULL)) { + if (!quiet) + fprintf_ln(stderr, _("Cannot record " + "working tree state")); + ret = -1; + goto done; + } + +done: + strbuf_release(&commit_tree_label); + strbuf_release(&msg); + strbuf_release(&untracked_files); + return ret; +} + +static int create_stash(int argc, const char **argv, const char *prefix) +{ + int ret = 0; + struct strbuf stash_msg_buf = STRBUF_INIT; + struct stash_info info; + struct pathspec ps; + + /* Starting with argv[1], since argv[0] is "create" */ + strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' '); + + memset(&ps, 0, sizeof(ps)); + if (!check_changes_tracked_files(&ps)) + return 0; + + ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info, + NULL, 0); + if (!ret) + printf_ln("%s", oid_to_hex(&info.w_commit)); + + strbuf_release(&stash_msg_buf); + return ret; +} + +static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet, + int keep_index, int patch_mode, int include_untracked) +{ + int ret = 0; + struct stash_info info; + struct strbuf patch = STRBUF_INIT; + struct strbuf stash_msg_buf = STRBUF_INIT; + struct strbuf untracked_files = STRBUF_INIT; + + if (patch_mode && keep_index == -1) + keep_index = 1; + + if (patch_mode && include_untracked) { + fprintf_ln(stderr, _("Can't use --patch and --include-untracked" + " or --all at the same time")); + ret = -1; + goto done; + } + + read_cache_preload(NULL); + if (!include_untracked && ps->nr) { + int i; + char *ps_matched = xcalloc(ps->nr, 1); + + for (i = 0; i < active_nr; i++) + ce_path_match(&the_index, active_cache[i], ps, + ps_matched); + + if (report_path_error(ps_matched, ps)) { + fprintf_ln(stderr, _("Did you forget to 'git add'?")); + ret = -1; + free(ps_matched); + goto done; + } + free(ps_matched); + } + + if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) { + ret = -1; + goto done; + } + + if (!check_changes(ps, include_untracked, &untracked_files)) { + if (!quiet) + printf_ln(_("No local changes to save")); + goto done; + } + + if (!reflog_exists(ref_stash) && do_clear_stash()) { + ret = -1; + if (!quiet) + fprintf_ln(stderr, _("Cannot initialize stash")); + goto done; + } + + if (stash_msg) + strbuf_addstr(&stash_msg_buf, stash_msg); + if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, + &info, &patch, quiet)) { + ret = -1; + goto done; + } + + if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) { + ret = -1; + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current status")); + goto done; + } + + if (!quiet) + printf_ln(_("Saved working directory and index state %s"), + stash_msg_buf.buf); + + if (!patch_mode) { + if (include_untracked && !ps->nr) { + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "clean", "--force", + "--quiet", "-d", NULL); + if (include_untracked == INCLUDE_ALL_FILES) + argv_array_push(&cp.args, "-x"); + if (run_command(&cp)) { + ret = -1; + goto done; + } + } + discard_cache(); + if (ps->nr) { + struct child_process cp_add = CHILD_PROCESS_INIT; + struct child_process cp_diff = CHILD_PROCESS_INIT; + struct child_process cp_apply = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + + cp_add.git_cmd = 1; + argv_array_push(&cp_add.args, "add"); + if (!include_untracked) + argv_array_push(&cp_add.args, "-u"); + if (include_untracked == INCLUDE_ALL_FILES) + argv_array_push(&cp_add.args, "--force"); + argv_array_push(&cp_add.args, "--"); + add_pathspecs(&cp_add.args, ps); + if (run_command(&cp_add)) { + ret = -1; + goto done; + } + + cp_diff.git_cmd = 1; + argv_array_pushl(&cp_diff.args, "diff-index", "-p", + "--cached", "--binary", "HEAD", "--", + NULL); + add_pathspecs(&cp_diff.args, ps); + if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) { + ret = -1; + goto done; + } + + cp_apply.git_cmd = 1; + argv_array_pushl(&cp_apply.args, "apply", "--index", + "-R", NULL); + if (pipe_command(&cp_apply, out.buf, out.len, NULL, 0, + NULL, 0)) { + ret = -1; + goto done; + } + } else { + struct child_process cp = CHILD_PROCESS_INIT; + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "reset", "--hard", "-q", + "--no-recurse-submodules", NULL); + if (run_command(&cp)) { + ret = -1; + goto done; + } + } + + if (keep_index == 1 && !is_null_oid(&info.i_tree)) { + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "checkout", "--no-overlay", + oid_to_hex(&info.i_tree), "--", NULL); + if (!ps->nr) + argv_array_push(&cp.args, ":/"); + else + add_pathspecs(&cp.args, ps); + if (run_command(&cp)) { + ret = -1; + goto done; + } + } + goto done; + } else { + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "apply", "-R", NULL); + + if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) { + if (!quiet) + fprintf_ln(stderr, _("Cannot remove " + "worktree changes")); + ret = -1; + goto done; + } + + if (keep_index < 1) { + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "reset", "-q", "--", NULL); + add_pathspecs(&cp.args, ps); + if (run_command(&cp)) { + ret = -1; + goto done; + } + } + goto done; + } + +done: + strbuf_release(&stash_msg_buf); + return ret; +} + +static int push_stash(int argc, const char **argv, const char *prefix, + int push_assumed) +{ + int force_assume = 0; + int keep_index = -1; + int patch_mode = 0; + int include_untracked = 0; + int quiet = 0; + int pathspec_file_nul = 0; + const char *stash_msg = NULL; + const char *pathspec_from_file = NULL; + struct pathspec ps; + struct option options[] = { + OPT_BOOL('k', "keep-index", &keep_index, + N_("keep index")), + OPT_BOOL('p', "patch", &patch_mode, + N_("stash in patch mode")), + OPT__QUIET(&quiet, N_("quiet mode")), + OPT_BOOL('u', "include-untracked", &include_untracked, + N_("include untracked files in stash")), + OPT_SET_INT('a', "all", &include_untracked, + N_("include ignore files"), 2), + OPT_STRING('m', "message", &stash_msg, N_("message"), + N_("stash message")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), + OPT_END() + }; + + if (argc) { + force_assume = !strcmp(argv[0], "-p"); + argc = parse_options(argc, argv, prefix, options, + git_stash_push_usage, + PARSE_OPT_KEEP_DASHDASH); + } + + if (argc) { + if (!strcmp(argv[0], "--")) { + argc--; + argv++; + } else if (push_assumed && !force_assume) { + die("subcommand wasn't specified; 'push' can't be assumed due to unexpected token '%s'", + argv[0]); + } + } + + parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN, + prefix, argv); + + if (pathspec_from_file) { + if (patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + + if (ps.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&ps, 0, + PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, + include_untracked); +} + +static int save_stash(int argc, const char **argv, const char *prefix) +{ + int keep_index = -1; + int patch_mode = 0; + int include_untracked = 0; + int quiet = 0; + int ret = 0; + const char *stash_msg = NULL; + struct pathspec ps; + struct strbuf stash_msg_buf = STRBUF_INIT; + struct option options[] = { + OPT_BOOL('k', "keep-index", &keep_index, + N_("keep index")), + OPT_BOOL('p', "patch", &patch_mode, + N_("stash in patch mode")), + OPT__QUIET(&quiet, N_("quiet mode")), + OPT_BOOL('u', "include-untracked", &include_untracked, + N_("include untracked files in stash")), + OPT_SET_INT('a', "all", &include_untracked, + N_("include ignore files"), 2), + OPT_STRING('m', "message", &stash_msg, "message", + N_("stash message")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_save_usage, + PARSE_OPT_KEEP_DASHDASH); + + if (argc) + stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' '); + + memset(&ps, 0, sizeof(ps)); + ret = do_push_stash(&ps, stash_msg, quiet, keep_index, + patch_mode, include_untracked); + + strbuf_release(&stash_msg_buf); + return ret; +} + +int cmd_stash(int argc, const char **argv, const char *prefix) +{ + pid_t pid = getpid(); + const char *index_file; + struct argv_array args = ARGV_ARRAY_INIT; + + struct option options[] = { + OPT_END() + }; + + git_config(git_stash_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); + + index_file = get_index_file(); + strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, + (uintmax_t)pid); + + if (!argc) + return !!push_stash(0, NULL, prefix, 0); + else if (!strcmp(argv[0], "apply")) + return !!apply_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "clear")) + return !!clear_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "drop")) + return !!drop_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "pop")) + return !!pop_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "branch")) + return !!branch_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "list")) + return !!list_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "show")) + return !!show_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "store")) + return !!store_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "create")) + return !!create_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "push")) + return !!push_stash(argc, argv, prefix, 0); + else if (!strcmp(argv[0], "save")) + return !!save_stash(argc, argv, prefix); + else if (*argv[0] != '-') + usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), + git_stash_usage, options); + + /* Assume 'stash push' */ + argv_array_push(&args, "push"); + argv_array_pushv(&args, argv); + return !!push_stash(args.argc, args.argv, prefix, 1); +} diff --git a/builtin/stripspace.c b/builtin/stripspace.c index bdf0328869..be33eb83c1 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -30,6 +30,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; enum stripspace_mode mode = STRIP_DEFAULT; + int nongit; const struct option options[] = { OPT_CMDMODE('s', "strip-comments", &mode, @@ -46,7 +47,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) usage_with_options(stripspace_usage, options); if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) { - setup_git_directory_gently(NULL); + setup_git_directory_gently(&nongit); git_config(git_default_config, NULL); } diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index a404df3ea4..46c03d2a12 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1,3 +1,4 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "repository.h" #include "cache.h" @@ -12,11 +13,14 @@ #include "run-command.h" #include "remote.h" #include "refs.h" +#include "refspec.h" #include "connect.h" #include "revision.h" #include "diffcore.h" #include "diff.h" #include "object-store.h" +#include "dir.h" +#include "advice.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -54,7 +58,7 @@ static char *get_default_remote(void) static int print_default_remote(int argc, const char **argv, const char *prefix) { - const char *remote; + char *remote; if (argc != 1) die(_("submodule--helper print-default-remote takes no arguments")); @@ -63,6 +67,7 @@ static int print_default_remote(int argc, const char **argv, const char *prefix) if (remote) printf("%s\n", remote); + free(remote); return 0; } @@ -329,7 +334,7 @@ static int module_list_compute(int argc, const char **argv, for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; - if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), + if (!match_pathspec(&the_index, pathspec, ce->name, ce_namelen(ce), 0, ps_matched, 1) || !S_ISGITLINK(ce->ce_mode)) continue; @@ -345,7 +350,7 @@ static int module_list_compute(int argc, const char **argv, i++; } - if (ps_matched && report_path_error(ps_matched, pathspec, prefix)) + if (ps_matched && report_path_error(ps_matched, pathspec)) result = -1; free(ps_matched); @@ -421,7 +426,7 @@ static int module_list(int argc, const char **argv, const char *prefix) const struct cache_entry *ce = list.entries[i]; if (ce_stage(ce)) - printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1)); + printf("%06o %s U\t", ce->ce_mode, oid_to_hex(&null_oid)); else printf("%06o %s %d\t", ce->ce_mode, oid_to_hex(&ce->oid), ce_stage(ce)); @@ -439,6 +444,170 @@ static void for_each_listed_submodule(const struct module_list *list, fn(list->entries[i], cb_data); } +struct foreach_cb { + int argc; + const char **argv; + const char *prefix; + int quiet; + int recursive; +}; +#define FOREACH_CB_INIT { 0 } + +static void runcommand_in_submodule_cb(const struct cache_entry *list_item, + void *cb_data) +{ + struct foreach_cb *info = cb_data; + const char *path = list_item->name; + const struct object_id *ce_oid = &list_item->oid; + + const struct submodule *sub; + struct child_process cp = CHILD_PROCESS_INIT; + char *displaypath; + + displaypath = get_submodule_displaypath(path, info->prefix); + + sub = submodule_from_path(the_repository, &null_oid, path); + + if (!sub) + die(_("No url found for submodule path '%s' in .gitmodules"), + displaypath); + + if (!is_submodule_populated_gently(path, NULL)) + goto cleanup; + + prepare_submodule_repo_env(&cp.env_array); + + /* + * For the purpose of executing <command> in the submodule, + * separate shell is used for the purpose of running the + * child process. + */ + cp.use_shell = 1; + cp.dir = path; + + /* + * NEEDSWORK: the command currently has access to the variables $name, + * $sm_path, $displaypath, $sha1 and $toplevel only when the command + * contains a single argument. This is done for maintaining a faithful + * translation from shell script. + */ + if (info->argc == 1) { + char *toplevel = xgetcwd(); + struct strbuf sb = STRBUF_INIT; + + argv_array_pushf(&cp.env_array, "name=%s", sub->name); + argv_array_pushf(&cp.env_array, "sm_path=%s", path); + argv_array_pushf(&cp.env_array, "displaypath=%s", displaypath); + argv_array_pushf(&cp.env_array, "sha1=%s", + oid_to_hex(ce_oid)); + argv_array_pushf(&cp.env_array, "toplevel=%s", toplevel); + + /* + * Since the path variable was accessible from the script + * before porting, it is also made available after porting. + * The environment variable "PATH" has a very special purpose + * on windows. And since environment variables are + * case-insensitive in windows, it interferes with the + * existing PATH variable. Hence, to avoid that, we expose + * path via the args argv_array and not via env_array. + */ + sq_quote_buf(&sb, path); + argv_array_pushf(&cp.args, "path=%s; %s", + sb.buf, info->argv[0]); + strbuf_release(&sb); + free(toplevel); + } else { + argv_array_pushv(&cp.args, info->argv); + } + + if (!info->quiet) + printf(_("Entering '%s'\n"), displaypath); + + if (info->argv[0] && run_command(&cp)) + die(_("run_command returned non-zero status for %s\n."), + displaypath); + + if (info->recursive) { + struct child_process cpr = CHILD_PROCESS_INIT; + + cpr.git_cmd = 1; + cpr.dir = path; + prepare_submodule_repo_env(&cpr.env_array); + + argv_array_pushl(&cpr.args, "--super-prefix", NULL); + argv_array_pushf(&cpr.args, "%s/", displaypath); + argv_array_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive", + NULL); + + if (info->quiet) + argv_array_push(&cpr.args, "--quiet"); + + argv_array_push(&cpr.args, "--"); + argv_array_pushv(&cpr.args, info->argv); + + if (run_command(&cpr)) + die(_("run_command returned non-zero status while " + "recursing in the nested submodules of %s\n."), + displaypath); + } + +cleanup: + free(displaypath); +} + +static int module_foreach(int argc, const char **argv, const char *prefix) +{ + struct foreach_cb info = FOREACH_CB_INIT; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + + struct option module_foreach_options[] = { + OPT__QUIET(&info.quiet, N_("Suppress output of entering each submodule command")), + OPT_BOOL(0, "recursive", &info.recursive, + N_("Recurse into nested submodules")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper foreach [--quiet] [--recursive] [--] <command>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_foreach_options, + git_submodule_helper_usage, 0); + + if (module_list_compute(0, NULL, prefix, &pathspec, &list) < 0) + return 1; + + info.argc = argc; + info.argv = argv; + info.prefix = prefix; + + for_each_listed_submodule(&list, runcommand_in_submodule_cb, &info); + + return 0; +} + +static char *compute_submodule_clone_url(const char *rel_url) +{ + char *remoteurl, *relurl; + char *remote = get_default_remote(); + struct strbuf remotesb = STRBUF_INIT; + + strbuf_addf(&remotesb, "remote.%s.url", remote); + if (git_config_get_string(remotesb.buf, &remoteurl)) { + warning(_("could not look up configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf); + remoteurl = xgetcwd(); + } + relurl = relative_url(remoteurl, rel_url, NULL); + + free(remote); + free(remoteurl); + strbuf_release(&remotesb); + + return relurl; +} + struct init_cb { const char *prefix; unsigned int flags; @@ -455,7 +624,7 @@ static void init_submodule(const char *path, const char *prefix, displaypath = get_submodule_displaypath(path, prefix); - sub = submodule_from_path(&null_oid, path); + sub = submodule_from_path(the_repository, &null_oid, path); if (!sub) die(_("No url found for submodule path '%s' in .gitmodules"), @@ -489,21 +658,9 @@ static void init_submodule(const char *path, const char *prefix, /* Possibly a url relative to parent */ if (starts_with_dot_dot_slash(url) || starts_with_dot_slash(url)) { - char *remoteurl, *relurl; - char *remote = get_default_remote(); - struct strbuf remotesb = STRBUF_INIT; - strbuf_addf(&remotesb, "remote.%s.url", remote); - free(remote); - - if (git_config_get_string(remotesb.buf, &remoteurl)) { - warning(_("could not lookup configuration '%s'. Assuming this repository is its own authoritative upstream."), remotesb.buf); - remoteurl = xgetcwd(); - } - relurl = relative_url(remoteurl, url, NULL); - strbuf_release(&remotesb); - free(remoteurl); - free(url); - url = relurl; + char *oldurl = url; + url = compute_submodule_clone_url(oldurl); + free(oldurl); } if (git_config_set_gently(sb.buf, url)) @@ -555,7 +712,7 @@ static int module_init(int argc, const char **argv, const char *prefix) }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper init [<path>]"), + N_("git submodule--helper init [<options>] [<path>]"), NULL }; @@ -596,8 +753,12 @@ static void print_status(unsigned int flags, char state, const char *path, printf("%c%s %s", state, oid_to_hex(oid), displaypath); - if (state == ' ' || state == '+') - printf(" (%s)", compute_rev_name(path, oid_to_hex(oid))); + if (state == ' ' || state == '+') { + const char *name = compute_rev_name(path, oid_to_hex(oid)); + + if (name) + printf(" (%s)", name); + } printf("\n"); } @@ -621,8 +782,10 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, struct argv_array diff_files_args = ARGV_ARRAY_INIT; struct rev_info rev; int diff_files_result; + struct strbuf buf = STRBUF_INIT; + const char *git_dir; - if (!submodule_from_path(&null_oid, path)) + if (!submodule_from_path(the_repository, &null_oid, path)) die(_("no submodule mapping found in .gitmodules for path '%s'"), path); @@ -633,17 +796,26 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, goto cleanup; } - if (!is_submodule_active(the_repository, path)) { + strbuf_addf(&buf, "%s/.git", path); + git_dir = read_gitfile(buf.buf); + if (!git_dir) + git_dir = buf.buf; + + if (!is_submodule_active(the_repository, path) || + !is_git_directory(git_dir)) { print_status(flags, '-', path, ce_oid, displaypath); + strbuf_release(&buf); goto cleanup; } + strbuf_release(&buf); argv_array_pushl(&diff_files_args, "diff-files", "--ignore-submodules=dirty", "--quiet", "--", path, NULL); git_config(git_diff_basic_config, NULL); - init_revisions(&rev, prefix); + + repo_init_revisions(the_repository, &rev, NULL); rev.abbrev = 0; diff_files_args.argc = setup_revisions(diff_files_args.argc, diff_files_args.argv, @@ -746,7 +918,7 @@ static int module_name(int argc, const char **argv, const char *prefix) if (argc != 2) usage(_("git submodule--helper name <path>")); - sub = submodule_from_path(&null_oid, argv[1]); + sub = submodule_from_path(the_repository, &null_oid, argv[1]); if (!sub) die(_("no submodule mapping found in .gitmodules for path '%s'"), @@ -777,7 +949,7 @@ static void sync_submodule(const char *path, const char *prefix, if (!is_submodule_active(the_repository, path)) return; - sub = submodule_from_path(&null_oid, path); + sub = submodule_from_path(the_repository, &null_oid, path); if (sub && sub->url) { if (starts_with_dot_dot_slash(sub->url) || @@ -875,7 +1047,6 @@ static void sync_submodule_cb(const struct cache_entry *list_item, void *cb_data { struct sync_cb *info = cb_data; sync_submodule(list_item->name, info->prefix, info->flags); - } static int module_sync(int argc, const char **argv, const char *prefix) @@ -930,7 +1101,7 @@ static void deinit_submodule(const char *path, const char *prefix, struct strbuf sb_config = STRBUF_INIT; char *sub_git_dir = xstrfmt("%s/.git", path); - sub = submodule_from_path(&null_oid, path); + sub = submodule_from_path(the_repository, &null_oid, path); if (!sub || !sub->name) goto cleanup; @@ -975,6 +1146,8 @@ static void deinit_submodule(const char *path, const char *prefix, if (!(flags & OPT_QUIET)) printf(format, displaypath); + submodule_unset_core_worktree(sub); + strbuf_release(&sb_rm); } @@ -1061,8 +1234,8 @@ static int module_deinit(int argc, const char **argv, const char *prefix) } static int clone_submodule(const char *path, const char *gitdir, const char *url, - const char *depth, struct string_list *reference, - int quiet, int progress) + const char *depth, struct string_list *reference, int dissociate, + int quiet, int progress, int single_branch) { struct child_process cp = CHILD_PROCESS_INIT; @@ -1080,9 +1253,16 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url argv_array_pushl(&cp.args, "--reference", item->string, NULL); } + if (dissociate) + argv_array_push(&cp.args, "--dissociate"); if (gitdir && *gitdir) argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); + if (single_branch >= 0) + argv_array_push(&cp.args, single_branch ? + "--single-branch" : + "--no-single-branch"); + argv_array_push(&cp.args, "--"); argv_array_push(&cp.args, url); argv_array_push(&cp.args, path); @@ -1105,20 +1285,28 @@ struct submodule_alternate_setup { #define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \ SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL } +static const char alternate_error_advice[] = N_( +"An alternate computed from a superproject's alternate is invalid.\n" +"To allow Git to clone without an alternate in such a case, set\n" +"submodule.alternateErrorStrategy to 'info' or, equivalently, clone with\n" +"'--reference-if-able' instead of '--reference'." +); + static int add_possible_reference_from_superproject( - struct alternate_object_database *alt, void *sas_cb) + struct object_directory *odb, void *sas_cb) { struct submodule_alternate_setup *sas = sas_cb; + size_t len; /* * If the alternate object store is another repository, try the * standard layout with .git/(modules/<name>)+/objects */ - if (ends_with(alt->path, "/objects")) { + if (strip_suffix(odb->path, "/objects", &len)) { char *sm_alternate; struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; - strbuf_add(&sb, alt->path, strlen(alt->path) - strlen("objects")); + strbuf_add(&sb, odb->path, len); /* * We need to end the new path with '/' to mark it as a dir, @@ -1126,7 +1314,7 @@ static int add_possible_reference_from_superproject( * as the last part of a missing submodule reference would * be taken as a file name. */ - strbuf_addf(&sb, "modules/%s/", sas->submodule_name); + strbuf_addf(&sb, "/modules/%s/", sas->submodule_name); sm_alternate = compute_alternate_path(sb.buf, &err); if (sm_alternate) { @@ -1135,10 +1323,12 @@ static int add_possible_reference_from_superproject( } else { switch (sas->error_mode) { case SUBMODULE_ALTERNATE_ERROR_DIE: + if (advice_submodule_alternate_error_strategy_die) + advise(_(alternate_error_advice)); die(_("submodule '%s' cannot add alternate: %s"), sas->submodule_name, err.buf); case SUBMODULE_ALTERNATE_ERROR_INFO: - fprintf(stderr, _("submodule '%s' cannot add alternate: %s"), + fprintf_ln(stderr, _("submodule '%s' cannot add alternate: %s"), sas->submodule_name, err.buf); case SUBMODULE_ALTERNATE_ERROR_IGNORE: ; /* nothing */ @@ -1195,7 +1385,9 @@ static int module_clone(int argc, const char **argv, const char *prefix) char *p, *path = NULL, *sm_gitdir; struct strbuf sb = STRBUF_INIT; struct string_list reference = STRING_LIST_INIT_NODUP; + int dissociate = 0, require_init = 0; char *sm_alternate = NULL, *error_strategy = NULL; + int single_branch = -1; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &prefix, @@ -1213,18 +1405,25 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT_STRING_LIST(0, "reference", &reference, N_("repo"), N_("reference repository")), + OPT_BOOL(0, "dissociate", &dissociate, + N_("use --reference only while cloning")), OPT_STRING(0, "depth", &depth, N_("string"), N_("depth for shallow clones")), OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), OPT_BOOL(0, "progress", &progress, N_("force cloning progress")), + OPT_BOOL(0, "require-init", &require_init, + N_("disallow cloning into non-empty directory")), + OPT_BOOL(0, "single-branch", &single_branch, + N_("clone only one branch, HEAD or --branch")), OPT_END() }; const char *const git_submodule_helper_usage[] = { N_("git submodule--helper clone [--prefix=<path>] [--quiet] " "[--reference <repository>] [--name <name>] [--depth <depth>] " + "[--single-branch] " "--url <url> --path <path>"), NULL }; @@ -1246,17 +1445,23 @@ static int module_clone(int argc, const char **argv, const char *prefix) } else path = xstrdup(path); + if (validate_submodule_git_dir(sm_gitdir, name) < 0) + die(_("refusing to create/use '%s' in another submodule's " + "git dir"), sm_gitdir); + if (!file_exists(sm_gitdir)) { if (safe_create_leading_directories_const(sm_gitdir) < 0) die(_("could not create directory '%s'"), sm_gitdir); prepare_possible_alternates(name, &reference); - if (clone_submodule(path, sm_gitdir, url, depth, &reference, - quiet, progress)) + if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate, + quiet, progress, single_branch)) die(_("clone of '%s' into submodule path '%s' failed"), url, path); } else { + if (require_init && !access(path, X_OK) && !is_empty_dir(path)) + die(_("directory not empty: '%s'"), path); if (safe_create_leading_directories_const(path) < 0) die(_("could not create directory '%s'"), path); strbuf_addf(&sb, "%s/index", sm_gitdir); @@ -1264,8 +1469,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) strbuf_reset(&sb); } - /* Connect module worktree and git dir */ - connect_work_tree_and_git_dir(path, sm_gitdir); + connect_work_tree_and_git_dir(path, sm_gitdir, 0); p = git_pathdup_submodule(path, "config"); if (!p) @@ -1291,6 +1495,72 @@ static int module_clone(int argc, const char **argv, const char *prefix) return 0; } +static void determine_submodule_update_strategy(struct repository *r, + int just_cloned, + const char *path, + const char *update, + struct submodule_update_strategy *out) +{ + const struct submodule *sub = submodule_from_path(r, &null_oid, path); + char *key; + const char *val; + + key = xstrfmt("submodule.%s.update", sub->name); + + if (update) { + if (parse_submodule_update_strategy(update, out) < 0) + die(_("Invalid update mode '%s' for submodule path '%s'"), + update, path); + } else if (!repo_config_get_string_const(r, key, &val)) { + if (parse_submodule_update_strategy(val, out) < 0) + die(_("Invalid update mode '%s' configured for submodule path '%s'"), + val, path); + } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { + if (sub->update_strategy.type == SM_UPDATE_COMMAND) + BUG("how did we read update = !command from .gitmodules?"); + out->type = sub->update_strategy.type; + out->command = sub->update_strategy.command; + } else + out->type = SM_UPDATE_CHECKOUT; + + if (just_cloned && + (out->type == SM_UPDATE_MERGE || + out->type == SM_UPDATE_REBASE || + out->type == SM_UPDATE_NONE)) + out->type = SM_UPDATE_CHECKOUT; + + free(key); +} + +static int module_update_module_mode(int argc, const char **argv, const char *prefix) +{ + const char *path, *update = NULL; + int just_cloned; + struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT }; + + if (argc < 3 || argc > 4) + die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]"); + + just_cloned = git_config_int("just_cloned", argv[1]); + path = argv[2]; + + if (argc == 4) + update = argv[3]; + + determine_submodule_update_strategy(the_repository, + just_cloned, path, update, + &update_strategy); + fputs(submodule_strategy_to_string(&update_strategy), stdout); + + return 0; +} + +struct update_clone_data { + const struct submodule *sub; + struct object_id oid; + unsigned just_cloned; +}; + struct submodule_update_clone { /* index into 'list', the list of submodules to look into for cloning */ int current; @@ -1305,12 +1575,16 @@ struct submodule_update_clone { int quiet; int recommend_shallow; struct string_list references; + int dissociate; + unsigned require_init; const char *depth; const char *recursive_prefix; const char *prefix; + int single_branch; - /* Machine-readable status lines to be consumed by git-submodule.sh */ - struct string_list projectlines; + /* to be consumed by git-submodule.sh */ + struct update_clone_data *update_clone; + int update_clone_nr; int update_clone_alloc; /* If we want to stop as fast as possible and return an error */ unsigned quickstop : 1; @@ -1318,11 +1592,17 @@ struct submodule_update_clone { /* failed clones to be retried again */ const struct cache_entry **failed_clones; int failed_clones_nr, failed_clones_alloc; + + int max_jobs; }; -#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ - SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \ - NULL, NULL, NULL, \ - STRING_LIST_INIT_DUP, 0, NULL, 0, 0} +#define SUBMODULE_UPDATE_CLONE_INIT { \ + .list = MODULE_LIST_INIT, \ + .update = SUBMODULE_UPDATE_STRATEGY_INIT, \ + .recommend_shallow = -1, \ + .references = STRING_LIST_INIT_DUP, \ + .single_branch = -1, \ + .max_jobs = 1, \ +} static void next_submodule_warn_missing(struct submodule_update_clone *suc, @@ -1361,6 +1641,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, struct strbuf sb = STRBUF_INIT; const char *displaypath = NULL; int needs_cloning = 0; + int need_free_url = 0; if (ce_stage(ce)) { if (suc->recursive_prefix) @@ -1372,7 +1653,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, goto cleanup; } - sub = submodule_from_path(&null_oid, ce->name); + sub = submodule_from_path(the_repository, &null_oid, ce->name); if (suc->recursive_prefix) displaypath = relative_path(suc->recursive_prefix, @@ -1409,18 +1690,25 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, strbuf_reset(&sb); strbuf_addf(&sb, "submodule.%s.url", sub->name); - if (repo_config_get_string_const(the_repository, sb.buf, &url)) - url = sub->url; + if (repo_config_get_string_const(the_repository, sb.buf, &url)) { + if (starts_with_dot_slash(sub->url) || + starts_with_dot_dot_slash(sub->url)) { + url = compute_submodule_clone_url(sub->url); + need_free_url = 1; + } else + url = sub->url; + } strbuf_reset(&sb); strbuf_addf(&sb, "%s/.git", ce->name); needs_cloning = !file_exists(sb.buf); - strbuf_reset(&sb); - strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode, - oid_to_hex(&ce->oid), ce_stage(ce), - needs_cloning, ce->name); - string_list_append(&suc->projectlines, sb.buf); + ALLOC_GROW(suc->update_clone, suc->update_clone_nr + 1, + suc->update_clone_alloc); + oidcpy(&suc->update_clone[suc->update_clone_nr].oid, &ce->oid); + suc->update_clone[suc->update_clone_nr].just_cloned = needs_cloning; + suc->update_clone[suc->update_clone_nr].sub = sub; + suc->update_clone_nr++; if (!needs_cloning) goto cleanup; @@ -1439,6 +1727,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL); if (suc->recommend_shallow && sub->recommend_shallow == 1) argv_array_push(&child->args, "--depth=1"); + if (suc->require_init) + argv_array_push(&child->args, "--require-init"); argv_array_pushl(&child->args, "--path", sub->path, NULL); argv_array_pushl(&child->args, "--name", sub->name, NULL); argv_array_pushl(&child->args, "--url", url, NULL); @@ -1447,12 +1737,20 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, for_each_string_list_item(item, &suc->references) argv_array_pushl(&child->args, "--reference", item->string, NULL); } + if (suc->dissociate) + argv_array_push(&child->args, "--dissociate"); if (suc->depth) argv_array_push(&child->args, suc->depth); + if (suc->single_branch >= 0) + argv_array_push(&child->args, suc->single_branch ? + "--single-branch" : + "--no-single-branch"); cleanup: strbuf_reset(&displaypath_sb); strbuf_reset(&sb); + if (need_free_url) + free((void*)url); return needs_cloning; } @@ -1550,8 +1848,8 @@ static int update_clone_task_finished(int result, return 0; } -static int gitmodules_update_clone_config(const char *var, const char *value, - void *cb) +static int git_update_clone_config(const char *var, const char *value, + void *cb) { int *max_jobs = cb; if (!strcmp(var, "submodule.fetchjobs")) @@ -1559,11 +1857,43 @@ static int gitmodules_update_clone_config(const char *var, const char *value, return 0; } +static void update_submodule(struct update_clone_data *ucd) +{ + fprintf(stdout, "dummy %s %d\t%s\n", + oid_to_hex(&ucd->oid), + ucd->just_cloned, + ucd->sub->path); +} + +static int update_submodules(struct submodule_update_clone *suc) +{ + int i; + + run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task, + update_clone_start_failure, + update_clone_task_finished, suc, "submodule", + "parallel/update"); + + /* + * We saved the output and put it out all at once now. + * That means: + * - the listener does not have to interleave their (checkout) + * work with our fetching. The writes involved in a + * checkout involve more straightforward sequential I/O. + * - the listener can avoid doing any work if fetching failed. + */ + if (suc->quickstop) + return 1; + + for (i = 0; i < suc->update_clone_nr; i++) + update_submodule(&suc->update_clone[i]); + + return 0; +} + static int update_clone(int argc, const char **argv, const char *prefix) { const char *update = NULL; - int max_jobs = 1; - struct string_list_item *item; struct pathspec pathspec; struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; @@ -1580,27 +1910,33 @@ static int update_clone(int argc, const char **argv, const char *prefix) N_("rebase, merge, checkout or none")), OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"), N_("reference repository")), + OPT_BOOL(0, "dissociate", &suc.dissociate, + N_("use --reference only while cloning")), OPT_STRING(0, "depth", &suc.depth, "<depth>", N_("Create a shallow clone truncated to the " "specified number of revisions")), - OPT_INTEGER('j', "jobs", &max_jobs, + OPT_INTEGER('j', "jobs", &suc.max_jobs, N_("parallel jobs")), OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow, N_("whether the initial clone should follow the shallow recommendation")), OPT__QUIET(&suc.quiet, N_("don't print cloning progress")), OPT_BOOL(0, "progress", &suc.progress, N_("force cloning progress")), + OPT_BOOL(0, "require-init", &suc.require_init, + N_("disallow cloning into non-empty directory")), + OPT_BOOL(0, "single-branch", &suc.single_branch, + N_("clone only one branch, HEAD or --branch")), OPT_END() }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper update_clone [--prefix=<path>] [<path>...]"), + N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"), NULL }; suc.prefix = prefix; - config_from_gitmodules(gitmodules_update_clone_config, &max_jobs); - git_config(gitmodules_update_clone_config, &max_jobs); + update_clone_config_from_gitmodules(&suc.max_jobs); + git_config(git_update_clone_config, &suc.max_jobs); argc = parse_options(argc, argv, prefix, module_update_clone_options, git_submodule_helper_usage, 0); @@ -1615,27 +1951,7 @@ static int update_clone(int argc, const char **argv, const char *prefix) if (pathspec.nr) suc.warn_if_uninitialized = 1; - run_processes_parallel(max_jobs, - update_clone_get_next_task, - update_clone_start_failure, - update_clone_task_finished, - &suc); - - /* - * We saved the output and put it out all at once now. - * That means: - * - the listener does not have to interleave their (checkout) - * work with our fetching. The writes involved in a - * checkout involve more straightforward sequential I/O. - * - the listener can avoid doing any work if fetching failed. - */ - if (suc.quickstop) - return 1; - - for_each_string_list_item(item, &suc.projectlines) - fprintf(stdout, "%s", item->string); - - return 0; + return update_submodules(&suc); } static int resolve_relative_path(int argc, const char **argv, const char *prefix) @@ -1655,7 +1971,7 @@ static const char *remote_submodule_branch(const char *path) const char *branch = NULL; char *key; - sub = submodule_from_path(&null_oid, path); + sub = submodule_from_path(the_repository, &null_oid, path); if (!sub) return NULL; @@ -1740,13 +2056,14 @@ static int push_check(int argc, const char **argv, const char *prefix) /* Check the refspec */ if (argc > 2) { - int i, refspec_nr = argc - 2; + int i; struct ref *local_refs = get_local_heads(); - struct refspec *refspec = parse_push_refspec(refspec_nr, - argv + 2); + struct refspec refspec = REFSPEC_INIT_PUSH; + + refspec_appendn(&refspec, argv + 2, argc - 2); - for (i = 0; i < refspec_nr; i++) { - struct refspec *rs = refspec + i; + for (i = 0; i < refspec.nr; i++) { + const struct refspec_item *rs = &refspec.items[i]; if (rs->pattern || rs->matching) continue; @@ -1773,13 +2090,52 @@ static int push_check(int argc, const char **argv, const char *prefix) rs->src); } } - free_refspec(refspec_nr, refspec); + refspec_clear(&refspec); } free(head); return 0; } +static int ensure_core_worktree(int argc, const char **argv, const char *prefix) +{ + const struct submodule *sub; + const char *path; + char *cw; + struct repository subrepo; + + if (argc != 2) + BUG("submodule--helper ensure-core-worktree <path>"); + + path = argv[1]; + + sub = submodule_from_path(the_repository, &null_oid, path); + if (!sub) + BUG("We could get the submodule handle before?"); + + if (repo_submodule_init(&subrepo, the_repository, sub)) + die(_("could not get a repository handle for submodule '%s'"), path); + + if (!repo_config_get_string(&subrepo, "core.worktree", &cw)) { + char *cfg_file, *abs_path; + const char *rel_path; + struct strbuf sb = STRBUF_INIT; + + cfg_file = repo_git_path(&subrepo, "config"); + + abs_path = absolute_pathdup(path); + rel_path = relative_path(abs_path, subrepo.gitdir, &sb); + + git_config_set_in_file(cfg_file, "core.worktree", rel_path); + + free(cfg_file); + free(abs_path); + strbuf_release(&sb); + } + + return 0; +} + static int absorb_git_dirs(int argc, const char **argv, const char *prefix) { int i; @@ -1797,7 +2153,7 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix) }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper embed-git-dir [<path>...]"), + N_("git submodule--helper absorb-git-dirs [<options>] [<path>...]"), NULL }; @@ -1808,8 +2164,7 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix) return 1; for (i = 0; i < list.nr; i++) - absorb_git_dir_into_superproject(prefix, - list.entries[i]->name, flags); + absorb_git_dir_into_superproject(list.entries[i]->name, flags); return 0; } @@ -1822,6 +2177,106 @@ static int is_active(int argc, const char **argv, const char *prefix) return !is_submodule_active(the_repository, argv[1]); } +/* + * Exit non-zero if any of the submodule names given on the command line is + * invalid. If no names are given, filter stdin to print only valid names + * (which is primarily intended for testing). + */ +static int check_name(int argc, const char **argv, const char *prefix) +{ + if (argc > 1) { + while (*++argv) { + if (check_submodule_name(*argv) < 0) + return 1; + } + } else { + struct strbuf buf = STRBUF_INIT; + while (strbuf_getline(&buf, stdin) != EOF) { + if (!check_submodule_name(buf.buf)) + printf("%s\n", buf.buf); + } + strbuf_release(&buf); + } + return 0; +} + +static int module_config(int argc, const char **argv, const char *prefix) +{ + enum { + CHECK_WRITEABLE = 1, + DO_UNSET = 2 + } command = 0; + + struct option module_config_options[] = { + OPT_CMDMODE(0, "check-writeable", &command, + N_("check if it is safe to write to the .gitmodules file"), + CHECK_WRITEABLE), + OPT_CMDMODE(0, "unset", &command, + N_("unset the config in the .gitmodules file"), + DO_UNSET), + OPT_END() + }; + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper config <name> [<value>]"), + N_("git submodule--helper config --unset <name>"), + N_("git submodule--helper config --check-writeable"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_config_options, + git_submodule_helper_usage, PARSE_OPT_KEEP_ARGV0); + + if (argc == 1 && command == CHECK_WRITEABLE) + return is_writing_gitmodules_ok() ? 0 : -1; + + /* Equivalent to ACTION_GET in builtin/config.c */ + if (argc == 2 && command != DO_UNSET) + return print_config_from_gitmodules(the_repository, argv[1]); + + /* Equivalent to ACTION_SET in builtin/config.c */ + if (argc == 3 || (argc == 2 && command == DO_UNSET)) { + const char *value = (argc == 3) ? argv[2] : NULL; + + if (!is_writing_gitmodules_ok()) + die(_("please make sure that the .gitmodules file is in the working tree")); + + return config_set_in_gitmodules_file_gently(argv[1], value); + } + + usage_with_options(git_submodule_helper_usage, module_config_options); +} + +static int module_set_url(int argc, const char **argv, const char *prefix) +{ + int quiet = 0; + const char *newurl; + const char *path; + char *config_name; + + struct option options[] = { + OPT__QUIET(&quiet, N_("Suppress output for setting url of a submodule")), + OPT_END() + }; + const char *const usage[] = { + N_("git submodule--helper set-url [--quiet] <path> <newurl>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (argc != 2 || !(path = argv[0]) || !(newurl = argv[1])) + usage_with_options(usage, options); + + config_name = xstrfmt("submodule.%s.url", path); + + config_set_in_gitmodules_file_gently(config_name, newurl); + sync_submodule(path, prefix, quiet ? OPT_QUIET : 0); + + free(config_name); + + return 0; +} + #define SUPPORT_SUPER_PREFIX (1<<0) struct cmd_struct { @@ -1834,10 +2289,13 @@ static struct cmd_struct commands[] = { {"list", module_list, 0}, {"name", module_name, 0}, {"clone", module_clone, 0}, + {"update-module-mode", module_update_module_mode, 0}, {"update-clone", update_clone, 0}, + {"ensure-core-worktree", ensure_core_worktree, 0}, {"relative-path", resolve_relative_path, 0}, {"resolve-relative-url", resolve_relative_url, 0}, {"resolve-relative-url-test", resolve_relative_url_test, 0}, + {"foreach", module_foreach, SUPPORT_SUPER_PREFIX}, {"init", module_init, SUPPORT_SUPER_PREFIX}, {"status", module_status, SUPPORT_SUPER_PREFIX}, {"print-default-remote", print_default_remote, 0}, @@ -1847,6 +2305,9 @@ static struct cmd_struct commands[] = { {"push-check", push_check, 0}, {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, {"is-active", is_active, 0}, + {"check-name", check_name, 0}, + {"config", module_config, 0}, + {"set-url", module_set_url, 0}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/builtin/tag.c b/builtin/tag.c index 8cff6d0b72..5cbd80dc3e 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -10,27 +10,30 @@ #include "config.h" #include "builtin.h" #include "refs.h" +#include "object-store.h" #include "tag.h" #include "run-command.h" #include "parse-options.h" #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" static const char * const git_tag_usage[] = { - N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"), + N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n" + "\t\t<tagname> [<head>]"), N_("git tag -d <tagname>..."), - N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]" - "\n\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), + N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n" + "\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), N_("git tag -v [--format=<format>] <tagname>..."), NULL }; static unsigned int colopts; static int force_sign_annotate; +static int config_sign_tag = -1; /* unspecified */ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format) @@ -118,7 +121,7 @@ static int verify_tag(const char *name, const char *ref, return -1; if (format->format) - pretty_print_ref(name, oid->hash, format); + pretty_print_ref(name, oid, format); return 0; } @@ -142,6 +145,11 @@ static int git_tag_config(const char *var, const char *value, void *cb) int status; struct ref_sorting **sorting_tail = (struct ref_sorting **)cb; + if (!strcmp(var, "tag.gpgsign")) { + config_sign_tag = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "tag.sort")) { if (!value) return config_error_nonbool(var); @@ -204,7 +212,14 @@ struct create_tag_options { } cleanup_mode; }; -static void create_tag(const struct object_id *object, const char *tag, +static const char message_advice_nested_tag[] = + N_("You have created a nested tag. The object referred to by your new tag is\n" + "already a tag. If you meant to tag the object that it points to, use:\n" + "\n" + "\tgit tag -f %s %s^{}"); + +static void create_tag(const struct object_id *object, const char *object_ref, + const char *tag, struct strbuf *buf, struct create_tag_options *opt, struct object_id *prev, struct object_id *result) { @@ -212,9 +227,13 @@ static void create_tag(const struct object_id *object, const char *tag, struct strbuf header = STRBUF_INIT; char *path = NULL; - type = oid_object_info(object, NULL); + type = oid_object_info(the_repository, object, NULL); if (type <= OBJ_NONE) - die(_("bad object type.")); + die(_("bad object type.")); + + if (type == OBJ_TAG) + advise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag), + tag, object_ref); strbuf_addf(&header, "object %s\n" @@ -298,7 +317,7 @@ static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb) } strbuf_addstr(sb, " ("); - type = oid_object_info(oid, NULL); + type = oid_object_info(the_repository, oid, NULL); switch (type) { default: strbuf_addstr(sb, "object of unknown type"); @@ -312,7 +331,7 @@ static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb) } free(buf); - if ((c = lookup_commit_reference(oid)) != NULL) + if ((c = lookup_commit_reference(the_repository, oid)) != NULL) strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT))); break; case OBJ_TREE: @@ -337,6 +356,8 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset) { struct msg_arg *msg = opt->value; + BUG_ON_OPT_NEG(unset); + if (!arg) return -1; if (msg->buf.len) @@ -389,13 +410,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_GROUP(N_("Tag creation options")), OPT_BOOL('a', "annotate", &annotate, N_("annotated tag, needs a message")), - OPT_CALLBACK('m', "message", &msg, N_("message"), - N_("tag message"), parse_msg_arg), + OPT_CALLBACK_F('m', "message", &msg, N_("message"), + N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg), OPT_FILENAME('F', "file", &msgfile, N_("read message from file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")), OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")), - OPT_STRING(0, "cleanup", &cleanup_arg, N_("mode"), - N_("how to strip spaces and #comments from message")), + OPT_CLEANUP(&cleanup_arg), OPT_STRING('u', "local-user", &keyid, N_("key-id"), N_("use another key to sign the tag")), OPT__FORCE(&force, N_("replace the tag if exists"), 0), @@ -409,8 +429,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")), OPT_MERGED(&filter, N_("print only tags that are merged")), OPT_NO_MERGED(&filter, N_("print only tags that are not merged")), - OPT_CALLBACK(0 , "sort", sorting_tail, N_("key"), - N_("field name to sort on"), &parse_opt_ref_sorting), + OPT_REF_SORT(sorting_tail), { OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT, @@ -430,15 +449,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix) memset(&opt, 0, sizeof(opt)); memset(&filter, 0, sizeof(filter)); filter.lines = -1; + opt.sign = -1; argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0); - if (keyid) { - opt.sign = 1; - set_signing_key(keyid); - } - create_tag_object = (opt.sign || annotate || msg.given || msgfile); - if (!cmdmode) { if (argc == 0) cmdmode = 'l'; @@ -451,6 +465,15 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (cmdmode == 'l') setup_auto_pager("tag", 1); + if (opt.sign == -1) + opt.sign = cmdmode ? 0 : config_sign_tag > 0; + + if (keyid) { + opt.sign = 1; + set_signing_key(keyid); + } + create_tag_object = (opt.sign || annotate || msg.given || msgfile); + if ((create_tag_object || force) && (cmdmode != 0)) usage_with_options(git_tag_usage, options); @@ -462,7 +485,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) } if (!sorting) sorting = ref_default_sorting(); - sorting->ignore_case = icase; + ref_sorting_icase_all(sorting, icase); filter.ignore_case = icase; if (cmdmode == 'l') { int ret; @@ -547,7 +570,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (create_tag_object) { if (force_sign_annotate && !annotate) opt.sign = 1; - create_tag(&object, tag, &buf, &opt, &prev, &object); + create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object); } transaction = ref_transaction_begin(&err); @@ -558,7 +581,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); - if (force && !is_null_oid(&prev) && oidcmp(&prev, &object)) + if (force && !is_null_oid(&prev) && !oideq(&prev, &object)) printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(&prev, DEFAULT_ABBREV)); diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 300eb59657..58652229f2 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "config.h" +#include "object-store.h" static char *create_temp_file(struct object_id *oid) { diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index b7755c6cc5..dd4a75e030 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -1,6 +1,7 @@ #include "builtin.h" #include "cache.h" #include "config.h" +#include "object-store.h" #include "object.h" #include "delta.h" #include "pack.h" @@ -23,6 +24,7 @@ static off_t consumed_bytes; static off_t max_input_size; static git_hash_ctx ctx; static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; +static struct progress *progress; /* * When running under --strict mode, objects whose reachability are @@ -91,6 +93,7 @@ static void use(int bytes) consumed_bytes += bytes; if (max_input_size && consumed_bytes > max_input_size) die(_("pack exceeds maximum allowed size")); + display_throughput(progress, consumed_bytes); } static void *get_data(unsigned long size) @@ -199,7 +202,7 @@ static int check_object(struct object *obj, int type, void *data, struct fsck_op if (!(obj->flags & FLAG_OPEN)) { unsigned long size; - int type = oid_object_info(&obj->oid, &size); + int type = oid_object_info(the_repository, &obj->oid, &size); if (type != obj->type || type <= 0) die("object of unexpected type"); obj->flags |= FLAG_WRITTEN; @@ -210,7 +213,7 @@ static int check_object(struct object *obj, int type, void *data, struct fsck_op if (!obj_buf) die("Whoops! Cannot find object '%s'", oid_to_hex(&obj->oid)); if (fsck_object(obj, obj_buf->buffer, obj_buf->size, &fsck_options)) - die("Error in object"); + die("fsck error in packed object"); fsck_options.walk = check_object; if (fsck_walk(obj, NULL, &fsck_options)) die("Error on reachable objects of %s", oid_to_hex(&obj->oid)); @@ -253,7 +256,7 @@ static void write_object(unsigned nr, enum object_type type, added_object(nr, type, buf, size); free(buf); - blob = lookup_blob(&obj_list[nr].oid); + blob = lookup_blob(the_repository, &obj_list[nr].oid); if (blob) blob->object.flags |= FLAG_WRITTEN; else @@ -262,9 +265,11 @@ static void write_object(unsigned nr, enum object_type type, } else { struct object *obj; int eaten; - hash_object_file(buf, size, type_name(type), &obj_list[nr].oid); + hash_object_file(the_hash_algo, buf, size, type_name(type), + &obj_list[nr].oid); added_object(nr, type, buf, size); - obj = parse_object_buffer(&obj_list[nr].oid, type, size, buf, + obj = parse_object_buffer(the_repository, &obj_list[nr].oid, + type, size, buf, &eaten); if (!obj) die("invalid %s", type_name(type)); @@ -301,7 +306,7 @@ static void added_object(unsigned nr, enum object_type type, struct delta_info *info; while ((info = *p) != NULL) { - if (!oidcmp(&info->base_oid, &obj_list[nr].oid) || + if (oideq(&info->base_oid, &obj_list[nr].oid) || info->base_offset == obj_list[nr].offset) { *p = info->next; p = &delta_list; @@ -330,7 +335,7 @@ static int resolve_against_held(unsigned nr, const struct object_id *base, { struct object *obj; struct obj_buffer *obj_buffer; - obj = lookup_object(base->hash); + obj = lookup_object(the_repository, base); if (!obj) return 0; obj_buffer = lookup_object_buffer(obj); @@ -482,7 +487,6 @@ static void unpack_one(unsigned nr) static void unpack_all(void) { int i; - struct progress *progress = NULL; struct pack_header *hdr = fill(sizeof(struct pack_header)); nr_objects = ntohl(hdr->hdr_entries); @@ -512,7 +516,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) int i; struct object_id oid; - check_replace_refs = 0; + read_replace_refs = 0; git_config(git_default_config, NULL); @@ -572,9 +576,12 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) unpack_all(); the_hash_algo->update_fn(&ctx, buffer, offset); the_hash_algo->final_fn(oid.hash, &ctx); - if (strict) + if (strict) { write_rest(); - if (hashcmp(fill(the_hash_algo->rawsz), oid.hash)) + if (fsck_finish(&fsck_options)) + die(_("fsck error in pack objects")); + } + if (!hasheq(fill(the_hash_algo->rawsz), oid.hash)) die("final sha1 did not match"); use(the_hash_algo->rawsz); diff --git a/builtin/update-index.c b/builtin/update-index.c index 10d070a76f..79087bccea 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -3,6 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "lockfile.h" @@ -34,6 +35,7 @@ static int verbose; static int mark_valid_only; static int mark_skip_worktree_only; static int mark_fsmonitor_only; +static int ignore_skip_worktree_entries; #define MARK_FLAG 1 #define UNMARK_FLAG 2 static struct strbuf mtime_dir = STRBUF_INIT; @@ -268,30 +270,29 @@ static int process_lstat_error(const char *path, int err) static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st) { - int option, size; + int option; struct cache_entry *ce; /* Was the old index entry already up-to-date? */ if (old && !ce_stage(old) && !ce_match_stat(old, st, 0)) return 0; - size = cache_entry_size(len); - ce = xcalloc(1, size); + ce = make_empty_cache_entry(&the_index, len); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(0); ce->ce_namelen = len; - fill_stat_cache_info(ce, st); + fill_stat_cache_info(&the_index, ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(&ce->oid, path, st, + if (index_path(&the_index, &ce->oid, path, st, info_only ? 0 : HASH_WRITE_OBJECT)) { - free(ce); + discard_cache_entry(ce); return -1; } option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; if (add_cache_entry(ce, option)) { - free(ce); + discard_cache_entry(ce); return error("%s: cannot add to the index - missing --add option?", path); } return 0; @@ -364,10 +365,9 @@ static int process_directory(const char *path, int len, struct stat *st) return error("%s: is a directory - add files inside instead", path); } -static int process_path(const char *path) +static int process_path(const char *path, struct stat *st, int stat_errno) { int pos, len; - struct stat st; const struct cache_entry *ce; len = strlen(path); @@ -382,7 +382,8 @@ static int process_path(const char *path) * so updating it does not make sense. * On the other hand, removing it from index should work */ - if (allow_remove && remove_file_from_cache(path)) + if (!ignore_skip_worktree_entries && allow_remove && + remove_file_from_cache(path)) return error("%s: cannot remove from the index", path); return 0; } @@ -391,27 +392,26 @@ static int process_path(const char *path) * First things first: get the stat information, to decide * what to do about the pathname! */ - if (lstat(path, &st) < 0) - return process_lstat_error(path, errno); + if (stat_errno) + return process_lstat_error(path, stat_errno); - if (S_ISDIR(st.st_mode)) - return process_directory(path, len, &st); + if (S_ISDIR(st->st_mode)) + return process_directory(path, len, st); - return add_one_path(ce, path, len, &st); + return add_one_path(ce, path, len, st); } static int add_cacheinfo(unsigned int mode, const struct object_id *oid, const char *path, int stage) { - int size, len, option; + int len, option; struct cache_entry *ce; - if (!verify_path(path)) + if (!verify_path(path, mode)) return error("Invalid path '%s'", path); len = strlen(path); - size = cache_entry_size(len); - ce = xcalloc(1, size); + ce = make_empty_cache_entry(&the_index, len); oidcpy(&ce->oid, oid); memcpy(ce->name, path, len); @@ -449,7 +449,18 @@ static void chmod_path(char flip, const char *path) static void update_one(const char *path) { - if (!verify_path(path)) { + int stat_errno = 0; + struct stat st; + + if (mark_valid_only || mark_skip_worktree_only || force_remove || + mark_fsmonitor_only) + st.st_mode = 0; + else if (lstat(path, &st) < 0) { + st.st_mode = 0; + stat_errno = errno; + } /* else stat is valid */ + + if (!verify_path(path, st.st_mode)) { fprintf(stderr, "Ignoring path %s\n", path); return; } @@ -475,13 +486,14 @@ static void update_one(const char *path) report("remove '%s'", path); return; } - if (process_path(path)) + if (process_path(path, &st, stat_errno)) die("Unable to process path %s", path); report("add '%s'", path); } static void read_index_info(int nul_term_line) { + const int hexsz = the_hash_algo->hexsz; struct strbuf buf = STRBUF_INIT; struct strbuf uq = STRBUF_INIT; strbuf_getline_fn getline_fn; @@ -519,7 +531,7 @@ static void read_index_info(int nul_term_line) mode = ul; tab = strchr(ptr, '\t'); - if (!tab || tab - ptr < GIT_SHA1_HEXSZ + 1) + if (!tab || tab - ptr < hexsz + 1) goto bad_line; if (tab[-2] == ' ' && '0' <= tab[-1] && tab[-1] <= '3') { @@ -532,8 +544,8 @@ static void read_index_info(int nul_term_line) ptr = tab + 1; /* point at the head of path */ } - if (get_oid_hex(tab - GIT_SHA1_HEXSZ, &oid) || - tab[-(GIT_SHA1_HEXSZ + 1)] != ' ') + if (get_oid_hex(tab - hexsz, &oid) || + tab[-(hexsz + 1)] != ' ') goto bad_line; path_name = ptr; @@ -545,7 +557,7 @@ static void read_index_info(int nul_term_line) path_name = uq.buf; } - if (!verify_path(path_name)) { + if (!verify_path(path_name, mode)) { fprintf(stderr, "Ignoring path %s\n", path_name); continue; } @@ -561,7 +573,7 @@ static void read_index_info(int nul_term_line) * ptr[-1] points at tab, * ptr[-41] is at the beginning of sha1 */ - ptr[-(GIT_SHA1_HEXSZ + 2)] = ptr[-1] = 0; + ptr[-(hexsz + 2)] = ptr[-1] = 0; if (add_cacheinfo(mode, &oid, path_name, stage)) die("git update-index: unable to update %s", path_name); @@ -587,12 +599,11 @@ static struct cache_entry *read_one_ent(const char *which, struct object_id *ent, const char *path, int namelen, int stage) { - unsigned mode; + unsigned short mode; struct object_id oid; - int size; struct cache_entry *ce; - if (get_tree_entry(ent, path, &oid, &mode)) { + if (get_tree_entry(the_repository, ent, path, &oid, &mode)) { if (which) error("%s: not in %s branch.", path, which); return NULL; @@ -602,8 +613,7 @@ static struct cache_entry *read_one_ent(const char *which, error("%s: not a blob in %s branch.", path, which); return NULL; } - size = cache_entry_size(namelen); - ce = xcalloc(1, size); + ce = make_empty_cache_entry(&the_index, namelen); oidcpy(&ce->oid, &oid); memcpy(ce->name, path, namelen); @@ -662,7 +672,7 @@ static int unresolve_one(const char *path) ret = -1; goto free_return; } - if (!oidcmp(&ce_2->oid, &ce_3->oid) && + if (oideq(&ce_2->oid, &ce_3->oid) && ce_2->ce_mode == ce_3->ce_mode) { fprintf(stderr, "%s: identical in both, skipping.\n", path); @@ -680,8 +690,8 @@ static int unresolve_one(const char *path) error("%s: cannot add their version to the index.", path); ret = -1; free_return: - free(ce_2); - free(ce_3); + discard_cache_entry(ce_2); + discard_cache_entry(ce_3); return ret; } @@ -716,7 +726,7 @@ static int do_unresolve(int ac, const char **av, } static int do_reupdate(int ac, const char **av, - const char *prefix, int prefix_length) + const char *prefix) { /* Read HEAD and run update-index on paths that are * merged and already different between index and HEAD. @@ -741,14 +751,14 @@ static int do_reupdate(int ac, const char **av, int save_nr; char *path; - if (ce_stage(ce) || !ce_path_match(ce, &pathspec, NULL)) + if (ce_stage(ce) || !ce_path_match(&the_index, ce, &pathspec, NULL)) continue; if (has_head) old = read_one_ent(NULL, &head_oid, ce->name, ce_namelen(ce), 0); if (old && ce->ce_mode == old->ce_mode && - !oidcmp(&ce->oid, &old->oid)) { - free(old); + oideq(&ce->oid, &old->oid)) { + discard_cache_entry(old); continue; /* unchanged */ } /* Be careful. The working tree may not have the @@ -759,7 +769,7 @@ static int do_reupdate(int ac, const char **av, path = xstrdup(ce->name); update_one(path); free(path); - free(old); + discard_cache_entry(old); if (save_nr != active_nr) goto redo; } @@ -775,7 +785,7 @@ struct refresh_params { static int refresh(struct refresh_params *o, unsigned int flag) { setup_work_tree(); - read_cache_preload(NULL); + read_cache(); *o->has_errors |= refresh_cache(o->flags | flag); return 0; } @@ -783,12 +793,16 @@ static int refresh(struct refresh_params *o, unsigned int flag) static int refresh_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); return refresh(opt->value, 0); } static int really_refresh_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); return refresh(opt->value, REFRESH_REALLY); } @@ -796,6 +810,7 @@ static int chmod_callback(const struct option *opt, const char *arg, int unset) { char *flip = opt->value; + BUG_ON_OPT_NEG(unset); if ((arg[0] != '-' && arg[0] != '+') || arg[1] != 'x' || arg[2]) return error("option 'chmod' expects \"+x\" or \"-x\""); *flip = arg[0]; @@ -805,6 +820,8 @@ static int chmod_callback(const struct option *opt, static int resolve_undo_clear_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); resolve_undo_clear(); return 0; } @@ -816,6 +833,7 @@ static int parse_new_style_cacheinfo(const char *arg, { unsigned long ul; char *endp; + const char *p; if (!arg) return -1; @@ -826,19 +844,23 @@ static int parse_new_style_cacheinfo(const char *arg, return -1; /* not a new-style cacheinfo */ *mode = ul; endp++; - if (get_oid_hex(endp, oid) || endp[GIT_SHA1_HEXSZ] != ',') + if (parse_oid_hex(endp, oid, &p) || *p != ',') return -1; - *path = endp + GIT_SHA1_HEXSZ + 1; + *path = p + 1; return 0; } -static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +static enum parse_opt_result cacheinfo_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { struct object_id oid; unsigned int mode; const char *path; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + if (!parse_new_style_cacheinfo(ctx->argv[1], &mode, &oid, &path)) { if (add_cacheinfo(mode, &oid, path, 0)) die("git update-index: --cacheinfo cannot add %s", path); @@ -856,11 +878,15 @@ static int cacheinfo_callback(struct parse_opt_ctx_t *ctx, return 0; } -static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +static enum parse_opt_result stdin_cacheinfo_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { int *nul_term_line = opt->value; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + if (ctx->argc != 1) return error("option '%s' must be the last argument", opt->long_name); allow_add = allow_replace = allow_remove = 1; @@ -868,23 +894,31 @@ static int stdin_cacheinfo_callback(struct parse_opt_ctx_t *ctx, return 0; } -static int stdin_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int unset) +static enum parse_opt_result stdin_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { int *read_from_stdin = opt->value; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + if (ctx->argc != 1) return error("option '%s' must be the last argument", opt->long_name); *read_from_stdin = 1; return 0; } -static int unresolve_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int flags) +static enum parse_opt_result unresolve_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { int *has_errors = opt->value; const char *prefix = startup_info->prefix; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + /* consume remaining arguments. */ *has_errors = do_unresolve(ctx->argc, ctx->argv, prefix, prefix ? strlen(prefix) : 0); @@ -896,16 +930,19 @@ static int unresolve_callback(struct parse_opt_ctx_t *ctx, return 0; } -static int reupdate_callback(struct parse_opt_ctx_t *ctx, - const struct option *opt, int flags) +static enum parse_opt_result reupdate_callback( + struct parse_opt_ctx_t *ctx, const struct option *opt, + const char *arg, int unset) { int *has_errors = opt->value; const char *prefix = startup_info->prefix; + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + /* consume remaining arguments. */ setup_work_tree(); - *has_errors = do_reupdate(ctx->argc, ctx->argv, - prefix, prefix ? strlen(prefix) : 0); + *has_errors = do_reupdate(ctx->argc, ctx->argv, prefix); if (*has_errors) active_cache_changed = 0; @@ -931,6 +968,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) struct parse_opt_ctx_t ctx; strbuf_getline_fn getline_fn; int parseopt_state = PARSE_OPT_UNKNOWN; + struct repository *r = the_repository; struct option options[] = { OPT_BIT('q', NULL, &refresh_args.flags, N_("continue refresh even when index needs update"), @@ -947,24 +985,25 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) OPT_BIT(0, "unmerged", &refresh_args.flags, N_("refresh even if index contains unmerged entries"), REFRESH_UNMERGED), - {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL, + OPT_CALLBACK_F(0, "refresh", &refresh_args, NULL, N_("refresh stat information"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, - refresh_callback}, - {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL, + refresh_callback), + OPT_CALLBACK_F(0, "really-refresh", &refresh_args, NULL, N_("like --refresh, but ignore assume-unchanged setting"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, - really_refresh_callback}, + really_refresh_callback), {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL, N_("<mode>,<object>,<path>"), N_("add the specified entry to the index"), PARSE_OPT_NOARG | /* disallow --cacheinfo=<mode> form */ PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, - (parse_opt_cb *) cacheinfo_callback}, - {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, N_("(+/-)x"), + NULL, 0, + cacheinfo_callback}, + OPT_CALLBACK_F(0, "chmod", &set_executable_bit, "(+|-)x", N_("override the executable bit of the listed files"), - PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, - chmod_callback}, + PARSE_OPT_NONEG, + chmod_callback), {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL, N_("mark files as \"not changing\""), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, @@ -977,6 +1016,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL, N_("clear skip-worktree bit"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + OPT_BOOL(0, "ignore-skip-worktree-entries", &ignore_skip_worktree_entries, + N_("do not touch index-only entries")), OPT_SET_INT(0, "info-only", &info_only, N_("add to index only; do not add content to object database"), 1), OPT_SET_INT(0, "force-remove", &force_remove, @@ -986,28 +1027,28 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) {OPTION_LOWLEVEL_CALLBACK, 0, "stdin", &read_from_stdin, NULL, N_("read list of paths to be updated from standard input"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, - (parse_opt_cb *) stdin_callback}, + NULL, 0, stdin_callback}, {OPTION_LOWLEVEL_CALLBACK, 0, "index-info", &nul_term_line, NULL, N_("add entries from standard input to the index"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, - (parse_opt_cb *) stdin_cacheinfo_callback}, + NULL, 0, stdin_cacheinfo_callback}, {OPTION_LOWLEVEL_CALLBACK, 0, "unresolve", &has_errors, NULL, N_("repopulate stages #2 and #3 for the listed paths"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, - (parse_opt_cb *) unresolve_callback}, + NULL, 0, unresolve_callback}, {OPTION_LOWLEVEL_CALLBACK, 'g', "again", &has_errors, NULL, N_("only update entries that differ from HEAD"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, - (parse_opt_cb *) reupdate_callback}, + NULL, 0, reupdate_callback}, OPT_BIT(0, "ignore-missing", &refresh_args.flags, N_("ignore files missing from worktree"), REFRESH_IGNORE_MISSING), OPT_SET_INT(0, "verbose", &verbose, N_("report actions to standard output"), 1), - {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL, + OPT_CALLBACK_F(0, "clear-resolve-undo", NULL, NULL, N_("(for porcelains) forget saved unresolved conflicts"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, - resolve_undo_clear_callback}, + resolve_undo_clear_callback), OPT_INTEGER(0, "index-version", &preferred_index_format, N_("write index in this format")), OPT_BOOL(0, "split-index", &split_index, @@ -1045,6 +1086,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) if (entries < 0) die("cache corrupted"); + the_index.updated_skipworktree = 1; + /* * Custom copy of parse_options() because we want to handle * filename arguments as they come. @@ -1061,6 +1104,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) case PARSE_OPT_HELP: case PARSE_OPT_ERROR: exit(129); + case PARSE_OPT_COMPLETE: + exit(0); case PARSE_OPT_NON_OPTION: case PARSE_OPT_DONE: { @@ -1140,11 +1185,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) remove_split_index(&the_index); } + prepare_repo_settings(r); switch (untracked_cache) { case UC_UNSPECIFIED: break; case UC_DISABLE: - if (git_config_get_untracked_cache() == 1) + if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE) warning(_("core.untrackedCache is set to true; " "remove or change it, if you really want to " "disable the untracked cache")); @@ -1156,7 +1202,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) return !test_if_untracked_cache_is_supported(); case UC_ENABLE: case UC_FORCE: - if (git_config_get_untracked_cache() == 0) + if (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE) warning(_("core.untrackedCache is set to false; " "remove or change it, if you really want to " "enable the untracked cache")); @@ -1164,7 +1210,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) report(_("Untracked cache enabled for '%s'"), get_git_work_tree()); break; default: - die("BUG: bad untracked_cache value: %d", untracked_cache); + BUG("bad untracked_cache value: %d", untracked_cache); } if (fsmonitor > 0) { diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 4b4714b3fd..b74dd9a69d 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -14,7 +14,8 @@ static const char * const git_update_ref_usage[] = { }; static char line_termination = '\n'; -static int update_flags; +static unsigned int update_flags; +static unsigned int default_flags; static unsigned create_reflog_flag; static const char *msg; @@ -49,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; @@ -94,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) @@ -102,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) { @@ -127,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; @@ -177,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) @@ -205,25 +206,23 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, msg, &err)) die("%s", err.buf); - update_flags = 0; + 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)) @@ -237,26 +236,24 @@ static const char *parse_cmd_create(struct ref_transaction *transaction, msg, &err)) die("%s", err.buf); - update_flags = 0; + 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 { @@ -273,25 +270,23 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction, update_flags, msg, &err)) die("%s", err.buf); - update_flags = 0; + 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); @@ -302,52 +297,182 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, update_flags, &err)) die("%s", err.buf); - update_flags = 0; + 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) { - if (!strncmp(next, "no-deref", 8) && next[8] == line_termination) + 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 next + 8; } -static void update_refs_stdin(struct ref_transaction *transaction) +static void parse_cmd_start(struct ref_transaction *transaction, + const char *next, const char *end) { - struct strbuf input = STRBUF_INIT; - const char *next; + 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, 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 (starts_with(next, "update ")) - next = parse_cmd_update(transaction, &input, next + 7); - else if (starts_with(next, "create ")) - next = parse_cmd_create(transaction, &input, next + 7); - else if (starts_with(next, "delete ")) - next = parse_cmd_delete(transaction, &input, next + 7); - else if (starts_with(next, "verify ")) - next = parse_cmd_verify(transaction, &input, next + 7); - else if (starts_with(next, "option ")) - next = parse_cmd_option(&input, next + 7); - 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); } @@ -356,7 +481,6 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) const char *refname, *oldval; struct object_id oid, oldoid; int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0; - unsigned int flags = 0; int create_reflog = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), @@ -377,22 +501,17 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0; - if (read_stdin) { - struct strbuf err = STRBUF_INIT; - struct ref_transaction *transaction; + if (no_deref) { + default_flags = REF_NO_DEREF; + update_flags = default_flags; + } - transaction = ref_transaction_begin(&err); - if (!transaction) - die("%s", err.buf); - if (delete || no_deref || argc > 0) + if (read_stdin) { + 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; } @@ -426,8 +545,6 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) die("%s: not a valid old SHA1", oldval); } - if (no_deref) - flags = REF_NO_DEREF; if (delete) /* * For purposes of backwards compatibility, we treat @@ -435,9 +552,9 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) */ return delete_ref(msg, refname, (oldval && !is_null_oid(&oldoid)) ? &oldoid : NULL, - flags); + default_flags); else return update_ref(msg, refname, &oid, oldval ? &oldoid : NULL, - flags | create_reflog_flag, + default_flags | create_reflog_flag, UPDATE_REFS_DIE_ON_ERR); } diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 84532ae9a9..018879737a 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -28,6 +28,8 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) if (!enter_repo(argv[1], 0)) die("'%s' does not appear to be a git repository", argv[1]); + init_archivers(); + /* put received options in sent_argv[] */ argv_array_push(&sent_argv, "git-upload-archive"); for (;;) { @@ -43,7 +45,8 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) } /* parse all options sent by the client */ - return write_archive(sent_argv.argc, sent_argv.argv, prefix, NULL, 1); + return write_archive(sent_argv.argc, sent_argv.argv, prefix, + the_repository, NULL, 1); } __attribute__((format (printf, 1, 2))) diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c new file mode 100644 index 0000000000..6da8fa2607 --- /dev/null +++ b/builtin/upload-pack.c @@ -0,0 +1,74 @@ +#include "cache.h" +#include "builtin.h" +#include "exec-cmd.h" +#include "pkt-line.h" +#include "parse-options.h" +#include "protocol.h" +#include "upload-pack.h" +#include "serve.h" + +static const char * const upload_pack_usage[] = { + N_("git upload-pack [<options>] <dir>"), + NULL +}; + +int cmd_upload_pack(int argc, const char **argv, const char *prefix) +{ + const char *dir; + int strict = 0; + struct upload_pack_options opts = { 0 }; + struct serve_options serve_opts = SERVE_OPTIONS_INIT; + struct option options[] = { + OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc, + N_("quit after a single request/response exchange")), + OPT_BOOL(0, "advertise-refs", &opts.advertise_refs, + N_("exit immediately after initial ref advertisement")), + OPT_BOOL(0, "strict", &strict, + N_("do not try <directory>/.git/ if <directory> is no Git directory")), + OPT_INTEGER(0, "timeout", &opts.timeout, + N_("interrupt transfer after <n> seconds of inactivity")), + OPT_END() + }; + + packet_trace_identity("upload-pack"); + read_replace_refs = 0; + + argc = parse_options(argc, argv, prefix, options, upload_pack_usage, 0); + + if (argc != 1) + usage_with_options(upload_pack_usage, options); + + if (opts.timeout) + opts.daemon_mode = 1; + + setup_path(); + + dir = argv[0]; + + if (!enter_repo(dir, strict)) + die("'%s' does not appear to be a git repository", dir); + + switch (determine_protocol_version_server()) { + case protocol_v2: + serve_opts.advertise_capabilities = opts.advertise_refs; + serve_opts.stateless_rpc = opts.stateless_rpc; + serve(&serve_opts); + break; + case protocol_v1: + /* + * v1 is just the original protocol with a version string, + * so just fall through after writing the version string. + */ + if (opts.advertise_refs || !opts.stateless_rpc) + packet_write_fmt(1, "version 1\n"); + + /* fallthrough */ + case protocol_v0: + upload_pack(&opts); + break; + case protocol_unknown_version: + BUG("unknown protocol version"); + } + + return 0; +} diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c index dcdaada111..40c69a0bed 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -8,9 +8,10 @@ #include "cache.h" #include "config.h" #include "builtin.h" +#include "object-store.h" +#include "repository.h" #include "commit.h" #include "run-command.h" -#include <signal.h> #include "parse-options.h" #include "gpg-interface.h" @@ -19,14 +20,14 @@ static const char * const verify_commit_usage[] = { NULL }; -static int run_gpg_verify(const struct object_id *oid, const char *buf, unsigned long size, unsigned flags) +static int run_gpg_verify(struct commit *commit, unsigned flags) { struct signature_check signature_check; int ret; memset(&signature_check, 0, sizeof(signature_check)); - ret = check_commit_signature(lookup_commit(oid), &signature_check); + ret = check_commit_signature(commit, &signature_check); print_signature_buffer(&signature_check, flags); signature_check_clear(&signature_check); @@ -35,26 +36,20 @@ static int run_gpg_verify(const struct object_id *oid, const char *buf, unsigned static int verify_commit(const char *name, unsigned flags) { - enum object_type type; struct object_id oid; - char *buf; - unsigned long size; - int ret; + struct object *obj; if (get_oid(name, &oid)) return error("commit '%s' not found.", name); - buf = read_object_file(&oid, &type, &size); - if (!buf) + obj = parse_object(the_repository, &oid); + if (!obj) return error("%s: unable to read file.", name); - if (type != OBJ_COMMIT) + if (obj->type != OBJ_COMMIT) return error("%s: cannot verify a non-commit object of type %s.", - name, type_name(type)); - - ret = run_gpg_verify(&oid, buf, size, flags); + name, type_name(obj->type)); - free(buf); - return ret; + return run_gpg_verify((struct commit *)obj, flags); } static int git_verify_commit_config(const char *var, const char *value, void *cb) diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index ad7b79fa5c..f45136a06b 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -10,7 +10,6 @@ #include "builtin.h" #include "tag.h" #include "run-command.h" -#include <signal.h> #include "parse-options.h" #include "gpg-interface.h" #include "ref-filter.h" @@ -72,7 +71,7 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) } if (format.format) - pretty_print_ref(name, oid.hash, &format); + pretty_print_ref(name, &oid, &format); } return had_error; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 40a438ed6c..d99db35668 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -9,7 +9,7 @@ #include "refs.h" #include "run-command.h" #include "sigchain.h" -#include "refs.h" +#include "submodule.h" #include "utf8.h" #include "worktree.h" @@ -27,10 +27,9 @@ static const char * const worktree_usage[] = { struct add_opts { int force; int detach; + int quiet; int checkout; int keep_locked; - const char *new_branch; - int force_new_branch; }; static int show_only; @@ -48,6 +47,26 @@ static int git_worktree_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int delete_git_dir(const char *id) +{ + struct strbuf sb = STRBUF_INIT; + int ret; + + strbuf_addstr(&sb, git_common_path("worktrees/%s", id)); + ret = remove_dir_recursively(&sb, 0); + if (ret < 0 && errno == ENOTDIR) + ret = unlink(sb.buf); + if (ret) + error_errno(_("failed to delete '%s'"), sb.buf); + strbuf_release(&sb); + return ret; +} + +static void delete_worktrees_dir_if_empty(void) +{ + rmdir(git_path("worktrees")); /* ignore failed removal */ +} + static int prune_worktree(const char *id, struct strbuf *reason) { struct stat st; @@ -117,10 +136,8 @@ static int prune_worktree(const char *id, struct strbuf *reason) static void prune_worktrees(void) { struct strbuf reason = STRBUF_INIT; - struct strbuf path = STRBUF_INIT; DIR *dir = opendir(git_path("worktrees")); struct dirent *d; - int ret; if (!dir) return; while ((d = readdir(dir)) != NULL) { @@ -133,18 +150,12 @@ static void prune_worktrees(void) printf("%s\n", reason.buf); if (show_only) continue; - git_path_buf(&path, "worktrees/%s", d->d_name); - ret = remove_dir_recursively(&path, 0); - if (ret < 0 && errno == ENOTDIR) - ret = unlink(path.buf); - if (ret) - error_errno(_("failed to remove '%s'"), path.buf); + delete_git_dir(d->d_name); } closedir(dir); if (!show_only) - rmdir(git_path("worktrees")); + delete_worktrees_dir_if_empty(); strbuf_release(&reason); - strbuf_release(&path); } static int prune(int ac, const char **av, const char *prefix) @@ -213,22 +224,52 @@ static const char *worktree_basename(const char *path, int *olen) return name; } +static void validate_worktree_add(const char *path, const struct add_opts *opts) +{ + struct worktree **worktrees; + struct worktree *wt; + int locked; + + if (file_exists(path) && !is_empty_dir(path)) + die(_("'%s' already exists"), path); + + worktrees = get_worktrees(0); + wt = find_worktree_by_path(worktrees, path); + if (!wt) + goto done; + + locked = !!worktree_lock_reason(wt); + if ((!locked && opts->force) || (locked && opts->force > 1)) { + if (delete_git_dir(wt->id)) + die(_("unable to re-add worktree '%s'"), path); + goto done; + } + + if (locked) + die(_("'%s' is a missing but locked worktree;\nuse 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path); + else + die(_("'%s' is a missing but already registered worktree;\nuse 'add -f' to override, or 'prune' or 'remove' to clear"), path); + +done: + free_worktrees(worktrees); +} + 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 stat st; struct child_process cp = CHILD_PROCESS_INIT; struct argv_array child_env = ARGV_ARRAY_INIT; - int counter = 0, len, ret; + unsigned int counter = 0; + int len, ret; struct strbuf symref = STRBUF_INIT; struct commit *commit = NULL; int is_branch = 0; + struct strbuf sb_name = STRBUF_INIT; - if (file_exists(path) && !is_empty_dir(path)) - die(_("'%s' already exists"), path); + validate_worktree_add(path, opts); /* is 'refname' a branch or commit? */ if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && @@ -242,13 +283,23 @@ static int add_worktree(const char *path, const char *refname, die(_("invalid reference: %s"), refname); name = worktree_basename(path, &len); - git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name); + strbuf_add(&sb, name, path + len - name); + sanitize_refname_component(sb.buf, &sb_name); + if (!sb_name.len) + BUG("How come '%s' becomes empty after sanitization?", sb.buf); + strbuf_reset(&sb); + name = sb_name.buf; + git_path_buf(&sb_repo, "worktrees/%s", name); len = sb_repo.len; if (safe_create_leading_directories_const(sb_repo.buf)) die_errno(_("could not create leading directories of '%s'"), sb_repo.buf); - while (!stat(sb_repo.buf, &st)) { + + while (mkdir(sb_repo.buf, 0777)) { counter++; + if ((errno != EEXIST) || !counter /* overflow */) + die_errno(_("could not create directory of '%s'"), + sb_repo.buf); strbuf_setlen(&sb_repo, len); strbuf_addf(&sb_repo, "%d", counter); } @@ -258,8 +309,6 @@ static int add_worktree(const char *path, const char *refname, atexit(remove_junk); sigchain_push_common(remove_junk_on_signal); - if (mkdir(sb_repo.buf, 0777)) - die_errno(_("could not create directory of '%s'"), sb_repo.buf); junk_git_dir = xstrdup(sb_repo.buf); is_junk = 1; @@ -281,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 @@ -293,13 +344,11 @@ static int add_worktree(const char *path, const char *refname, */ strbuf_reset(&sb); strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); - write_file(sb.buf, "%s", sha1_to_hex(null_sha1)); + write_file(sb.buf, "%s", oid_to_hex(&null_oid)); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); - fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name); - argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf); argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); cp.git_cmd = 1; @@ -307,9 +356,13 @@ static int add_worktree(const char *path, const char *refname, if (!is_branch) argv_array_pushl(&cp.args, "update-ref", "HEAD", oid_to_hex(&commit->object.oid), NULL); - else + else { argv_array_pushl(&cp.args, "symbolic-ref", "HEAD", symref.buf, NULL); + if (opts->quiet) + argv_array_push(&cp.args, "--quiet"); + } + cp.env = child_env.argv; ret = run_command(&cp); if (ret) @@ -318,7 +371,9 @@ static int add_worktree(const char *path, const char *refname, if (opts->checkout) { cp.argv = NULL; argv_array_clear(&cp.args); - argv_array_pushl(&cp.args, "reset", "--hard", NULL); + argv_array_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL); + if (opts->quiet) + argv_array_push(&cp.args, "--quiet"); cp.env = child_env.argv; ret = run_command(&cp); if (ret) @@ -350,6 +405,7 @@ done: cp.dir = path; cp.env = env; cp.argv = NULL; + cp.trace2_hook_name = "post-checkout"; argv_array_pushl(&cp.args, absolute_path(hook), oid_to_hex(&null_oid), oid_to_hex(&commit->object.oid), @@ -363,27 +419,87 @@ done: strbuf_release(&symref); strbuf_release(&sb_repo); strbuf_release(&sb_git); + strbuf_release(&sb_name); + strbuf_release(&realpath); return ret; } +static void print_preparing_worktree_line(int detach, + const char *branch, + const char *new_branch, + int force_new_branch) +{ + if (force_new_branch) { + struct commit *commit = lookup_commit_reference_by_name(new_branch); + if (!commit) + printf_ln(_("Preparing worktree (new branch '%s')"), new_branch); + else + printf_ln(_("Preparing worktree (resetting branch '%s'; was at %s)"), + new_branch, + find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV)); + } else if (new_branch) { + printf_ln(_("Preparing worktree (new branch '%s')"), new_branch); + } else { + struct strbuf s = STRBUF_INIT; + if (!detach && !strbuf_check_branch_ref(&s, branch) && + ref_exists(s.buf)) + printf_ln(_("Preparing worktree (checking out '%s')"), + branch); + else { + struct commit *commit = lookup_commit_reference_by_name(branch); + if (!commit) + die(_("invalid reference: %s"), branch); + printf_ln(_("Preparing worktree (detached HEAD %s)"), + find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV)); + } + strbuf_release(&s); + } +} + +static const char *dwim_branch(const char *path, const char **new_branch) +{ + int n; + const char *s = worktree_basename(path, &n); + const char *branchname = xstrndup(s, n); + struct strbuf ref = STRBUF_INIT; + + UNLEAK(branchname); + if (!strbuf_check_branch_ref(&ref, branchname) && + ref_exists(ref.buf)) { + strbuf_release(&ref); + return branchname; + } + + *new_branch = branchname; + if (guess_remote) { + struct object_id oid; + const char *remote = + unique_tracking_name(*new_branch, &oid, NULL); + return remote; + } + return NULL; +} + static int add(int ac, const char **av, const char *prefix) { struct add_opts opts; const char *new_branch_force = NULL; char *path; const char *branch; + const char *new_branch = NULL; const char *opt_track = NULL; struct option options[] = { OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree"), PARSE_OPT_NOCOMPLETE), - OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), + OPT_STRING('b', NULL, &new_branch, N_("branch"), N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), N_("create or reset a branch")), OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")), + OPT__QUIET(&opts.quiet, N_("suppress progress reporting")), OPT_PASSTHRU(0, "track", &opt_track, NULL, N_("set up tracking mode (see git-branch(1))"), PARSE_OPT_NOARG | PARSE_OPT_OPTARG), @@ -395,7 +511,7 @@ static int add(int ac, const char **av, const char *prefix) memset(&opts, 0, sizeof(opts)); opts.checkout = 1; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); - if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1) + if (!!opts.detach + !!new_branch + !!new_branch_force > 1) die(_("-b, -B, and --detach are mutually exclusive")); if (ac < 1 || ac > 2) usage_with_options(worktree_usage, options); @@ -406,60 +522,56 @@ static int add(int ac, const char **av, const char *prefix) if (!strcmp(branch, "-")) branch = "@{-1}"; - opts.force_new_branch = !!new_branch_force; - if (opts.force_new_branch) { + if (new_branch_force) { struct strbuf symref = STRBUF_INIT; - opts.new_branch = new_branch_force; + new_branch = new_branch_force; if (!opts.force && - !strbuf_check_branch_ref(&symref, opts.new_branch) && + !strbuf_check_branch_ref(&symref, new_branch) && ref_exists(symref.buf)) die_if_checked_out(symref.buf, 0); strbuf_release(&symref); } - if (ac < 2 && !opts.new_branch && !opts.detach) { - int n; - const char *s = worktree_basename(path, &n); - opts.new_branch = xstrndup(s, n); - if (guess_remote) { - struct object_id oid; - const char *remote = - unique_tracking_name(opts.new_branch, &oid); - if (remote) - branch = remote; - } + if (ac < 2 && !new_branch && !opts.detach) { + const char *s = dwim_branch(path, &new_branch); + if (s) + branch = s; } - if (ac == 2 && !opts.new_branch && !opts.detach) { + if (ac == 2 && !new_branch && !opts.detach) { struct object_id oid; struct commit *commit; const char *remote; commit = lookup_commit_reference_by_name(branch); if (!commit) { - remote = unique_tracking_name(branch, &oid); + remote = unique_tracking_name(branch, &oid, NULL); if (remote) { - opts.new_branch = branch; + new_branch = branch; branch = remote; } } } + if (!opts.quiet) + print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force); - if (opts.new_branch) { + if (new_branch) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; argv_array_push(&cp.args, "branch"); - if (opts.force_new_branch) + if (new_branch_force) argv_array_push(&cp.args, "--force"); - argv_array_push(&cp.args, opts.new_branch); + if (opts.quiet) + argv_array_push(&cp.args, "--quiet"); + argv_array_push(&cp.args, new_branch); argv_array_push(&cp.args, branch); if (opt_track) argv_array_push(&cp.args, opt_track); if (run_command(&cp)) return -1; - branch = opts.new_branch; + branch = new_branch; } else if (opt_track) { die(_("--[no-]track can only be used if a new branch is created")); } @@ -577,7 +689,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix) if (is_main_worktree(wt)) die(_("The main working tree cannot be locked or unlocked")); - old_reason = is_worktree_locked(wt); + old_reason = worktree_lock_reason(wt); if (old_reason) { if (*old_reason) die(_("'%s' is already locked, reason: %s"), @@ -609,7 +721,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) die(_("'%s' is not a working tree"), av[0]); if (is_main_worktree(wt)) die(_("The main working tree cannot be locked or unlocked")); - if (!is_worktree_locked(wt)) + if (!worktree_lock_reason(wt)) die(_("'%s' is not locked"), av[0]); ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id)); free_worktrees(worktrees); @@ -619,20 +731,36 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) static void validate_no_submodules(const struct worktree *wt) { struct index_state istate = { NULL }; + struct strbuf path = STRBUF_INIT; int i, found_submodules = 0; - if (read_index_from(&istate, worktree_git_path(wt, "index"), - get_worktree_git_dir(wt)) > 0) { + if (is_directory(worktree_git_path(wt, "modules"))) { + /* + * There could be false positives, e.g. the "modules" + * directory exists but is empty. But it's a rare case and + * this simpler check is probably good enough for now. + */ + found_submodules = 1; + } else if (read_index_from(&istate, worktree_git_path(wt, "index"), + get_worktree_git_dir(wt)) > 0) { for (i = 0; i < istate.cache_nr; i++) { struct cache_entry *ce = istate.cache[i]; + int err; - if (S_ISGITLINK(ce->ce_mode)) { - found_submodules = 1; - break; - } + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + strbuf_reset(&path); + strbuf_addf(&path, "%s/%s", wt->path, ce->name); + if (!is_submodule_populated_gently(path.buf, &err)) + continue; + + found_submodules = 1; + break; } } discard_index(&istate); + strbuf_release(&path); if (found_submodules) die(_("working trees containing submodules cannot be moved or removed")); @@ -640,13 +768,17 @@ static void validate_no_submodules(const struct worktree *wt) static int move_worktree(int ac, const char **av, const char *prefix) { + int force = 0; struct option options[] = { + OPT__FORCE(&force, + N_("force move even if worktree is dirty or locked"), + PARSE_OPT_NOCOMPLETE), OPT_END() }; struct worktree **worktrees, *wt; struct strbuf dst = STRBUF_INIT; struct strbuf errmsg = STRBUF_INIT; - const char *reason; + const char *reason = NULL; char *path; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); @@ -677,12 +809,13 @@ static int move_worktree(int ac, const char **av, const char *prefix) validate_no_submodules(wt); - reason = is_worktree_locked(wt); + if (force < 2) + reason = worktree_lock_reason(wt); if (reason) { if (*reason) - die(_("cannot move a locked working tree, lock reason: %s"), + die(_("cannot move a locked working tree, lock reason: %s\nuse 'move -f -f' to override or unlock first"), reason); - die(_("cannot move a locked working tree")); + die(_("cannot move a locked working tree;\nuse 'move -f -f' to override or unlock first")); } if (validate_worktree(wt, &errmsg, 0)) die(_("validation failed, cannot move working tree: %s"), @@ -742,7 +875,7 @@ static void check_clean_worktree(struct worktree *wt, original_path); ret = xread(cp.out, buf, sizeof(buf)); if (ret) - die(_("'%s' is dirty, use --force to delete it"), + die(_("'%s' contains modified or untracked files, use --force to delete it"), original_path); close(cp.out); ret = finish_command(&cp); @@ -765,31 +898,18 @@ static int delete_git_work_tree(struct worktree *wt) return ret; } -static int delete_git_dir(struct worktree *wt) -{ - struct strbuf sb = STRBUF_INIT; - int ret = 0; - - strbuf_addstr(&sb, git_common_path("worktrees/%s", wt->id)); - if (remove_dir_recursively(&sb, 0)) { - error_errno(_("failed to delete '%s'"), sb.buf); - ret = -1; - } - strbuf_release(&sb); - return ret; -} - static int remove_worktree(int ac, const char **av, const char *prefix) { int force = 0; struct option options[] = { - OPT_BOOL(0, "force", &force, - N_("force removing even if the worktree is dirty")), + OPT__FORCE(&force, + N_("force removal even if worktree is dirty or locked"), + PARSE_OPT_NOCOMPLETE), OPT_END() }; struct worktree **worktrees, *wt; struct strbuf errmsg = STRBUF_INIT; - const char *reason; + const char *reason = NULL; int ret = 0; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); @@ -802,12 +922,13 @@ static int remove_worktree(int ac, const char **av, const char *prefix) die(_("'%s' is not a working tree"), av[0]); if (is_main_worktree(wt)) die(_("'%s' is a main working tree"), av[0]); - reason = is_worktree_locked(wt); + if (force < 2) + reason = worktree_lock_reason(wt); if (reason) { if (*reason) - die(_("cannot remove a locked working tree, lock reason: %s"), + die(_("cannot remove a locked working tree, lock reason: %s\nuse 'remove -f -f' to override or unlock first"), reason); - die(_("cannot remove a locked working tree")); + die(_("cannot remove a locked working tree;\nuse 'remove -f -f' to override or unlock first")); } if (validate_worktree(wt, &errmsg, WT_VALIDATE_WORKTREE_MISSING_OK)) die(_("validation failed, cannot remove working tree: %s"), @@ -824,7 +945,8 @@ static int remove_worktree(int ac, const char **av, const char *prefix) * continue on even if ret is non-zero, there's no going back * from here. */ - ret |= delete_git_dir(wt); + ret |= delete_git_dir(wt->id); + delete_worktrees_dir_if_empty(); free_worktrees(worktrees); return ret; diff --git a/builtin/write-tree.c b/builtin/write-tree.c index c9d3c544e7..45d61707e7 100644 --- a/builtin/write-tree.c +++ b/builtin/write-tree.c @@ -3,6 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "cache.h" #include "config.h" @@ -15,18 +16,17 @@ static const char * const write_tree_usage[] = { NULL }; -int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) +int cmd_write_tree(int argc, const char **argv, const char *cmd_prefix) { int flags = 0, ret; - const char *prefix = NULL; + const char *tree_prefix = NULL; struct object_id oid; const char *me = "git-write-tree"; struct option write_tree_options[] = { OPT_BIT(0, "missing-ok", &flags, N_("allow missing objects"), WRITE_TREE_MISSING_OK), - { OPTION_STRING, 0, "prefix", &prefix, N_("<prefix>/"), - N_("write tree object for a subdirectory <prefix>") , - PARSE_OPT_LITERAL_ARGHELP }, + OPT_STRING(0, "prefix", &tree_prefix, N_("<prefix>/"), + N_("write tree object for a subdirectory <prefix>")), { OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL, N_("only useful for debugging"), PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, NULL, @@ -35,10 +35,10 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) }; git_config(git_default_config, NULL); - argc = parse_options(argc, argv, unused_prefix, write_tree_options, + argc = parse_options(argc, argv, cmd_prefix, write_tree_options, write_tree_usage, 0); - ret = write_cache_as_tree(&oid, flags, prefix); + ret = write_cache_as_tree(&oid, flags, tree_prefix); switch (ret) { case 0: printf("%s\n", oid_to_hex(&oid)); @@ -50,7 +50,7 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) die("%s: error building trees", me); break; case WRITE_TREE_PREFIX_ERROR: - die("%s: prefix %s not found", me, prefix); + die("%s: prefix %s not found", me, tree_prefix); break; } return ret; |