diff options
Diffstat (limited to 'builtin')
98 files changed, 5012 insertions, 5099 deletions
diff --git a/builtin/add.c b/builtin/add.c index 9f53f020d0..9ef7fb02d5 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -4,6 +4,7 @@ * Copyright (C) 2006 Linus Torvalds */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "lockfile.h" #include "dir.h" @@ -17,6 +18,7 @@ #include "revision.h" #include "bulk-checkin.h" #include "argv-array.h" +#include "submodule.h" static const char * const builtin_add_usage[] = { N_("git add [<options>] [--] <pathspec>..."), @@ -24,13 +26,14 @@ static const char * const builtin_add_usage[] = { }; static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; +static int add_renormalize; struct update_callback_data { int flags; int add_errors; }; -static void chmod_pathspec(struct pathspec *pathspec, int force_mode) +static void chmod_pathspec(struct pathspec *pathspec, char flip) { int i; @@ -40,8 +43,8 @@ static void chmod_pathspec(struct pathspec *pathspec, int force_mode) if (pathspec && !ce_path_match(ce, pathspec, NULL)) continue; - if (chmod_cache_entry(ce, force_mode) < 0) - fprintf(stderr, "cannot chmod '%s'", ce->name); + if (chmod_cache_entry(ce, flip) < 0) + fprintf(stderr, "cannot chmod %cx '%s'\n", flip, ce->name); } } @@ -114,11 +117,32 @@ int add_files_to_cache(const char *prefix, rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; rev.diffopt.format_callback_data = &data; + rev.diffopt.flags.override_submodule_config = 1; rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); + clear_pathspec(&rev.prune_data); return !!data.add_errors; } +static int renormalize_tracked_files(const struct pathspec *pathspec, int flags) +{ + int i, retval = 0; + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + + if (ce_stage(ce)) + 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)) + continue; + retval |= add_file_to_cache(ce->name, flags | HASH_RENORMALIZE); + } + + return retval; +} + static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix) { char *seen; @@ -135,7 +159,7 @@ static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, *dst++ = entry; } dir->nr = dst - dir->entries; - add_pathspec_matches_against_index(pathspec, seen); + add_pathspec_matches_against_index(pathspec, &the_index, seen); return seen; } @@ -214,7 +238,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_PATCH; rev.diffopt.use_color = 0; - DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); + rev.diffopt.flags.ignore_dirty_submodules = 1; out = open(file, O_CREAT | O_WRONLY, 0666); if (out < 0) die(_("Could not open '%s' for writing."), file); @@ -248,6 +272,7 @@ N_("The following paths are ignored by one of your .gitignore files:\n"); static int verbose, show_only, ignored_too, refresh_only; static int ignore_add_errors, intent_to_add, ignore_missing; +static int warn_on_embedded_repo = 1; #define ADDREMOVE_DEFAULT 1 static int addremove = ADDREMOVE_DEFAULT; @@ -269,8 +294,9 @@ static struct option builtin_add_options[] = { OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")), OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")), OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")), - OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")), + OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0), OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")), + 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, @@ -281,6 +307,8 @@ static struct option builtin_add_options[] = { 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_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo, + N_("warn when adding an embedded repository")), OPT_END(), }; @@ -294,6 +322,45 @@ static int add_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static const char embedded_advice[] = N_( +"You've added another git repository inside your current repository.\n" +"Clones of the outer repository will not contain the contents of\n" +"the embedded repository and will not know how to obtain it.\n" +"If you meant to add a submodule, use:\n" +"\n" +" git submodule add <url> %s\n" +"\n" +"If you added this path by mistake, you can remove it from the\n" +"index with:\n" +"\n" +" git rm --cached %s\n" +"\n" +"See \"git help submodule\" for more information." +); + +static void check_embedded_repo(const char *path) +{ + struct strbuf name = STRBUF_INIT; + + if (!warn_on_embedded_repo) + return; + if (!ends_with(path, "/")) + return; + + /* Drop trailing slash for aesthetics */ + strbuf_addstr(&name, path); + strbuf_strip_suffix(&name, "/"); + + warning(_("adding embedded git repository: %s"), name.buf); + if (advice_add_embedded_repo) { + advise(embedded_advice, name.buf, name.buf); + /* there may be multiple entries; advise only once */ + advice_add_embedded_repo = 0; + } + + strbuf_release(&name); +} + static int add_files(struct dir_struct *dir, int flags) { int i, exit_status = 0; @@ -306,12 +373,14 @@ static int add_files(struct dir_struct *dir, int flags) exit_status = 1; } - for (i = 0; i < dir->nr; i++) + 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; } + } return exit_status; } @@ -358,7 +427,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) chmod_arg[1] != 'x' || chmod_arg[2])) die(_("--chmod param '%s' must be either -x or +x"), chmod_arg); - add_new_files = !take_worktree_changes && !refresh_only; + add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize; require_pathspec = !(take_worktree_changes || (0 < addremove_explicit)); hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); @@ -379,16 +448,19 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (read_cache() < 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 | - PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE, + PATHSPEC_SYMLINK_LEADING_PATH, prefix, argv); + die_path_inside_submodule(&the_index, &pathspec); + if (add_new_files) { int baselen; @@ -400,7 +472,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) } /* This picks up the paths that are not tracked */ - baselen = fill_directory(&dir, &pathspec); + baselen = fill_directory(&dir, &the_index, &pathspec); if (pathspec.nr) seen = prune_directory(&dir, &pathspec, baselen); } @@ -414,7 +486,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) int i; if (!seen) - seen = find_pathspecs_matching_against_index(&pathspec); + seen = find_pathspecs_matching_against_index(&pathspec, &the_index); /* * file_exists() assumes exact match @@ -436,8 +508,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) !file_exists(path))) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (is_excluded(&dir, path, &dtype)) - dir_add_ignored(&dir, path, pathspec.items[i].len); + if (is_excluded(&dir, &the_index, path, &dtype)) + dir_add_ignored(&dir, &the_index, + path, pathspec.items[i].len); } else die(_("pathspec '%s' did not match any files"), pathspec.items[i].original); @@ -448,7 +521,10 @@ int cmd_add(int argc, const char **argv, const char *prefix) plug_bulk_checkin(); - exit_status |= add_files_to_cache(prefix, &pathspec, flags); + if (add_renormalize) + exit_status |= renormalize_tracked_files(&pathspec, flags); + else + exit_status |= add_files_to_cache(prefix, &pathspec, flags); if (add_new_files) exit_status |= add_files(&dir, flags); @@ -458,10 +534,11 @@ int cmd_add(int argc, const char **argv, const char *prefix) unplug_bulk_checkin(); finish: - if (active_cache_changed) { - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) - die(_("Unable to write new index file")); - } + if (write_locked_index(&the_index, &lock_file, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) + die(_("Unable to write new index file")); + UNLEAK(pathspec); + UNLEAK(dir); return exit_status; } diff --git a/builtin/am.c b/builtin/am.c index 593c1b5dda..1151b5c73a 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -4,6 +4,7 @@ * Based on git-am.sh by Junio C Hamano. */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" @@ -30,6 +31,7 @@ #include "mailinfo.h" #include "apply.h" #include "string-list.h" +#include "packfile.h" /** * Returns 1 if the file is empty or does not exist, 0 otherwise. @@ -430,6 +432,14 @@ static void am_load(struct am_state *state) read_state_file(&sb, state, "utf8", 1); state->utf8 = !strcmp(sb.buf, "t"); + if (file_exists(am_path(state, "rerere-autoupdate"))) { + read_state_file(&sb, state, "rerere-autoupdate", 1); + state->allow_rerere_autoupdate = strcmp(sb.buf, "t") ? + RERERE_NOAUTOUPDATE : RERERE_AUTOUPDATE; + } else { + state->allow_rerere_autoupdate = 0; + } + read_state_file(&sb, state, "keep", 1); if (!strcmp(sb.buf, "t")) state->keep = KEEP_TRUE; @@ -483,8 +493,7 @@ static int run_applypatch_msg_hook(struct am_state *state) ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL); if (!ret) { - free(state->msg); - state->msg = NULL; + FREE_AND_NULL(state->msg); if (read_commit_msg(state) < 0) die(_("'%s' was deleted by the applypatch-msg hook"), am_path(state, "final-commit")); @@ -563,7 +572,7 @@ static int copy_notes_for_rebase(const struct am_state *state) goto finish; } - if (copy_note_for_rewrite(c, from_obj.hash, to_obj.hash)) + if (copy_note_for_rewrite(c, &from_obj, &to_obj)) ret = error(_("Failed to copy notes from '%s' to '%s'"), oid_to_hex(&from_obj), oid_to_hex(&to_obj)); } @@ -662,9 +671,7 @@ static int detect_patch_format(const char **paths) goto done; } - strbuf_reset(&l2); strbuf_getline(&l2, fp); - strbuf_reset(&l3); strbuf_getline(&l3, fp); /* @@ -687,6 +694,8 @@ static int detect_patch_format(const char **paths) done: fclose(fp); strbuf_release(&l1); + strbuf_release(&l2); + strbuf_release(&l3); return ret; } @@ -699,6 +708,7 @@ static int split_mail_mbox(struct am_state *state, const char **paths, { struct child_process cp = CHILD_PROCESS_INIT; struct strbuf last = STRBUF_INIT; + int ret; cp.git_cmd = 1; argv_array_push(&cp.args, "mailsplit"); @@ -712,13 +722,16 @@ static int split_mail_mbox(struct am_state *state, const char **paths, argv_array_push(&cp.args, "--"); argv_array_pushv(&cp.args, paths); - if (capture_command(&cp, &last, 8)) - return -1; + ret = capture_command(&cp, &last, 8); + if (ret) + goto exit; state->cur = 1; state->last = strtol(last.buf, NULL, 10); - return 0; +exit: + strbuf_release(&last); + return ret ? -1 : 0; } /** @@ -872,6 +885,7 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths, static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr) { struct strbuf sb = STRBUF_INIT; + int rc = 0; while (!strbuf_getline_lf(&sb, in)) { const char *str; @@ -879,25 +893,33 @@ static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr) if (skip_prefix(sb.buf, "# User ", &str)) fprintf(out, "From: %s\n", str); else if (skip_prefix(sb.buf, "# Date ", &str)) { - unsigned long timestamp; + timestamp_t timestamp; long tz, tz2; char *end; errno = 0; - timestamp = strtoul(str, &end, 10); - if (errno) - return error(_("invalid timestamp")); + timestamp = parse_timestamp(str, &end, 10); + if (errno) { + rc = error(_("invalid timestamp")); + goto exit; + } - if (!skip_prefix(end, " ", &str)) - return error(_("invalid Date line")); + if (!skip_prefix(end, " ", &str)) { + rc = error(_("invalid Date line")); + goto exit; + } errno = 0; tz = strtol(str, &end, 10); - if (errno) - return error(_("invalid timezone offset")); + if (errno) { + rc = error(_("invalid timezone offset")); + goto exit; + } - if (*end) - return error(_("invalid Date line")); + if (*end) { + rc = error(_("invalid Date line")); + goto exit; + } /* * mercurial's timezone is in seconds west of UTC, @@ -922,9 +944,9 @@ static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr) fwrite(sb.buf, 1, sb.len, out); strbuf_reset(&sb); } - +exit: strbuf_release(&sb); - return 0; + return rc; } /** @@ -989,6 +1011,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, if (mkdir(state->dir, 0777) < 0 && errno != EEXIST) die_errno(_("failed to create directory '%s'"), state->dir); + delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); if (split_mail(state, patch_format, paths, keep_cr) < 0) { am_destroy(state); @@ -1003,6 +1026,10 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, write_state_bool(state, "sign", state->signoff); write_state_bool(state, "utf8", state->utf8); + if (state->allow_rerere_autoupdate) + write_state_bool(state, "rerere-autoupdate", + state->allow_rerere_autoupdate == RERERE_AUTOUPDATE); + switch (state->keep) { case KEEP_FALSE: str = "f"; @@ -1035,7 +1062,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, } write_state_text(state, "scissors", str); - sq_quote_argv(&sb, state->git_apply_opts.argv, 0); + sq_quote_argv(&sb, state->git_apply_opts.argv); write_state_text(state, "apply-opt", sb.buf); if (state->rebasing) @@ -1046,8 +1073,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, if (!get_oid("HEAD", &curr_head)) { write_state_text(state, "abort-safety", oid_to_hex(&curr_head)); if (!state->rebasing) - update_ref_oid("am", "ORIG_HEAD", &curr_head, NULL, 0, - UPDATE_REFS_DIE_ON_ERR); + update_ref("am", "ORIG_HEAD", &curr_head, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); } else { write_state_text(state, "abort-safety", ""); if (!state->rebasing) @@ -1073,17 +1100,10 @@ static void am_next(struct am_state *state) { struct object_id head; - free(state->author_name); - state->author_name = NULL; - - free(state->author_email); - state->author_email = NULL; - - free(state->author_date); - state->author_date = NULL; - - free(state->msg); - state->msg = NULL; + FREE_AND_NULL(state->author_name); + FREE_AND_NULL(state->author_email); + FREE_AND_NULL(state->author_date); + FREE_AND_NULL(state->msg); state->msg_len = 0; unlink(am_path(state, "author-script")); @@ -1091,6 +1111,7 @@ static void am_next(struct am_state *state) oidclr(&state->orig_commit); unlink(am_path(state, "original-commit")); + delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); if (!get_oid("HEAD", &head)) write_state_text(state, "abort-safety", oid_to_hex(&head)); @@ -1119,52 +1140,15 @@ static const char *msgnum(const struct am_state *state) */ static void refresh_and_write_cache(void) { - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + struct lock_file lock_file = LOCK_INIT; - hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write index file")); } /** - * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn - * branch, returns 1 if there are entries in the index, 0 otherwise. If an - * strbuf is provided, the space-separated list of files that differ will be - * appended to it. - */ -static int index_has_changes(struct strbuf *sb) -{ - struct object_id head; - int i; - - if (!get_sha1_tree("HEAD", head.hash)) { - struct diff_options opt; - - diff_setup(&opt); - DIFF_OPT_SET(&opt, EXIT_WITH_STATUS); - if (!sb) - DIFF_OPT_SET(&opt, QUICK); - do_diff_cache(head.hash, &opt); - diffcore_std(&opt); - for (i = 0; sb && i < diff_queued_diff.nr; i++) { - if (i) - strbuf_addch(sb, ' '); - strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path); - } - diff_flush(&opt); - return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0; - } else { - for (i = 0; sb && i < active_nr; i++) { - if (i) - strbuf_addch(sb, ' '); - strbuf_addstr(sb, active_cache[i]->name); - } - return !!active_nr; - } -} - -/** * Dies with a user-friendly message on how to proceed after resolving the * problem. This message can be overridden with state->resolvemsg. */ @@ -1188,34 +1172,10 @@ static void NORETURN die_user_resolve(const struct am_state *state) */ static void am_append_signoff(struct am_state *state) { - char *cp; - struct strbuf mine = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len); - - /* our sign-off */ - strbuf_addf(&mine, "\n%s%s\n", - sign_off_header, - fmt_name(getenv("GIT_COMMITTER_NAME"), - getenv("GIT_COMMITTER_EMAIL"))); - - /* Does sb end with it already? */ - if (mine.len < sb.len && - !strcmp(mine.buf, sb.buf + sb.len - mine.len)) - goto exit; /* no need to duplicate */ - - /* Does it have any Signed-off-by: in the text */ - for (cp = sb.buf; - cp && *cp && (cp = strstr(cp, sign_off_header)) != NULL; - cp = strchr(cp, '\n')) { - if (sb.buf == cp || cp[-1] == '\n') - break; - } - - strbuf_addstr(&sb, mine.buf + !!cp); -exit: - strbuf_release(&mine); + append_signoff(&sb, 0, 0); state->msg = strbuf_detach(&sb, &state->msg_len); } @@ -1275,12 +1235,8 @@ static int parse_mail(struct am_state *state, const char *mail) die("BUG: invalid value for state->scissors"); } - mi.input = fopen(mail, "r"); - if (!mi.input) - die("could not open input"); - mi.output = fopen(am_path(state, "info"), "w"); - if (!mi.output) - die("could not open output 'info'"); + mi.input = xfopen(mail, "r"); + mi.output = xfopen(am_path(state, "info"), "w"); if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch"))) die("could not parse patch"); @@ -1422,8 +1378,8 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm rev_info.show_root_diff = 1; rev_info.diffopt.output_format = DIFF_FORMAT_PATCH; rev_info.no_commit_id = 1; - DIFF_OPT_SET(&rev_info.diffopt, BINARY); - DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX); + rev_info.diffopt.flags.binary = 1; + rev_info.diffopt.flags.full_index = 1; rev_info.diffopt.use_color = 0; rev_info.diffopt.file = fp; rev_info.diffopt.close_file = 1; @@ -1443,10 +1399,10 @@ static void write_index_patch(const struct am_state *state) struct rev_info rev_info; FILE *fp; - if (!get_sha1_tree("HEAD", head.hash)) - tree = lookup_tree(head.hash); + if (!get_oid_tree("HEAD", &head)) + tree = lookup_tree(&head); else - tree = lookup_tree(EMPTY_TREE_SHA1_BIN); + tree = lookup_tree(the_hash_algo->empty_tree); fp = xfopen(am_path(state, "patch"), "w"); init_revisions(&rev_info, NULL); @@ -1479,7 +1435,7 @@ static int parse_mail_rebase(struct am_state *state, const char *mail) if (get_mail_commit_oid(&commit_oid, mail) < 0) die(_("could not parse %s"), mail); - commit = lookup_commit_or_die(commit_oid.hash, mail); + commit = lookup_commit_or_die(&commit_oid, mail); get_commit_info(state, commit); @@ -1487,6 +1443,8 @@ static int parse_mail_rebase(struct am_state *state, const char *mail) oidcpy(&state->orig_commit, &commit_oid); write_state_text(state, "original-commit", oid_to_hex(&commit_oid)); + update_ref("am", "REBASE_HEAD", &commit_oid, + NULL, REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR); return 0; } @@ -1501,11 +1459,10 @@ static int run_apply(const struct am_state *state, const char *index_file) struct argv_array apply_opts = ARGV_ARRAY_INIT; struct apply_state apply_state; int res, opts_left; - static struct lock_file lock_file; int force_apply = 0; int options = 0; - if (init_apply_state(&apply_state, NULL, &lock_file)) + if (init_apply_state(&apply_state, NULL)) die("BUG: init_apply_state() failed"); argv_array_push(&apply_opts, "apply"); @@ -1609,7 +1566,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa init_revisions(&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); - add_pending_sha1(&rev_info, "HEAD", our_tree.hash, 0); + add_pending_oid(&rev_info, "HEAD", &our_tree, 0); diff_setup_done(&rev_info.diffopt); run_diff_index(&rev_info, 1); } @@ -1672,9 +1629,9 @@ static void do_commit(const struct am_state *state) if (write_cache_as_tree(tree.hash, 0, NULL)) die(_("git write-tree failed to write a tree")); - if (!get_sha1_commit("HEAD", parent.hash)) { + if (!get_oid_commit("HEAD", &parent)) { old_oid = &parent; - commit_list_insert(lookup_commit(parent.hash), &parents); + commit_list_insert(lookup_commit(&parent), &parents); } else { old_oid = NULL; say(state, stderr, _("applying to an empty history")); @@ -1688,8 +1645,8 @@ static void do_commit(const struct am_state *state) setenv("GIT_COMMITTER_DATE", state->ignore_date ? "" : state->author_date, 1); - if (commit_tree(state->msg, state->msg_len, tree.hash, parents, commit.hash, - author, state->sign_commit)) + if (commit_tree(state->msg, state->msg_len, &tree, parents, &commit, + author, state->sign_commit)) die(_("failed to write commit object")); reflog_msg = getenv("GIT_REFLOG_ACTION"); @@ -1699,8 +1656,8 @@ static void do_commit(const struct am_state *state) strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg), state->msg); - update_ref_oid(sb.buf, "HEAD", &commit, old_oid, 0, - UPDATE_REFS_DIE_ON_ERR); + update_ref(sb.buf, "HEAD", &commit, old_oid, 0, + UPDATE_REFS_DIE_ON_ERR); if (state->rebasing) { FILE *fp = xfopen(am_path(state, "rewritten"), "a"); @@ -1878,8 +1835,7 @@ static void am_run(struct am_state *state, int resume) git_config_get_bool("advice.amworkdir", &advice_amworkdir); if (advice_amworkdir) - printf_ln(_("The copy of the patch that failed is found in: %s"), - am_path(state, "patch")); + printf_ln(_("Use 'git am --show-current-patch' to see the failed patch")); die_user_resolve(state); } @@ -1959,15 +1915,14 @@ next: */ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) { - struct lock_file *lock_file; + struct lock_file lock_file = LOCK_INIT; struct unpack_trees_options opts; struct tree_desc t[2]; if (parse_tree(head) || parse_tree(remote)) return -1; - lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); @@ -1983,11 +1938,11 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) init_tree_desc(&t[1], remote->buffer, remote->size); if (unpack_trees(2, t, &opts)) { - rollback_lock_file(lock_file); + rollback_lock_file(&lock_file); return -1; } - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); return 0; @@ -1999,15 +1954,14 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) */ static int merge_tree(struct tree *tree) { - struct lock_file *lock_file; + struct lock_file lock_file = LOCK_INIT; struct unpack_trees_options opts; struct tree_desc t[1]; if (parse_tree(tree)) return -1; - lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; @@ -2018,11 +1972,11 @@ static int merge_tree(struct tree *tree) init_tree_desc(&t[0], tree->buffer, tree->size); if (unpack_trees(1, t, &opts)) { - rollback_lock_file(lock_file); + rollback_lock_file(&lock_file); return -1; } - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); return 0; @@ -2037,11 +1991,11 @@ static int clean_index(const struct object_id *head, const struct object_id *rem struct tree *head_tree, *remote_tree, *index_tree; struct object_id index; - head_tree = parse_tree_indirect(head->hash); + head_tree = parse_tree_indirect(head); if (!head_tree) return error(_("Could not parse object '%s'."), oid_to_hex(head)); - remote_tree = parse_tree_indirect(remote->hash); + remote_tree = parse_tree_indirect(remote); if (!remote_tree) return error(_("Could not parse object '%s'."), oid_to_hex(remote)); @@ -2053,7 +2007,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem if (write_cache_as_tree(index.hash, 0, NULL)) return -1; - index_tree = parse_tree_indirect(index.hash); + index_tree = parse_tree_indirect(&index); if (!index_tree) return error(_("Could not parse object '%s'."), oid_to_hex(&index)); @@ -2118,6 +2072,7 @@ static int safe_to_abort(const struct am_state *state) die(_("could not parse %s"), am_path(state, "abort-safety")); } else oidclr(&abort_safety); + strbuf_release(&sb); if (get_oid("HEAD", &head)) oidclr(&head); @@ -2147,7 +2102,7 @@ static void am_abort(struct am_state *state) am_rerere_clear(); - curr_branch = resolve_refdup("HEAD", 0, curr_head.hash, NULL); + 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); @@ -2159,16 +2114,44 @@ static void am_abort(struct am_state *state) clean_index(&curr_head, &orig_head); if (has_orig_head) - update_ref_oid("am --abort", "HEAD", &orig_head, - has_curr_head ? &curr_head : NULL, 0, - UPDATE_REFS_DIE_ON_ERR); + update_ref("am --abort", "HEAD", &orig_head, + has_curr_head ? &curr_head : NULL, 0, + UPDATE_REFS_DIE_ON_ERR); else if (curr_branch) - delete_ref(NULL, curr_branch, NULL, REF_NODEREF); + delete_ref(NULL, curr_branch, NULL, REF_NO_DEREF); free(curr_branch); am_destroy(state); } +static int show_patch(struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + const char *patch_path; + int len; + + if (!is_null_oid(&state->orig_commit)) { + const char *av[4] = { "show", NULL, "--", NULL }; + char *new_oid_str; + int ret; + + av[1] = new_oid_str = xstrdup(oid_to_hex(&state->orig_commit)); + ret = run_command_v_opt(av, RUN_GIT_CMD); + free(new_oid_str); + return ret; + } + + patch_path = am_path(state, msgnum(state)); + len = strbuf_read_file(&sb, patch_path, 0); + if (len < 0) + die_errno(_("failed to read '%s'"), patch_path); + + setup_pager(); + write_in_full(1, sb.buf, sb.len); + strbuf_release(&sb); + return 0; +} + /** * parse_options() callback that validates and sets opt->value to the * PATCH_FORMAT_* enum value corresponding to `arg`. @@ -2197,7 +2180,9 @@ enum resume_mode { RESUME_APPLY, RESUME_RESOLVED, RESUME_SKIP, - RESUME_ABORT + RESUME_ABORT, + RESUME_QUIT, + RESUME_SHOW_PATCH }; static int git_am_config(const char *k, const char *v, void *cb) @@ -2219,6 +2204,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) int patch_format = PATCH_FORMAT_UNKNOWN; enum resume_mode resume = RESUME_FALSE; int in_progress; + int ret = 0; const char * const usage[] = { N_("git am [<options>] [(<mbox> | <Maildir>)...]"), @@ -2297,6 +2283,12 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "abort", &resume, N_("restore the original branch and abort the patching operation."), RESUME_ABORT), + OPT_CMDMODE(0, "quit", &resume, + 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), OPT_BOOL(0, "committer-date-is-author-date", &state.committer_date_is_author_date, N_("lie about committer date")), @@ -2365,7 +2357,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) { + if (resume == RESUME_ABORT || resume == RESUME_QUIT) { am_destroy(&state); am_state_release(&state); return 0; @@ -2407,11 +2399,18 @@ int cmd_am(int argc, const char **argv, const char *prefix) case RESUME_ABORT: am_abort(&state); break; + case RESUME_QUIT: + am_rerere_clear(); + am_destroy(&state); + break; + case RESUME_SHOW_PATCH: + ret = show_patch(&state); + break; default: die("BUG: invalid resume value"); } am_state_release(&state); - return 0; + return ret; } diff --git a/builtin/apply.c b/builtin/apply.c index 81b9a61c37..48d3989331 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -9,8 +9,6 @@ static const char * const apply_usage[] = { NULL }; -static struct lock_file lock_file; - int cmd_apply(int argc, const char **argv, const char *prefix) { int force_apply = 0; @@ -18,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, &lock_file)) + if (init_apply_state(&state, prefix)) exit(128); argc = apply_parse_options(argc, argv, diff --git a/builtin/archive.c b/builtin/archive.c index f863465a0f..73971d0dd2 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -55,7 +55,7 @@ static int run_remote_archiver(int argc, const char **argv, buf = packet_read_line(fd[0], NULL); if (!buf) - die(_("git archive: expected ACK/NAK, got EOF")); + 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); diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 3324229025..4b5fadcbe1 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -2,19 +2,128 @@ #include "cache.h" #include "parse-options.h" #include "bisect.h" +#include "refs.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 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"), NULL }; +/* + * Check whether the string `term` belongs to the set of strings + * included in the variable arguments. + */ +LAST_ARG_MUST_BE_NULL +static int one_of(const char *term, ...) +{ + int res = 0; + va_list matches; + const char *match; + + va_start(matches, term); + while (!res && (match = va_arg(matches, const char *))) + res = !strcmp(term, match); + va_end(matches); + + return res; +} + +static int check_term_format(const char *term, const char *orig_term) +{ + int res; + char *new_term = xstrfmt("refs/bisect/%s", term); + + res = check_refname_format(new_term, 0); + free(new_term); + + if (res) + return error(_("'%s' is not a valid term"), term); + + if (one_of(term, "help", "start", "skip", "next", "reset", + "visualize", "view", "replay", "log", "run", "terms", NULL)) + return error(_("can't use the builtin command '%s' as a term"), term); + + /* + * In theory, nothing prevents swapping completely good and bad, + * but this situation could be confusing and hasn't been tested + * enough. Forbid it for now. + */ + + if ((strcmp(orig_term, "bad") && one_of(term, "bad", "new", NULL)) || + (strcmp(orig_term, "good") && one_of(term, "good", "old", NULL))) + return error(_("can't change the meaning of the term '%s'"), term); + + return 0; +} + +static int write_terms(const char *bad, const char *good) +{ + FILE *fp = NULL; + int res; + + if (!strcmp(bad, good)) + return error(_("please use two different terms")); + + if (check_term_format(bad, "bad") || check_term_format(good, "good")) + return -1; + + fp = fopen(git_path_bisect_terms(), "w"); + if (!fp) + return error_errno(_("could not open the file BISECT_TERMS")); + + res = fprintf(fp, "%s\n%s\n", bad, good); + res |= fclose(fp); + return (res < 0) ? -1 : 0; +} + +static int is_expected_rev(const char *expected_hex) +{ + struct strbuf actual_hex = STRBUF_INIT; + int res = 0; + if (strbuf_read_file(&actual_hex, git_path_bisect_expected_rev(), 0) >= 40) { + strbuf_trim(&actual_hex); + res = !strcmp(actual_hex.buf, expected_hex); + } + strbuf_release(&actual_hex); + return res; +} + +static void check_expected_revs(const char **revs, int rev_nr) +{ + int i; + + for (i = 0; i < rev_nr; i++) { + if (!is_expected_rev(revs[i])) { + unlink_or_warn(git_path_bisect_ancestors_ok()); + unlink_or_warn(git_path_bisect_expected_rev()); + } + } +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { - int next_all = 0; + enum { + NEXT_ALL = 1, + WRITE_TERMS, + BISECT_CLEAN_STATE, + CHECK_EXPECTED_REVS + } cmdmode = 0; int no_checkout = 0; struct option options[] = { - OPT_BOOL(0, "next-all", &next_all, - N_("perform 'git bisect next'")), + OPT_CMDMODE(0, "next-all", &cmdmode, + N_("perform 'git bisect next'"), NEXT_ALL), + OPT_CMDMODE(0, "write-terms", &cmdmode, + N_("write the terms to .git/BISECT_TERMS"), WRITE_TERMS), + OPT_CMDMODE(0, "bisect-clean-state", &cmdmode, + N_("cleanup the bisection state"), BISECT_CLEAN_STATE), + OPT_CMDMODE(0, "check-expected-revs", &cmdmode, + N_("check for expected revs"), CHECK_EXPECTED_REVS), OPT_BOOL(0, "no-checkout", &no_checkout, N_("update BISECT_HEAD instead of checking out the current commit")), OPT_END() @@ -23,9 +132,25 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_bisect_helper_usage, 0); - if (!next_all) + if (!cmdmode) usage_with_options(git_bisect_helper_usage, options); - /* next-all */ - return bisect_next_all(prefix, no_checkout); + switch (cmdmode) { + case NEXT_ALL: + return bisect_next_all(prefix, no_checkout); + case WRITE_TERMS: + if (argc != 2) + return error(_("--write-terms requires two arguments")); + return write_terms(argv[0], argv[1]); + case BISECT_CLEAN_STATE: + if (argc != 0) + return error(_("--bisect-clean-state requires no arguments")); + return bisect_clean_state(); + case CHECK_EXPECTED_REVS: + check_expected_revs(argv, argc); + return 0; + default: + return error("BUG: unknown subcommand '%d'", cmdmode); + } + return 0; } diff --git a/builtin/blame.c b/builtin/blame.c index ca9ebe40e7..9dcb367b90 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -6,21 +6,14 @@ */ #include "cache.h" -#include "refs.h" +#include "config.h" #include "builtin.h" -#include "blob.h" #include "commit.h" -#include "tag.h" -#include "tree-walk.h" #include "diff.h" -#include "diffcore.h" #include "revision.h" #include "quote.h" -#include "xdiff-interface.h" -#include "cache-tree.h" #include "string-list.h" #include "mailmap.h" -#include "mergesort.h" #include "parse-options.h" #include "prio-queue.h" #include "utf8.h" @@ -29,6 +22,7 @@ #include "line-log.h" #include "dir.h" #include "progress.h" +#include "blame.h" static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); @@ -62,1497 +56,21 @@ static struct string_list mailmap = STRING_LIST_INIT_NODUP; #define DEBUG 0 #endif -/* stats */ -static int num_read_blob; -static int num_get_patch; -static int num_commits; - -#define PICKAXE_BLAME_MOVE 01 -#define PICKAXE_BLAME_COPY 02 -#define PICKAXE_BLAME_COPY_HARDER 04 -#define PICKAXE_BLAME_COPY_HARDEST 010 - -/* - * blame for a blame_entry with score lower than these thresholds - * is not passed to the parent using move/copy logic. - */ static unsigned blame_move_score; static unsigned blame_copy_score; -#define BLAME_DEFAULT_MOVE_SCORE 20 -#define BLAME_DEFAULT_COPY_SCORE 40 /* Remember to update object flag allocation in object.h */ #define METAINFO_SHOWN (1u<<12) #define MORE_THAN_ONE_PATH (1u<<13) -/* - * One blob in a commit that is being suspected - */ -struct origin { - int refcnt; - /* Record preceding blame record for this blob */ - struct origin *previous; - /* origins are put in a list linked via `next' hanging off the - * corresponding commit's util field in order to make finding - * them fast. The presence in this chain does not count - * towards the origin's reference count. It is tempting to - * let it count as long as the commit is pending examination, - * but even under circumstances where the commit will be - * present multiple times in the priority queue of unexamined - * commits, processing the first instance will not leave any - * work requiring the origin data for the second instance. An - * interspersed commit changing that would have to be - * preexisting with a different ancestry and with the same - * commit date in order to wedge itself between two instances - * of the same commit in the priority queue _and_ produce - * blame entries relevant for it. While we don't want to let - * us get tripped up by this case, it certainly does not seem - * worth optimizing for. - */ - struct origin *next; - struct commit *commit; - /* `suspects' contains blame entries that may be attributed to - * this origin's commit or to parent commits. When a commit - * is being processed, all suspects will be moved, either by - * assigning them to an origin in a different commit, or by - * shipping them to the scoreboard's ent list because they - * cannot be attributed to a different commit. - */ - struct blame_entry *suspects; - mmfile_t file; - struct object_id blob_oid; - unsigned mode; - /* guilty gets set when shipping any suspects to the final - * blame list instead of other commits - */ - char guilty; - char path[FLEX_ARRAY]; -}; - struct progress_info { struct progress *progress; int blamed_lines; }; -static int diff_hunks(mmfile_t *file_a, mmfile_t *file_b, - xdl_emit_hunk_consume_func_t hunk_func, void *cb_data) -{ - xpparam_t xpp = {0}; - xdemitconf_t xecfg = {0}; - xdemitcb_t ecb = {NULL}; - - xpp.flags = xdl_opts; - xecfg.hunk_func = hunk_func; - ecb.priv = cb_data; - return xdi_diff(file_a, file_b, &xpp, &xecfg, &ecb); -} - -/* - * Prepare diff_filespec and convert it using diff textconv API - * if the textconv driver exists. - * Return 1 if the conversion succeeds, 0 otherwise. - */ -int textconv_object(const char *path, - unsigned mode, - const struct object_id *oid, - int oid_valid, - char **buf, - unsigned long *buf_size) -{ - struct diff_filespec *df; - struct userdiff_driver *textconv; - - df = alloc_filespec(path); - fill_filespec(df, oid->hash, oid_valid, mode); - textconv = get_textconv(df); - if (!textconv) { - free_filespec(df); - return 0; - } - - *buf_size = fill_textconv(textconv, df, buf); - free_filespec(df); - return 1; -} - -/* - * Given an origin, prepare mmfile_t structure to be used by the - * diff machinery - */ -static void fill_origin_blob(struct diff_options *opt, - struct origin *o, mmfile_t *file) -{ - if (!o->file.ptr) { - enum object_type type; - unsigned long file_size; - - num_read_blob++; - if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(o->path, o->mode, &o->blob_oid, 1, &file->ptr, &file_size)) - ; - else - file->ptr = read_sha1_file(o->blob_oid.hash, &type, - &file_size); - file->size = file_size; - - if (!file->ptr) - die("Cannot read blob %s for path %s", - oid_to_hex(&o->blob_oid), - o->path); - o->file = *file; - } - else - *file = o->file; -} - -/* - * Origin is refcounted and usually we keep the blob contents to be - * reused. - */ -static inline struct origin *origin_incref(struct origin *o) -{ - if (o) - o->refcnt++; - return o; -} - -static void origin_decref(struct origin *o) -{ - if (o && --o->refcnt <= 0) { - struct origin *p, *l = NULL; - if (o->previous) - origin_decref(o->previous); - free(o->file.ptr); - /* Should be present exactly once in commit chain */ - for (p = o->commit->util; p; l = p, p = p->next) { - if (p == o) { - if (l) - l->next = p->next; - else - o->commit->util = p->next; - free(o); - return; - } - } - die("internal error in blame::origin_decref"); - } -} - -static void drop_origin_blob(struct origin *o) -{ - if (o->file.ptr) { - free(o->file.ptr); - o->file.ptr = NULL; - } -} - -/* - * Each group of lines is described by a blame_entry; it can be split - * as we pass blame to the parents. They are arranged in linked lists - * kept as `suspects' of some unprocessed origin, or entered (when the - * blame origin has been finalized) into the scoreboard structure. - * While the scoreboard structure is only sorted at the end of - * processing (according to final image line number), the lists - * attached to an origin are sorted by the target line number. - */ -struct blame_entry { - struct blame_entry *next; - - /* the first line of this group in the final image; - * internally all line numbers are 0 based. - */ - int lno; - - /* how many lines this group has */ - int num_lines; - - /* the commit that introduced this group into the final image */ - struct origin *suspect; - - /* the line number of the first line of this group in the - * suspect's file; internally all line numbers are 0 based. - */ - int s_lno; - - /* how significant this entry is -- cached to avoid - * scanning the lines over and over. - */ - unsigned score; -}; - -/* - * Any merge of blames happens on lists of blames that arrived via - * different parents in a single suspect. In this case, we want to - * sort according to the suspect line numbers as opposed to the final - * image line numbers. The function body is somewhat longish because - * it avoids unnecessary writes. - */ - -static struct blame_entry *blame_merge(struct blame_entry *list1, - struct blame_entry *list2) -{ - struct blame_entry *p1 = list1, *p2 = list2, - **tail = &list1; - - if (!p1) - return p2; - if (!p2) - return p1; - - if (p1->s_lno <= p2->s_lno) { - do { - tail = &p1->next; - if ((p1 = *tail) == NULL) { - *tail = p2; - return list1; - } - } while (p1->s_lno <= p2->s_lno); - } - for (;;) { - *tail = p2; - do { - tail = &p2->next; - if ((p2 = *tail) == NULL) { - *tail = p1; - return list1; - } - } while (p1->s_lno > p2->s_lno); - *tail = p1; - do { - tail = &p1->next; - if ((p1 = *tail) == NULL) { - *tail = p2; - return list1; - } - } while (p1->s_lno <= p2->s_lno); - } -} - -static void *get_next_blame(const void *p) -{ - return ((struct blame_entry *)p)->next; -} - -static void set_next_blame(void *p1, void *p2) -{ - ((struct blame_entry *)p1)->next = p2; -} - -/* - * Final image line numbers are all different, so we don't need a - * three-way comparison here. - */ - -static int compare_blame_final(const void *p1, const void *p2) -{ - return ((struct blame_entry *)p1)->lno > ((struct blame_entry *)p2)->lno - ? 1 : -1; -} - -static int compare_blame_suspect(const void *p1, const void *p2) -{ - const struct blame_entry *s1 = p1, *s2 = p2; - /* - * to allow for collating suspects, we sort according to the - * respective pointer value as the primary sorting criterion. - * The actual relation is pretty unimportant as long as it - * establishes a total order. Comparing as integers gives us - * that. - */ - if (s1->suspect != s2->suspect) - return (intptr_t)s1->suspect > (intptr_t)s2->suspect ? 1 : -1; - if (s1->s_lno == s2->s_lno) - return 0; - return s1->s_lno > s2->s_lno ? 1 : -1; -} - -static struct blame_entry *blame_sort(struct blame_entry *head, - int (*compare_fn)(const void *, const void *)) -{ - return llist_mergesort (head, get_next_blame, set_next_blame, compare_fn); -} - -static int compare_commits_by_reverse_commit_date(const void *a, - const void *b, - void *c) -{ - return -compare_commits_by_commit_date(a, b, c); -} - -/* - * The current state of the blame assignment. - */ -struct scoreboard { - /* the final commit (i.e. where we started digging from) */ - struct commit *final; - /* Priority queue for commits with unassigned blame records */ - struct prio_queue commits; - struct rev_info *revs; - const char *path; - - /* - * The contents in the final image. - * Used by many functions to obtain contents of the nth line, - * indexed with scoreboard.lineno[blame_entry.lno]. - */ - const char *final_buf; - unsigned long final_buf_size; - - /* linked list of blames */ - struct blame_entry *ent; - - /* look-up a line in the final buffer */ - int num_lines; - int *lineno; -}; - -static void sanity_check_refcnt(struct scoreboard *); - -/* - * If two blame entries that are next to each other came from - * contiguous lines in the same origin (i.e. <commit, path> pair), - * merge them together. - */ -static void coalesce(struct scoreboard *sb) -{ - struct blame_entry *ent, *next; - - for (ent = sb->ent; ent && (next = ent->next); ent = next) { - if (ent->suspect == next->suspect && - ent->s_lno + ent->num_lines == next->s_lno) { - ent->num_lines += next->num_lines; - ent->next = next->next; - origin_decref(next->suspect); - free(next); - ent->score = 0; - next = ent; /* again */ - } - } - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); -} - -/* - * Merge the given sorted list of blames into a preexisting origin. - * If there were no previous blames to that commit, it is entered into - * the commit priority queue of the score board. - */ - -static void queue_blames(struct scoreboard *sb, struct origin *porigin, - struct blame_entry *sorted) -{ - if (porigin->suspects) - porigin->suspects = blame_merge(porigin->suspects, sorted); - else { - struct origin *o; - for (o = porigin->commit->util; o; o = o->next) { - if (o->suspects) { - porigin->suspects = sorted; - return; - } - } - porigin->suspects = sorted; - prio_queue_put(&sb->commits, porigin->commit); - } -} - -/* - * Given a commit and a path in it, create a new origin structure. - * The callers that add blame to the scoreboard should use - * get_origin() to obtain shared, refcounted copy instead of calling - * this function directly. - */ -static struct origin *make_origin(struct commit *commit, const char *path) -{ - struct origin *o; - FLEX_ALLOC_STR(o, path, path); - o->commit = commit; - o->refcnt = 1; - o->next = commit->util; - commit->util = o; - return o; -} - -/* - * Locate an existing origin or create a new one. - * This moves the origin to front position in the commit util list. - */ -static struct origin *get_origin(struct scoreboard *sb, - struct commit *commit, - const char *path) -{ - struct origin *o, *l; - - for (o = commit->util, l = NULL; o; l = o, o = o->next) { - if (!strcmp(o->path, path)) { - /* bump to front */ - if (l) { - l->next = o->next; - o->next = commit->util; - commit->util = o; - } - return origin_incref(o); - } - } - return make_origin(commit, path); -} - -/* - * Fill the blob_sha1 field of an origin if it hasn't, so that later - * call to fill_origin_blob() can use it to locate the data. blob_sha1 - * for an origin is also used to pass the blame for the entire file to - * the parent to detect the case where a child's blob is identical to - * that of its parent's. - * - * This also fills origin->mode for corresponding tree path. - */ -static int fill_blob_sha1_and_mode(struct origin *origin) -{ - if (!is_null_oid(&origin->blob_oid)) - return 0; - if (get_tree_entry(origin->commit->object.oid.hash, - origin->path, - origin->blob_oid.hash, &origin->mode)) - goto error_out; - if (sha1_object_info(origin->blob_oid.hash, NULL) != OBJ_BLOB) - goto error_out; - return 0; - error_out: - oidclr(&origin->blob_oid); - origin->mode = S_IFINVALID; - return -1; -} - -/* - * We have an origin -- check if the same path exists in the - * parent and return an origin structure to represent it. - */ -static struct origin *find_origin(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin; - struct diff_options diff_opts; - const char *paths[2]; - - /* First check any existing origins */ - for (porigin = parent->util; porigin; porigin = porigin->next) - if (!strcmp(porigin->path, origin->path)) { - /* - * The same path between origin and its parent - * without renaming -- the most common case. - */ - return origin_incref (porigin); - } - - /* See if the origin->path is different between parent - * and origin first. Most of the time they are the - * same and diff-tree is fairly efficient about this. - */ - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.detect_rename = 0; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - paths[0] = origin->path; - paths[1] = NULL; - - parse_pathspec(&diff_opts.pathspec, - PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, - PATHSPEC_LITERAL_PATH, "", paths); - diff_setup_done(&diff_opts); - - if (is_null_oid(&origin->commit->object.oid)) - do_diff_cache(parent->tree->object.oid.hash, &diff_opts); - else - diff_tree_sha1(parent->tree->object.oid.hash, - origin->commit->tree->object.oid.hash, - "", &diff_opts); - diffcore_std(&diff_opts); - - if (!diff_queued_diff.nr) { - /* The path is the same as parent */ - porigin = get_origin(sb, parent, origin->path); - oidcpy(&porigin->blob_oid, &origin->blob_oid); - porigin->mode = origin->mode; - } else { - /* - * Since origin->path is a pathspec, if the parent - * commit had it as a directory, we will see a whole - * bunch of deletion of files in the directory that we - * do not care about. - */ - int i; - struct diff_filepair *p = NULL; - for (i = 0; i < diff_queued_diff.nr; i++) { - const char *name; - p = diff_queued_diff.queue[i]; - name = p->one->path ? p->one->path : p->two->path; - if (!strcmp(name, origin->path)) - break; - } - if (!p) - die("internal error in blame::find_origin"); - switch (p->status) { - default: - die("internal error in blame::find_origin (%c)", - p->status); - case 'M': - porigin = get_origin(sb, parent, origin->path); - oidcpy(&porigin->blob_oid, &p->one->oid); - porigin->mode = p->one->mode; - break; - case 'A': - case 'T': - /* Did not exist in parent, or type changed */ - break; - } - } - diff_flush(&diff_opts); - clear_pathspec(&diff_opts.pathspec); - return porigin; -} - -/* - * We have an origin -- find the path that corresponds to it in its - * parent and return an origin structure to represent it. - */ -static struct origin *find_rename(struct scoreboard *sb, - struct commit *parent, - struct origin *origin) -{ - struct origin *porigin = NULL; - struct diff_options diff_opts; - int i; - - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.detect_rename = DIFF_DETECT_RENAME; - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_opts.single_follow = origin->path; - diff_setup_done(&diff_opts); - - if (is_null_oid(&origin->commit->object.oid)) - do_diff_cache(parent->tree->object.oid.hash, &diff_opts); - else - diff_tree_sha1(parent->tree->object.oid.hash, - origin->commit->tree->object.oid.hash, - "", &diff_opts); - diffcore_std(&diff_opts); - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - if ((p->status == 'R' || p->status == 'C') && - !strcmp(p->two->path, origin->path)) { - porigin = get_origin(sb, parent, p->one->path); - oidcpy(&porigin->blob_oid, &p->one->oid); - porigin->mode = p->one->mode; - break; - } - } - diff_flush(&diff_opts); - clear_pathspec(&diff_opts.pathspec); - return porigin; -} - -/* - * Append a new blame entry to a given output queue. - */ -static void add_blame_entry(struct blame_entry ***queue, - const struct blame_entry *src) -{ - struct blame_entry *e = xmalloc(sizeof(*e)); - memcpy(e, src, sizeof(*e)); - origin_incref(e->suspect); - - e->next = **queue; - **queue = e; - *queue = &e->next; -} - -/* - * src typically is on-stack; we want to copy the information in it to - * a malloced blame_entry that gets added to the given queue. The - * origin of dst loses a refcnt. - */ -static void dup_entry(struct blame_entry ***queue, - struct blame_entry *dst, struct blame_entry *src) -{ - origin_incref(src->suspect); - origin_decref(dst->suspect); - memcpy(dst, src, sizeof(*src)); - dst->next = **queue; - **queue = dst; - *queue = &dst->next; -} - -static const char *nth_line(struct scoreboard *sb, long lno) -{ - return sb->final_buf + sb->lineno[lno]; -} - static const char *nth_line_cb(void *data, long lno) { - return nth_line((struct scoreboard *)data, lno); -} - -/* - * It is known that lines between tlno to same came from parent, and e - * has an overlap with that range. it also is known that parent's - * line plno corresponds to e's line tlno. - * - * <---- e -----> - * <------> - * <------------> - * <------------> - * <------------------> - * - * Split e into potentially three parts; before this chunk, the chunk - * to be blamed for the parent, and after that portion. - */ -static void split_overlap(struct blame_entry *split, - struct blame_entry *e, - int tlno, int plno, int same, - struct origin *parent) -{ - int chunk_end_lno; - memset(split, 0, sizeof(struct blame_entry [3])); - - if (e->s_lno < tlno) { - /* there is a pre-chunk part not blamed on parent */ - split[0].suspect = origin_incref(e->suspect); - split[0].lno = e->lno; - split[0].s_lno = e->s_lno; - split[0].num_lines = tlno - e->s_lno; - split[1].lno = e->lno + tlno - e->s_lno; - split[1].s_lno = plno; - } - else { - split[1].lno = e->lno; - split[1].s_lno = plno + (e->s_lno - tlno); - } - - if (same < e->s_lno + e->num_lines) { - /* there is a post-chunk part not blamed on parent */ - split[2].suspect = origin_incref(e->suspect); - split[2].lno = e->lno + (same - e->s_lno); - split[2].s_lno = e->s_lno + (same - e->s_lno); - split[2].num_lines = e->s_lno + e->num_lines - same; - chunk_end_lno = split[2].lno; - } - else - chunk_end_lno = e->lno + e->num_lines; - split[1].num_lines = chunk_end_lno - split[1].lno; - - /* - * if it turns out there is nothing to blame the parent for, - * forget about the splitting. !split[1].suspect signals this. - */ - if (split[1].num_lines < 1) - return; - split[1].suspect = origin_incref(parent); -} - -/* - * split_overlap() divided an existing blame e into up to three parts - * in split. Any assigned blame is moved to queue to - * reflect the split. - */ -static void split_blame(struct blame_entry ***blamed, - struct blame_entry ***unblamed, - struct blame_entry *split, - struct blame_entry *e) -{ - if (split[0].suspect && split[2].suspect) { - /* The first part (reuse storage for the existing entry e) */ - dup_entry(unblamed, e, &split[0]); - - /* The last part -- me */ - add_blame_entry(unblamed, &split[2]); - - /* ... and the middle part -- parent */ - add_blame_entry(blamed, &split[1]); - } - else if (!split[0].suspect && !split[2].suspect) - /* - * The parent covers the entire area; reuse storage for - * e and replace it with the parent. - */ - dup_entry(blamed, e, &split[1]); - else if (split[0].suspect) { - /* me and then parent */ - dup_entry(unblamed, e, &split[0]); - add_blame_entry(blamed, &split[1]); - } - else { - /* parent and then me */ - dup_entry(blamed, e, &split[1]); - add_blame_entry(unblamed, &split[2]); - } -} - -/* - * After splitting the blame, the origins used by the - * on-stack blame_entry should lose one refcnt each. - */ -static void decref_split(struct blame_entry *split) -{ - int i; - - for (i = 0; i < 3; i++) - origin_decref(split[i].suspect); -} - -/* - * reverse_blame reverses the list given in head, appending tail. - * That allows us to build lists in reverse order, then reverse them - * afterwards. This can be faster than building the list in proper - * order right away. The reason is that building in proper order - * requires writing a link in the _previous_ element, while building - * in reverse order just requires placing the list head into the - * _current_ element. - */ - -static struct blame_entry *reverse_blame(struct blame_entry *head, - struct blame_entry *tail) -{ - while (head) { - struct blame_entry *next = head->next; - head->next = tail; - tail = head; - head = next; - } - return tail; -} - -/* - * Process one hunk from the patch between the current suspect for - * blame_entry e and its parent. This first blames any unfinished - * entries before the chunk (which is where target and parent start - * differing) on the parent, and then splits blame entries at the - * start and at the end of the difference region. Since use of -M and - * -C options may lead to overlapping/duplicate source line number - * ranges, all we can rely on from sorting/merging is the order of the - * first suspect line number. - */ -static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq, - int tlno, int offset, int same, - struct origin *parent) -{ - struct blame_entry *e = **srcq; - struct blame_entry *samep = NULL, *diffp = NULL; - - while (e && e->s_lno < tlno) { - struct blame_entry *next = e->next; - /* - * current record starts before differing portion. If - * it reaches into it, we need to split it up and - * examine the second part separately. - */ - if (e->s_lno + e->num_lines > tlno) { - /* Move second half to a new record */ - int len = tlno - e->s_lno; - struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry)); - n->suspect = e->suspect; - n->lno = e->lno + len; - n->s_lno = e->s_lno + len; - n->num_lines = e->num_lines - len; - e->num_lines = len; - e->score = 0; - /* Push new record to diffp */ - n->next = diffp; - diffp = n; - } else - origin_decref(e->suspect); - /* Pass blame for everything before the differing - * chunk to the parent */ - e->suspect = origin_incref(parent); - e->s_lno += offset; - e->next = samep; - samep = e; - e = next; - } - /* - * As we don't know how much of a common stretch after this - * diff will occur, the currently blamed parts are all that we - * can assign to the parent for now. - */ - - if (samep) { - **dstq = reverse_blame(samep, **dstq); - *dstq = &samep->next; - } - /* - * Prepend the split off portions: everything after e starts - * after the blameable portion. - */ - e = reverse_blame(diffp, e); - - /* - * Now retain records on the target while parts are different - * from the parent. - */ - samep = NULL; - diffp = NULL; - while (e && e->s_lno < same) { - struct blame_entry *next = e->next; - - /* - * If current record extends into sameness, need to split. - */ - if (e->s_lno + e->num_lines > same) { - /* - * Move second half to a new record to be - * processed by later chunks - */ - int len = same - e->s_lno; - struct blame_entry *n = xcalloc(1, sizeof (struct blame_entry)); - n->suspect = origin_incref(e->suspect); - n->lno = e->lno + len; - n->s_lno = e->s_lno + len; - n->num_lines = e->num_lines - len; - e->num_lines = len; - e->score = 0; - /* Push new record to samep */ - n->next = samep; - samep = n; - } - e->next = diffp; - diffp = e; - e = next; - } - **srcq = reverse_blame(diffp, reverse_blame(samep, e)); - /* Move across elements that are in the unblamable portion */ - if (diffp) - *srcq = &diffp->next; -} - -struct blame_chunk_cb_data { - struct origin *parent; - long offset; - struct blame_entry **dstq; - struct blame_entry **srcq; -}; - -/* diff chunks are from parent to target */ -static int blame_chunk_cb(long start_a, long count_a, - long start_b, long count_b, void *data) -{ - struct blame_chunk_cb_data *d = data; - if (start_a - start_b != d->offset) - die("internal error in blame::blame_chunk_cb"); - blame_chunk(&d->dstq, &d->srcq, start_b, start_a - start_b, - start_b + count_b, d->parent); - d->offset = start_a + count_a - (start_b + count_b); - return 0; -} - -/* - * We are looking at the origin 'target' and aiming to pass blame - * for the lines it is suspected to its parent. Run diff to find - * which lines came from parent and pass blame for them. - */ -static void pass_blame_to_parent(struct scoreboard *sb, - struct origin *target, - struct origin *parent) -{ - mmfile_t file_p, file_o; - struct blame_chunk_cb_data d; - struct blame_entry *newdest = NULL; - - if (!target->suspects) - return; /* nothing remains for this target */ - - d.parent = parent; - d.offset = 0; - d.dstq = &newdest; d.srcq = &target->suspects; - - fill_origin_blob(&sb->revs->diffopt, parent, &file_p); - fill_origin_blob(&sb->revs->diffopt, target, &file_o); - num_get_patch++; - - if (diff_hunks(&file_p, &file_o, blame_chunk_cb, &d)) - die("unable to generate diff (%s -> %s)", - oid_to_hex(&parent->commit->object.oid), - oid_to_hex(&target->commit->object.oid)); - /* The rest are the same as the parent */ - blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent); - *d.dstq = NULL; - queue_blames(sb, parent, newdest); - - return; -} - -/* - * The lines in blame_entry after splitting blames many times can become - * very small and trivial, and at some point it becomes pointless to - * blame the parents. E.g. "\t\t}\n\t}\n\n" appears everywhere in any - * ordinary C program, and it is not worth to say it was copied from - * totally unrelated file in the parent. - * - * Compute how trivial the lines in the blame_entry are. - */ -static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e) -{ - unsigned score; - const char *cp, *ep; - - if (e->score) - return e->score; - - score = 1; - cp = nth_line(sb, e->lno); - ep = nth_line(sb, e->lno + e->num_lines); - while (cp < ep) { - unsigned ch = *((unsigned char *)cp); - if (isalnum(ch)) - score++; - cp++; - } - e->score = score; - return score; -} - -/* - * best_so_far[] and this[] are both a split of an existing blame_entry - * that passes blame to the parent. Maintain best_so_far the best split - * so far, by comparing this and best_so_far and copying this into - * bst_so_far as needed. - */ -static void copy_split_if_better(struct scoreboard *sb, - struct blame_entry *best_so_far, - struct blame_entry *this) -{ - int i; - - if (!this[1].suspect) - return; - if (best_so_far[1].suspect) { - if (ent_score(sb, &this[1]) < ent_score(sb, &best_so_far[1])) - return; - } - - for (i = 0; i < 3; i++) - origin_incref(this[i].suspect); - decref_split(best_so_far); - memcpy(best_so_far, this, sizeof(struct blame_entry [3])); -} - -/* - * We are looking at a part of the final image represented by - * ent (tlno and same are offset by ent->s_lno). - * tlno is where we are looking at in the final image. - * up to (but not including) same match preimage. - * plno is where we are looking at in the preimage. - * - * <-------------- final image ----------------------> - * <------ent------> - * ^tlno ^same - * <---------preimage-----> - * ^plno - * - * All line numbers are 0-based. - */ -static void handle_split(struct scoreboard *sb, - struct blame_entry *ent, - int tlno, int plno, int same, - struct origin *parent, - struct blame_entry *split) -{ - if (ent->num_lines <= tlno) - return; - if (tlno < same) { - struct blame_entry this[3]; - tlno += ent->s_lno; - same += ent->s_lno; - split_overlap(this, ent, tlno, plno, same, parent); - copy_split_if_better(sb, split, this); - decref_split(this); - } -} - -struct handle_split_cb_data { - struct scoreboard *sb; - struct blame_entry *ent; - struct origin *parent; - struct blame_entry *split; - long plno; - long tlno; -}; - -static int handle_split_cb(long start_a, long count_a, - long start_b, long count_b, void *data) -{ - struct handle_split_cb_data *d = data; - handle_split(d->sb, d->ent, d->tlno, d->plno, start_b, d->parent, - d->split); - d->plno = start_a + count_a; - d->tlno = start_b + count_b; - return 0; -} - -/* - * Find the lines from parent that are the same as ent so that - * we can pass blames to it. file_p has the blob contents for - * the parent. - */ -static void find_copy_in_blob(struct scoreboard *sb, - struct blame_entry *ent, - struct origin *parent, - struct blame_entry *split, - mmfile_t *file_p) -{ - const char *cp; - mmfile_t file_o; - struct handle_split_cb_data d; - - memset(&d, 0, sizeof(d)); - d.sb = sb; d.ent = ent; d.parent = parent; d.split = split; - /* - * Prepare mmfile that contains only the lines in ent. - */ - cp = nth_line(sb, ent->lno); - file_o.ptr = (char *) cp; - file_o.size = nth_line(sb, ent->lno + ent->num_lines) - cp; - - /* - * file_o is a part of final image we are annotating. - * file_p partially may match that image. - */ - memset(split, 0, sizeof(struct blame_entry [3])); - if (diff_hunks(file_p, &file_o, handle_split_cb, &d)) - die("unable to generate diff (%s)", - oid_to_hex(&parent->commit->object.oid)); - /* remainder, if any, all match the preimage */ - handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split); -} - -/* Move all blame entries from list *source that have a score smaller - * than score_min to the front of list *small. - * Returns a pointer to the link pointing to the old head of the small list. - */ - -static struct blame_entry **filter_small(struct scoreboard *sb, - struct blame_entry **small, - struct blame_entry **source, - unsigned score_min) -{ - struct blame_entry *p = *source; - struct blame_entry *oldsmall = *small; - while (p) { - if (ent_score(sb, p) <= score_min) { - *small = p; - small = &p->next; - p = *small; - } else { - *source = p; - source = &p->next; - p = *source; - } - } - *small = oldsmall; - *source = NULL; - return small; -} - -/* - * See if lines currently target is suspected for can be attributed to - * parent. - */ -static void find_move_in_parent(struct scoreboard *sb, - struct blame_entry ***blamed, - struct blame_entry **toosmall, - struct origin *target, - struct origin *parent) -{ - struct blame_entry *e, split[3]; - struct blame_entry *unblamed = target->suspects; - struct blame_entry *leftover = NULL; - mmfile_t file_p; - - if (!unblamed) - return; /* nothing remains for this target */ - - fill_origin_blob(&sb->revs->diffopt, parent, &file_p); - if (!file_p.ptr) - return; - - /* At each iteration, unblamed has a NULL-terminated list of - * entries that have not yet been tested for blame. leftover - * contains the reversed list of entries that have been tested - * without being assignable to the parent. - */ - do { - struct blame_entry **unblamedtail = &unblamed; - struct blame_entry *next; - for (e = unblamed; e; e = next) { - next = e->next; - find_copy_in_blob(sb, e, parent, split, &file_p); - if (split[1].suspect && - blame_move_score < ent_score(sb, &split[1])) { - split_blame(blamed, &unblamedtail, split, e); - } else { - e->next = leftover; - leftover = e; - } - decref_split(split); - } - *unblamedtail = NULL; - toosmall = filter_small(sb, toosmall, &unblamed, blame_move_score); - } while (unblamed); - target->suspects = reverse_blame(leftover, NULL); -} - -struct blame_list { - struct blame_entry *ent; - struct blame_entry split[3]; -}; - -/* - * Count the number of entries the target is suspected for, - * and prepare a list of entry and the best split. - */ -static struct blame_list *setup_blame_list(struct blame_entry *unblamed, - int *num_ents_p) -{ - struct blame_entry *e; - int num_ents, i; - struct blame_list *blame_list = NULL; - - for (e = unblamed, num_ents = 0; e; e = e->next) - num_ents++; - if (num_ents) { - blame_list = xcalloc(num_ents, sizeof(struct blame_list)); - for (e = unblamed, i = 0; e; e = e->next) - blame_list[i++].ent = e; - } - *num_ents_p = num_ents; - return blame_list; -} - -/* - * For lines target is suspected for, see if we can find code movement - * across file boundary from the parent commit. porigin is the path - * in the parent we already tried. - */ -static void find_copy_in_parent(struct scoreboard *sb, - struct blame_entry ***blamed, - struct blame_entry **toosmall, - struct origin *target, - struct commit *parent, - struct origin *porigin, - int opt) -{ - struct diff_options diff_opts; - int i, j; - struct blame_list *blame_list; - int num_ents; - struct blame_entry *unblamed = target->suspects; - struct blame_entry *leftover = NULL; - - if (!unblamed) - return; /* nothing remains for this target */ - - diff_setup(&diff_opts); - DIFF_OPT_SET(&diff_opts, RECURSIVE); - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; - - diff_setup_done(&diff_opts); - - /* Try "find copies harder" on new path if requested; - * we do not want to use diffcore_rename() actually to - * match things up; find_copies_harder is set only to - * force diff_tree_sha1() to feed all filepairs to diff_queue, - * and this code needs to be after diff_setup_done(), which - * usually makes find-copies-harder imply copy detection. - */ - if ((opt & PICKAXE_BLAME_COPY_HARDEST) - || ((opt & PICKAXE_BLAME_COPY_HARDER) - && (!porigin || strcmp(target->path, porigin->path)))) - DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER); - - if (is_null_oid(&target->commit->object.oid)) - do_diff_cache(parent->tree->object.oid.hash, &diff_opts); - else - diff_tree_sha1(parent->tree->object.oid.hash, - target->commit->tree->object.oid.hash, - "", &diff_opts); - - if (!DIFF_OPT_TST(&diff_opts, FIND_COPIES_HARDER)) - diffcore_std(&diff_opts); - - do { - struct blame_entry **unblamedtail = &unblamed; - blame_list = setup_blame_list(unblamed, &num_ents); - - for (i = 0; i < diff_queued_diff.nr; i++) { - struct diff_filepair *p = diff_queued_diff.queue[i]; - struct origin *norigin; - mmfile_t file_p; - struct blame_entry this[3]; - - if (!DIFF_FILE_VALID(p->one)) - continue; /* does not exist in parent */ - if (S_ISGITLINK(p->one->mode)) - continue; /* ignore git links */ - if (porigin && !strcmp(p->one->path, porigin->path)) - /* find_move already dealt with this path */ - continue; - - norigin = get_origin(sb, parent, p->one->path); - oidcpy(&norigin->blob_oid, &p->one->oid); - norigin->mode = p->one->mode; - fill_origin_blob(&sb->revs->diffopt, norigin, &file_p); - if (!file_p.ptr) - continue; - - for (j = 0; j < num_ents; j++) { - find_copy_in_blob(sb, blame_list[j].ent, - norigin, this, &file_p); - copy_split_if_better(sb, blame_list[j].split, - this); - decref_split(this); - } - origin_decref(norigin); - } - - for (j = 0; j < num_ents; j++) { - struct blame_entry *split = blame_list[j].split; - if (split[1].suspect && - blame_copy_score < ent_score(sb, &split[1])) { - split_blame(blamed, &unblamedtail, split, - blame_list[j].ent); - } else { - blame_list[j].ent->next = leftover; - leftover = blame_list[j].ent; - } - decref_split(split); - } - free(blame_list); - *unblamedtail = NULL; - toosmall = filter_small(sb, toosmall, &unblamed, blame_copy_score); - } while (unblamed); - target->suspects = reverse_blame(leftover, NULL); - diff_flush(&diff_opts); - clear_pathspec(&diff_opts.pathspec); -} - -/* - * The blobs of origin and porigin exactly match, so everything - * origin is suspected for can be blamed on the parent. - */ -static void pass_whole_blame(struct scoreboard *sb, - struct origin *origin, struct origin *porigin) -{ - struct blame_entry *e, *suspects; - - if (!porigin->file.ptr && origin->file.ptr) { - /* Steal its file */ - porigin->file = origin->file; - origin->file.ptr = NULL; - } - suspects = origin->suspects; - origin->suspects = NULL; - for (e = suspects; e; e = e->next) { - origin_incref(porigin); - origin_decref(e->suspect); - e->suspect = porigin; - } - queue_blames(sb, porigin, suspects); -} - -/* - * We pass blame from the current commit to its parents. We keep saying - * "parent" (and "porigin"), but what we mean is to find scapegoat to - * exonerate ourselves. - */ -static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit *commit) -{ - if (!reverse) { - if (revs->first_parent_only && - commit->parents && - commit->parents->next) { - free_commit_list(commit->parents->next); - commit->parents->next = NULL; - } - return commit->parents; - } - return lookup_decoration(&revs->children, &commit->object); -} - -static int num_scapegoats(struct rev_info *revs, struct commit *commit) -{ - struct commit_list *l = first_scapegoat(revs, commit); - return commit_list_count(l); -} - -/* Distribute collected unsorted blames to the respected sorted lists - * in the various origins. - */ -static void distribute_blame(struct scoreboard *sb, struct blame_entry *blamed) -{ - blamed = blame_sort(blamed, compare_blame_suspect); - while (blamed) - { - struct origin *porigin = blamed->suspect; - struct blame_entry *suspects = NULL; - do { - struct blame_entry *next = blamed->next; - blamed->next = suspects; - suspects = blamed; - blamed = next; - } while (blamed && blamed->suspect == porigin); - suspects = reverse_blame(suspects, NULL); - queue_blames(sb, porigin, suspects); - } -} - -#define MAXSG 16 - -static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) -{ - struct rev_info *revs = sb->revs; - int i, pass, num_sg; - struct commit *commit = origin->commit; - struct commit_list *sg; - struct origin *sg_buf[MAXSG]; - struct origin *porigin, **sg_origin = sg_buf; - struct blame_entry *toosmall = NULL; - struct blame_entry *blames, **blametail = &blames; - - num_sg = num_scapegoats(revs, commit); - if (!num_sg) - goto finish; - else if (num_sg < ARRAY_SIZE(sg_buf)) - memset(sg_buf, 0, sizeof(sg_buf)); - else - sg_origin = xcalloc(num_sg, sizeof(*sg_origin)); - - /* - * The first pass looks for unrenamed path to optimize for - * common cases, then we look for renames in the second pass. - */ - for (pass = 0; pass < 2 - no_whole_file_rename; pass++) { - struct origin *(*find)(struct scoreboard *, - struct commit *, struct origin *); - find = pass ? find_rename : find_origin; - - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct commit *p = sg->item; - int j, same; - - if (sg_origin[i]) - continue; - if (parse_commit(p)) - continue; - porigin = find(sb, p, origin); - if (!porigin) - continue; - if (!oidcmp(&porigin->blob_oid, &origin->blob_oid)) { - pass_whole_blame(sb, origin, porigin); - origin_decref(porigin); - goto finish; - } - for (j = same = 0; j < i; j++) - if (sg_origin[j] && - !oidcmp(&sg_origin[j]->blob_oid, &porigin->blob_oid)) { - same = 1; - break; - } - if (!same) - sg_origin[i] = porigin; - else - origin_decref(porigin); - } - } - - num_commits++; - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (!porigin) - continue; - if (!origin->previous) { - origin_incref(porigin); - origin->previous = porigin; - } - pass_blame_to_parent(sb, origin, porigin); - if (!origin->suspects) - goto finish; - } - - /* - * Optionally find moves in parents' files. - */ - if (opt & PICKAXE_BLAME_MOVE) { - filter_small(sb, &toosmall, &origin->suspects, blame_move_score); - if (origin->suspects) { - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - if (!porigin) - continue; - find_move_in_parent(sb, &blametail, &toosmall, origin, porigin); - if (!origin->suspects) - break; - } - } - } - - /* - * Optionally find copies from parents' files. - */ - if (opt & PICKAXE_BLAME_COPY) { - if (blame_copy_score > blame_move_score) - filter_small(sb, &toosmall, &origin->suspects, blame_copy_score); - else if (blame_copy_score < blame_move_score) { - origin->suspects = blame_merge(origin->suspects, toosmall); - toosmall = NULL; - filter_small(sb, &toosmall, &origin->suspects, blame_copy_score); - } - if (!origin->suspects) - goto finish; - - for (i = 0, sg = first_scapegoat(revs, commit); - i < num_sg && sg; - sg = sg->next, i++) { - struct origin *porigin = sg_origin[i]; - find_copy_in_parent(sb, &blametail, &toosmall, - origin, sg->item, porigin, opt); - if (!origin->suspects) - goto finish; - } - } - -finish: - *blametail = NULL; - distribute_blame(sb, blames); - /* - * prepend toosmall to origin->suspects - * - * There is no point in sorting: this ends up on a big - * unsorted list in the caller anyway. - */ - if (toosmall) { - struct blame_entry **tail = &toosmall; - while (*tail) - tail = &(*tail)->next; - *tail = origin->suspects; - origin->suspects = toosmall; - } - for (i = 0; i < num_sg; i++) { - if (sg_origin[i]) { - drop_origin_blob(sg_origin[i]); - origin_decref(sg_origin[i]); - } - } - drop_origin_blob(origin); - if (sg_buf != sg_origin) - free(sg_origin); + return blame_nth_line((struct blame_scoreboard *)data, lno); } /* @@ -1561,13 +79,13 @@ finish: struct commit_info { struct strbuf author; struct strbuf author_mail; - unsigned long author_time; + timestamp_t author_time; struct strbuf author_tz; /* filled only when asked for details */ struct strbuf committer; struct strbuf committer_mail; - unsigned long committer_time; + timestamp_t committer_time; struct strbuf committer_tz; struct strbuf summary; @@ -1578,7 +96,7 @@ struct commit_info { */ static void get_ac_line(const char *inbuf, const char *what, struct strbuf *name, struct strbuf *mail, - unsigned long *time, struct strbuf *tz) + timestamp_t *time, struct strbuf *tz) { struct ident_split ident; size_t len, maillen, namelen; @@ -1699,10 +217,10 @@ static void get_commit_info(struct commit *commit, * To allow LF and other nonportable characters in pathnames, * they are c-style quoted as needed. */ -static void write_filename_info(struct origin *suspect) +static void write_filename_info(struct blame_origin *suspect) { if (suspect->previous) { - struct origin *prev = suspect->previous; + struct blame_origin *prev = suspect->previous; printf("previous %s ", oid_to_hex(&prev->commit->object.oid)); write_name_quoted(prev->path, stdout, '\n'); } @@ -1716,7 +234,7 @@ static void write_filename_info(struct origin *suspect) * the first time each commit appears in the output (unless the * user has specifically asked for us to repeat). */ -static int emit_one_suspect_detail(struct origin *suspect, int repeat) +static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat) { struct commit_info ci; @@ -1727,11 +245,11 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) get_commit_info(suspect->commit, &ci, 1); printf("author %s\n", ci.author.buf); printf("author-mail %s\n", ci.author_mail.buf); - printf("author-time %lu\n", ci.author_time); + printf("author-time %"PRItime"\n", ci.author_time); printf("author-tz %s\n", ci.author_tz.buf); printf("committer %s\n", ci.committer.buf); printf("committer-mail %s\n", ci.committer_mail.buf); - printf("committer-time %lu\n", ci.committer_time); + printf("committer-time %"PRItime"\n", ci.committer_time); printf("committer-tz %s\n", ci.committer_tz.buf); printf("summary %s\n", ci.summary.buf); if (suspect->commit->object.flags & UNINTERESTING) @@ -1746,11 +264,12 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) * The blame_entry is found to be guilty for the range. * Show it in incremental output. */ -static void found_guilty_entry(struct blame_entry *ent, - struct progress_info *pi) +static void found_guilty_entry(struct blame_entry *ent, void *data) { + struct progress_info *pi = (struct progress_info *)data; + if (incremental) { - struct origin *suspect = ent->suspect; + struct blame_origin *suspect = ent->suspect; printf("%s %d %d %d\n", oid_to_hex(&suspect->commit->object.oid), @@ -1763,88 +282,14 @@ static void found_guilty_entry(struct blame_entry *ent, display_progress(pi->progress, pi->blamed_lines); } -/* - * The main loop -- while we have blobs with lines whose true origin - * is still unknown, pick one blob, and allow its lines to pass blames - * to its parents. */ -static void assign_blame(struct scoreboard *sb, int opt) -{ - struct rev_info *revs = sb->revs; - struct commit *commit = prio_queue_get(&sb->commits); - struct progress_info pi = { NULL, 0 }; - - if (show_progress) - pi.progress = start_progress_delay(_("Blaming lines"), - sb->num_lines, 50, 1); - - while (commit) { - struct blame_entry *ent; - struct origin *suspect = commit->util; - - /* find one suspect to break down */ - while (suspect && !suspect->suspects) - suspect = suspect->next; - - if (!suspect) { - commit = prio_queue_get(&sb->commits); - continue; - } - - assert(commit == suspect->commit); - - /* - * We will use this suspect later in the loop, - * so hold onto it in the meantime. - */ - origin_incref(suspect); - parse_commit(commit); - if (reverse || - (!(commit->object.flags & UNINTERESTING) && - !(revs->max_age != -1 && commit->date < revs->max_age))) - pass_blame(sb, suspect, opt); - else { - commit->object.flags |= UNINTERESTING; - if (commit->object.parsed) - mark_parents_uninteresting(commit); - } - /* treat root commit as boundary */ - if (!commit->parents && !show_root) - commit->object.flags |= UNINTERESTING; - - /* Take responsibility for the remaining entries */ - ent = suspect->suspects; - if (ent) { - suspect->guilty = 1; - for (;;) { - struct blame_entry *next = ent->next; - found_guilty_entry(ent, &pi); - if (next) { - ent = next; - continue; - } - ent->next = sb->ent; - sb->ent = suspect->suspects; - suspect->suspects = NULL; - break; - } - } - origin_decref(suspect); - - if (DEBUG) /* sanity */ - sanity_check_refcnt(sb); - } - - stop_progress(&pi.progress); -} - -static const char *format_time(unsigned long time, const char *tz_str, +static const char *format_time(timestamp_t time, const char *tz_str, int show_raw_time) { static struct strbuf time_buf = STRBUF_INIT; strbuf_reset(&time_buf); if (show_raw_time) { - strbuf_addf(&time_buf, "%lu %s", time, tz_str); + strbuf_addf(&time_buf, "%"PRItime" %s", time, tz_str); } else { const char *time_str; @@ -1876,20 +321,20 @@ static const char *format_time(unsigned long time, const char *tz_str, #define OUTPUT_SHOW_EMAIL 0400 #define OUTPUT_LINE_PORCELAIN 01000 -static void emit_porcelain_details(struct origin *suspect, int repeat) +static void emit_porcelain_details(struct blame_origin *suspect, int repeat) { if (emit_one_suspect_detail(suspect, repeat) || (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) write_filename_info(suspect); } -static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, +static void emit_porcelain(struct blame_scoreboard *sb, struct blame_entry *ent, int opt) { int repeat = opt & OUTPUT_LINE_PORCELAIN; int cnt; const char *cp; - struct origin *suspect = ent->suspect; + struct blame_origin *suspect = ent->suspect; char hex[GIT_MAX_HEXSZ + 1]; oid_to_hex_r(hex, &suspect->commit->object.oid); @@ -1900,7 +345,7 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, ent->num_lines); emit_porcelain_details(suspect, repeat); - cp = nth_line(sb, ent->lno); + cp = blame_nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; if (cnt) { @@ -1922,11 +367,11 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent, putchar('\n'); } -static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) +static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt) { int cnt; const char *cp; - struct origin *suspect = ent->suspect; + struct blame_origin *suspect = ent->suspect; struct commit_info ci; char hex[GIT_MAX_HEXSZ + 1]; int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); @@ -1934,7 +379,7 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) get_commit_info(suspect->commit, &ci, 1); oid_to_hex_r(hex, &suspect->commit->object.oid); - cp = nth_line(sb, ent->lno); + cp = blame_nth_line(sb, ent->lno); for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev; @@ -2001,14 +446,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) commit_info_destroy(&ci); } -static void output(struct scoreboard *sb, int option) +static void output(struct blame_scoreboard *sb, int option) { struct blame_entry *ent; if (option & OUTPUT_PORCELAIN) { for (ent = sb->ent; ent; ent = ent->next) { int count = 0; - struct origin *suspect; + struct blame_origin *suspect; struct commit *commit = ent->suspect->commit; if (commit->object.flags & MORE_THAN_ONE_PATH) continue; @@ -2030,40 +475,6 @@ static void output(struct scoreboard *sb, int option) } } -static const char *get_next_line(const char *start, const char *end) -{ - const char *nl = memchr(start, '\n', end - start); - return nl ? nl + 1 : end; -} - -/* - * To allow quick access to the contents of nth line in the - * final image, prepare an index in the scoreboard. - */ -static int prepare_lines(struct scoreboard *sb) -{ - const char *buf = sb->final_buf; - unsigned long len = sb->final_buf_size; - const char *end = buf + len; - const char *p; - int *lineno; - int num = 0; - - for (p = buf; p < end; p = get_next_line(p, end)) - num++; - - ALLOC_ARRAY(sb->lineno, num + 1); - lineno = sb->lineno; - - for (p = buf; p < end; p = get_next_line(p, end)) - *lineno++ = p - buf; - - *lineno = len; - - sb->num_lines = num; - return sb->num_lines; -} - /* * Add phony grafts for use with -S; this is primarily to * support git's cvsserver that wants to give a linear history @@ -2071,13 +482,13 @@ static int prepare_lines(struct scoreboard *sb) */ static int read_ancestry(const char *graft_file) { - FILE *fp = fopen(graft_file, "r"); + FILE *fp = fopen_or_warn(graft_file, "r"); struct strbuf buf = STRBUF_INIT; if (!fp) return -1; while (!strbuf_getwholeline(&buf, fp, '\n')) { /* The format is just "Commit Parent1 Parent2 ...\n" */ - struct commit_graft *graft = read_graft_line(buf.buf, buf.len); + struct commit_graft *graft = read_graft_line(&buf); if (graft) register_commit_graft(graft, 0); } @@ -2086,7 +497,7 @@ static int read_ancestry(const char *graft_file) return 0; } -static int update_auto_abbrev(int auto_abbrev, struct origin *suspect) +static int update_auto_abbrev(int auto_abbrev, struct blame_origin *suspect) { const char *uniq = find_unique_abbrev(suspect->commit->object.oid.hash, auto_abbrev); @@ -2100,7 +511,7 @@ static int update_auto_abbrev(int auto_abbrev, struct origin *suspect) * How many columns do we need to show line numbers, authors, * and filenames? */ -static void find_alignment(struct scoreboard *sb, int *option) +static void find_alignment(struct blame_scoreboard *sb, int *option) { int longest_src_lines = 0; int longest_dst_lines = 0; @@ -2110,7 +521,7 @@ static void find_alignment(struct scoreboard *sb, int *option) int auto_abbrev = DEFAULT_ABBREV; for (e = sb->ent; e; e = e->next) { - struct origin *suspect = e->suspect; + struct blame_origin *suspect = e->suspect; int num; if (compute_auto_abbrev) @@ -2138,8 +549,8 @@ static void find_alignment(struct scoreboard *sb, int *option) num = e->lno + e->num_lines; if (longest_dst_lines < num) longest_dst_lines = num; - if (largest_score < ent_score(sb, e)) - largest_score = ent_score(sb, e); + if (largest_score < blame_entry_score(sb, e)) + largest_score = blame_entry_score(sb, e); } max_orig_digits = decimal_width(longest_src_lines); max_digits = decimal_width(longest_dst_lines); @@ -2150,31 +561,12 @@ static void find_alignment(struct scoreboard *sb, int *option) abbrev = auto_abbrev + 1; } -/* - * For debugging -- origin is refcounted, and this asserts that - * we do not underflow. - */ -static void sanity_check_refcnt(struct scoreboard *sb) +static void sanity_check_on_fail(struct blame_scoreboard *sb, int baa) { - int baa = 0; - struct blame_entry *ent; - - for (ent = sb->ent; ent; ent = ent->next) { - /* Nobody should have zero or negative refcnt */ - if (ent->suspect->refcnt <= 0) { - fprintf(stderr, "%s in %s has negative refcnt %d\n", - ent->suspect->path, - oid_to_hex(&ent->suspect->commit->object.oid), - ent->suspect->refcnt); - baa = 1; - } - } - if (baa) { - int opt = 0160; - find_alignment(sb, &opt); - output(sb, opt); - die("Baa %d!", baa); - } + int opt = OUTPUT_SHOW_SCORE | OUTPUT_SHOW_NUMBER | OUTPUT_SHOW_NAME; + find_alignment(sb, &opt); + output(sb, opt); + die("Baa %d!", baa); } static unsigned parse_score(const char *arg) @@ -2224,301 +616,6 @@ static int git_blame_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -static void verify_working_tree_path(struct commit *work_tree, const char *path) -{ - struct commit_list *parents; - int pos; - - for (parents = work_tree->parents; parents; parents = parents->next) { - const struct object_id *commit_oid = &parents->item->object.oid; - struct object_id blob_oid; - unsigned mode; - - if (!get_tree_entry(commit_oid->hash, path, blob_oid.hash, &mode) && - sha1_object_info(blob_oid.hash, NULL) == OBJ_BLOB) - return; - } - - pos = cache_name_pos(path, strlen(path)); - if (pos >= 0) - ; /* path is in the index */ - else if (-1 - pos < active_nr && - !strcmp(active_cache[-1 - pos]->name, path)) - ; /* path is in the index, unmerged */ - else - die("no such path '%s' in HEAD", path); -} - -static struct commit_list **append_parent(struct commit_list **tail, const struct object_id *oid) -{ - struct commit *parent; - - parent = lookup_commit_reference(oid->hash); - if (!parent) - die("no such commit %s", oid_to_hex(oid)); - return &commit_list_insert(parent, tail)->next; -} - -static void append_merge_parents(struct commit_list **tail) -{ - int merge_head; - struct strbuf line = STRBUF_INIT; - - merge_head = open(git_path_merge_head(), O_RDONLY); - if (merge_head < 0) { - if (errno == ENOENT) - return; - die("cannot open '%s' for reading", git_path_merge_head()); - } - - while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) { - struct object_id oid; - if (line.len < GIT_SHA1_HEXSZ || get_oid_hex(line.buf, &oid)) - die("unknown line in '%s': %s", git_path_merge_head(), line.buf); - tail = append_parent(tail, &oid); - } - close(merge_head); - strbuf_release(&line); -} - -/* - * This isn't as simple as passing sb->buf and sb->len, because we - * want to transfer ownership of the buffer to the commit (so we - * must use detach). - */ -static void set_commit_buffer_from_strbuf(struct commit *c, struct strbuf *sb) -{ - size_t len; - void *buf = strbuf_detach(sb, &len); - set_commit_buffer(c, buf, len); -} - -/* - * Prepare a dummy commit that represents the work tree (or staged) item. - * Note that annotating work tree item never works in the reverse. - */ -static struct commit *fake_working_tree_commit(struct diff_options *opt, - const char *path, - const char *contents_from) -{ - struct commit *commit; - struct origin *origin; - struct commit_list **parent_tail, *parent; - struct object_id head_oid; - struct strbuf buf = STRBUF_INIT; - const char *ident; - time_t now; - int size, len; - struct cache_entry *ce; - unsigned mode; - struct strbuf msg = STRBUF_INIT; - - read_cache(); - time(&now); - commit = alloc_commit_node(); - commit->object.parsed = 1; - commit->date = now; - parent_tail = &commit->parents; - - if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL)) - die("no such ref: HEAD"); - - parent_tail = append_parent(parent_tail, &head_oid); - append_merge_parents(parent_tail); - verify_working_tree_path(commit, path); - - origin = make_origin(commit, path); - - ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0); - strbuf_addstr(&msg, "tree 0000000000000000000000000000000000000000\n"); - for (parent = commit->parents; parent; parent = parent->next) - strbuf_addf(&msg, "parent %s\n", - oid_to_hex(&parent->item->object.oid)); - strbuf_addf(&msg, - "author %s\n" - "committer %s\n\n" - "Version of %s from %s\n", - ident, ident, path, - (!contents_from ? path : - (!strcmp(contents_from, "-") ? "standard input" : contents_from))); - set_commit_buffer_from_strbuf(commit, &msg); - - if (!contents_from || strcmp("-", contents_from)) { - struct stat st; - const char *read_from; - char *buf_ptr; - unsigned long buf_len; - - if (contents_from) { - if (stat(contents_from, &st) < 0) - die_errno("Cannot stat '%s'", contents_from); - read_from = contents_from; - } - else { - if (lstat(path, &st) < 0) - die_errno("Cannot lstat '%s'", path); - read_from = path; - } - mode = canon_mode(st.st_mode); - - switch (st.st_mode & S_IFMT) { - case S_IFREG: - if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) && - textconv_object(read_from, mode, &null_oid, 0, &buf_ptr, &buf_len)) - strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1); - else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) - die_errno("cannot open or read '%s'", read_from); - break; - case S_IFLNK: - if (strbuf_readlink(&buf, read_from, st.st_size) < 0) - die_errno("cannot readlink '%s'", read_from); - break; - default: - die("unsupported file type %s", read_from); - } - } - else { - /* Reading from stdin */ - mode = 0; - if (strbuf_read(&buf, 0, 0) < 0) - die_errno("failed to read from stdin"); - } - convert_to_git(path, buf.buf, buf.len, &buf, 0); - origin->file.ptr = buf.buf; - origin->file.size = buf.len; - pretend_sha1_file(buf.buf, buf.len, OBJ_BLOB, origin->blob_oid.hash); - - /* - * Read the current index, replace the path entry with - * origin->blob_sha1 without mucking with its mode or type - * bits; we are not going to write this index out -- we just - * want to run "diff-index --cached". - */ - discard_cache(); - read_cache(); - - len = strlen(path); - if (!mode) { - int pos = cache_name_pos(path, len); - if (0 <= pos) - mode = active_cache[pos]->ce_mode; - else - /* Let's not bother reading from HEAD tree */ - mode = S_IFREG | 0644; - } - size = cache_entry_size(len); - ce = xcalloc(1, size); - oidcpy(&ce->oid, &origin->blob_oid); - memcpy(ce->name, path, len); - ce->ce_flags = create_ce_flags(0); - ce->ce_namelen = len; - ce->ce_mode = create_ce_mode(mode); - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - - cache_tree_invalidate_path(&the_index, path); - - return commit; -} - -static struct commit *find_single_final(struct rev_info *revs, - const char **name_p) -{ - int i; - struct commit *found = NULL; - const char *name = NULL; - - for (i = 0; i < revs->pending.nr; i++) { - struct object *obj = revs->pending.objects[i].item; - if (obj->flags & UNINTERESTING) - continue; - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - die("Non commit %s?", revs->pending.objects[i].name); - if (found) - die("More than one commit to dig from %s and %s?", - revs->pending.objects[i].name, name); - found = (struct commit *)obj; - name = revs->pending.objects[i].name; - } - if (name_p) - *name_p = name; - return found; -} - -static char *prepare_final(struct scoreboard *sb) -{ - const char *name; - sb->final = find_single_final(sb->revs, &name); - return xstrdup_or_null(name); -} - -static const char *dwim_reverse_initial(struct scoreboard *sb) -{ - /* - * DWIM "git blame --reverse ONE -- PATH" as - * "git blame --reverse ONE..HEAD -- PATH" but only do so - * when it makes sense. - */ - struct object *obj; - struct commit *head_commit; - unsigned char head_sha1[20]; - - if (sb->revs->pending.nr != 1) - return NULL; - - /* Is that sole rev a committish? */ - obj = sb->revs->pending.objects[0].item; - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - return NULL; - - /* Do we have HEAD? */ - if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL)) - return NULL; - head_commit = lookup_commit_reference_gently(head_sha1, 1); - if (!head_commit) - return NULL; - - /* Turn "ONE" into "ONE..HEAD" then */ - obj->flags |= UNINTERESTING; - add_pending_object(sb->revs, &head_commit->object, "HEAD"); - - sb->final = (struct commit *)obj; - return sb->revs->pending.objects[0].name; -} - -static char *prepare_initial(struct scoreboard *sb) -{ - int i; - const char *final_commit_name = NULL; - struct rev_info *revs = sb->revs; - - /* - * There must be one and only one negative commit, and it must be - * the boundary. - */ - for (i = 0; i < revs->pending.nr; i++) { - struct object *obj = revs->pending.objects[i].item; - if (!(obj->flags & UNINTERESTING)) - continue; - obj = deref_tag(obj, NULL, 0); - if (obj->type != OBJ_COMMIT) - die("Non commit %s?", revs->pending.objects[i].name); - if (sb->final) - die("More than one commit to dig up from, %s and %s?", - revs->pending.objects[i].name, - final_commit_name); - sb->final = (struct commit *) obj; - final_commit_name = revs->pending.objects[i].name; - } - - if (!final_commit_name) - final_commit_name = dwim_reverse_initial(sb); - if (!final_commit_name) - die("No commit to dig up from?"); - return xstrdup(final_commit_name); -} - static int blame_copy_callback(const struct option *option, const char *arg, int unset) { int *opt = option->value; @@ -2552,17 +649,24 @@ static int blame_move_callback(const struct option *option, const char *arg, int return 0; } +static int is_a_rev(const char *name) +{ + struct object_id oid; + + if (get_oid(name, &oid)) + return 0; + return OBJ_NONE < sha1_object_info(oid.hash, NULL); +} + int cmd_blame(int argc, const char **argv, const char *prefix) { struct rev_info revs; const char *path; - struct scoreboard sb; - struct origin *o; + struct blame_scoreboard sb; + struct blame_origin *o; struct blame_entry *ent = NULL; long dashdash_pos, lno; - char *final_commit_name = NULL; - enum object_type type; - struct commit *final_commit = NULL; + struct progress_info pi = { NULL, 0 }; struct string_list range_list = STRING_LIST_INIT_NODUP; int output_option = 0, opt = 0; @@ -2613,8 +717,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) git_config(git_blame_config, &output_option); init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; - DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); - DIFF_OPT_SET(&revs.diffopt, FOLLOW_RENAMES); + revs.diffopt.flags.allow_textconv = 1; + revs.diffopt.flags.follow_renames = 1; save_commit_buffer = 0; dashdash_pos = 0; @@ -2639,9 +743,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_revision_opt(&revs, &ctx, options, blame_opt_usage); } parse_done: - no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); + no_whole_file_rename = !revs.diffopt.flags.follow_renames; xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC; - DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); + revs.diffopt.flags.follow_renames = 0; argc = parse_options_end(&ctx); if (incremental || (output_option & OUTPUT_PORCELAIN)) { @@ -2708,15 +812,10 @@ parse_done: } blame_date_width -= 1; /* strip the null */ - if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER)) + if (revs.diffopt.flags.find_copies_harder) opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE | PICKAXE_BLAME_COPY_HARDER); - if (!blame_move_score) - blame_move_score = BLAME_DEFAULT_MOVE_SCORE; - if (!blame_copy_score) - blame_copy_score = BLAME_DEFAULT_COPY_SCORE; - /* * We have collected options unknown to us in argv[1..unk] * which are to be passed to revision machinery if we are @@ -2755,108 +854,26 @@ parse_done: } else { if (argc < 2) usage_with_options(blame_opt_usage, options); - path = add_prefix(prefix, argv[argc - 1]); - if (argc == 3 && !file_exists(path)) { /* (2b) */ + if (argc == 3 && is_a_rev(argv[argc - 1])) { /* (2b) */ path = add_prefix(prefix, argv[1]); argv[1] = argv[2]; + } else { /* (2a) */ + if (argc == 2 && is_a_rev(argv[1]) && !get_git_work_tree()) + die("missing <path> to blame"); + path = add_prefix(prefix, argv[argc - 1]); } argv[argc - 1] = "--"; - - setup_work_tree(); - if (!file_exists(path)) - die_errno("cannot stat path '%s'", path); } revs.disable_stdin = 1; setup_revisions(argc, argv, &revs, NULL); - memset(&sb, 0, sizeof(sb)); + init_scoreboard(&sb); sb.revs = &revs; - if (!reverse) { - final_commit_name = prepare_final(&sb); - sb.commits.compare = compare_commits_by_commit_date; - } - else if (contents_from) - die(_("--contents and --reverse do not blend well.")); - else { - final_commit_name = prepare_initial(&sb); - sb.commits.compare = compare_commits_by_reverse_commit_date; - if (revs.first_parent_only) - revs.children.name = NULL; - } - - if (!sb.final) { - /* - * "--not A B -- path" without anything positive; - * do not default to HEAD, but use the working tree - * or "--contents". - */ - setup_work_tree(); - sb.final = fake_working_tree_commit(&sb.revs->diffopt, - path, contents_from); - add_pending_object(&revs, &(sb.final->object), ":"); - } - else if (contents_from) - die(_("cannot use --contents with final commit object name")); - - if (reverse && revs.first_parent_only) { - final_commit = find_single_final(sb.revs, NULL); - if (!final_commit) - die(_("--reverse and --first-parent together require specified latest commit")); - } - - /* - * If we have bottom, this will mark the ancestors of the - * bottom commits we would reach while traversing as - * uninteresting. - */ - if (prepare_revision_walk(&revs)) - die(_("revision walk setup failed")); - - if (reverse && revs.first_parent_only) { - struct commit *c = final_commit; - - sb.revs->children.name = "children"; - while (c->parents && - oidcmp(&c->object.oid, &sb.final->object.oid)) { - struct commit_list *l = xcalloc(1, sizeof(*l)); - - l->item = c; - if (add_decoration(&sb.revs->children, - &c->parents->item->object, l)) - die("BUG: not unique item in first-parent chain"); - c = c->parents->item; - } - - if (oidcmp(&c->object.oid, &sb.final->object.oid)) - die(_("--reverse --first-parent together require range along first-parent chain")); - } - - if (is_null_oid(&sb.final->object.oid)) { - o = sb.final->util; - sb.final_buf = xmemdupz(o->file.ptr, o->file.size); - sb.final_buf_size = o->file.size; - } - else { - o = get_origin(&sb, sb.final, path); - if (fill_blob_sha1_and_mode(o)) - die(_("no such path %s in %s"), path, final_commit_name); - - if (DIFF_OPT_TST(&sb.revs->diffopt, ALLOW_TEXTCONV) && - textconv_object(path, o->mode, &o->blob_oid, 1, (char **) &sb.final_buf, - &sb.final_buf_size)) - ; - else - sb.final_buf = read_sha1_file(o->blob_oid.hash, &type, - &sb.final_buf_size); - - if (!sb.final_buf) - die(_("cannot read blob %s for path %s"), - oid_to_hex(&o->blob_oid), - path); - } - num_read_blob++; - lno = prepare_lines(&sb); + sb.contents_from = contents_from; + sb.reverse = reverse; + setup_scoreboard(&sb, path, &o); + lno = sb.num_lines; if (lno && !range_list.nr) string_list_append(&range_list, "1"); @@ -2885,22 +902,13 @@ parse_done: for (range_i = ranges.nr; range_i > 0; --range_i) { const struct range *r = &ranges.ranges[range_i - 1]; - long bottom = r->start; - long top = r->end; - struct blame_entry *next = ent; - ent = xcalloc(1, sizeof(*ent)); - ent->lno = bottom; - ent->num_lines = top - bottom; - ent->suspect = o; - ent->s_lno = bottom; - ent->next = next; - origin_incref(o); + ent = blame_entry_prepend(ent, r->start, r->end, o); } o->suspects = ent; prio_queue_put(&sb.commits, o->commit); - origin_decref(o); + blame_origin_decref(o); range_set_release(&ranges); string_list_clear(&range_list, 0); @@ -2908,21 +916,37 @@ parse_done: sb.ent = NULL; sb.path = path; + if (blame_move_score) + sb.move_score = blame_move_score; + if (blame_copy_score) + sb.copy_score = blame_copy_score; + + sb.debug = DEBUG; + sb.on_sanity_fail = &sanity_check_on_fail; + + sb.show_root = show_root; + sb.xdl_opts = xdl_opts; + sb.no_whole_file_rename = no_whole_file_rename; + read_mailmap(&mailmap, NULL); + sb.found_guilty_entry = &found_guilty_entry; + sb.found_guilty_entry_data = π + if (show_progress) + pi.progress = start_delayed_progress(_("Blaming lines"), sb.num_lines); + assign_blame(&sb, opt); + stop_progress(&pi.progress); + if (!incremental) setup_pager(); - - free(final_commit_name); - - if (incremental) + else return 0; - sb.ent = blame_sort(sb.ent, compare_blame_final); + blame_sort_final(&sb); - coalesce(&sb); + blame_coalesce(&sb); if (!(output_option & OUTPUT_PORCELAIN)) find_alignment(&sb, &output_option); @@ -2936,9 +960,9 @@ parse_done: } if (show_stats) { - printf("num read blob: %d\n", num_read_blob); - printf("num get patch: %d\n", num_get_patch); - printf("num commits: %d\n", num_commits); + printf("num read blob: %d\n", sb.num_read_blob); + printf("num get patch: %d\n", sb.num_get_patch); + printf("num commits: %d\n", sb.num_commits); } return 0; } diff --git a/builtin/branch.c b/builtin/branch.c index a3bd2262b3..6d0cea9d4b 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -6,6 +6,7 @@ */ #include "cache.h" +#include "config.h" #include "color.h" #include "refs.h" #include "commit.h" @@ -27,6 +28,7 @@ static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), + N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"), N_("git branch [<options>] [-r | -a] [--points-at]"), N_("git branch [<options>] [-r | -a] [--format]"), NULL @@ -123,8 +125,8 @@ static int branch_merged(int kind, const char *name, if (upstream && (reference_name = reference_name_to_free = resolve_refdup(upstream, RESOLVE_REF_READING, - oid.hash, NULL)) != NULL) - reference_rev = lookup_commit_reference(oid.hash); + &oid, NULL)) != NULL) + reference_rev = lookup_commit_reference(&oid); } if (!reference_rev) reference_rev = head_rev; @@ -157,7 +159,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->hash); + struct commit *rev = lookup_commit_reference(oid); if (!rev) { error(_("Couldn't look up commit object for '%s'"), refname); return -1; @@ -211,11 +213,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, } if (!force) { - head_rev = lookup_commit_reference(head_oid.hash); + head_rev = lookup_commit_reference(&head_oid); if (!head_rev) die(_("Couldn't look up commit object for HEAD")); } - for (i = 0; i < argc; i++, strbuf_release(&bname)) { + for (i = 0; i < argc; i++, strbuf_reset(&bname)) { char *target = NULL; int flags = 0; @@ -239,7 +241,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE | RESOLVE_REF_ALLOW_BAD_NAME, - oid.hash, &flags); + &oid, &flags); if (!target) { error(remote_branch ? _("remote-tracking branch '%s' not found.") @@ -255,8 +257,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, goto next; } - if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : oid.hash, - REF_NODEREF)) { + if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid, + REF_NO_DEREF)) { error(remote_branch ? _("Error deleting remote-tracking branch '%s'") : _("Error deleting branch '%s'"), @@ -280,8 +282,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, } free(name); + strbuf_release(&bname); - return(ret); + return ret; } static int calc_maxwidth(struct ref_array *refs, int remote_bonus) @@ -351,7 +354,7 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r strbuf_addf(&obname, "%%(objectname:short=%d)", filter->abbrev); strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth); - strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET)); + strbuf_addstr(&local, branch_get_color(BRANCH_COLOR_RESET)); strbuf_addf(&local, " %s ", obname.buf); if (filter->verbose > 1) @@ -382,7 +385,7 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r return strbuf_detach(&fmt, NULL); } -static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format) { int i; struct ref_array array; @@ -406,14 +409,17 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin if (filter->verbose) maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); - if (!format) - format = to_free = build_format(filter, maxwidth, remote_prefix); - verify_ref_format(format); + if (!format->format) + format->format = to_free = build_format(filter, maxwidth, remote_prefix); + format->use_color = branch_use_color; + + if (verify_ref_format(format)) + die(_("unable to parse format string")); ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) { - format_ref_array_item(array.items[i], format, 0, &out); + format_ref_array_item(array.items[i], format, &out); if (column_active(colopts)) { assert(!filter->verbose && "--column and --verbose are incompatible"); /* format to a string_list to let print_columns() do its job */ @@ -452,15 +458,20 @@ static void reject_rebase_or_bisect_branch(const char *target) free_worktrees(worktrees); } -static void rename_branch(const char *oldname, const char *newname, int force) +static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force) { struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT; + const char *interpreted_oldname = NULL; + const char *interpreted_newname = NULL; int recovery = 0; - int clobber_head_ok; - if (!oldname) - die(_("cannot rename the current branch while not on any.")); + if (!oldname) { + if (copy) + die(_("cannot copy the current branch while not on any.")); + else + die(_("cannot rename the current branch while not on any.")); + } if (strbuf_check_branch_ref(&oldref, oldname)) { /* @@ -477,32 +488,53 @@ static void rename_branch(const char *oldname, const char *newname, int force) * A command like "git branch -M currentbranch currentbranch" cannot * cause the worktree to become inconsistent with HEAD, so allow it. */ - clobber_head_ok = !strcmp(oldname, newname); - - validate_new_branchname(newname, &newref, force, clobber_head_ok); + if (!strcmp(oldname, newname)) + validate_branchname(newname, &newref); + else + validate_new_branchname(newname, &newref, force); reject_rebase_or_bisect_branch(oldref.buf); - strbuf_addf(&logmsg, "Branch: renamed %s to %s", - oldref.buf, newref.buf); + if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) || + !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) { + die("BUG: expected prefix missing for refs"); + } + + if (copy) + strbuf_addf(&logmsg, "Branch: copied %s to %s", + oldref.buf, newref.buf); + else + strbuf_addf(&logmsg, "Branch: renamed %s to %s", + oldref.buf, newref.buf); - if (rename_ref(oldref.buf, newref.buf, logmsg.buf)) + if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch rename failed")); + if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf)) + die(_("Branch copy failed")); - if (recovery) - warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11); + if (recovery) { + if (copy) + warning(_("Created a copy of a misnamed branch '%s'"), + interpreted_oldname); + else + warning(_("Renamed a misnamed branch '%s' away"), + interpreted_oldname); + } - if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf)) + if (!copy && + replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch renamed to %s, but HEAD is not updated!"), newname); strbuf_release(&logmsg); - strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); + strbuf_addf(&oldsection, "branch.%s", interpreted_oldname); strbuf_release(&oldref); - strbuf_addf(&newsection, "branch.%s", newref.buf + 11); + strbuf_addf(&newsection, "branch.%s", interpreted_newname); strbuf_release(&newref); - if (git_config_rename_section(oldsection.buf, newsection.buf) < 0) + if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0) die(_("Branch is renamed, but update of config-file failed")); + if (copy && strcmp(oldname, newname) && git_config_copy_section(oldsection.buf, newsection.buf) < 0) + die(_("Branch is copied, but update of config-file failed")); strbuf_release(&oldsection); strbuf_release(&newsection); } @@ -540,7 +572,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, force = 0, list = 0; + int delete = 0, rename = 0, copy = 0, force = 0, list = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; const char *new_upstream = NULL; @@ -548,7 +580,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct ref_filter filter; int icase = 0; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; - const char *format = NULL; + struct ref_format format = REF_FORMAT_INIT; struct option options[] = { OPT_GROUP(N_("Generic options")), @@ -557,8 +589,8 @@ 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), - OPT_SET_INT( 0, "set-upstream", &track, N_("change upstream info"), - BRANCH_TRACK_OVERRIDE), + { OPTION_SET_INT, 0, "set-upstream", &track, NULL, N_("do not use"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, BRANCH_TRACK_OVERRIDE }, 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__COLOR(&branch_use_color, N_("use colored output")), @@ -577,11 +609,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2), OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1), 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(0, "edit-description", &edit_description, N_("edit the description for the branch")), - OPT__FORCE(&force, N_("force creation, move/rename, deletion")), + 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")), @@ -592,7 +626,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) N_("print only branches of the object"), 0, parse_opt_object_name }, OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), - OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT_END(), }; @@ -609,7 +643,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) track = git_branch_track; - head = resolve_refdup("HEAD", 0, head_oid.hash, NULL); + head = resolve_refdup("HEAD", 0, &head_oid, NULL); if (!head) die(_("Failed to resolve HEAD as a valid ref.")); if (!strcmp(head, "HEAD")) @@ -620,14 +654,14 @@ 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 && !edit_description && !new_upstream && !unset_upstream && argc == 0) + if (!delete && !rename && !copy && !edit_description && !new_upstream && !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 + !!new_upstream + + if (!!delete + !!rename + !!copy + !!new_upstream + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); @@ -645,8 +679,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (force) { delete *= 2; rename *= 2; + copy *= 2; } + if (list) + setup_auto_pager("branch", 1); + if (delete) { if (!argc) die(_("branch name required")); @@ -666,7 +704,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!sorting) sorting = ref_default_sorting(); sorting->ignore_case = icase; - print_ref_list(&filter, sorting, format); + print_ref_list(&filter, sorting, &format); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); return 0; @@ -699,20 +737,29 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (edit_branch_description(branch_name)) return 1; + } else if (copy) { + if (!argc) + die(_("branch name required")); + else if (argc == 1) + copy_or_rename_branch(head, argv[0], 1, copy > 1); + else if (argc == 2) + copy_or_rename_branch(argv[0], argv[1], 1, copy > 1); + else + die(_("too many branches for a copy operation")); } else if (rename) { if (!argc) die(_("branch name required")); else if (argc == 1) - rename_branch(head, argv[0], rename > 1); + copy_or_rename_branch(head, argv[0], 0, rename > 1); else if (argc == 2) - rename_branch(argv[0], argv[1], rename > 1); + copy_or_rename_branch(argv[0], argv[1], 0, rename > 1); else - die(_("too many branches for a rename operation")); + die(_("too many arguments for a rename operation")); } else if (new_upstream) { struct branch *branch = branch_get(argv[0]); if (argc > 1) - die(_("too many branches to set new upstream")); + die(_("too many arguments to set new upstream")); if (!branch) { if (!argc || !strcmp(argv[0], "HEAD")) @@ -735,7 +782,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; if (argc > 1) - die(_("too many branches to unset upstream")); + die(_("too many arguments to unset upstream")); if (!branch) { if (!argc || !strcmp(argv[0], "HEAD")) @@ -755,11 +802,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix) strbuf_release(&buf); } else if (argc > 0 && argc <= 2) { struct branch *branch = branch_get(argv[0]); - int branch_existed = 0, remote_tracking = 0; - struct strbuf buf = STRBUF_INIT; - - if (!strcmp(argv[0], "HEAD")) - die(_("it does not make sense to create 'HEAD' manually")); if (!branch) die(_("no such branch '%s'"), argv[0]); @@ -768,27 +810,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) die(_("-a and -r options to 'git branch' do not make sense with a branch name")); if (track == BRANCH_TRACK_OVERRIDE) - fprintf(stderr, _("The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to\n")); + die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.")); - strbuf_addf(&buf, "refs/remotes/%s", branch->name); - remote_tracking = ref_exists(buf.buf); - strbuf_release(&buf); - - branch_existed = ref_exists(branch->refname); create_branch(argv[0], (argc == 2) ? argv[1] : head, - force, reflog, 0, quiet, track); - - /* - * We only show the instructions if the user gave us - * one branch which doesn't exist locally, but is the - * name of a remote-tracking branch. - */ - if (argc == 1 && track == BRANCH_TRACK_OVERRIDE && - !branch_existed && remote_tracking) { - fprintf(stderr, _("\nIf you wanted to make '%s' track '%s', do this:\n\n"), head, branch->name); - fprintf(stderr, " git branch -d %s\n", branch->name); - fprintf(stderr, " git branch --set-upstream-to %s\n", branch->name); - } + force, 0, reflog, quiet, track); } else usage_with_options(builtin_branch_usage, options); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 73c81f0cb1..d90170f070 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -4,12 +4,15 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "builtin.h" +#include "diff.h" #include "parse-options.h" #include "userdiff.h" #include "streaming.h" #include "tree-walk.h" #include "sha1-array.h" +#include "packfile.h" struct batch_options { int enabled; @@ -55,14 +58,14 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, struct object_context obj_context; struct object_info oi = OBJECT_INFO_INIT; struct strbuf sb = STRBUF_INIT; - unsigned flags = LOOKUP_REPLACE_OBJECT; + unsigned flags = OBJECT_INFO_LOOKUP_REPLACE; const char *path = force_path; if (unknown_type) - flags |= LOOKUP_UNKNOWN_OBJECT; + flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE; - if (get_sha1_with_context(obj_name, GET_SHA1_RECORD_PATH, - oid.hash, &obj_context)) + if (get_oid_with_context(obj_name, GET_OID_RECORD_PATH, + &oid, &obj_context)) die("Not a valid object name %s", obj_name); if (!path) @@ -73,7 +76,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, buf = NULL; switch (opt) { case 't': - oi.typename = &sb; + oi.type_name = &sb; if (sha1_object_info_extended(oid.hash, &oi, flags) < 0) die("git cat-file: could not get object info"); if (sb.len) { @@ -94,7 +97,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, return !has_object_file(&oid); case 'w': - if (!path[0]) + if (!path) die("git cat-file --filters %s: <object> must be " "<sha1:path>", obj_name); @@ -104,12 +107,13 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, break; case 'c': - if (!path[0]) + if (!path) die("git cat-file --textconv %s: <object> must be <sha1:path>", obj_name); if (textconv_object(path, obj_context.mode, &oid, 1, &buf, &size)) break; + /* else fallthrough */ case 'p': type = sha1_object_info(oid.hash, NULL); @@ -225,7 +229,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, if (data->mark_query) data->info.typep = &data->type; else - strbuf_addstr(sb, typename(data->type)); + strbuf_addstr(sb, type_name(data->type)); } else if (is_atom("objectsize", atom, len)) { if (data->mark_query) data->info.sizep = &data->size; @@ -336,7 +340,8 @@ static void batch_object_write(const char *obj_name, struct batch_options *opt, struct strbuf buf = STRBUF_INIT; if (!data->skip_object_info && - sha1_object_info_extended(data->oid.hash, &data->info, LOOKUP_REPLACE_OBJECT) < 0) { + sha1_object_info_extended(data->oid.hash, &data->info, + OBJECT_INFO_LOOKUP_REPLACE) < 0) { printf("%s missing\n", obj_name ? obj_name : oid_to_hex(&data->oid)); fflush(stdout); @@ -358,10 +363,10 @@ static void batch_one_object(const char *obj_name, struct batch_options *opt, struct expand_data *data) { struct object_context ctx; - int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0; + int flags = opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0; enum follow_symlinks_result result; - result = get_sha1_with_context(obj_name, flags, data->oid.hash, &ctx); + result = get_oid_with_context(obj_name, flags, &data->oid, &ctx); if (result != FOUND) { switch (result) { case MISSING_OBJECT: @@ -470,6 +475,8 @@ static int batch_objects(struct batch_options *opt) 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."); cb.opt = opt; cb.expand = &data; diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 4d01ca0c8b..91444dc044 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "attr.h" #include "quote.h" #include "parse-options.h" diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 1d73d3ca3d..ec9a959e08 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -1,9 +1,11 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "dir.h" #include "quote.h" #include "pathspec.h" #include "parse-options.h" +#include "submodule.h" static int quiet, verbose, stdin_paths, show_non_matching, no_index; static const char * const check_ignore_usage[] = { @@ -70,7 +72,7 @@ static int check_ignore(struct dir_struct *dir, { const char *full_path; char *seen; - int num_ignored = 0, dtype = DT_UNKNOWN, i; + int num_ignored = 0, i; struct exclude *exclude; struct pathspec pathspec; @@ -87,21 +89,24 @@ static int check_ignore(struct dir_struct *dir, parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP, PATHSPEC_SYMLINK_LEADING_PATH | - PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE | PATHSPEC_KEEP_ORDER, prefix, argv); + die_path_inside_submodule(&the_index, &pathspec); + /* * look for pathspecs matching entries in the index, since these * should not be ignored, in order to be consistent with * 'git status', 'git add' etc. */ - seen = find_pathspecs_matching_against_index(&pathspec); + seen = find_pathspecs_matching_against_index(&pathspec, &the_index); for (i = 0; i < pathspec.nr; i++) { full_path = pathspec.items[i].match; exclude = NULL; if (!seen[i]) { - exclude = last_exclude_matching(dir, full_path, &dtype); + int dtype = DT_UNKNOWN; + exclude = last_exclude_matching(dir, &the_index, + full_path, &dtype); } if (!quiet && (exclude || show_non_matching)) output_exclude(pathspec.items[i].original, exclude); diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c index cf0f54f6b9..cdce144f3b 100644 --- a/builtin/check-mailmap.c +++ b/builtin/check-mailmap.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "mailmap.h" #include "parse-options.h" #include "string-list.h" diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index eac499450f..bc67d3f0a8 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -39,12 +39,15 @@ static char *collapse_slashes(const char *refname) static int check_ref_format_branch(const char *arg) { struct strbuf sb = STRBUF_INIT; + const char *name; int nongit; setup_git_directory_gently(&nongit); - if (strbuf_check_branch_ref(&sb, arg)) + if (strbuf_check_branch_ref(&sb, arg) || + !skip_prefix(sb.buf, "refs/heads/", &name)) die("'%s' is not a valid branch name", arg); - printf("%s\n", sb.buf + 11); + printf("%s\n", name); + strbuf_release(&sb); return 0; } diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 07631d0c9c..a730f6a1aa 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -5,6 +5,7 @@ * */ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "quote.h" #include "cache-tree.h" @@ -128,8 +129,6 @@ static const char * const builtin_checkout_index_usage[] = { NULL }; -static struct lock_file lock_file; - static int option_parse_stage(const struct option *opt, const char *arg, int unset) { @@ -149,7 +148,7 @@ static int option_parse_stage(const struct option *opt, int cmd_checkout_index(int argc, const char **argv, const char *prefix) { int i; - int newfd = -1; + struct lock_file lock_file = LOCK_INIT; int all = 0; int read_from_stdin = 0; int prefix_length; @@ -158,7 +157,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) struct option builtin_checkout_index_options[] = { OPT_BOOL('a', "all", &all, N_("check out all files in the index")), - OPT__FORCE(&force, N_("force overwrite of existing files")), + OPT__FORCE(&force, N_("force overwrite of existing files"), 0), OPT__QUIET(&quiet, N_("no warning for existing files and files not in index")), OPT_BOOL('n', "no-create", ¬_new, @@ -205,7 +204,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) if (index_opt && !state.base_dir_len && !to_tempfile) { state.refresh_cache = 1; state.istate = &the_index; - newfd = hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); } /* Check out named files first */ @@ -250,7 +249,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) if (all) checkout_all(prefix, prefix_length); - if (0 <= newfd && + if (is_lock_file_locked(&lock_file) && write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die("Unable to write new index file"); return 0; diff --git a/builtin/checkout.c b/builtin/checkout.c index b360943455..d76e13c852 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1,4 +1,6 @@ #include "builtin.h" +#include "config.h" +#include "checkout.h" #include "lockfile.h" #include "parse-options.h" #include "refs.h" @@ -21,31 +23,12 @@ #include "submodule-config.h" #include "submodule.h" -static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; - static const char * const checkout_usage[] = { N_("git checkout [<options>] <branch>"), N_("git checkout [<options>] [<branch>] -- <file>..."), NULL, }; -static int option_parse_recurse_submodules(const struct option *opt, - const char *arg, int unset) -{ - if (unset) { - recurse_submodules = RECURSE_SUBMODULES_OFF; - return 0; - } - if (arg) - recurse_submodules = - parse_update_recurse_submodules_arg(opt->long_name, - arg); - else - recurse_submodules = RECURSE_SUBMODULES_ON; - - return 0; -} - struct checkout_opts { int patch_mode; int quiet; @@ -71,14 +54,14 @@ struct checkout_opts { struct tree *source_tree; }; -static int post_checkout_hook(struct commit *old, struct commit *new, +static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, int changed) { return run_hook_le(NULL, "post-checkout", - oid_to_hex(old ? &old->object.oid : &null_oid), - oid_to_hex(new ? &new->object.oid : &null_oid), + oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid), + oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid), changed ? "1" : "0", NULL); - /* "new" can be NULL when checking out from the index before + /* "new_commit" can be NULL when checking out from the index before a commit exists. */ } @@ -244,8 +227,7 @@ static int checkout_merged(int pos, const struct checkout *state) * (it also writes the merge result to the object database even * when it may contain conflicts). */ - if (write_sha1_file(result_buf.ptr, result_buf.size, - blob_type, oid.hash)) + 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); @@ -265,7 +247,7 @@ static int checkout_paths(const struct checkout_opts *opts, struct object_id rev; struct commit *head; int errs = 0; - struct lock_file *lock_file; + struct lock_file lock_file = LOCK_INIT; if (opts->track != BRANCH_TRACK_UNSPECIFIED) die(_("'%s' cannot be used with updating paths"), "--track"); @@ -293,9 +275,7 @@ static int checkout_paths(const struct checkout_opts *opts, return run_add_interactive(revision, "--patch=checkout", &opts->pathspec); - lock_file = xcalloc(1, sizeof(struct lock_file)); - - hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); if (read_cache_preload(&opts->pathspec) < 0) return error(_("index file corrupt")); @@ -376,6 +356,8 @@ static int checkout_paths(const struct checkout_opts *opts, state.force = 1; state.refresh_cache = 1; state.istate = &the_index; + + enable_delayed_checkout(&state); for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { @@ -390,12 +372,13 @@ static int checkout_paths(const struct checkout_opts *opts, pos = skip_same_name(ce, pos) - 1; } } + errs |= finish_delayed_checkout(&state); - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - read_ref_full("HEAD", 0, rev.hash, NULL); - head = lookup_commit_reference_gently(rev.hash, 1); + read_ref_full("HEAD", 0, &rev, NULL); + head = lookup_commit_reference_gently(&rev, 1); errs |= post_checkout_hook(head, head, 0); return errs; @@ -417,10 +400,16 @@ static void show_local_changes(struct object *head, static void describe_detached_head(const char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; + if (!parse_commit(commit)) pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb); - fprintf(stderr, "%s %s... %s\n", msg, - find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf); + if (print_sha1_ellipsis()) { + fprintf(stderr, "%s %s... %s\n", msg, + find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf); + } else { + fprintf(stderr, "%s %s %s\n", msg, + find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf); + } strbuf_release(&sb); } @@ -451,6 +440,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, * update paths in the work tree, and we cannot revert * them. */ + /* fallthrough */ case 0: return 0; default: @@ -481,20 +471,20 @@ static void setup_branch_path(struct branch_info *branch) } static int merge_working_tree(const struct checkout_opts *opts, - struct branch_info *old, - struct branch_info *new, + struct branch_info *old_branch_info, + struct branch_info *new_branch_info, int *writeout_error) { int ret; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + struct lock_file lock_file = LOCK_INIT; - hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); + 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->commit->tree, opts, 1, writeout_error); + ret = reset_tree(new_branch_info->commit->tree, opts, 1, writeout_error); if (ret) return ret; } else { @@ -520,7 +510,7 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.initial_checkout = is_cache_unborn(); topts.update = 1; topts.merge = 1; - topts.gently = opts->merge && old->commit; + topts.gently = opts->merge && old_branch_info->commit; topts.verbose_update = opts->show_progress; topts.fn = twoway_merge; if (opts->overwrite_ignore) { @@ -528,11 +518,11 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.dir->flags |= DIR_SHOW_IGNORED; setup_standard_excludes(topts.dir); } - tree = parse_tree_indirect(old->commit ? - old->commit->object.oid.hash : - EMPTY_TREE_SHA1_BIN); + tree = parse_tree_indirect(old_branch_info->commit ? + &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->commit->object.oid.hash); + tree = parse_tree_indirect(&new_branch_info->commit->object.oid); init_tree_desc(&trees[1], tree->buffer, tree->size); ret = unpack_trees(2, trees, &topts); @@ -549,10 +539,10 @@ static int merge_working_tree(const struct checkout_opts *opts, return 1; /* - * Without old->commit, the below is the same as + * Without old_branch_info->commit, the below is the same as * the two-tree unpack we already tried and failed. */ - if (!old->commit) + if (!old_branch_info->commit) return 1; /* Do more real merge */ @@ -580,18 +570,18 @@ static int merge_working_tree(const struct checkout_opts *opts, o.verbosity = 0; work = write_tree_from_memory(&o); - ret = reset_tree(new->commit->tree, opts, 1, + ret = reset_tree(new_branch_info->commit->tree, opts, 1, writeout_error); if (ret) return ret; - o.ancestor = old->name; - o.branch1 = new->name; + o.ancestor = old_branch_info->name; + o.branch1 = new_branch_info->name; o.branch2 = "local"; - ret = merge_trees(&o, new->commit->tree, work, - old->commit->tree, &result); + ret = merge_trees(&o, new_branch_info->commit->tree, work, + old_branch_info->commit->tree, &result); if (ret < 0) exit(128); - ret = reset_tree(new->commit->tree, opts, 0, + ret = reset_tree(new_branch_info->commit->tree, opts, 0, writeout_error); strbuf_release(&o.obuf); if (ret) @@ -605,29 +595,29 @@ static int merge_working_tree(const struct checkout_opts *opts, if (!cache_tree_fully_valid(active_cache_tree)) cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); if (!opts->force && !opts->quiet) - show_local_changes(&new->commit->object, &opts->diff_options); + show_local_changes(&new_branch_info->commit->object, &opts->diff_options); return 0; } -static void report_tracking(struct branch_info *new) +static void report_tracking(struct branch_info *new_branch_info) { struct strbuf sb = STRBUF_INIT; - struct branch *branch = branch_get(new->name); + struct branch *branch = branch_get(new_branch_info->name); - if (!format_tracking_info(branch, &sb)) + if (!format_tracking_info(branch, &sb, AHEAD_BEHIND_FULL)) return; fputs(sb.buf, stdout); strbuf_release(&sb); } static void update_refs_for_switch(const struct checkout_opts *opts, - struct branch_info *old, - struct branch_info *new) + struct branch_info *old_branch_info, + struct branch_info *new_branch_info) { struct strbuf msg = STRBUF_INIT; const char *old_desc, *reflog_msg; @@ -654,76 +644,76 @@ static void update_refs_for_switch(const struct checkout_opts *opts, free(refname); } else - create_branch(opts->new_branch, new->name, + create_branch(opts->new_branch, new_branch_info->name, opts->new_branch_force ? 1 : 0, - opts->new_branch_log, opts->new_branch_force ? 1 : 0, + opts->new_branch_log, opts->quiet, opts->track); - new->name = opts->new_branch; - setup_branch_path(new); + new_branch_info->name = opts->new_branch; + setup_branch_path(new_branch_info); } - old_desc = old->name; - if (!old_desc && old->commit) - old_desc = oid_to_hex(&old->commit->object.oid); + old_desc = old_branch_info->name; + if (!old_desc && old_branch_info->commit) + old_desc = oid_to_hex(&old_branch_info->commit->object.oid); reflog_msg = getenv("GIT_REFLOG_ACTION"); if (!reflog_msg) strbuf_addf(&msg, "checkout: moving from %s to %s", - old_desc ? old_desc : "(invalid)", new->name); + old_desc ? old_desc : "(invalid)", new_branch_info->name); else strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg)); - if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) { + if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) { /* Nothing to do. */ - } else if (opts->force_detach || !new->path) { /* No longer on any branch. */ - update_ref(msg.buf, "HEAD", new->commit->object.oid.hash, NULL, - REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); + } else if (opts->force_detach || !new_branch_info->path) { /* No longer on any branch. */ + update_ref(msg.buf, "HEAD", &new_branch_info->commit->object.oid, NULL, + REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR); if (!opts->quiet) { - if (old->path && + if (old_branch_info->path && advice_detached_head && !opts->force_detach) - detach_advice(new->name); - describe_detached_head(_("HEAD is now at"), new->commit); + detach_advice(new_branch_info->name); + describe_detached_head(_("HEAD is now at"), new_branch_info->commit); } - } else if (new->path) { /* Switch branches. */ - if (create_symref("HEAD", new->path, msg.buf) < 0) + } else if (new_branch_info->path) { /* Switch branches. */ + if (create_symref("HEAD", new_branch_info->path, msg.buf) < 0) die(_("unable to update HEAD")); if (!opts->quiet) { - if (old->path && !strcmp(new->path, old->path)) { + if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) { if (opts->new_branch_force) fprintf(stderr, _("Reset branch '%s'\n"), - new->name); + new_branch_info->name); else fprintf(stderr, _("Already on '%s'\n"), - new->name); + new_branch_info->name); } else if (opts->new_branch) { if (opts->branch_exists) - fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name); + fprintf(stderr, _("Switched to and reset branch '%s'\n"), new_branch_info->name); else - fprintf(stderr, _("Switched to a new branch '%s'\n"), new->name); + fprintf(stderr, _("Switched to a new branch '%s'\n"), new_branch_info->name); } else { fprintf(stderr, _("Switched to branch '%s'\n"), - new->name); + new_branch_info->name); } } - if (old->path && old->name) { - if (!ref_exists(old->path) && reflog_exists(old->path)) - delete_reflog(old->path); + if (old_branch_info->path && old_branch_info->name) { + if (!ref_exists(old_branch_info->path) && reflog_exists(old_branch_info->path)) + delete_reflog(old_branch_info->path); } } remove_branch_state(); strbuf_release(&msg); if (!opts->quiet && - (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD")))) - report_tracking(new); + (new_branch_info->path || (!opts->force_detach && !strcmp(new_branch_info->name, "HEAD")))) + report_tracking(new_branch_info); } static int add_pending_uninteresting_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data) { - add_pending_sha1(cb_data, refname, oid->hash, UNINTERESTING); + add_pending_oid(cb_data, refname, oid, UNINTERESTING); return 0; } @@ -796,11 +786,10 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) * HEAD. If it is not reachable from any ref, this is the last chance * for the user to do so without resorting to reflog. */ -static void orphaned_commit_warning(struct commit *old, struct commit *new) +static void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit) { struct rev_info revs; - struct object *object = &old->object; - struct object_array refs; + struct object *object = &old_commit->object; init_revisions(&revs, NULL); setup_revisions(0, NULL, &revs, NULL); @@ -809,60 +798,57 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new) add_pending_object(&revs, object, oid_to_hex(&object->oid)); for_each_ref(add_pending_uninteresting_ref, &revs); - add_pending_sha1(&revs, "HEAD", new->object.oid.hash, UNINTERESTING); - - refs = revs.pending; - revs.leak_pending = 1; + add_pending_oid(&revs, "HEAD", &new_commit->object.oid, UNINTERESTING); if (prepare_revision_walk(&revs)) die(_("internal error in revision walk")); - if (!(old->object.flags & UNINTERESTING)) - suggest_reattach(old, &revs); + if (!(old_commit->object.flags & UNINTERESTING)) + suggest_reattach(old_commit, &revs); else - describe_detached_head(_("Previous HEAD position was"), old); + describe_detached_head(_("Previous HEAD position was"), old_commit); - clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS); - free(refs.objects); + /* Clean up objects used, as they will be reused. */ + clear_commit_marks_all(ALL_REV_FLAGS); } static int switch_branches(const struct checkout_opts *opts, - struct branch_info *new) + struct branch_info *new_branch_info) { int ret = 0; - struct branch_info old; + struct branch_info old_branch_info; void *path_to_free; struct object_id rev; int flag, writeout_error = 0; - memset(&old, 0, sizeof(old)); - old.path = path_to_free = resolve_refdup("HEAD", 0, rev.hash, &flag); - if (old.path) - old.commit = lookup_commit_reference_gently(rev.hash, 1); + 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); if (!(flag & REF_ISSYMREF)) - old.path = NULL; + old_branch_info.path = NULL; - if (old.path) - skip_prefix(old.path, "refs/heads/", &old.name); + if (old_branch_info.path) + skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name); - if (!new->name) { - new->name = "HEAD"; - new->commit = old.commit; - if (!new->commit) + 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->commit); + parse_commit_or_die(new_branch_info->commit); } - ret = merge_working_tree(opts, &old, new, &writeout_error); + 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.path && old.commit && new->commit != old.commit) - orphaned_commit_warning(old.commit, new->commit); + if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit) + orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit); - update_refs_for_switch(opts, &old, new); + update_refs_for_switch(opts, &old_branch_info, new_branch_info); - ret = post_checkout_hook(old.commit, new->commit, 1); + ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1); free(path_to_free); return ret || writeout_error; } @@ -876,54 +862,14 @@ static int git_checkout_config(const char *var, const char *value, void *cb) } if (starts_with(var, "submodule.")) - return parse_submodule_config_option(var, value); + return git_default_submodule_config(var, value, NULL); return git_xmerge_config(var, value, NULL); } -struct tracking_name_data { - /* const */ char *src_ref; - char *dst_ref; - struct object_id *dst_oid; - int unique; -}; - -static int check_tracking_name(struct remote *remote, void *cb_data) -{ - struct tracking_name_data *cb = cb_data; - struct refspec query; - memset(&query, 0, sizeof(struct refspec)); - query.src = cb->src_ref; - if (remote_find_tracking(remote, &query) || - get_oid(query.dst, cb->dst_oid)) { - free(query.dst); - return 0; - } - if (cb->dst_ref) { - free(query.dst); - cb->unique = 0; - return 0; - } - cb->dst_ref = query.dst; - return 0; -} - -static const char *unique_tracking_name(const char *name, struct object_id *oid) -{ - struct tracking_name_data cb_data = { NULL, NULL, NULL, 1 }; - cb_data.src_ref = xstrfmt("refs/heads/%s", name); - cb_data.dst_oid = oid; - for_each_remote(check_tracking_name, &cb_data); - free(cb_data.src_ref); - if (cb_data.unique) - return cb_data.dst_ref; - free(cb_data.dst_ref); - return NULL; -} - static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, - struct branch_info *new, + struct branch_info *new_branch_info, struct checkout_opts *opts, struct object_id *rev) { @@ -1041,22 +987,22 @@ static int parse_branchname_arg(int argc, const char **argv, argv++; argc--; - new->name = arg; - setup_branch_path(new); + new_branch_info->name = arg; + setup_branch_path(new_branch_info); - if (!check_refname_format(new->path, 0) && - !read_ref(new->path, branch_rev.hash)) + if (!check_refname_format(new_branch_info->path, 0) && + !read_ref(new_branch_info->path, &branch_rev)) oidcpy(rev, &branch_rev); else - new->path = NULL; /* not an existing branch */ + new_branch_info->path = NULL; /* not an existing branch */ - new->commit = lookup_commit_reference_gently(rev->hash, 1); - if (!new->commit) { + new_branch_info->commit = lookup_commit_reference_gently(rev, 1); + if (!new_branch_info->commit) { /* not a commit */ - *source_tree = parse_tree_indirect(rev->hash); + *source_tree = parse_tree_indirect(rev); } else { - parse_commit_or_die(new->commit); - *source_tree = new->commit->tree; + parse_commit_or_die(new_branch_info->commit); + *source_tree = new_branch_info->commit->tree; } if (!*source_tree) /* case (1): want a tree */ @@ -1096,7 +1042,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts) } static int checkout_branch(struct checkout_opts *opts, - struct branch_info *new) + struct branch_info *new_branch_info) { if (opts->pathspec.nr) die(_("paths cannot be used with switching branches")); @@ -1125,36 +1071,35 @@ static int checkout_branch(struct checkout_opts *opts, } else if (opts->track == BRANCH_TRACK_UNSPECIFIED) opts->track = git_branch_track; - if (new->name && !new->commit) + if (new_branch_info->name && !new_branch_info->commit) die(_("Cannot switch branch to a non-commit '%s'"), - new->name); + new_branch_info->name); - if (new->path && !opts->force_detach && !opts->new_branch && + if (new_branch_info->path && !opts->force_detach && !opts->new_branch && !opts->ignore_other_worktrees) { - struct object_id oid; int flag; - char *head_ref = resolve_refdup("HEAD", 0, oid.hash, &flag); + char *head_ref = resolve_refdup("HEAD", 0, NULL, &flag); if (head_ref && - (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path))) - die_if_checked_out(new->path, 1); + (!(flag & REF_ISSYMREF) || strcmp(head_ref, new_branch_info->path))) + die_if_checked_out(new_branch_info->path, 1); free(head_ref); } - if (!new->commit && opts->new_branch) { + if (!new_branch_info->commit && opts->new_branch) { struct object_id rev; int flag; - if (!read_ref_full("HEAD", 0, rev.hash, &flag) && + if (!read_ref_full("HEAD", 0, &rev, &flag) && (flag & REF_ISSYMREF) && is_null_oid(&rev)) return switch_unborn_to_new_branch(opts); } - return switch_branches(opts, new); + return switch_branches(opts, new_branch_info); } int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; - struct branch_info new; + struct branch_info new_branch_info; char *conflict_style = NULL; int dwim_new_local_branch = 1; struct option options[] = { @@ -1172,9 +1117,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 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(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")), + 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")), @@ -1184,20 +1132,19 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) N_("second guess 'git checkout <no-such-branch>'")), 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", &recurse_submodules, + { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "checkout", "control recursive updating of submodules", - PARSE_OPT_OPTARG, option_parse_recurse_submodules }, + PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")), OPT_END(), }; memset(&opts, 0, sizeof(opts)); - memset(&new, 0, sizeof(new)); + memset(&new_branch_info, 0, sizeof(new_branch_info)); opts.overwrite_ignore = 1; opts.prefix = prefix; opts.show_progress = -1; - gitmodules_config(); git_config(git_checkout_config, &opts); opts.track = BRANCH_TRACK_UNSPECIFIED; @@ -1217,12 +1164,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) git_xmerge_config("merge.conflictstyle", conflict_style, NULL); } - if (recurse_submodules != RECURSE_SUBMODULES_OFF) { - git_config(submodule_config, NULL); - if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) - set_config_update_recurse_submodules(recurse_submodules); - } - if ((!!opts.new_branch + !!opts.new_branch_force + !!opts.new_orphan_branch) > 1) die(_("-b, -B and --orphan are mutually exclusive")); @@ -1271,7 +1212,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.track == BRANCH_TRACK_UNSPECIFIED && !opts.new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, - &new, &opts, &rev); + &new_branch_info, &opts, &rev); argv += n; argc -= n; } @@ -1304,16 +1245,17 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; - opts.branch_exists = - validate_new_branchname(opts.new_branch, &buf, - !!opts.new_branch_force, - !!opts.new_branch_force); - + 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); strbuf_release(&buf); } + UNLEAK(opts); if (opts.patch_mode || opts.pathspec.nr) - return checkout_paths(&opts, new.name); + return checkout_paths(&opts, new_branch_info.name); else - return checkout_branch(&opts, &new); + return checkout_branch(&opts, &new_branch_info); } diff --git a/builtin/clean.c b/builtin/clean.c index 937eb17b66..fad533a0a7 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -8,6 +8,7 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "dir.h" #include "parse-options.h" #include "string-list.h" @@ -32,15 +33,6 @@ 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 int clean_use_color = -1; -static char clean_colors[][COLOR_MAXLEN] = { - GIT_COLOR_RESET, - GIT_COLOR_NORMAL, /* PLAIN */ - GIT_COLOR_BOLD_BLUE, /* PROMPT */ - GIT_COLOR_BOLD, /* HEADER */ - GIT_COLOR_BOLD_RED, /* HELP */ - GIT_COLOR_BOLD_RED, /* ERROR */ -}; enum color_clean { CLEAN_COLOR_RESET = 0, CLEAN_COLOR_PLAIN = 1, @@ -50,6 +42,16 @@ enum color_clean { CLEAN_COLOR_ERROR = 5 }; +static int clean_use_color = -1; +static char clean_colors[][COLOR_MAXLEN] = { + [CLEAN_COLOR_ERROR] = GIT_COLOR_BOLD_RED, + [CLEAN_COLOR_HEADER] = GIT_COLOR_BOLD, + [CLEAN_COLOR_HELP] = GIT_COLOR_BOLD_RED, + [CLEAN_COLOR_PLAIN] = GIT_COLOR_NORMAL, + [CLEAN_COLOR_PROMPT] = GIT_COLOR_BOLD_BLUE, + [CLEAN_COLOR_RESET] = GIT_COLOR_RESET, +}; + #define MENU_OPTS_SINGLETON 01 #define MENU_OPTS_IMMEDIATE 02 #define MENU_OPTS_LIST_ONLY 04 @@ -166,7 +168,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, } *dir_gone = 0; - return 0; + goto out; } dir = opendir(path->buf); @@ -180,7 +182,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; } - return res; + ret = res; + goto out; } strbuf_complete(path, '/'); @@ -248,6 +251,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, for (i = 0; i < dels.nr; i++) printf(dry_run ? _(msg_would_remove) : _(msg_remove), dels.items[i].string); } +out: + strbuf_release("ed); string_list_clear(&dels, 0); return ret; } @@ -683,7 +688,7 @@ static int filter_by_patterns_cmd(void) for_each_string_list_item(item, &del_list) { int dtype = DT_UNKNOWN; - if (is_excluded(&dir, item->string, &dtype)) { + if (is_excluded(&dir, &the_index, item->string, &dtype)) { *item->string = '\0'; changed++; } @@ -837,8 +842,7 @@ static void interactive_main_loop(void) int ret; ret = menus[*chosen].fn(); if (ret != MENU_RETURN_NO_LOOP) { - free(chosen); - chosen = NULL; + FREE_AND_NULL(chosen); if (!del_list.nr) { clean_print_color(CLEAN_COLOR_ERROR); printf_ln(_("No more files to clean, exiting.")); @@ -851,8 +855,7 @@ static void interactive_main_loop(void) quit_cmd(); } - free(chosen); - chosen = NULL; + FREE_AND_NULL(chosen); break; } } @@ -906,7 +909,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct option options[] = { OPT__QUIET(&quiet, N_("do not print names of files removed")), OPT__DRY_RUN(&dry_run, N_("dry run")), - OPT__FORCE(&force, N_("force")), + OPT__FORCE(&force, N_("force"), PARSE_OPT_NOCOMPLETE), OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")), OPT_BOOL('d', NULL, &remove_directories, N_("remove whole directories")), @@ -965,7 +968,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) PATHSPEC_PREFER_CWD, prefix, argv); - fill_directory(&dir, &pathspec); + fill_directory(&dir, &the_index, &pathspec); correct_untracked_entries(&dir); for (i = 0; i < dir.nr; i++) { diff --git a/builtin/clone.c b/builtin/clone.c index a6ae7d6180..101c27a593 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -9,6 +9,7 @@ */ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "parse-options.h" #include "fetch-pack.h" @@ -24,6 +25,8 @@ #include "remote.h" #include "run-command.h" #include "connected.h" +#include "packfile.h" +#include "list-objects-filter-options.h" /* * Overall FIXMEs: @@ -40,6 +43,7 @@ static const char * const builtin_clone_usage[] = { static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1; static int option_local = -1, option_no_hardlinks, option_shared; +static int option_no_tags; static int option_shallow_submodules; static int deepen; static char *option_template, *option_depth, *option_since; @@ -57,6 +61,7 @@ static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP; 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 int recurse_submodules_cb(const struct option *opt, const char *arg, int unset) @@ -120,6 +125,8 @@ static struct option builtin_clone_options[] = { N_("deepen history of shallow clone, excluding rev")), OPT_BOOL(0, "single-branch", &option_single_branch, N_("clone only one branch, HEAD or --branch")), + OPT_BOOL(0, "no-tags", &option_no_tags, + N_("don't clone any tags, and make later fetches not to follow them")), OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules, N_("any cloned submodules will be shallow")), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), @@ -130,6 +137,7 @@ static struct option builtin_clone_options[] = { 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_END() }; @@ -357,7 +365,7 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst, * to turn entries with paths relative to the original * absolute, so that they can be used in the new repository. */ - FILE *in = fopen(src->buf, "r"); + FILE *in = xfopen(src->buf, "r"); struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, in) != EOF) { @@ -447,7 +455,8 @@ static void clone_local(const char *src_repo, const char *dest_repo) { if (option_shared) { struct strbuf alt = STRBUF_INIT; - strbuf_addf(&alt, "%s/objects", src_repo); + get_common_dir(&alt, src_repo); + strbuf_addstr(&alt, "/objects"); add_to_alternates_file(alt.buf); strbuf_release(&alt); } else { @@ -467,7 +476,9 @@ static void clone_local(const char *src_repo, const char *dest_repo) } static const char *junk_work_tree; +static int junk_work_tree_flags; static const char *junk_git_dir; +static int junk_git_dir_flags; static enum { JUNK_LEAVE_NONE, JUNK_LEAVE_REPO, @@ -496,14 +507,14 @@ static void remove_junk(void) if (junk_git_dir) { strbuf_addstr(&sb, junk_git_dir); - remove_dir_recursively(&sb, 0); + remove_dir_recursively(&sb, junk_git_dir_flags); strbuf_reset(&sb); } if (junk_work_tree) { strbuf_addstr(&sb, junk_work_tree); - remove_dir_recursively(&sb, 0); - strbuf_reset(&sb); + remove_dir_recursively(&sb, junk_work_tree_flags); } + strbuf_release(&sb); } static void remove_junk_on_signal(int signo) @@ -563,7 +574,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs, } else get_fetch_map(refs, refspec, &tail, 0); - if (!option_mirror && !option_single_branch) + if (!option_mirror && !option_single_branch && !option_no_tags) get_fetch_map(refs, tag_refspec, &tail, 0); return local_refs; @@ -583,7 +594,7 @@ static void write_remote_refs(const struct ref *local_refs) for (r = local_refs; r; r = r->next) { if (!r->peer_ref) continue; - if (ref_transaction_create(t, r->peer_ref->name, r->old_oid.hash, + if (ref_transaction_create(t, r->peer_ref->name, &r->old_oid, 0, NULL, &err)) die("%s", err.buf); } @@ -605,12 +616,12 @@ static void write_followtags(const struct ref *refs, const char *msg) continue; if (!has_object_file(&ref->old_oid)) continue; - update_ref(msg, ref->name, ref->old_oid.hash, - NULL, 0, UPDATE_REFS_DIE_ON_ERR); + update_ref(msg, ref->name, &ref->old_oid, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); } } -static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) +static int iterate_ref_map(void *cb_data, struct object_id *oid) { struct ref **rm = cb_data; struct ref *ref = *rm; @@ -625,7 +636,7 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) if (!ref) return -1; - hashcpy(sha1, ref->old_oid.hash); + oidcpy(oid, &ref->old_oid); *rm = ref->next; return 0; } @@ -652,7 +663,7 @@ static void update_remote_refs(const struct ref *refs, if (refs) { write_remote_refs(mapped_refs); - if (option_single_branch) + if (option_single_branch && !option_no_tags) write_followtags(refs, msg); } @@ -677,23 +688,23 @@ static void update_head(const struct ref *our, const struct ref *remote, if (create_symref("HEAD", our->name, NULL) < 0) die(_("unable to update HEAD")); if (!option_bare) { - update_ref(msg, "HEAD", our->old_oid.hash, NULL, 0, + update_ref(msg, "HEAD", &our->old_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); install_branch_config(0, head, option_origin, our->name); } } else if (our) { - struct commit *c = lookup_commit_reference(our->old_oid.hash); + struct commit *c = lookup_commit_reference(&our->old_oid); /* --branch specifies a non-branch (i.e. tags), detach HEAD */ - update_ref(msg, "HEAD", c->object.oid.hash, - NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); + update_ref(msg, "HEAD", &c->object.oid, NULL, REF_NO_DEREF, + UPDATE_REFS_DIE_ON_ERR); } else if (remote) { /* * We know remote HEAD points to a non-branch, or * HEAD points to a branch but we don't know which one. * Detach HEAD in all these cases. */ - update_ref(msg, "HEAD", remote->old_oid.hash, - NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); + update_ref(msg, "HEAD", &remote->old_oid, NULL, REF_NO_DEREF, + UPDATE_REFS_DIE_ON_ERR); } } @@ -701,7 +712,7 @@ static int checkout(int submodule_progress) { struct object_id oid; char *head; - struct lock_file *lock_file; + struct lock_file lock_file = LOCK_INIT; struct unpack_trees_options opts; struct tree *tree; struct tree_desc t; @@ -710,7 +721,7 @@ static int checkout(int submodule_progress) if (option_no_checkout) return 0; - head = resolve_refdup("HEAD", RESOLVE_REF_READING, oid.hash, NULL); + head = resolve_refdup("HEAD", RESOLVE_REF_READING, &oid, NULL); if (!head) { warning(_("remote HEAD refers to nonexistent ref, " "unable to checkout.\n")); @@ -728,8 +739,7 @@ static int checkout(int submodule_progress) /* We need to be in the new work tree for the checkout */ setup_work_tree(); - lock_file = xcalloc(1, sizeof(struct lock_file)); - hold_locked_index(lock_file, LOCK_DIE_ON_ERROR); + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); memset(&opts, 0, sizeof opts); opts.update = 1; @@ -739,13 +749,13 @@ static int checkout(int submodule_progress) opts.src_index = &the_index; opts.dst_index = &the_index; - tree = parse_tree_indirect(oid.hash); + tree = parse_tree_indirect(&oid); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts) < 0) die(_("unable to checkout working tree")); - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + 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), @@ -764,6 +774,9 @@ static int checkout(int submodule_progress) if (submodule_progress) argv_array_push(&args, "--progress"); + if (option_verbosity < 0) + argv_array_push(&args, "--quiet"); + err = run_command_v_opt(args.argv, RUN_GIT_CMD); argv_array_clear(&args); } @@ -855,10 +868,15 @@ static void dissociate_from_references(void) free(alternates); } +static int dir_exists(const char *path) +{ + struct stat sb; + return !stat(path, &sb); +} + int cmd_clone(int argc, const char **argv, const char *prefix) { int is_bundle = 0, is_local; - struct stat buf; const char *repo_name, *repo, *work_tree, *git_dir; char *path, *dir; int dest_exists; @@ -878,6 +896,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct refspec *refspec; const char *fetch_pattern; + fetch_if_missing = 0; + packet_trace_identity("clone"); argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); @@ -930,7 +950,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 = !stat(dir, &buf); + dest_exists = dir_exists(dir); if (dest_exists && !is_empty_dir(dir)) die(_("destination path '%s' already exists and is not " "an empty directory."), dir); @@ -941,7 +961,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 && !stat(work_tree, &buf)) + if (work_tree && dir_exists(work_tree)) die(_("working tree '%s' already exists."), work_tree); } @@ -959,14 +979,24 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (safe_create_leading_directories_const(work_tree) < 0) die_errno(_("could not create leading directories of '%s'"), work_tree); - if (!dest_exists && mkdir(work_tree, 0777)) + if (dest_exists) + junk_work_tree_flags |= REMOVE_DIR_KEEP_TOPLEVEL; + else if (mkdir(work_tree, 0777)) die_errno(_("could not create work tree dir '%s'"), work_tree); junk_work_tree = work_tree; set_git_work_tree(work_tree); } - junk_git_dir = real_git_dir ? real_git_dir : git_dir; + if (real_git_dir) { + if (dir_exists(real_git_dir)) + junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL; + junk_git_dir = real_git_dir; + } else { + if (dest_exists) + junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL; + junk_git_dir = git_dir; + } if (safe_create_leading_directories_const(git_dir) < 0) die(_("could not create leading directories of '%s'"), git_dir); @@ -1037,6 +1067,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set(key.buf, repo); strbuf_reset(&key); + if (option_no_tags) { + strbuf_addf(&key, "remote.%s.tagOpt", option_origin); + git_config_set(key.buf, "--no-tags"); + strbuf_reset(&key); + } + if (option_required_reference.nr || option_optional_reference.nr) setup_reference(); @@ -1059,6 +1095,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) warning(_("--shallow-since is ignored in local clones; use file:// instead.")); if (option_not.nr) warning(_("--shallow-exclude is ignored in local clones; use file:// instead.")); + if (filter_options.choice) + warning(_("--filter is ignored in local clones; use file:// instead.")); if (!access(mkpath("%s/shallow", path), F_OK)) { if (option_local > 0) warning(_("source repository is shallow, ignoring --local")); @@ -1069,9 +1107,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) warning(_("--local is ignored")); transport->cloning = 1; - if (!transport->get_refs_list || (!is_local && !transport->fetch)) - die(_("Don't know how to clone %s"), transport->url); - transport_set_option(transport, TRANS_OPT_KEEP, "yes"); if (option_depth) @@ -1090,7 +1125,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_set_option(transport, TRANS_OPT_UPLOADPACK, option_upload_pack); - if (transport->smart_options && !deepen) + if (filter_options.choice) { + transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, + filter_options.filter_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); @@ -1150,13 +1191,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix) write_refspec_config(src_ref_prefix, our_head_points_at, remote_head_points_at, &branch_top); + if (filter_options.choice) + partial_clone_register("origin", &filter_options); + if (is_local) clone_local(path, git_dir); else if (refs && complete_refs_before_fetch) transport_fetch_refs(transport, mapped_refs); update_remote_refs(refs, mapped_refs, remote_head_points_at, - branch_top.buf, reflog_msg.buf, transport, !is_local); + branch_top.buf, reflog_msg.buf, transport, + !is_local && !filter_options.choice); update_head(our_head_points_at, remote_head, reflog_msg.buf); @@ -1177,6 +1222,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } junk_mode = JUNK_LEAVE_REPO; + fetch_if_missing = 1; err = checkout(submodule_progress); strbuf_release(&reflog_msg); diff --git a/builtin/column.c b/builtin/column.c index 33314b4d71..0c3223d64b 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "strbuf.h" #include "parse-options.h" #include "string-list.h" diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 605017261c..e5bdf57b1e 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "commit.h" #include "tree.h" #include "builtin.h" @@ -55,10 +56,10 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) struct object_id oid; if (argc <= ++i) usage(commit_tree_usage); - if (get_sha1_commit(argv[i], oid.hash)) + if (get_oid_commit(argv[i], &oid)) die("Not a valid object name %s", argv[i]); assert_sha1_type(oid.hash, OBJ_COMMIT); - new_parent(lookup_commit(oid.hash), &parents); + new_parent(lookup_commit(&oid), &parents); continue; } @@ -101,11 +102,10 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) if (fd && close(fd)) die_errno("git commit-tree: failed to close '%s'", argv[i]); - strbuf_complete_line(&buffer); continue; } - if (get_sha1_tree(arg, tree_oid.hash)) + 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"); @@ -117,8 +117,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) die_errno("git commit-tree: failed to read"); } - if (commit_tree(buffer.buf, buffer.len, tree_oid.hash, parents, - commit_oid.hash, NULL, sign_commit)) { + if (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid, + NULL, sign_commit)) { strbuf_release(&buffer); return 1; } diff --git a/builtin/commit.c b/builtin/commit.c index aff6bf7aad..37fcb55ab0 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -6,6 +6,7 @@ */ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "cache-tree.h" #include "color.h" @@ -30,9 +31,7 @@ #include "gpg-interface.h" #include "column.h" #include "sequencer.h" -#include "notes-utils.h" #include "mailmap.h" -#include "sigchain.h" static const char * const builtin_commit_usage[] = { N_("git commit [<options>] [--] <pathspec>..."), @@ -44,31 +43,6 @@ static const char * const builtin_status_usage[] = { NULL }; -static const char implicit_ident_advice_noconfig[] = -N_("Your name and email address were configured automatically based\n" -"on your username and hostname. Please check that they are accurate.\n" -"You can suppress this message by setting them explicitly. Run the\n" -"following command and follow the instructions in your editor to edit\n" -"your configuration file:\n" -"\n" -" git config --global --edit\n" -"\n" -"After doing this, you may fix the identity used for this commit with:\n" -"\n" -" git commit --amend --reset-author\n"); - -static const char implicit_ident_advice_config[] = -N_("Your name and email address were configured automatically based\n" -"on your username and hostname. Please check that they are accurate.\n" -"You can suppress this message by setting them explicitly:\n" -"\n" -" git config --global user.name \"Your Name\"\n" -" git config --global user.email you@example.com\n" -"\n" -"After doing this, you may fix the identity used for this commit with:\n" -"\n" -" git commit --amend --reset-author\n"); - static const char empty_amend_advice[] = N_("You asked to amend the most recent commit, but doing so would make\n" "it empty. You can repeat your command with --allow-empty, or you can\n" @@ -92,8 +66,6 @@ N_("If you wish to skip this commit, use:\n" "Then \"git cherry-pick --continue\" will resume cherry-picking\n" "the remaining commits.\n"); -static GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG") - static const char *use_message_buffer; static struct lock_file index_lock; /* real index */ static struct lock_file false_lock; /* used only for partial commits */ @@ -117,7 +89,7 @@ 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 char *untracked_files_arg, *force_date, *ignore_submodule_arg; +static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg; static char *sign_commit; /* @@ -127,19 +99,13 @@ static char *sign_commit; * if editor is used, and only the whitespaces if the message * is specified explicitly. */ -static enum { - CLEANUP_SPACE, - CLEANUP_NONE, - CLEANUP_SCISSORS, - CLEANUP_ALL -} cleanup_mode; +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 show_ignored_in_status, have_option_m; -static const char *only_include_assumed; +static int have_option_m; static struct strbuf message = STRBUF_INIT; static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED; @@ -195,7 +161,6 @@ 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); - gitmodules_config(); git_config(fn, s); determine_whence(s); init_diff_ui_defaults(); @@ -253,7 +218,8 @@ static int list_paths(struct string_list *list, const char *with_tree, if (with_tree) { char *max_prefix = common_prefix(pattern); - overlay_tree_on_cache(with_tree, max_prefix ? max_prefix : prefix); + overlay_tree_on_index(&the_index, with_tree, + max_prefix ? max_prefix : prefix); free(max_prefix); } @@ -313,7 +279,7 @@ static void create_base_index(const struct commit *current_head) opts.dst_index = &the_index; opts.fn = oneway_merge; - tree = parse_tree_indirect(current_head->object.oid.hash); + tree = parse_tree_indirect(¤t_head->object.oid); if (!tree) die(_("failed to unpack HEAD tree object")); parse_tree(tree); @@ -335,7 +301,7 @@ static void refresh_cache_or_die(int refresh_flags) static const char *prepare_index(int argc, const char **argv, const char *prefix, const struct commit *current_head, int is_status) { - struct string_list partial; + struct string_list partial = STRING_LIST_INIT_DUP; struct pathspec pathspec; int refresh_flags = REFRESH_QUIET; const char *ret; @@ -355,7 +321,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix refresh_cache_or_die(refresh_flags); - if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) + if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to create temporary index")); old_index_env = getenv(INDEX_ENVIRONMENT); @@ -374,13 +340,14 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { if (reopen_lock_file(&index_lock) < 0) die(_("unable to write index file")); - if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) + if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to update temporary index")); } else warning(_("Failed to update main cache tree")); commit_style = COMMIT_NORMAL; - return get_lock_file_path(&index_lock); + ret = get_lock_file_path(&index_lock); + goto out; } /* @@ -400,10 +367,11 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix add_files_to_cache(also ? prefix : NULL, &pathspec, 0); refresh_cache_or_die(refresh_flags); update_main_cache_tree(WRITE_TREE_SILENT); - if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) + if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to write new_index file")); commit_style = COMMIT_NORMAL; - return get_lock_file_path(&index_lock); + ret = get_lock_file_path(&index_lock); + goto out; } /* @@ -421,15 +389,12 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (active_cache_changed || !cache_tree_fully_valid(active_cache_tree)) update_main_cache_tree(WRITE_TREE_SILENT); - if (active_cache_changed) { - if (write_locked_index(&the_index, &index_lock, - COMMIT_LOCK)) - die(_("unable to write new_index file")); - } else { - rollback_lock_file(&index_lock); - } + if (write_locked_index(&the_index, &index_lock, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) + die(_("unable to write new_index file")); commit_style = COMMIT_AS_IS; - return get_index_file(); + ret = get_index_file(); + goto out; } /* @@ -460,7 +425,6 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix die(_("cannot do a partial commit during a cherry-pick.")); } - string_list_init(&partial, 1); if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec)) exit(1); @@ -472,7 +436,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix add_remove_files(&partial); refresh_cache(REFRESH_QUIET); update_main_cache_tree(WRITE_TREE_SILENT); - if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK)) + if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to write new_index file")); hold_lock_file_for_update(&false_lock, @@ -484,12 +448,15 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix add_remove_files(&partial); refresh_cache(REFRESH_QUIET); - if (write_locked_index(&the_index, &false_lock, CLOSE_LOCK)) + if (write_locked_index(&the_index, &false_lock, 0)) die(_("unable to write temporary index file")); discard_cache(); ret = get_lock_file_path(&false_lock); read_cache_from(ret); +out: + string_list_clear(&partial, 0); + clear_pathspec(&pathspec); return ret; } @@ -509,7 +476,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->index_file = index_file; s->fp = fp; s->nowarn = nowarn; - s->is_initial = get_sha1(s->reference, oid.hash) ? 1 : 0; + s->is_initial = get_oid(s->reference, &oid) ? 1 : 0; if (!s->is_initial) hashcpy(s->sha1_commit, oid.hash); s->status_format = status_format; @@ -668,7 +635,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, struct strbuf sb = STRBUF_INIT; const char *hook_arg1 = NULL; const char *hook_arg2 = NULL; - int clean_message_contents = (cleanup_mode != CLEANUP_NONE); + int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE); int old_display_comment_prefix; /* This checks and barfs if author is badly specified */ @@ -696,7 +663,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } } - if (have_option_m) { + if (have_option_m && !fixup_message) { strbuf_addbuf(&sb, &message); hook_arg1 = "message"; } else if (logfile && !strcmp(logfile, "-")) { @@ -726,6 +693,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, ctx.output_encoding = get_commit_output_encoding(); format_commit_message(commit, "fixup! %s\n\n", &sb, &ctx); + if (have_option_m) + strbuf_addbuf(&sb, &message); hook_arg1 = "message"; } else if (!stat(git_path_merge_msg(), &statbuf)) { /* @@ -807,7 +776,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, struct ident_split ci, ai; if (whence != FROM_COMMIT) { - if (cleanup_mode == CLEANUP_SCISSORS) + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) wt_status_add_cut_line(s->fp); status_printf_ln(s, GIT_COLOR_NORMAL, whence == FROM_MERGE @@ -827,23 +796,21 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } fprintf(s->fp, "\n"); - if (cleanup_mode == CLEANUP_ALL) + if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL) status_printf(s, GIT_COLOR_NORMAL, _("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 == CLEANUP_SCISSORS && whence == FROM_COMMIT) + else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS && + whence == FROM_COMMIT) wt_status_add_cut_line(s->fp); - else /* CLEANUP_SPACE, that is. */ + else /* COMMIT_MSG_CLEANUP_SPACE, that is. */ status_printf(s, GIT_COLOR_NORMAL, _("Please enter the commit message for your changes." " Lines starting\n" "with '%c' will be kept; you may remove them" " yourself if you want to.\n" "An empty message aborts the commit.\n"), comment_line_char); - if (only_include_assumed) - status_printf_ln(s, GIT_COLOR_NORMAL, - "%s", only_include_assumed); /* * These should never fail because they come from our own @@ -877,8 +844,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, (int)(ci.name_end - ci.name_begin), ci.name_begin, (int)(ci.mail_end - ci.mail_begin), ci.mail_begin); - if (ident_shown) - status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); + status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); /* Add new line for clarity */ saved_color_setting = s->use_color; s->use_color = 0; @@ -894,7 +860,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (amend) parent = "HEAD^1"; - if (get_sha1(parent, oid.hash)) { + if (get_oid(parent, &oid)) { int i, ita_nr = 0; for (i = 0; i < active_nr; i++) @@ -911,11 +877,12 @@ static int prepare_to_commit(const char *index_file, const char *prefix, * submodules which were manually staged, which would * be really confusing. */ - int diff_flags = DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG; + struct diff_flags flags = DIFF_FLAGS_INIT; + flags.override_submodule_config = 1; if (ignore_submodule_arg && !strcmp(ignore_submodule_arg, "all")) - diff_flags |= DIFF_OPT_IGNORE_SUBMODULES; - commitable = index_differs_from(parent, diff_flags, 1); + flags.ignore_submodules = 1; + commitable = index_differs_from(parent, &flags, 1); } } strbuf_release(&committer_ident); @@ -943,13 +910,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; } - /* - * Re-read the index as pre-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. - */ - discard_cache(); + if (!no_verify && find_hook("pre-commit")) { + /* + * Re-read the index as pre-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. + */ + discard_cache(); + } read_cache_from(index_file); + if (update_main_cache_tree(0)) { error(_("Error building trees")); return 0; @@ -979,65 +949,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 1; } -static int rest_is_empty(struct strbuf *sb, int start) -{ - int i, eol; - const char *nl; - - /* Check if the rest is just whitespace and Signed-off-by's. */ - for (i = start; i < sb->len; i++) { - nl = memchr(sb->buf + i, '\n', sb->len - i); - if (nl) - eol = nl - sb->buf; - else - eol = sb->len; - - if (strlen(sign_off_header) <= eol - i && - starts_with(sb->buf + i, sign_off_header)) { - i = eol; - continue; - } - while (i < eol) - if (!isspace(sb->buf[i++])) - return 0; - } - - return 1; -} - -/* - * Find out if the message in the strbuf contains only whitespace and - * Signed-off-by lines. - */ -static int message_is_empty(struct strbuf *sb) -{ - if (cleanup_mode == CLEANUP_NONE && sb->len) - return 0; - return rest_is_empty(sb, 0); -} - -/* - * See if the user edited the message in the editor or left what - * was in the template intact - */ -static int template_untouched(struct strbuf *sb) -{ - struct strbuf tmpl = STRBUF_INIT; - const char *start; - - if (cleanup_mode == CLEANUP_NONE && sb->len) - return 0; - - if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0) - return 0; - - strbuf_stripspace(&tmpl, cleanup_mode == CLEANUP_ALL); - if (!skip_prefix(sb->buf, tmpl.buf, &start)) - start = sb->buf; - strbuf_release(&tmpl); - return rest_is_empty(sb, start - sb->buf); -} - static const char *find_author_by_nickname(const char *name) { struct rev_info revs; @@ -1071,6 +982,19 @@ static const char *find_author_by_nickname(const char *name) die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name); } +static void handle_ignored_arg(struct wt_status *s) +{ + if (!ignored_arg) + ; /* default already initialized */ + else if (!strcmp(ignored_arg, "traditional")) + s->show_ignored_mode = SHOW_TRADITIONAL_IGNORED; + else if (!strcmp(ignored_arg, "no")) + s->show_ignored_mode = SHOW_NO_IGNORED; + else if (!strcmp(ignored_arg, "matching")) + s->show_ignored_mode = SHOW_MATCHING_IGNORED; + else + die(_("Invalid ignored mode '%s'"), ignored_arg); +} static void handle_untracked_files_arg(struct wt_status *s) { @@ -1133,6 +1057,9 @@ static void finalize_deferred_config(struct wt_status *s) s->show_branch = status_deferred_config.show_branch; if (s->show_branch < 0) s->show_branch = 0; + + if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED) + s->ahead_behind_flags = AHEAD_BEHIND_FULL; } static int parse_and_validate_options(int argc, const char *argv[], @@ -1179,8 +1106,8 @@ static int parse_and_validate_options(int argc, const char *argv[], f++; if (f > 1) die(_("Only one of -c/-C/-F/--fixup can be used.")); - if (have_option_m && f > 0) - die((_("Option -m cannot be combined with -c/-C/-F/--fixup."))); + if (have_option_m && (edit_message || use_message || logfile)) + die((_("Option -m cannot be combined with -c/-C/-F."))); if (f || have_option_m) template_file = NULL; if (edit_message) @@ -1208,18 +1135,18 @@ static int parse_and_validate_options(int argc, const char *argv[], die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); if (argc == 0 && (also || (only && !amend && !allow_empty))) die(_("No paths with --include/--only does not make sense.")); - if (argc > 0 && !also && !only) - only_include_assumed = _("Explicit paths specified without -i or -o; assuming --only paths..."); if (!cleanup_arg || !strcmp(cleanup_arg, "default")) - cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE; + cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_ALL : + COMMIT_MSG_CLEANUP_SPACE; else if (!strcmp(cleanup_arg, "verbatim")) - cleanup_mode = CLEANUP_NONE; + cleanup_mode = COMMIT_MSG_CLEANUP_NONE; else if (!strcmp(cleanup_arg, "whitespace")) - cleanup_mode = CLEANUP_SPACE; + cleanup_mode = COMMIT_MSG_CLEANUP_SPACE; else if (!strcmp(cleanup_arg, "strip")) - cleanup_mode = CLEANUP_ALL; + cleanup_mode = COMMIT_MSG_CLEANUP_ALL; else if (!strcmp(cleanup_arg, "scissors")) - cleanup_mode = use_editor ? CLEANUP_SCISSORS : CLEANUP_SPACE; + cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_SCISSORS : + COMMIT_MSG_CLEANUP_SPACE; else die(_("Invalid cleanup mode %s"), cleanup_arg); @@ -1263,6 +1190,10 @@ static int parse_status_slot(const char *slot) 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; } @@ -1291,6 +1222,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.showstash")) { + s->show_stash = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { s->use_color = git_config_colorbool(k, v); return 0; @@ -1339,6 +1274,10 @@ int cmd_status(int argc, const char **argv, const char *prefix) N_("show status concisely"), STATUS_FORMAT_SHORT), OPT_BOOL('b', "branch", &s.show_branch, N_("show branch information")), + OPT_BOOL(0, "show-stash", &s.show_stash, + 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, N_("version"), N_("machine-readable output"), PARSE_OPT_OPTARG, opt_parse_porcelain }, @@ -1351,8 +1290,10 @@ int cmd_status(int argc, const char **argv, const char *prefix) N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_BOOL(0, "ignored", &show_ignored_in_status, - N_("show ignored files")), + { OPTION_STRING, 0, "ignored", &ignored_arg, + N_("mode"), + N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"), + PARSE_OPT_OPTARG, NULL, (intptr_t)"traditional" }, { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"), N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, @@ -1371,8 +1312,12 @@ int cmd_status(int argc, const char **argv, const char *prefix) finalize_deferred_config(&s); handle_untracked_files_arg(&s); - if (show_ignored_in_status) - s.show_ignored_files = 1; + handle_ignored_arg(&s); + + if (s.show_ignored_mode == SHOW_MATCHING_IGNORED && + s.show_untracked_files == SHOW_NO_UNTRACKED_FILES) + die(_("Unsupported combination of ignored and untracked-files arguments")); + parse_pathspec(&s.pathspec, 0, PATHSPEC_PREFER_FULL, prefix, argv); @@ -1380,9 +1325,12 @@ int cmd_status(int argc, const char **argv, const char *prefix) read_cache_preload(&s.pathspec); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL); - fd = hold_locked_index(&index_lock, 0); + if (use_optional_locks()) + fd = hold_locked_index(&index_lock, 0); + else + fd = -1; - s.is_initial = get_sha1(s.reference, oid.hash) ? 1 : 0; + s.is_initial = get_oid(s.reference, &oid) ? 1 : 0; if (!s.is_initial) hashcpy(s.sha1_commit, oid.hash); @@ -1402,97 +1350,6 @@ int cmd_status(int argc, const char **argv, const char *prefix) return 0; } -static const char *implicit_ident_advice(void) -{ - char *user_config = expand_user_path("~/.gitconfig", 0); - char *xdg_config = xdg_config_home("config"); - int config_exists = file_exists(user_config) || file_exists(xdg_config); - - free(user_config); - free(xdg_config); - - if (config_exists) - return _(implicit_ident_advice_config); - else - return _(implicit_ident_advice_noconfig); - -} - -static void print_summary(const char *prefix, const struct object_id *oid, - int initial_commit) -{ - struct rev_info rev; - struct commit *commit; - struct strbuf format = STRBUF_INIT; - struct object_id junk_oid; - const char *head; - struct pretty_print_context pctx = {0}; - struct strbuf author_ident = STRBUF_INIT; - struct strbuf committer_ident = STRBUF_INIT; - - commit = lookup_commit(oid->hash); - if (!commit) - die(_("couldn't look up newly created commit")); - if (parse_commit(commit)) - die(_("could not parse newly created commit")); - - strbuf_addstr(&format, "format:%h] %s"); - - format_commit_message(commit, "%an <%ae>", &author_ident, &pctx); - format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx); - if (strbuf_cmp(&author_ident, &committer_ident)) { - strbuf_addstr(&format, "\n Author: "); - strbuf_addbuf_percentquote(&format, &author_ident); - } - if (author_date_is_interesting()) { - struct strbuf date = STRBUF_INIT; - format_commit_message(commit, "%ad", &date, &pctx); - strbuf_addstr(&format, "\n Date: "); - strbuf_addbuf_percentquote(&format, &date); - strbuf_release(&date); - } - if (!committer_ident_sufficiently_given()) { - strbuf_addstr(&format, "\n Committer: "); - strbuf_addbuf_percentquote(&format, &committer_ident); - if (advice_implicit_identity) { - strbuf_addch(&format, '\n'); - strbuf_addstr(&format, implicit_ident_advice()); - } - } - strbuf_release(&author_ident); - strbuf_release(&committer_ident); - - init_revisions(&rev, prefix); - setup_revisions(0, NULL, &rev, NULL); - - rev.diff = 1; - rev.diffopt.output_format = - DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY; - - rev.verbose_header = 1; - rev.show_root_diff = 1; - get_commit_format(format.buf, &rev); - rev.always_show_header = 0; - rev.diffopt.detect_rename = 1; - rev.diffopt.break_opt = 0; - diff_setup_done(&rev.diffopt); - - head = resolve_ref_unsafe("HEAD", 0, junk_oid.hash, NULL); - if (!strcmp(head, "HEAD")) - head = _("detached HEAD"); - else - skip_prefix(head, "refs/heads/", &head); - printf("[%s%s ", head, initial_commit ? _(" (root-commit)") : ""); - - if (!log_tree_commit(&rev, commit)) { - rev.always_show_header = 1; - rev.use_terminator = 1; - log_tree_commit(&rev, commit); - } - - strbuf_release(&format); -} - static int git_commit_config(const char *k, const char *v, void *cb) { struct wt_status *s = cb; @@ -1522,37 +1379,6 @@ static int git_commit_config(const char *k, const char *v, void *cb) return git_status_config(k, v, s); } -static int run_rewrite_hook(const struct object_id *oldoid, - const struct object_id *newoid) -{ - struct child_process proc = CHILD_PROCESS_INIT; - const char *argv[3]; - int code; - struct strbuf sb = STRBUF_INIT; - - argv[0] = find_hook("post-rewrite"); - if (!argv[0]) - return 0; - - argv[1] = "amend"; - argv[2] = NULL; - - proc.argv = argv; - proc.in = -1; - proc.stdout_to_stderr = 1; - - code = start_command(&proc); - if (code) - return code; - strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid)); - sigchain_push(SIGPIPE, SIG_IGN); - write_in_full(proc.in, sb.buf, sb.len); - close(proc.in); - strbuf_release(&sb); - sigchain_pop(SIGPIPE); - return finish_command(&proc); -} - int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) { struct argv_array hook_env = ARGV_ARRAY_INIT; @@ -1577,6 +1403,7 @@ int run_commit_hook(int editor_is_used, const char *index_file, const char *name 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")), @@ -1612,6 +1439,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_SET_INT(0, "short", &status_format, N_("show status concisely"), STATUS_FORMAT_SHORT), OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")), + OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags, + N_("compute full ahead/behind values")), OPT_SET_INT(0, "porcelain", &status_format, N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), OPT_SET_INT(0, "long", &status_format, @@ -1635,26 +1464,25 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct strbuf sb = STRBUF_INIT; struct strbuf author_ident = STRBUF_INIT; const char *index_file, *reflog_msg; - char *nl; struct object_id oid; struct commit_list *parents = NULL; struct stat statbuf; struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; - struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); status_init_config(&s, git_commit_config); + s.commit_template = 1; status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ s.colopts = 0; - if (get_sha1("HEAD", oid.hash)) + if (get_oid("HEAD", &oid)) current_head = NULL; else { - current_head = lookup_commit_or_die(oid.hash, "HEAD"); + current_head = lookup_commit_or_die(&oid, "HEAD"); if (parse_commit(current_head)) die(_("could not parse HEAD commit")); } @@ -1695,10 +1523,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 = fopen(git_path_merge_head(), "r"); - if (fp == NULL) - die_errno(_("could not open '%s' for reading"), - git_path_merge_head()); + fp = xfopen(git_path_merge_head(), "r"); while (strbuf_getline_lf(&m, fp) != EOF) { struct commit *parent; @@ -1716,7 +1541,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) allow_fast_forward = 0; } if (allow_fast_forward) - parents = reduce_heads(parents); + reduce_heads_replace(&parents); } else { if (!reflog_msg) reflog_msg = (whence == FROM_CHERRY_PICK) @@ -1734,19 +1559,19 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } if (verbose || /* Truncate the message just before the diff, if any. */ - cleanup_mode == CLEANUP_SCISSORS) + 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); - if (cleanup_mode != CLEANUP_NONE) - strbuf_stripspace(&sb, cleanup_mode == CLEANUP_ALL); - if (template_untouched(&sb) && !allow_empty_message) { + if (message_is_empty(&sb, cleanup_mode) && !allow_empty_message) { rollback_index_files(); - fprintf(stderr, _("Aborting commit; you did not edit the message.\n")); + fprintf(stderr, _("Aborting commit due to empty commit message.\n")); exit(1); } - if (message_is_empty(&sb) && !allow_empty_message) { + if (template_untouched(&sb, template_file, cleanup_mode) && !allow_empty_message) { rollback_index_files(); - fprintf(stderr, _("Aborting commit due to empty commit message.\n")); + fprintf(stderr, _("Aborting commit; you did not edit the message.\n")); exit(1); } @@ -1758,33 +1583,20 @@ int cmd_commit(int argc, const char **argv, const char *prefix) append_merge_tag_headers(parents, &tail); } - if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1, - parents, oid.hash, author_ident.buf, sign_commit, extra)) { + if (commit_tree_extended(sb.buf, sb.len, &active_cache_tree->oid, + parents, &oid, author_ident.buf, sign_commit, + extra)) { rollback_index_files(); die(_("failed to write commit object")); } strbuf_release(&author_ident); free_commit_extra_headers(extra); - nl = strchr(sb.buf, '\n'); - if (nl) - strbuf_setlen(&sb, nl + 1 - sb.buf); - else - strbuf_addch(&sb, '\n'); - strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg)); - strbuf_insert(&sb, strlen(reflog_msg), ": ", 2); - - transaction = ref_transaction_begin(&err); - if (!transaction || - ref_transaction_update(transaction, "HEAD", oid.hash, - current_head - ? current_head->object.oid.hash : null_sha1, - 0, sb.buf, &err) || - ref_transaction_commit(transaction, &err)) { + if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb, + &err)) { rollback_index_files(); die("%s", err.buf); } - ref_transaction_free(transaction); unlink(git_path_cherry_pick_head()); unlink(git_path_revert_head()); @@ -1799,20 +1611,22 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "not exceeded, and then \"git reset HEAD\" to recover.")); rerere(0); + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { - struct notes_rewrite_cfg *cfg; - cfg = init_copy_notes_for_rewrite("amend"); - if (cfg) { - /* we are amending, so current_head is not NULL */ - copy_note_for_rewrite(cfg, current_head->object.oid.hash, oid.hash); - finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'"); - } - run_rewrite_hook(¤t_head->object.oid, &oid); + commit_post_rewrite(current_head, &oid); + } + if (!quiet) { + unsigned int flags = 0; + + if (!current_head) + flags |= SUMMARY_INITIAL_COMMIT; + if (author_date_is_interesting()) + flags |= SUMMARY_SHOW_AUTHOR_DATE; + print_commit_summary(prefix, &oid, flags); } - if (!quiet) - print_summary(prefix, &oid, !current_head); - strbuf_release(&err); + UNLEAK(err); + UNLEAK(sb); return 0; } diff --git a/builtin/config.c b/builtin/config.c index 7f6c25d4d9..01169dd628 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "color.h" #include "parse-options.h" #include "urlmatch.h" @@ -47,10 +48,18 @@ static int show_origin; #define ACTION_GET_COLORBOOL (1<<14) #define ACTION_GET_URLMATCH (1<<15) +/* + * The actions "ACTION_LIST | ACTION_GET_*" which may produce more than + * one line of output and which should therefore be paged. + */ +#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) static struct option builtin_config_options[] = { OPT_GROUP(N_("Config file location")), @@ -79,6 +88,7 @@ static struct option builtin_config_options[] = { 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_GROUP(N_("Other")), OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")), OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), @@ -158,6 +168,11 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value return -1; strbuf_addstr(buf, v); free((char *)v); + } else if (types == TYPE_EXPIRY_DATE) { + timestamp_t t; + if (git_config_expiry_date(&t, key_, value_) < 0) + return -1; + strbuf_addf(buf, "%"PRItime, t); } else if (value_) { strbuf_addstr(buf, value_); } else { @@ -214,8 +229,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_); - free(key_regexp); - key_regexp = NULL; + FREE_AND_NULL(key_regexp); ret = CONFIG_INVALID_PATTERN; goto free_strings; } @@ -235,15 +249,14 @@ 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_); - free(regexp); - regexp = NULL; + FREE_AND_NULL(regexp); ret = CONFIG_INVALID_PATTERN; goto free_strings; } } - git_config_with_options(collect_config, &values, - &given_config_source, &config_options); + config_with_options(collect_config, &values, + &given_config_source, &config_options); ret = !values.nr; @@ -274,12 +287,13 @@ static char *normalize_value(const char *key, const char *value) if (!value) return NULL; - if (types == 0 || types == TYPE_PATH) + if (types == 0 || types == TYPE_PATH || types == TYPE_EXPIRY_DATE) /* * We don't do normalization for TYPE_PATH here: If * the path is like ~/foobar/, we prefer to store * "~/foobar/" in the config file, and to expand the ~ * when retrieving the value. + * Also don't do normalization for expiry dates. */ return xstrdup(value); if (types == TYPE_INT) @@ -320,8 +334,8 @@ static void get_color(const char *var, const char *def_color) get_color_slot = var; get_color_found = 0; parsed_color[0] = '\0'; - git_config_with_options(git_get_color_config, NULL, - &given_config_source, &config_options); + config_with_options(git_get_color_config, NULL, + &given_config_source, &config_options); if (!get_color_found && def_color) { if (color_parse(def_color, parsed_color) < 0) @@ -352,8 +366,8 @@ static int get_colorbool(const char *var, int print) get_colorbool_found = -1; get_diff_color_found = -1; get_color_ui_found = -1; - git_config_with_options(git_get_colorbool_config, NULL, - &given_config_source, &config_options); + config_with_options(git_get_colorbool_config, NULL, + &given_config_source, &config_options); if (get_colorbool_found < 0) { if (!strcmp(get_colorbool_slot, "color.diff")) @@ -441,8 +455,8 @@ static int get_urlmatch(const char *var, const char *url) show_keys = 1; } - git_config_with_options(urlmatch_config_entry, &config, - &given_config_source, &config_options); + config_with_options(urlmatch_config_entry, &config, + &given_config_source, &config_options); ret = !values.nr; @@ -519,10 +533,13 @@ int cmd_config(int argc, const char **argv, const char *prefix) die("$HOME not set"); if (access_or_warn(user_config, R_OK, 0) && - xdg_config && !access_or_warn(xdg_config, R_OK, 0)) + xdg_config && !access_or_warn(xdg_config, R_OK, 0)) { given_config_source.file = xdg_config; - else + free(user_config); + } else { given_config_source.file = user_config; + free(xdg_config); + } } else if (use_system_config) given_config_source.file = git_etc_gitconfig(); @@ -538,6 +555,10 @@ int cmd_config(int argc, const char **argv, const char *prefix) config_options.respect_includes = !given_config_source.file; else config_options.respect_includes = respect_includes_opt; + if (!nongit) { + config_options.commondir = get_git_common_dir(); + config_options.git_dir = get_git_dir(); + } if (end_null) { term = '\0'; @@ -580,11 +601,14 @@ int cmd_config(int argc, const char **argv, const char *prefix) usage_with_options(builtin_config_usage, builtin_config_options); } + if (actions & PAGING_ACTIONS) + setup_auto_pager("config", 1); + if (actions == ACTION_LIST) { check_argc(argc, 0, 0); - if (git_config_with_options(show_all_config, NULL, - &given_config_source, - &config_options) < 0) { + if (config_with_options(show_all_config, NULL, + &given_config_source, + &config_options) < 0) { if (given_config_source.file) die_errno("unable to read config file '%s'", given_config_source.file); @@ -625,6 +649,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); + UNLEAK(value); ret = git_config_set_in_file_gently(given_config_source.file, argv[0], value); if (ret == CONFIG_NOTHING_SET) error(_("cannot overwrite multiple values with a single value\n" @@ -635,6 +660,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); + UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, argv[2], 0); } @@ -642,6 +668,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); + UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, CONFIG_REGEX_NONE, 0); @@ -650,6 +677,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); + UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, argv[2], 1); } diff --git a/builtin/count-objects.c b/builtin/count-objects.c index acb05940fc..33343818c8 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -5,10 +5,12 @@ */ #include "cache.h" +#include "config.h" #include "dir.h" #include "builtin.h" #include "parse-options.h" #include "quote.h" +#include "packfile.h" static unsigned long garbage; static off_t size_garbage; diff --git a/builtin/credential.c b/builtin/credential.c index 0412fa00f0..879acfbcda 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -10,9 +10,9 @@ int cmd_credential(int argc, const char **argv, const char *prefix) const char *op; struct credential c = CREDENTIAL_INIT; - op = argv[1]; - if (!op) + if (argc != 2 || !strcmp(argv[1], "-h")) usage(usage_msg); + op = argv[1]; if (credential_read(&c, stdin) < 0) die("unable to read credential from stdin"); diff --git a/builtin/describe.c b/builtin/describe.c index a5cd8c513f..e4869df7b4 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -1,17 +1,21 @@ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "commit.h" #include "tag.h" +#include "blob.h" #include "refs.h" #include "builtin.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 "list-objects.h" -#define SEEN (1u << 0) #define MAX_TAGS (FLAG_BITS - 1) static const char * const describe_usage[] = { @@ -53,9 +57,14 @@ static const char *prio_names[] = { N_("head"), N_("lightweight"), N_("annotated"), }; -static int commit_name_cmp(const struct commit_name *cn1, - const struct commit_name *cn2, const void *peeled) +static int commit_name_cmp(const void *unused_cmp_data, + const void *entry, + const void *entry_or_key, + const void *peeled) { + const struct commit_name *cn1 = entry; + const struct commit_name *cn2 = entry_or_key; + return oidcmp(&cn1->peeled, peeled ? peeled : &cn2->peeled); } @@ -79,13 +88,13 @@ static int replace_name(struct commit_name *e, struct tag *t; if (!e->tag) { - t = lookup_tag(e->oid.hash); + t = lookup_tag(&e->oid); if (!t || parse_tag(t)) return 1; e->tag = t; } - t = lookup_tag(oid->hash); + t = lookup_tag(oid); if (!t || parse_tag(t)) return 0; *tag = t; @@ -123,13 +132,24 @@ static void add_to_known_names(const char *path, static int get_name(const char *path, const struct object_id *oid, int flag, void *cb_data) { - int is_tag = starts_with(path, "refs/tags/"); + int is_tag = 0; struct object_id peeled; int is_annotated, prio; - - /* Reject anything outside refs/tags/ unless --all */ - if (!all && !is_tag) + const char *path_to_match = NULL; + + if (skip_prefix(path, "refs/tags/", &path_to_match)) { + is_tag = 1; + } else if (all) { + if ((exclude_patterns.nr || patterns.nr) && + !skip_prefix(path, "refs/heads/", &path_to_match) && + !skip_prefix(path, "refs/remotes/", &path_to_match)) { + /* Only accept reference of known type if there are match/exclude patterns */ + return 0; + } + } else { + /* Reject anything outside refs/tags/ unless --all */ return 0; + } /* * If we're given exclude patterns, first exclude any tag which match @@ -138,11 +158,8 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi if (exclude_patterns.nr) { struct string_list_item *item; - if (!is_tag) - return 0; - for_each_string_list_item(item, &exclude_patterns) { - if (!wildmatch(item->string, path + 10, 0, NULL)) + if (!wildmatch(item->string, path_to_match, 0)) return 0; } } @@ -152,22 +169,22 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi * pattern. */ if (patterns.nr) { + int found = 0; struct string_list_item *item; - if (!is_tag) - return 0; - for_each_string_list_item(item, &patterns) { - if (!wildmatch(item->string, path + 10, 0, NULL)) + if (!wildmatch(item->string, path_to_match, 0)) { + found = 1; break; + } + } - /* If we get here, no pattern matched. */ + if (!found) return 0; - } } /* Is it annotated? */ - if (!peel_ref(path, peeled.hash)) { + if (!peel_ref(path, &peeled)) { is_annotated = !!oidcmp(oid, &peeled); } else { oidcpy(&peeled, oid); @@ -242,10 +259,10 @@ static unsigned long finish_depth_computation( return seen_commits; } -static void display_name(struct commit_name *n) +static void append_name(struct commit_name *n, struct strbuf *dst) { if (n->prio == 2 && !n->tag) { - n->tag = lookup_tag(n->oid.hash); + n->tag = lookup_tag(&n->oid); if (!n->tag || parse_tag(n->tag)) die(_("annotated tag %s not available"), n->path); } @@ -257,20 +274,22 @@ static void display_name(struct commit_name *n) n->name_checked = 1; } - if (n->tag) - printf("%s", n->tag->tag); - else - printf("%s", n->path); + if (n->tag) { + if (all) + strbuf_addstr(dst, "tags/"); + strbuf_addstr(dst, n->tag->tag); + } else { + strbuf_addstr(dst, n->path); + } } -static void show_suffix(int depth, const struct object_id *oid) +static void append_suffix(int depth, const struct object_id *oid, struct strbuf *dst) { - printf("-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev)); + strbuf_addf(dst, "-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev)); } -static void describe(const char *arg, int last_one) +static void describe_commit(struct object_id *oid, struct strbuf *dst) { - struct object_id oid; struct commit *cmit, *gave_up_on = NULL; struct commit_list *list; struct commit_name *n; @@ -279,37 +298,32 @@ static void describe(const char *arg, int last_one) unsigned long seen_commits = 0; unsigned int unannotated_cnt = 0; - if (get_oid(arg, &oid)) - die(_("Not a valid object name %s"), arg); - cmit = lookup_commit_reference(oid.hash); - if (!cmit) - die(_("%s is not a valid '%s' object"), arg, commit_type); + cmit = lookup_commit_reference(oid); n = find_commit_name(&cmit->object.oid); if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. */ - display_name(n); + append_name(n, dst); if (longformat) - show_suffix(0, n->tag ? &n->tag->tagged->oid : &oid); + append_suffix(0, n->tag ? &n->tag->tagged->oid : oid, dst); if (suffix) - printf("%s", suffix); - printf("\n"); + strbuf_addstr(dst, suffix); return; } if (!max_candidates) die(_("no tag exactly matches '%s'"), oid_to_hex(&cmit->object.oid)); if (debug) - fprintf(stderr, _("searching to describe %s\n"), arg); + fprintf(stderr, _("No exact match on refs or tags, searching to describe\n")); 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.hash, 1); + c = lookup_commit_reference_gently(&n->peeled, 1); if (c) c->util = n; } @@ -367,22 +381,21 @@ static void describe(const char *arg, int last_one) } if (!match_cnt) { - struct object_id *oid = &cmit->object.oid; + struct object_id *cmit_oid = &cmit->object.oid; if (always) { - printf("%s", find_unique_abbrev(oid->hash, abbrev)); + strbuf_add_unique_abbrev(dst, cmit_oid->hash, abbrev); if (suffix) - printf("%s", suffix); - printf("\n"); + strbuf_addstr(dst, suffix); return; } if (unannotated_cnt) die(_("No annotated tags can describe '%s'.\n" "However, there were unannotated tags: try --tags."), - oid_to_hex(oid)); + oid_to_hex(cmit_oid)); else die(_("No tags can describe '%s'.\n" "Try --always, or create some tags."), - oid_to_hex(oid)); + oid_to_hex(cmit_oid)); } QSORT(all_matches, match_cnt, compare_pt); @@ -420,15 +433,86 @@ static void describe(const char *arg, int last_one) } } - display_name(all_matches[0].name); + append_name(all_matches[0].name, dst); if (abbrev) - show_suffix(all_matches[0].depth, &cmit->object.oid); + append_suffix(all_matches[0].depth, &cmit->object.oid, dst); if (suffix) - printf("%s", suffix); - printf("\n"); + strbuf_addstr(dst, suffix); +} + +struct process_commit_data { + struct object_id current_commit; + struct object_id looking_for; + struct strbuf *dst; + struct rev_info *revs; +}; + +static void process_commit(struct commit *commit, void *data) +{ + struct process_commit_data *pcd = data; + pcd->current_commit = commit->object.oid; +} + +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) { + reset_revision_walk(); + describe_commit(&pcd->current_commit, pcd->dst); + strbuf_addf(pcd->dst, ":%s", path); + free_commit_list(pcd->revs->commits); + pcd->revs->commits = NULL; + } +} + +static void describe_blob(struct object_id oid, struct strbuf *dst) +{ + struct rev_info revs; + struct argv_array args = ARGV_ARRAY_INIT; + struct process_commit_data pcd = { null_oid, oid, dst, &revs}; + + argv_array_pushl(&args, "internal: The first arg is not parsed", + "--objects", "--in-commit-order", "--reverse", "HEAD", + NULL); + + init_revisions(&revs, NULL); + if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1) + BUG("setup_revisions could not handle all args?"); + + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); + + traverse_commit_list(&revs, process_commit, process_object, &pcd); + reset_revision_walk(); +} + +static void describe(const char *arg, int last_one) +{ + struct object_id oid; + struct commit *cmit; + struct strbuf sb = STRBUF_INIT; + + if (debug) + fprintf(stderr, _("describe %s\n"), arg); + + if (get_oid(arg, &oid)) + die(_("Not a valid object name %s"), arg); + cmit = lookup_commit_reference_gently(&oid, 1); + + if (cmit) + describe_commit(&oid, &sb); + else if (sha1_object_info(oid.hash, NULL) == OBJ_BLOB) + describe_blob(oid, &sb); + else + die(_("%s is neither a commit nor blob"), arg); + + puts(sb.buf); if (!last_one) clear_commit_marks(cmit, -1); + + strbuf_release(&sb); } int cmd_describe(int argc, const char **argv, const char *prefix) @@ -500,9 +584,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix) return cmd_name_rev(args.argc, args.argv, prefix); } - hashmap_init(&names, (hashmap_cmp_fn) commit_name_cmp, 0); + hashmap_init(&names, commit_name_cmp, NULL, 0); for_each_rawref(get_name, NULL); - if (!names.size && !always) + if (!hashmap_get_size(&names) && !always) die(_("No names found, cannot describe anything.")); if (argc == 0) { @@ -529,7 +613,9 @@ int cmd_describe(int argc, const char **argv, const char *prefix) } } else if (dirty) { static struct lock_file index_lock; - int fd; + struct rev_info revs; + struct argv_array args = ARGV_ARRAY_INIT; + int fd, result; read_cache_preload(NULL); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, @@ -538,8 +624,13 @@ int cmd_describe(int argc, const char **argv, const char *prefix) if (0 <= fd) update_index_if_able(&the_index, &index_lock); - if (!cmd_diff_index(ARRAY_SIZE(diff_index_args) - 1, - diff_index_args, prefix)) + init_revisions(&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"); + result = run_diff_index(&revs, 0); + + if (!diff_result_code(&revs.diffopt, result)) suffix = NULL; else suffix = dirty; diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 15c61fd8d1..e88493ffe5 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "diff.h" #include "commit.h" #include "revision.h" @@ -20,9 +21,11 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) int result; unsigned options = 0; - init_revisions(&rev, prefix); - gitmodules_config(); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(diff_files_usage); + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ + init_revisions(&rev, prefix); rev.abbrev = 0; precompose_argv(argc, argv); diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 1af373d002..522f4fdffd 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "diff.h" #include "commit.h" #include "revision.h" @@ -17,9 +18,11 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) int i; int result; - init_revisions(&rev, prefix); - gitmodules_config(); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(diff_cache_usage); + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ + init_revisions(&rev, prefix); rev.abbrev = 0; precompose_argv(argc, argv); @@ -53,5 +56,6 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) return -1; } result = run_diff_index(&rev, cached); + UNLEAK(rev); return diff_result_code(&rev.diffopt, result); } diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index a570fea55b..473615117e 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "diff.h" #include "commit.h" #include "log-tree.h" @@ -7,9 +8,9 @@ static struct rev_info log_tree_opt; -static int diff_tree_commit_sha1(const struct object_id *oid) +static int diff_tree_commit_oid(const struct object_id *oid) { - struct commit *commit = lookup_commit_reference(oid->hash); + struct commit *commit = lookup_commit_reference(oid); if (!commit) return -1; return log_tree_commit(&log_tree_opt, commit); @@ -23,7 +24,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.hash); + struct commit *parent = lookup_commit(&oid); if (!pptr) { /* Free the real parent list */ free_commit_list(commit->parents); @@ -44,13 +45,13 @@ 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.hash); + tree2 = lookup_tree(&oid); if (!tree2 || parse_tree(tree2)) return -1; printf("%s %s\n", oid_to_hex(&tree1->object.oid), oid_to_hex(&tree2->object.oid)); - diff_tree_sha1(tree1->object.oid.hash, tree2->object.oid.hash, - "", &log_tree_opt.diffopt); + diff_tree_oid(&tree1->object.oid, &tree2->object.oid, + "", &log_tree_opt.diffopt); log_tree_diff_flush(&log_tree_opt); return 0; } @@ -67,7 +68,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.hash); + obj = parse_object(&oid); if (!obj) return -1; if (obj->type == OBJ_COMMIT) @@ -75,7 +76,7 @@ static int diff_tree_stdin(char *line) if (obj->type == OBJ_TREE) return stdin_diff_trees((struct tree *)obj, p); error("Object %s is a %s, not a commit or tree", - oid_to_hex(&oid), typename(obj->type)); + oid_to_hex(&oid), type_name(obj->type)); return -1; } @@ -98,16 +99,19 @@ static void diff_tree_tweak_rev(struct rev_info *rev, struct setup_revision_opt int cmd_diff_tree(int argc, const char **argv, const char *prefix) { - int nr_sha1; char line[1000]; struct object *tree1, *tree2; static struct rev_info *opt = &log_tree_opt; struct setup_revision_opt s_r_opt; int read_stdin = 0; - init_revisions(opt, prefix); - gitmodules_config(); + 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); + if (read_cache() < 0) + die(_("index file corrupt")); opt->abbrev = 0; opt->diff = 1; opt->disable_stdin = 1; @@ -134,15 +138,14 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) * second one is marked UNINTERESTING, we recover the original * order the user gave, i.e. "a..b", by swapping the trees. */ - nr_sha1 = opt->pending.nr; - switch (nr_sha1) { + switch (opt->pending.nr) { case 0: if (!read_stdin) usage(diff_tree_usage); break; case 1: tree1 = opt->pending.objects[0].item; - diff_tree_commit_sha1(&tree1->oid); + diff_tree_commit_oid(&tree1->oid); break; case 2: tree1 = opt->pending.objects[0].item; @@ -150,9 +153,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) if (tree2->flags & UNINTERESTING) { SWAP(tree2, tree1); } - diff_tree_sha1(tree1->oid.hash, - tree2->oid.hash, - "", &opt->diffopt); + diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt); log_tree_diff_flush(opt); break; } diff --git a/builtin/diff.c b/builtin/diff.c index 5e7c6428c9..16bfb22f73 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -4,6 +4,7 @@ * Copyright (c) 2006 Junio C Hamano */ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "color.h" #include "commit.h" @@ -43,7 +44,7 @@ static void stuff_change(struct diff_options *opt, !oidcmp(old_oid, new_oid) && (old_mode == new_mode)) return; - if (DIFF_OPT_TST(opt, REVERSE_DIFF)) { + if (opt->flags.reverse_diff) { SWAP(old_mode, new_mode); SWAP(old_oid, new_oid); SWAP(old_path, new_path); @@ -56,8 +57,8 @@ static void stuff_change(struct diff_options *opt, one = alloc_filespec(old_path); two = alloc_filespec(new_path); - fill_filespec(one, old_oid->hash, old_oid_valid, old_mode); - fill_filespec(two, new_oid->hash, new_oid_valid, new_mode); + fill_filespec(one, old_oid, old_oid_valid, old_mode); + fill_filespec(two, new_oid, new_oid_valid, new_mode); diff_queue(&diff_queued_diff, one, two); } @@ -174,7 +175,7 @@ static int builtin_diff_tree(struct rev_info *revs, swap = 1; oid[swap] = &ent0->item->oid; oid[1 - swap] = &ent1->item->oid; - diff_tree_sha1(oid[0]->hash, oid[1]->hash, "", &revs->diffopt); + diff_tree_oid(oid[0], oid[1], "", &revs->diffopt); log_tree_diff_flush(revs); return 0; } @@ -194,7 +195,7 @@ static int builtin_diff_combined(struct rev_info *revs, revs->dense_combined_merges = revs->combine_merges = 1; for (i = 1; i < ents; i++) oid_array_append(&parents, &ent[i].item->oid); - diff_tree_combined(ent[0].item->oid.hash, &parents, + diff_tree_combined(&ent[0].item->oid, &parents, revs->dense_combined_merges, revs); oid_array_clear(&parents); return 0; @@ -202,17 +203,16 @@ static int builtin_diff_combined(struct rev_info *revs, static void refresh_index_quietly(void) { - struct lock_file *lock_file; + struct lock_file lock_file = LOCK_INIT; int fd; - lock_file = xcalloc(1, sizeof(struct lock_file)); - fd = hold_locked_index(lock_file, 0); + fd = hold_locked_index(&lock_file, 0); if (fd < 0) return; discard_cache(); read_cache(); refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED); - update_index_if_able(&the_index, lock_file); + update_index_if_able(&the_index, &lock_file); } static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv) @@ -314,8 +314,6 @@ int cmd_diff(int argc, const char **argv, const char *prefix) no_index = DIFF_NO_INDEX_IMPLICIT; } - if (!no_index) - gitmodules_config(); init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); precompose_argv(argc, argv); @@ -351,8 +349,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) rev.diffopt.stat_graph_width = -1; /* Default to let external and textconv be used */ - DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL); - DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); + rev.diffopt.flags.allow_external = 1; + rev.diffopt.flags.allow_textconv = 1; if (nongit) die(_("Not a git repository")); @@ -362,7 +360,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) diff_setup_done(&rev.diffopt); } - DIFF_OPT_SET(&rev.diffopt, RECURSIVE); + rev.diffopt.flags.recursive = 1; setup_diff_pager(&rev.diffopt); @@ -381,7 +379,7 @@ 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(EMPTY_TREE_SHA1_BIN); + tree = lookup_tree(the_hash_algo->empty_tree); add_pending_object(&rev, &tree->object, "HEAD"); } break; @@ -395,7 +393,7 @@ 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.hash); + obj = parse_object(&obj->oid); obj = deref_tag(obj, NULL, 0); if (!obj) die(_("invalid object '%s' given."), name); @@ -465,5 +463,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) result = diff_result_code(&rev.diffopt, result); if (1 < rev.diffopt.skip_stat_unmatch) refresh_index_quietly(); + UNLEAK(rev); + UNLEAK(ent); + UNLEAK(blob); return result; } diff --git a/builtin/difftool.c b/builtin/difftool.c index b9a892f269..bcc79d1888 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -12,6 +12,7 @@ * Copyright (C) 2016 Johannes Schindelin */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "run-command.h" #include "exec_cmd.h" @@ -110,7 +111,7 @@ 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.hash, fd, &st, OBJ_BLOB, name, 0)) { + !index_fd(&wt_oid, fd, &st, OBJ_BLOB, name, 0)) { if (is_null_oid(oid)) { oidcpy(oid, &wt_oid); use = 1; @@ -129,9 +130,13 @@ struct working_tree_entry { char path[FLEX_ARRAY]; }; -static int working_tree_entry_cmp(struct working_tree_entry *a, - struct working_tree_entry *b, void *keydata) +static int working_tree_entry_cmp(const void *unused_cmp_data, + const void *entry, + const void *entry_or_key, + const void *unused_keydata) { + const struct working_tree_entry *a = entry; + const struct working_tree_entry *b = entry_or_key; return strcmp(a->path, b->path); } @@ -145,8 +150,14 @@ struct pair_entry { const char path[FLEX_ARRAY]; }; -static int pair_cmp(struct pair_entry *a, struct pair_entry *b, void *keydata) +static int pair_cmp(const void *unused_cmp_data, + const void *entry, + const void *entry_or_key, + const void *unused_keydata) { + const struct pair_entry *a = entry; + const struct pair_entry *b = entry_or_key; + return strcmp(a->path, b->path); } @@ -173,8 +184,14 @@ struct path_entry { char path[FLEX_ARRAY]; }; -static int path_entry_cmp(struct path_entry *a, struct path_entry *b, void *key) +static int path_entry_cmp(const void *unused_cmp_data, + const void *entry, + const void *entry_or_key, + const void *key) { + const struct path_entry *a = entry; + const struct path_entry *b = entry_or_key; + return strcmp(a->path, key ? key : b->path); } @@ -365,10 +382,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, rdir_len = rdir.len; wtdir_len = wtdir.len; - hashmap_init(&working_tree_dups, - (hashmap_cmp_fn)working_tree_entry_cmp, 0); - hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, 0); - hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, 0); + hashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0); + hashmap_init(&submodules, pair_cmp, NULL, 0); + hashmap_init(&symlinks2, pair_cmp, NULL, 0); child.no_stdin = 1; child.git_cmd = 1; @@ -578,10 +594,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, * in the common case of --symlinks and the difftool updating * files through the symlink. */ - hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, - wtindex.cache_nr); - hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, - wtindex.cache_nr); + hashmap_init(&wt_modified, path_entry_cmp, NULL, wtindex.cache_nr); + hashmap_init(&tmp_modified, path_entry_cmp, NULL, wtindex.cache_nr); for (i = 0; i < wtindex.cache_nr; i++) { struct hashmap_entry dummy; @@ -602,7 +616,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { ret = error("could not write %s", buf.buf); - rollback_lock_file(&lock); goto finish; } changed_files(&wt_modified, buf.buf, workdir); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 64617ad8e3..27b2cc138e 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -5,6 +5,7 @@ */ #include "builtin.h" #include "cache.h" +#include "config.h" #include "refs.h" #include "commit.h" #include "object.h" @@ -92,8 +93,9 @@ struct anonymized_entry { size_t anon_len; }; -static int anonymized_entry_cmp(const void *va, const void *vb, - const void *data) +static int anonymized_entry_cmp(const void *unused_cmp_data, + const void *va, const void *vb, + const void *unused_keydata) { const struct anonymized_entry *a = va, *b = vb; return a->orig_len != b->orig_len || @@ -112,7 +114,7 @@ static const void *anonymize_mem(struct hashmap *map, struct anonymized_entry key, *ret; if (!map->cmpfn) - hashmap_init(map, anonymized_entry_cmp, 0); + hashmap_init(map, anonymized_entry_cmp, NULL, 0); hashmap_entry_init(&key, memhash(orig, *len)); key.orig = orig; @@ -232,15 +234,15 @@ static void export_blob(const struct object_id *oid) if (anonymize) { buf = anonymize_blob(&size); - object = (struct object *)lookup_blob(oid->hash); + object = (struct object *)lookup_blob(oid); eaten = 0; } else { buf = read_sha1_file(oid->hash, &type, &size); if (!buf) die ("Could not read blob %s", oid_to_hex(oid)); - if (check_sha1_signature(oid->hash, buf, size, typename(type)) < 0) + if (check_sha1_signature(oid->hash, buf, size, type_name(type)) < 0) die("sha1 mismatch in blob %s", oid_to_hex(oid)); - object = parse_object_buffer(oid->hash, type, size, buf, &eaten); + object = parse_object_buffer(oid, type, size, buf, &eaten); } if (!object) @@ -342,6 +344,7 @@ static void show_filemodify(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; + struct string_list *changed = data; /* * Handle files below a directory first, in case they are all deleted @@ -357,20 +360,31 @@ static void show_filemodify(struct diff_queue_struct *q, case DIFF_STATUS_DELETED: printf("D "); print_path(spec->path); + string_list_insert(changed, spec->path); putchar('\n'); break; case DIFF_STATUS_COPIED: case DIFF_STATUS_RENAMED: - printf("%c ", q->queue[i]->status); - print_path(ospec->path); - putchar(' '); - print_path(spec->path); - putchar('\n'); - - if (!oidcmp(&ospec->oid, &spec->oid) && - ospec->mode == spec->mode) - break; + /* + * If a change in the file corresponding to ospec->path + * has been observed, we cannot trust its contents + * because the diff is calculated based on the prior + * contents, not the current contents. So, declare a + * copy or rename only if there was no change observed. + */ + if (!string_list_has_string(changed, ospec->path)) { + printf("%c ", q->queue[i]->status); + print_path(ospec->path); + putchar(' '); + print_path(spec->path); + string_list_insert(changed, spec->path); + putchar('\n'); + + if (!oidcmp(&ospec->oid, &spec->oid) && + ospec->mode == spec->mode) + break; + } /* fallthrough */ case DIFF_STATUS_TYPE_CHANGED: @@ -391,6 +405,7 @@ static void show_filemodify(struct diff_queue_struct *q, get_object_mark(object)); } print_path(spec->path); + string_list_insert(changed, spec->path); putchar('\n'); break; @@ -526,7 +541,8 @@ static void anonymize_ident_line(const char **beg, const char **end) *end = out->buf + out->len; } -static void handle_commit(struct commit *commit, struct rev_info *rev) +static void handle_commit(struct commit *commit, struct rev_info *rev, + struct string_list *paths_of_changed_objects) { int saved_output_format = rev->diffopt.output_format; const char *commit_buffer; @@ -562,12 +578,12 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) get_object_mark(&commit->parents->item->object) != 0 && !full_tree) { parse_commit_or_die(commit->parents->item); - diff_tree_sha1(commit->parents->item->tree->object.oid.hash, - commit->tree->object.oid.hash, "", &rev->diffopt); + diff_tree_oid(&commit->parents->item->tree->object.oid, + &commit->tree->object.oid, "", &rev->diffopt); } else - diff_root_tree_sha1(commit->tree->object.oid.hash, - "", &rev->diffopt); + diff_root_tree_oid(&commit->tree->object.oid, + "", &rev->diffopt); /* Export the referenced blobs, and remember the marks. */ for (i = 0; i < diff_queued_diff.nr; i++) @@ -613,6 +629,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) if (full_tree) printf("deleteall\n"); log_tree_diff_flush(rev); + string_list_clear(paths_of_changed_objects, 0); rev->diffopt.output_format = saved_output_format; printf("\n"); @@ -628,15 +645,15 @@ static void *anonymize_tag(const void *old, size_t *len) return strbuf_detach(&out, len); } -static void handle_tail(struct object_array *commits, struct rev_info *revs) +static void handle_tail(struct object_array *commits, struct rev_info *revs, + struct string_list *paths_of_changed_objects) { struct commit *commit; while (commits->nr) { - commit = (struct commit *)commits->objects[commits->nr - 1].item; + commit = (struct commit *)object_array_pop(commits); if (has_unshown_parent(commit)) return; - handle_commit(commit, revs); - commits->nr--; + handle_commit(commit, revs, paths_of_changed_objects); } } @@ -740,7 +757,7 @@ static void handle_tag(const char *name, struct tag *tag) if (tagged->type != OBJ_COMMIT) { die ("Tag %s tags unexported %s!", oid_to_hex(&tag->object.oid), - typename(tagged->type)); + type_name(tagged->type)); } p = (struct commit *)tagged; for (;;) { @@ -779,7 +796,7 @@ 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.hash); + parse_object(&tag->object.oid); string_list_append(&extra_refs, full_name)->util = tag; tag = (struct tag *)tag->tagged; } @@ -806,7 +823,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) if (e->flags & UNINTERESTING) continue; - if (dwim_ref(e->name, strlen(e->name), oid.hash, &full_name) != 1) + if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1) continue; if (refspecs) { @@ -822,7 +839,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) if (!commit) { warning("%s: Unexpected object of type %s, skipping.", e->name, - typename(e->item->type)); + type_name(e->item->type)); continue; } @@ -834,7 +851,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) continue; default: /* OBJ_TAG (nested tags) is already handled */ warning("Tag points to object of unexpected type %s, skipping.", - typename(commit->object.type)); + type_name(commit->object.type)); continue; } @@ -878,7 +895,7 @@ static void export_marks(char *file) { unsigned int i; uint32_t mark; - struct object_decoration *deco = idnums.hash; + struct decoration_entry *deco = idnums.entries; FILE *f; int e = 0; @@ -907,9 +924,7 @@ static void export_marks(char *file) static void import_marks(char *input_file) { char line[512]; - FILE *f = fopen(input_file, "r"); - if (!f) - die_errno("cannot read '%s'", input_file); + FILE *f = xfopen(input_file, "r"); while (fgets(line, sizeof(line), f)) { uint32_t mark; @@ -940,7 +955,7 @@ static void import_marks(char *input_file) /* only commits */ continue; - commit = lookup_commit(oid.hash); + commit = lookup_commit(&oid); if (!commit) die("not a commit? can't happen: %s", oid_to_hex(&oid)); @@ -977,6 +992,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) char *export_filename = NULL, *import_filename = NULL; uint32_t lastimportid; struct string_list refspecs_list = STRING_LIST_INIT_NODUP; + struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP; struct option options[] = { OPT_INTEGER(0, "progress", &progress, N_("show progress after <n> objects")), @@ -1049,14 +1065,15 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); revs.diffopt.format_callback = show_filemodify; - DIFF_OPT_SET(&revs.diffopt, RECURSIVE); + revs.diffopt.format_callback_data = &paths_of_changed_objects; + revs.diffopt.flags.recursive = 1; while ((commit = get_revision(&revs))) { if (has_unshown_parent(commit)) { add_object_array(&commit->object, NULL, &commits); } else { - handle_commit(commit, &revs); - handle_tail(&commits, &revs); + handle_commit(commit, &revs, &paths_of_changed_objects); + handle_tail(&commits, &revs, &paths_of_changed_objects); } } diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 366b9d13f9..a7bc1366ab 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -53,6 +53,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct oid_array shallow = OID_ARRAY_INIT; struct string_list deepen_not = STRING_LIST_INIT_DUP; + fetch_if_missing = 0; + packet_trace_identity("fetch-pack"); memset(&args, 0, sizeof(args)); @@ -143,6 +145,22 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.update_shallow = 1; continue; } + if (!strcmp("--from-promisor", arg)) { + args.from_promisor = 1; + continue; + } + if (!strcmp("--no-dependents", arg)) { + args.no_dependents = 1; + continue; + } + if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) { + parse_list_objects_filter(&args.filter_options, arg); + continue; + } + if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { + list_objects_filter_set_no_filter(&args.filter_options); + continue; + } usage(fetch_pack_usage); } if (deepen_not.nr) diff --git a/builtin/fetch.c b/builtin/fetch.c index 5f2c2ab23e..6d73656a48 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -2,6 +2,8 @@ * "git fetch" */ #include "cache.h" +#include "config.h" +#include "repository.h" #include "refs.h" #include "commit.h" #include "builtin.h" @@ -16,6 +18,8 @@ #include "connected.h" #include "argv-array.h" #include "utf8.h" +#include "packfile.h" +#include "list-objects-filter-options.h" static const char * const builtin_fetch_usage[] = { N_("git fetch [<options>] [<repository> [<refspec>...]]"), @@ -35,10 +39,14 @@ static int fetch_prune_config = -1; /* unspecified */ static int prune = -1; /* unspecified */ #define PRUNE_BY_DEFAULT 0 /* do we prune by default? */ +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 progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static int progress = -1; static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen; -static int max_children = -1; +static int max_children = 1; static enum transport_family family; static const char *depth; static const char *deepen_since; @@ -48,24 +56,12 @@ static struct strbuf default_rla = STRBUF_INIT; static struct transport *gtransport; static struct transport *gsecondary; static const char *submodule_prefix = ""; -static const char *recurse_submodules_default; +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 int option_parse_recurse_submodules(const struct option *opt, - const char *arg, int unset) -{ - if (unset) { - recurse_submodules = RECURSE_SUBMODULES_OFF; - } else { - if (arg) - recurse_submodules = parse_fetch_recurse_submodules_arg(opt->long_name, arg); - else - recurse_submodules = RECURSE_SUBMODULES_ON; - } - return 0; -} +static struct list_objects_filter_options filter_options; static int git_fetch_config(const char *k, const char *v, void *cb) { @@ -73,9 +69,42 @@ static int git_fetch_config(const char *k, const char *v, void *cb) fetch_prune_config = git_config_bool(k, v); return 0; } + + if (!strcmp(k, "fetch.prunetags")) { + fetch_prune_tags_config = 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; + recurse_submodules = r; + } + + if (!strcmp(k, "submodule.fetchjobs")) { + max_children = 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); + return 0; + } + + return 0; +} + static int parse_refmap_arg(const struct option *opt, const char *arg, int unset) { ALLOC_GROW(refmap_array, refmap_nr + 1, refmap_alloc); @@ -97,7 +126,7 @@ static struct option builtin_fetch_options[] = { 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")), + OPT__FORCE(&force, N_("force overwrite of local branch"), 0), OPT_BOOL('m', "multiple", &multiple, N_("fetch from multiple remotes")), OPT_SET_INT('t', "tags", &tags, @@ -108,9 +137,11 @@ static struct option builtin_fetch_options[] = { N_("number of submodules fetched in parallel")), OPT_BOOL('p', "prune", &prune, N_("prune remote-tracking branches no longer on remote")), - { OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"), + 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"), N_("control recursive fetching of submodules"), - PARSE_OPT_OPTARG, option_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")), @@ -130,9 +161,11 @@ static struct option builtin_fetch_options[] = { PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1 }, { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"), N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN }, - { OPTION_STRING, 0, "recurse-submodules-default", - &recurse_submodules_default, NULL, - N_("default mode for recursion"), PARSE_OPT_HIDDEN }, + { OPTION_CALLBACK, 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 }, OPT_BOOL(0, "update-shallow", &update_shallow, N_("accept refs that update .git/shallow")), { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"), @@ -141,6 +174,7 @@ static struct option builtin_fetch_options[] = { 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_END() }; @@ -242,9 +276,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, HAS_SHA1_QUICK) && + !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, HAS_SHA1_QUICK) && + !has_sha1_file_with_flags(item->util, + OBJECT_INFO_QUICK) && !will_fetch(head, item->util)) item->util = NULL; item = NULL; @@ -258,7 +294,7 @@ static void find_non_local_tags(struct transport *transport, * fetch. */ if (item && - !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) && + !has_sha1_file_with_flags(item->util, OBJECT_INFO_QUICK) && !will_fetch(head, item->util)) item->util = NULL; @@ -279,7 +315,7 @@ static void find_non_local_tags(struct transport *transport, * checked to see if it needs fetching. */ if (item && - !has_sha1_file_with_flags(item->util, HAS_SHA1_QUICK) && + !has_sha1_file_with_flags(item->util, OBJECT_INFO_QUICK) && !will_fetch(head, item->util)) item->util = NULL; @@ -436,8 +472,8 @@ static int s_update_ref(const char *action, transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref->name, - ref->new_oid.hash, - check_old ? ref->old_oid.hash : NULL, + &ref->new_oid, + check_old ? &ref->old_oid : NULL, 0, msg, &err)) goto fail; @@ -636,8 +672,8 @@ static int update_local_ref(struct ref *ref, return r; } - current = lookup_commit_reference_gently(ref->old_oid.hash, 1); - updated = lookup_commit_reference_gently(ref->new_oid.hash, 1); + current = lookup_commit_reference_gently(&ref->old_oid, 1); + updated = lookup_commit_reference_gently(&ref->new_oid, 1); if (!current || !updated) { const char *msg; const char *what; @@ -706,7 +742,7 @@ static int update_local_ref(struct ref *ref, } } -static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) +static int iterate_ref_map(void *cb_data, struct object_id *oid) { struct ref **rm = cb_data; struct ref *ref = *rm; @@ -716,7 +752,7 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) if (!ref) return -1; /* end of the list */ *rm = ref->next; - hashcpy(sha1, ref->old_oid.hash); + oidcpy(oid, &ref->old_oid); return 0; } @@ -770,7 +806,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, continue; } - commit = lookup_commit_reference_gently(rm->old_oid.hash, 1); + commit = lookup_commit_reference_gently(&rm->old_oid, + 1); if (!commit) rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; @@ -940,7 +977,7 @@ static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map, for (ref = stale_refs; ref; ref = ref->next) string_list_append(&refnames, ref->name); - result = delete_refs(&refnames, 0); + result = delete_refs("fetch: prune", &refnames, 0); string_list_clear(&refnames, 0); } @@ -1022,6 +1059,11 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, "yes"); 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); + set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); + } return transport; } @@ -1072,9 +1114,6 @@ static int do_fetch(struct transport *transport, tags = TAGS_UNSET; } - if (!transport->get_refs_list || !transport->fetch) - die(_("Don't know how to fetch from %s"), transport->url); - /* if not appending, truncate FETCH_HEAD */ if (!append && !dry_run) { retcode = truncate_fetch_head(); @@ -1192,6 +1231,8 @@ static void add_options_to_argv(struct argv_array *argv) argv_array_push(argv, "--dry-run"); if (prune != -1) argv_array_push(argv, prune ? "--prune" : "--no-prune"); + if (prune_tags != -1) + argv_array_push(argv, prune_tags ? "--prune-tags" : "--no-prune-tags"); if (update_head_ok) argv_array_push(argv, "--update-head-ok"); if (force) @@ -1245,12 +1286,65 @@ static int fetch_multiple(struct string_list *list) return result; } -static int fetch_one(struct remote *remote, int argc, const char **argv) +/* + * Fetching from the promisor remote should use the given filter-spec + * or inherit the default filter-spec from the config. + */ +static inline void fetch_one_setup_partial(struct remote *remote) +{ + /* + * Explicit --no-filter argument overrides everything, regardless + * of any prior partial clones and fetches. + */ + if (filter_options.no_filter) + return; + + /* + * 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) + 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 (!repository_format_partial_clone && 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); + 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; int exit_code; + int maybe_prune_tags; + int remote_via_config = remote_is_configured(remote, 0); if (!remote) die(_("No remote repository specified. Please, specify either a URL or a\n" @@ -1260,18 +1354,39 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) if (prune < 0) { /* no command line request */ - if (0 <= gtransport->remote->prune) - prune = gtransport->remote->prune; + if (0 <= remote->prune) + prune = remote->prune; else if (0 <= fetch_prune_config) prune = fetch_prune_config; else prune = PRUNE_BY_DEFAULT; } + if (prune_tags < 0) { + /* no command line request */ + if (0 <= remote->prune_tags) + prune_tags = remote->prune_tags; + else if (0 <= fetch_prune_tags_config) + prune_tags = fetch_prune_tags_config; + else + prune_tags = PRUNE_TAGS_BY_DEFAULT; + } + + 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++; + } + } + if (argc > 0) { - int j = 0; int i; - refs = xcalloc(st_add(argc, 1), sizeof(const char *)); for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "tag")) { i++; @@ -1281,9 +1396,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv) argv[i], argv[i]); } else refs[j++] = argv[i]; + ref_nr++; } - refs[j] = NULL; - ref_nr = j; } sigchain_push_common(unlock_pack_on_signal); @@ -1300,17 +1414,21 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) { int i; struct string_list list = STRING_LIST_INIT_DUP; - struct remote *remote; + 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); git_config(git_fetch_config, NULL); argc = parse_options(argc, argv, prefix, @@ -1338,14 +1456,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth || deepen_since || deepen_not.nr) deepen = 1; - if (recurse_submodules != RECURSE_SUBMODULES_OFF) { - if (recurse_submodules_default) { - int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default); - set_config_fetch_recurse_submodules(arg); - } - gitmodules_config(); - git_config(submodule_config, NULL); - } + if (filter_options.choice && !repository_format_partial_clone) + die("--filter can only be used when extensions.partialClone is set"); if (all) { if (argc == 1) @@ -1353,17 +1465,14 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) else if (argc > 1) die(_("fetch --all does not make sense with refspecs")); (void) for_each_remote(get_one_remote_for_fetch, &list); - result = fetch_multiple(&list); } else if (argc == 0) { /* No arguments -- use default remote */ remote = remote_get(NULL); - result = fetch_one(remote, argc, argv); } else if (multiple) { /* All arguments are assumed to be remotes or groups */ for (i = 0; i < argc; i++) if (!add_remote_or_group(argv[i], &list)) die(_("No such remote or remote group: %s"), argv[i]); - result = fetch_multiple(&list); } else { /* Single remote or group */ (void) add_remote_or_group(argv[0], &list); @@ -1371,21 +1480,35 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) /* More than one remote */ if (argc > 1) die(_("Fetching a group and specifying refspecs does not make sense")); - result = fetch_multiple(&list); } else { /* Zero or one remotes */ remote = remote_get(argv[0]); - result = fetch_one(remote, argc-1, argv+1); + prune_tags_ok = (argc == 1); + argc--; + argv++; } } + if (remote) { + if (filter_options.choice || repository_format_partial_clone) + fetch_one_setup_partial(remote); + result = fetch_one(remote, argc, argv, prune_tags_ok); + } else { + if (filter_options.choice) + die(_("--filter can only be used with the remote configured in core.partialClone")); + /* TODO should this also die if we have a previous partial-clone? */ + result = fetch_multiple(&list); + } + if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { struct argv_array options = ARGV_ARRAY_INIT; add_options_to_argv(&options); - result = fetch_populated_submodules(&options, + result = fetch_populated_submodules(the_repository, + &options, submodule_prefix, recurse_submodules, + recurse_submodules_default, verbosity < 0, max_children); argv_array_clear(&options); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 6faa3c0d24..8e8a15ea4a 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "refs.h" #include "commit.h" #include "diff.h" @@ -341,7 +342,7 @@ static void shortlog(const char *name, const struct object_id *oid = &origin_data->oid; int limit = opts->shortlog_len; - branch = deref_tag(parse_object(oid->hash), oid_to_hex(oid), GIT_SHA1_HEXSZ); + branch = deref_tag(parse_object(oid), oid_to_hex(oid), GIT_SHA1_HEXSZ); if (!branch || branch->type != OBJ_COMMIT) return; @@ -376,7 +377,8 @@ static void shortlog(const char *name, string_list_append(&subjects, oid_to_hex(&commit->object.oid)); else - string_list_append(&subjects, strbuf_detach(&sb, NULL)); + string_list_append_nodup(&subjects, + strbuf_detach(&sb, NULL)); } if (opts->credit_people) @@ -407,7 +409,8 @@ static void shortlog(const char *name, } static void fmt_merge_msg_title(struct strbuf *out, - const char *current_branch) { + const char *current_branch) +{ int i = 0; char *sep = ""; @@ -559,17 +562,17 @@ static void find_merge_parents(struct merge_parents *result, * "name" here and we do not want to contaminate its * util field yet. */ - obj = parse_object(oid.hash); + 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->hash); + head_commit = lookup_commit(head); if (head_commit) commit_list_insert(head_commit, &parents); - parents = reduce_heads(parents); + reduce_heads_replace(&parents); while (parents) { struct commit *cmit = pop_commit(&parents); @@ -601,7 +604,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, /* get current branch */ current_branch = current_branch_to_free = - resolve_refdup("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL); + resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL); if (!current_branch) die("No current branch"); if (starts_with(current_branch, "refs/heads/")) @@ -633,7 +636,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, struct commit *head; struct rev_info rev; - head = lookup_commit_or_die(head_oid.hash, "HEAD"); + head = lookup_commit_or_die(&head_oid, "HEAD"); init_revisions(&rev, NULL); rev.commit_format = CMIT_FMT_ONELINE; rev.ignore_merges = 1; diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index eca365bf89..e931be9ce4 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "refs.h" #include "object.h" #include "parse-options.h" @@ -16,25 +17,26 @@ static char const * const for_each_ref_usage[] = { int cmd_for_each_ref(int argc, const char **argv, const char *prefix) { int i; - const char *format = "%(objectname) %(objecttype)\t%(refname)"; struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; - int maxcount = 0, quote_style = 0, icase = 0; + int maxcount = 0, icase = 0; struct ref_array array; struct ref_filter filter; + struct ref_format format = REF_FORMAT_INIT; struct option opts[] = { - OPT_BIT('s', "shell", "e_style, + OPT_BIT('s', "shell", &format.quote_style, N_("quote placeholders suitably for shells"), QUOTE_SHELL), - OPT_BIT('p', "perl", "e_style, + OPT_BIT('p', "perl", &format.quote_style, N_("quote placeholders suitably for perl"), QUOTE_PERL), - OPT_BIT(0 , "python", "e_style, + OPT_BIT(0 , "python", &format.quote_style, N_("quote placeholders suitably for python"), QUOTE_PYTHON), - OPT_BIT(0 , "tcl", "e_style, + OPT_BIT(0 , "tcl", &format.quote_style, N_("quote placeholders suitably for Tcl"), QUOTE_TCL), OPT_GROUP(""), OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")), - OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + 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_CALLBACK(0, "points-at", &filter.points_at, @@ -51,16 +53,20 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) memset(&array, 0, sizeof(array)); memset(&filter, 0, sizeof(filter)); + format.format = "%(objectname) %(objecttype)\t%(refname)"; + + git_config(git_default_config, NULL); + parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0); if (maxcount < 0) { error("invalid --count argument: `%d'", maxcount); usage_with_options(for_each_ref_usage, opts); } - if (HAS_MULTI_BITS(quote_style)) { + if (HAS_MULTI_BITS(format.quote_style)) { error("more than one quoting style?"); usage_with_options(for_each_ref_usage, opts); } - if (verify_ref_format(format)) + if (verify_ref_format(&format)) usage_with_options(for_each_ref_usage, opts); if (!sorting) @@ -68,9 +74,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) sorting->ignore_case = icase; filter.ignore_case = icase; - /* for warn_ambiguous_refs */ - git_config(git_default_config, NULL); - filter.name_patterns = argv; filter.match_as_path = 1; filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); @@ -79,7 +82,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (!maxcount || array.nr < maxcount) maxcount = array.nr; for (i = 0; i < maxcount; i++) - show_ref_array_item(array.items[i], format, quote_style); + show_ref_array_item(array.items[i], &format); ref_array_clear(&array); return 0; } diff --git a/builtin/fsck.c b/builtin/fsck.c index b5e13a4556..ef78c6c00c 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "commit.h" #include "tree.h" #include "blob.h" @@ -14,10 +15,13 @@ #include "progress.h" #include "streaming.h" #include "decorate.h" +#include "packfile.h" #define REACHABLE 0x0001 #define SEEN 0x0002 #define HAS_OBJ 0x0004 +/* This flag is set if something points to this object. */ +#define USED 0x0008 static int show_root; static int show_tags; @@ -66,7 +70,7 @@ static const char *printable_type(struct object *obj) object_as_type(obj, type, 0); } - ret = typename(obj->type); + ret = type_name(obj->type); if (!ret) ret = "unknown"; @@ -133,7 +137,7 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt printf("broken link from %7s %s\n", printable_type(parent), describe_object(parent)); printf("broken link from %7s %s\n", - (type == OBJ_ANY ? "unknown" : typename(type)), "unknown"); + (type == OBJ_ANY ? "unknown" : type_name(type)), "unknown"); errors_found |= ERROR_REACHABLE; return 1; } @@ -145,6 +149,15 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (obj->flags & REACHABLE) return 0; obj->flags |= REACHABLE; + + if (is_promisor_object(&obj->oid)) + /* + * Further recursion does not need to be performed on this + * object since it is a promisor object (so it does not need to + * be added to "pending"). + */ + return 0; + if (!(obj->flags & HAS_OBJ)) { if (parent && !has_object_file(&obj->oid)) { printf("broken link from %7s %s\n", @@ -167,17 +180,12 @@ static void mark_object_reachable(struct object *obj) static int traverse_one_object(struct object *obj) { - int result; - struct tree *tree = NULL; + int result = fsck_walk(obj, obj, &fsck_walk_options); if (obj->type == OBJ_TREE) { - tree = (struct tree *)obj; - if (parse_tree(tree) < 0) - return 1; /* error already displayed */ - } - result = fsck_walk(obj, obj, &fsck_walk_options); - if (tree) + struct tree *tree = (struct tree *)obj; free_tree_buffer(tree); + } return result; } @@ -187,14 +195,9 @@ static int traverse_reachable(void) unsigned int nr = 0; int result = 0; if (show_progress) - progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2); + progress = start_delayed_progress(_("Checking connectivity"), 0); while (pending.nr) { - struct object_array_entry *entry; - struct object *obj; - - entry = pending.objects + --pending.nr; - obj = entry->item; - result |= traverse_one_object(obj); + result |= traverse_one_object(object_array_pop(&pending)); display_progress(progress, ++nr); } stop_progress(&progress); @@ -205,7 +208,7 @@ static int mark_used(struct object *obj, int type, void *data, struct fsck_optio { if (!obj) return 1; - obj->used = 1; + obj->flags |= USED; return 0; } @@ -220,6 +223,8 @@ static void check_reachable_object(struct object *obj) * do a full fsck */ if (!(obj->flags & HAS_OBJ)) { + if (is_promisor_object(&obj->oid)) + return; if (has_sha1_pack(obj->oid.hash)) return; /* it is in pack - forget about it */ printf("missing %s %s\n", printable_type(obj), @@ -254,7 +259,7 @@ static void check_unreachable_object(struct object *obj) } /* - * "!used" means that nothing at all points to it, including + * "!USED" means that nothing at all points to it, including * other unreachable objects. In other words, it's the "tip" * of some set of unreachable objects, usually a commit that * got dropped. @@ -265,7 +270,7 @@ static void check_unreachable_object(struct object *obj) * deleted a branch by mistake, this is a prime candidate to * start looking at, for example. */ - if (!obj->used) { + if (!(obj->flags & USED)) { if (show_dangling) printf("dangling %s %s\n", printable_type(obj), describe_object(obj)); @@ -280,8 +285,7 @@ static void check_unreachable_object(struct object *obj) free(filename); return; } - if (!(f = fopen(filename, "w"))) - die_errno("Could not open '%s'", filename); + 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); @@ -335,6 +339,8 @@ static void check_connectivity(void) static int fsck_obj(struct object *obj) { + int err; + if (obj->flags & SEEN) return 0; obj->flags |= SEEN; @@ -345,20 +351,13 @@ static int fsck_obj(struct object *obj) if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, "broken links"); - if (fsck_object(obj, NULL, 0, &fsck_obj_options)) - return -1; - - if (obj->type == OBJ_TREE) { - struct tree *item = (struct tree *) obj; - - free_tree_buffer(item); - } + err = fsck_object(obj, NULL, 0, &fsck_obj_options); + if (err) + goto out; if (obj->type == OBJ_COMMIT) { struct commit *commit = (struct commit *) obj; - free_commit_buffer(commit); - if (!commit->parents && show_root) printf("root %s\n", describe_object(&commit->object)); } @@ -374,10 +373,15 @@ static int fsck_obj(struct object *obj) } } - return 0; +out: + if (obj->type == OBJ_TREE) + free_tree_buffer((struct tree *)obj); + if (obj->type == OBJ_COMMIT) + free_commit_buffer((struct commit *)obj); + return err; } -static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, +static int fsck_obj_buffer(const struct object_id *oid, enum object_type type, unsigned long size, void *buffer, int *eaten) { /* @@ -385,19 +389,20 @@ static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, * verify_packfile(), data_valid variable for details. */ struct object *obj; - obj = parse_object_buffer(sha1, type, size, buffer, eaten); + obj = parse_object_buffer(oid, type, size, buffer, eaten); if (!obj) { errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", sha1_to_hex(sha1)); + return error("%s: object corrupt or missing", oid_to_hex(oid)); } - obj->flags = HAS_OBJ; + obj->flags &= ~(REACHABLE | SEEN); + obj->flags |= HAS_OBJ; return fsck_obj(obj); } static int default_refs; static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, - unsigned long timestamp) + timestamp_t timestamp) { struct object *obj; @@ -407,10 +412,10 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, if (timestamp && name_objects) add_decoration(fsck_walk_options.object_names, obj, - xstrfmt("%s@{%ld}", refname, timestamp)); - obj->used = 1; + xstrfmt("%s@{%"PRItime"}", refname, timestamp)); + obj->flags |= USED; mark_object_reachable(obj); - } else { + } else if (!is_promisor_object(oid)) { error("%s: invalid reflog entry %s", refname, oid_to_hex(oid)); errors_found |= ERROR_REACHABLE; } @@ -418,7 +423,7 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, } static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, unsigned long timestamp, int tz, + const char *email, timestamp_t timestamp, int tz, const char *message, void *cb_data) { const char *refname = cb_data; @@ -444,8 +449,16 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, { struct object *obj; - obj = parse_object(oid->hash); + obj = parse_object(oid); if (!obj) { + if (is_promisor_object(oid)) { + /* + * Increment default_refs anyway, because this is a + * valid ref. + */ + default_refs++; + return 0; + } error("%s: invalid sha1 pointer %s", refname, oid_to_hex(oid)); errors_found |= ERROR_REACHABLE; /* We'll continue with the rest despite the error.. */ @@ -456,7 +469,7 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, errors_found |= ERROR_REFS; } default_refs++; - obj->used = 1; + obj->flags |= USED; if (name_objects) add_decoration(fsck_walk_options.object_names, obj, xstrdup(refname)); @@ -506,7 +519,7 @@ static struct object *parse_loose_object(const struct object_id *oid, if (!contents && type != OBJ_BLOB) die("BUG: read_loose_object streamed a non-blob"); - obj = parse_object_buffer(oid->hash, type, size, contents, &eaten); + obj = parse_object_buffer(oid, type, size, contents, &eaten); if (!eaten) free(contents); @@ -524,7 +537,8 @@ static int fsck_loose(const struct object_id *oid, const char *path, void *data) return 0; /* keep checking other objects */ } - obj->flags = HAS_OBJ; + obj->flags &= ~(REACHABLE | SEEN); + obj->flags |= HAS_OBJ; if (fsck_obj(obj)) errors_found |= ERROR_OBJECT; return 0; @@ -537,7 +551,7 @@ static int fsck_cruft(const char *basename, const char *path, void *data) return 0; } -static int fsck_subdir(int nr, const char *path, void *progress) +static int fsck_subdir(unsigned int nr, const char *path, void *progress) { display_progress(progress, nr + 1); return 0; @@ -566,7 +580,7 @@ static int fsck_head_link(void) if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, NULL); + head_points_at = resolve_ref_unsafe("HEAD", 0, &head_oid, NULL); if (!head_points_at) { errors_found |= ERROR_REFS; return error("Invalid HEAD"); @@ -599,14 +613,14 @@ static int fsck_cache_tree(struct cache_tree *it) fprintf(stderr, "Checking cache tree\n"); if (0 <= it->entry_count) { - struct object *obj = parse_object(it->sha1); + struct object *obj = parse_object(&it->oid); if (!obj) { error("%s: invalid sha1 pointer in cache-tree", - sha1_to_hex(it->sha1)); + oid_to_hex(&it->oid)); errors_found |= ERROR_REFS; return 1; } - obj->used = 1; + obj->flags |= USED; if (name_objects) add_decoration(fsck_walk_options.object_names, obj, xstrdup(":")); @@ -667,9 +681,12 @@ static struct option fsck_opts[] = { int cmd_fsck(int argc, const char **argv, const char *prefix) { - int i, heads; + int i; struct alternate_object_database *alt; + /* fsck knows how to handle missing promisor objects */ + fetch_if_missing = 0; + errors_found = 0; check_replace_refs = 0; @@ -735,25 +752,25 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } } - heads = 0; for (i = 0; i < argc; i++) { const char *arg = argv[i]; - unsigned char sha1[20]; - if (!get_sha1(arg, sha1)) { - struct object *obj = lookup_object(sha1); + struct object_id oid; + if (!get_oid(arg, &oid)) { + struct object *obj = lookup_object(oid.hash); if (!obj || !(obj->flags & HAS_OBJ)) { - error("%s: object missing", sha1_to_hex(sha1)); + if (is_promisor_object(&oid)) + continue; + error("%s: object missing", oid_to_hex(&oid)); errors_found |= ERROR_OBJECT; continue; } - obj->used = 1; + obj->flags |= USED; if (name_objects) add_decoration(fsck_walk_options.object_names, obj, xstrdup(arg)); mark_object_reachable(obj); - heads++; continue; } error("invalid parameter: expected sha1, got '%s'", arg); @@ -772,6 +789,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (keep_cache_objects) { verify_index_checksum = 1; + verify_ce_order = 1; read_cache(); for (i = 0; i < active_nr; i++) { unsigned int mode; @@ -781,11 +799,11 @@ 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.hash); + blob = lookup_blob(&active_cache[i]->oid); if (!blob) continue; obj = &blob->object; - obj->used = 1; + obj->flags |= USED; if (name_objects) add_decoration(fsck_walk_options.object_names, obj, diff --git a/builtin/gc.c b/builtin/gc.c index 2d2027d8b0..f51e5a6500 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -11,6 +11,7 @@ */ #include "builtin.h" +#include "config.h" #include "tempfile.h" #include "lockfile.h" #include "parse-options.h" @@ -18,6 +19,7 @@ #include "sigchain.h" #include "argv-array.h" #include "commit.h" +#include "packfile.h" #define FAILED_RUN "failed to run %s" @@ -33,7 +35,7 @@ static int aggressive_window = 250; static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; static int detach_auto = 1; -static unsigned long gc_log_expire_time; +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"; @@ -45,7 +47,7 @@ static struct argv_array prune = ARGV_ARRAY_INIT; static struct argv_array prune_worktrees = ARGV_ARRAY_INIT; static struct argv_array rerere = ARGV_ARRAY_INIT; -static struct tempfile pidfile; +static struct tempfile *pidfile; static struct lock_file log_lock; static struct string_list pack_garbage = STRING_LIST_INIT_DUP; @@ -76,7 +78,7 @@ static void process_log_file(void) */ int saved_errno = errno; fprintf(stderr, _("Failed to fstat %s: %s"), - get_tempfile_path(&log_lock.tempfile), + get_tempfile_path(log_lock.tempfile), strerror(saved_errno)); fflush(stderr); commit_lock_file(&log_lock); @@ -148,7 +150,7 @@ static int too_many_loose_objects(void) if (!dir) return 0; - auto_threshold = (gc_auto_threshold + 255) / 256; + 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') @@ -240,7 +242,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) int fd; char *pidfile_path; - if (is_tempfile_active(&pidfile)) + if (is_tempfile_active(pidfile)) /* already locked */ return NULL; @@ -256,7 +258,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) int should_exit; if (!scan_fmt) - scan_fmt = xstrfmt("%s %%%dc", "%"SCNuMAX, HOST_NAME_MAX); + scan_fmt = xstrfmt("%s %%%ds", "%"SCNuMAX, HOST_NAME_MAX); fp = fopen(pidfile_path, "r"); memset(locking_host, 0, sizeof(locking_host)); should_exit = @@ -291,7 +293,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); commit_lock_file(&lock); - register_tempfile(&pidfile, pidfile_path); + pidfile = register_tempfile(pidfile_path); free(pidfile_path); return NULL; } @@ -358,8 +360,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix) N_("prune unreferenced objects"), PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")), - OPT_BOOL(0, "auto", &auto_gc, N_("enable auto-gc mode")), - OPT_BOOL(0, "force", &force, N_("force running gc even if there may be another gc running")), + OPT_BOOL_F(0, "auto", &auto_gc, N_("enable auto-gc mode"), + PARSE_OPT_NOCOMPLETE), + OPT_BOOL_F(0, "force", &force, + N_("force running gc even if there may be another gc running"), + PARSE_OPT_NOCOMPLETE), OPT_END() }; @@ -456,6 +461,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix) argv_array_push(&prune, prune_expire); if (quiet) argv_array_push(&prune, "--no-progress"); + if (repository_format_partial_clone) + argv_array_push(&prune, + "--exclude-promisor-objects"); if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) return error(FAILED_RUN, prune.argv[0]); } diff --git a/builtin/get-tar-commit-id.c b/builtin/get-tar-commit-id.c index e21c5416cd..2706fcfaf2 100644 --- a/builtin/get-tar-commit-id.c +++ b/builtin/get-tar-commit-id.c @@ -26,15 +26,16 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) usage(builtin_get_tar_commit_id_usage); n = read_in_full(0, buffer, HEADERSIZE); - if (n < HEADERSIZE) - die("git get-tar-commit-id: read error"); + if (n < 0) + die_errno("git get-tar-commit-id: read error"); + if (n != HEADERSIZE) + 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)) return 1; - n = write_in_full(1, comment, 41); - if (n < 41) + if (write_in_full(1, comment, 41) < 0) die_errno("git get-tar-commit-id: write error"); return 0; diff --git a/builtin/grep.c b/builtin/grep.c index 254c1c7849..789a89133a 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -4,6 +4,8 @@ * Copyright (c) 2006 Junio C Hamano */ #include "cache.h" +#include "repository.h" +#include "config.h" #include "blob.h" #include "tree.h" #include "commit.h" @@ -26,13 +28,7 @@ static char const * const grep_usage[] = { NULL }; -static const char *super_prefix; static int recurse_submodules; -static struct argv_array submodule_options = ARGV_ARRAY_INIT; -static const char *parent_basename; - -static int grep_submodule_launch(struct grep_opt *opt, - const struct grep_source *gs); #define GREP_NUM_THREADS_DEFAULT 8 static int num_threads; @@ -73,14 +69,14 @@ static pthread_mutex_t grep_mutex; static inline void grep_lock(void) { - if (num_threads) - pthread_mutex_lock(&grep_mutex); + assert(num_threads); + pthread_mutex_lock(&grep_mutex); } static inline void grep_unlock(void) { - if (num_threads) - pthread_mutex_unlock(&grep_mutex); + assert(num_threads); + pthread_mutex_unlock(&grep_mutex); } /* Signalled when a new work_item is added to todo. */ @@ -96,8 +92,7 @@ static pthread_cond_t cond_result; static int skip_first_line; -static void add_work(struct grep_opt *opt, enum grep_source_type type, - const char *name, const char *path, const void *id) +static void add_work(struct grep_opt *opt, const struct grep_source *gs) { grep_lock(); @@ -105,7 +100,7 @@ static void add_work(struct grep_opt *opt, enum grep_source_type type, pthread_cond_wait(&cond_write, &grep_mutex); } - grep_source_init(&todo[todo_end].source, type, name, path, id); + todo[todo_end].source = *gs; if (opt->binary != GREP_BINARY_TEXT) grep_source_load_driver(&todo[todo_end].source); todo[todo_end].done = 0; @@ -184,10 +179,7 @@ static void *run(void *arg) break; opt->output_priv = w; - if (w->source.type == GREP_SOURCE_SUBMODULE) - hit |= grep_submodule_launch(opt, &w->source); - else - hit |= grep_source(opt, &w->source); + hit |= grep_source(opt, &w->source); grep_source_clear_data(&w->source); work_done(w); } @@ -224,7 +216,8 @@ static void start_threads(struct grep_opt *opt) int err; struct grep_opt *o = grep_opt_dup(opt); o->output = strbuf_out; - o->debug = 0; + if (i) + o->debug = 0; compile_grep_patterns(o); err = pthread_create(&threads[i], NULL, run, o); @@ -289,8 +282,22 @@ 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) { + /* + * TRANSLATORS: %s is the configuration + * variable for tweaking threads, currently + * grep.threads + */ + warning(_("no threads support, ignoring %s"), var); + num_threads = 0; + } +#endif } + if (!strcmp(var, "submodule.recurse")) + recurse_submodules = git_config_bool(var, value); + return st; } @@ -309,35 +316,31 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid, const char *path) { struct strbuf pathbuf = STRBUF_INIT; + struct grep_source gs; - if (super_prefix) { - strbuf_add(&pathbuf, filename, tree_name_len); - strbuf_addstr(&pathbuf, super_prefix); - strbuf_addstr(&pathbuf, filename + tree_name_len); + 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); } - if (opt->relative && opt->prefix_length) { - char *name = strbuf_detach(&pathbuf, NULL); - quote_path_relative(name + tree_name_len, opt->prefix, &pathbuf); - strbuf_insert(&pathbuf, 0, name, tree_name_len); - free(name); - } + grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid); + strbuf_release(&pathbuf); #ifndef NO_PTHREADS if (num_threads) { - add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, oid); - strbuf_release(&pathbuf); + /* + * 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 { - struct grep_source gs; int hit; - grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, oid); - strbuf_release(&pathbuf); hit = grep_source(opt, &gs); grep_source_clear(&gs); @@ -348,30 +351,29 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid, static int grep_file(struct grep_opt *opt, const char *filename) { struct strbuf buf = STRBUF_INIT; + struct grep_source gs; - if (super_prefix) - strbuf_addstr(&buf, super_prefix); - strbuf_addstr(&buf, filename); + if (opt->relative && opt->prefix_length) + quote_path_relative(filename, opt->prefix, &buf); + else + strbuf_addstr(&buf, filename); - if (opt->relative && opt->prefix_length) { - char *name = strbuf_detach(&buf, NULL); - quote_path_relative(name, opt->prefix, &buf); - free(name); - } + grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename); + strbuf_release(&buf); #ifndef NO_PTHREADS if (num_threads) { - add_work(opt, GREP_SOURCE_FILE, buf.buf, filename, filename); - strbuf_release(&buf); + /* + * 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 { - struct grep_source gs; int hit; - grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename); - strbuf_release(&buf); hit = grep_source(opt, &gs); grep_source_clear(&gs); @@ -404,282 +406,91 @@ static void run_pager(struct grep_opt *opt, const char *prefix) exit(status); } -static void compile_submodule_options(const struct grep_opt *opt, - const char **argv, - int cached, int untracked, - int opt_exclude, int use_index, - int pattern_type_arg) -{ - struct grep_pat *pattern; - - if (recurse_submodules) - argv_array_push(&submodule_options, "--recurse-submodules"); - - if (cached) - argv_array_push(&submodule_options, "--cached"); - if (!use_index) - argv_array_push(&submodule_options, "--no-index"); - if (untracked) - argv_array_push(&submodule_options, "--untracked"); - if (opt_exclude > 0) - argv_array_push(&submodule_options, "--exclude-standard"); - - if (opt->invert) - argv_array_push(&submodule_options, "-v"); - if (opt->ignore_case) - argv_array_push(&submodule_options, "-i"); - if (opt->word_regexp) - argv_array_push(&submodule_options, "-w"); - switch (opt->binary) { - case GREP_BINARY_NOMATCH: - argv_array_push(&submodule_options, "-I"); - break; - case GREP_BINARY_TEXT: - argv_array_push(&submodule_options, "-a"); - break; - default: - break; - } - if (opt->allow_textconv) - argv_array_push(&submodule_options, "--textconv"); - if (opt->max_depth != -1) - argv_array_pushf(&submodule_options, "--max-depth=%d", - opt->max_depth); - if (opt->linenum) - argv_array_push(&submodule_options, "-n"); - if (!opt->pathname) - argv_array_push(&submodule_options, "-h"); - if (!opt->relative) - argv_array_push(&submodule_options, "--full-name"); - if (opt->name_only) - argv_array_push(&submodule_options, "-l"); - if (opt->unmatch_name_only) - argv_array_push(&submodule_options, "-L"); - if (opt->null_following_name) - argv_array_push(&submodule_options, "-z"); - if (opt->count) - argv_array_push(&submodule_options, "-c"); - if (opt->file_break) - argv_array_push(&submodule_options, "--break"); - if (opt->heading) - argv_array_push(&submodule_options, "--heading"); - if (opt->pre_context) - argv_array_pushf(&submodule_options, "--before-context=%d", - opt->pre_context); - if (opt->post_context) - argv_array_pushf(&submodule_options, "--after-context=%d", - opt->post_context); - if (opt->funcname) - argv_array_push(&submodule_options, "-p"); - if (opt->funcbody) - argv_array_push(&submodule_options, "-W"); - if (opt->all_match) - argv_array_push(&submodule_options, "--all-match"); - if (opt->debug) - argv_array_push(&submodule_options, "--debug"); - if (opt->status_only) - argv_array_push(&submodule_options, "-q"); - - switch (pattern_type_arg) { - case GREP_PATTERN_TYPE_BRE: - argv_array_push(&submodule_options, "-G"); - break; - case GREP_PATTERN_TYPE_ERE: - argv_array_push(&submodule_options, "-E"); - break; - case GREP_PATTERN_TYPE_FIXED: - argv_array_push(&submodule_options, "-F"); - break; - case GREP_PATTERN_TYPE_PCRE: - argv_array_push(&submodule_options, "-P"); - break; - case GREP_PATTERN_TYPE_UNSPECIFIED: - break; - } - - for (pattern = opt->pattern_list; pattern != NULL; - pattern = pattern->next) { - switch (pattern->token) { - case GREP_PATTERN: - argv_array_pushf(&submodule_options, "-e%s", - pattern->pattern); - break; - case GREP_AND: - case GREP_OPEN_PAREN: - case GREP_CLOSE_PAREN: - case GREP_NOT: - case GREP_OR: - argv_array_push(&submodule_options, pattern->pattern); - break; - /* BODY and HEAD are not used by git-grep */ - case GREP_PATTERN_BODY: - case GREP_PATTERN_HEAD: - break; - } - } - - /* - * Limit number of threads for child process to use. - * This is to prevent potential fork-bomb behavior of git-grep as each - * submodule process has its own thread pool. - */ - argv_array_pushf(&submodule_options, "--threads=%d", - (num_threads + 1) / 2); - - /* Add Pathspecs */ - argv_array_push(&submodule_options, "--"); - for (; *argv; argv++) - argv_array_push(&submodule_options, *argv); -} +static int grep_cache(struct grep_opt *opt, struct repository *repo, + 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); -/* - * Launch child process to grep contents of a submodule - */ -static int grep_submodule_launch(struct grep_opt *opt, - const struct grep_source *gs) +static int grep_submodule(struct grep_opt *opt, struct repository *superproject, + const struct pathspec *pathspec, + const struct object_id *oid, + const char *filename, const char *path) { - struct child_process cp = CHILD_PROCESS_INIT; - int status, i; - const char *end_of_base; - const char *name; - struct strbuf child_output = STRBUF_INIT; - - end_of_base = strchr(gs->name, ':'); - if (gs->identifier && end_of_base) - name = end_of_base + 1; - else - name = gs->name; + struct repository submodule; + int hit; - prepare_submodule_repo_env(&cp.env_array); - argv_array_push(&cp.env_array, GIT_DIR_ENVIRONMENT); + if (!is_submodule_active(superproject, path)) + return 0; - if (opt->relative && opt->prefix_length) - argv_array_pushf(&cp.env_array, "%s=%s", - GIT_TOPLEVEL_PREFIX_ENVIRONMENT, - opt->prefix); + if (repo_submodule_init(&submodule, superproject, path)) + return 0; - /* Add super prefix */ - argv_array_pushf(&cp.args, "--super-prefix=%s%s/", - super_prefix ? super_prefix : "", - name); - argv_array_push(&cp.args, "grep"); + repo_read_gitmodules(&submodule); /* - * Add basename of parent project - * When performing grep on a tree object the filename is prefixed - * with the object's name: 'tree-name:filename'. In order to - * provide uniformity of output we want to pass the name of the - * parent project's object name to the submodule so the submodule can - * prefix its output with the parent's name and not its own SHA1. + * NEEDSWORK: This adds the submodule's object directory to the list of + * alternates for the single in-memory object store. This has some bad + * consequences for memory (processed objects will never be freed) and + * performance (this increases the number of pack files git has to pay + * attention to, to the sum of the number of pack files in all the + * repositories processed so far). This can be removed once the object + * store is no longer global and instead is a member of the repository + * object. */ - if (gs->identifier && end_of_base) - argv_array_pushf(&cp.args, "--parent-basename=%.*s", - (int) (end_of_base - gs->name), - gs->name); + grep_read_lock(); + add_to_alternates_memory(submodule.objectdir); + grep_read_unlock(); - /* Add options */ - for (i = 0; i < submodule_options.argc; i++) { - /* - * If there is a tree identifier for the submodule, add the - * rev after adding the submodule options but before the - * pathspecs. To do this we listen for the '--' and insert the - * sha1 before pushing the '--' onto the child process argv - * array. - */ - if (gs->identifier && - !strcmp("--", submodule_options.argv[i])) { - argv_array_push(&cp.args, sha1_to_hex(gs->identifier)); - } + if (oid) { + struct object *object; + struct tree_desc tree; + void *data; + unsigned long size; + struct strbuf base = STRBUF_INIT; - argv_array_push(&cp.args, submodule_options.argv[i]); - } + object = parse_object_or_die(oid, oid_to_hex(oid)); - cp.git_cmd = 1; - cp.dir = gs->path; + grep_read_lock(); + data = read_object_with_reference(object->oid.hash, tree_type, + &size, NULL); + grep_read_unlock(); - /* - * Capture output to output buffer and check the return code from the - * child process. A '0' indicates a hit, a '1' indicates no hit and - * anything else is an error. - */ - status = capture_command(&cp, &child_output, 0); - if (status && (status != 1)) { - /* flush the buffer */ - write_or_die(1, child_output.buf, child_output.len); - die("process for submodule '%s' failed with exit code: %d", - gs->name, status); - } + if (!data) + die(_("unable to read tree (%s)"), oid_to_hex(&object->oid)); - opt->output(opt, child_output.buf, child_output.len); - strbuf_release(&child_output); - /* invert the return code to make a hit equal to 1 */ - return !status; -} + strbuf_addstr(&base, filename); + strbuf_addch(&base, '/'); -/* - * Prep grep structures for a submodule grep - * sha1: the sha1 of the submodule or NULL if using the working tree - * filename: name of the submodule including tree name of parent - * path: location of the submodule - */ -static int grep_submodule(struct grep_opt *opt, const unsigned char *sha1, - const char *filename, const char *path) -{ - if (!is_submodule_initialized(path)) - return 0; - if (!is_submodule_populated_gently(path, NULL)) { - /* - * If searching history, check for the presense of the - * submodule's gitdir before skipping the submodule. - */ - if (sha1) { - const struct submodule *sub = - submodule_from_path(null_sha1, path); - if (sub) - path = git_path("modules/%s", sub->name); - - if (!(is_directory(path) && is_git_directory(path))) - return 0; - } else { - return 0; - } + init_tree_desc(&tree, data, size); + hit = grep_tree(opt, pathspec, &tree, &base, base.len, + object->type == OBJ_COMMIT, &submodule); + strbuf_release(&base); + free(data); + } else { + hit = grep_cache(opt, &submodule, pathspec, 1); } -#ifndef NO_PTHREADS - if (num_threads) { - add_work(opt, GREP_SOURCE_SUBMODULE, filename, path, sha1); - return 0; - } else -#endif - { - struct grep_source gs; - int hit; - - grep_source_init(&gs, GREP_SOURCE_SUBMODULE, - filename, path, sha1); - hit = grep_submodule_launch(opt, &gs); - - grep_source_clear(&gs); - return hit; - } + repo_clear(&submodule); + return hit; } -static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, - int cached) +static int grep_cache(struct grep_opt *opt, struct repository *repo, + const struct pathspec *pathspec, int cached) { int hit = 0; int nr; struct strbuf name = STRBUF_INIT; int name_base_len = 0; - if (super_prefix) { - name_base_len = strlen(super_prefix); - strbuf_addstr(&name, super_prefix); + if (repo->submodule_prefix) { + name_base_len = strlen(repo->submodule_prefix); + strbuf_addstr(&name, repo->submodule_prefix); } - read_cache(); + repo_read_index(repo); - for (nr = 0; nr < active_nr; nr++) { - const struct cache_entry *ce = active_cache[nr]; + for (nr = 0; nr < repo->index->cache_nr; nr++) { + const struct cache_entry *ce = repo->index->cache[nr]; strbuf_setlen(&name, name_base_len); strbuf_addstr(&name, ce->name); @@ -696,14 +507,14 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, ce_skip_worktree(ce)) { if (ce_stage(ce) || ce_intent_to_add(ce)) continue; - hit |= grep_oid(opt, &ce->oid, ce->name, - 0, ce->name); + hit |= grep_oid(opt, &ce->oid, name.buf, + 0, name.buf); } else { - hit |= grep_file(opt, ce->name); + 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, NULL, ce->name, ce->name); + hit |= grep_submodule(opt, repo, pathspec, NULL, ce->name, ce->name); } else { continue; } @@ -711,8 +522,8 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, if (ce_stage(ce)) { do { nr++; - } while (nr < active_nr && - !strcmp(ce->name, active_cache[nr]->name)); + } while (nr < repo->index->cache_nr && + !strcmp(ce->name, repo->index->cache[nr]->name)); nr--; /* compensate for loop control */ } if (hit && opt->status_only) @@ -725,7 +536,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, 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) + int check_attr, struct repository *repo) { int hit = 0; enum interesting match = entry_not_interesting; @@ -733,8 +544,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, int old_baselen = base->len; struct strbuf name = STRBUF_INIT; int name_base_len = 0; - if (super_prefix) { - strbuf_addstr(&name, super_prefix); + if (repo->submodule_prefix) { + strbuf_addstr(&name, repo->submodule_prefix); name_base_len = name.len; } @@ -772,11 +583,11 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); hit |= grep_tree(opt, pathspec, &sub, base, tn_len, - check_attr); + check_attr, repo); free(data); } else if (recurse_submodules && S_ISGITLINK(entry.mode)) { - hit |= grep_submodule(opt, entry.oid->hash, base->buf, - base->buf + tn_len); + hit |= grep_submodule(opt, repo, pathspec, entry.oid, + base->buf, base->buf + tn_len); } strbuf_setlen(base, old_baselen); @@ -790,7 +601,8 @@ 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 object *obj, const char *name, const char *path, + struct repository *repo) { if (obj->type == OBJ_BLOB) return grep_oid(opt, &obj->oid, name, 0, path); @@ -809,10 +621,6 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); - /* Use parent's name as base when recursing submodules */ - if (recurse_submodules && parent_basename) - name = parent_basename; - len = name ? strlen(name) : 0; strbuf_init(&base, PATH_MAX + len + 1); if (len) { @@ -821,15 +629,16 @@ 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); + obj->type == OBJ_COMMIT, repo); strbuf_release(&base); free(data); return hit; } - die(_("unable to grep from object of type %s"), typename(obj->type)); + die(_("unable to grep from object of type %s"), type_name(obj->type)); } static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, + struct repository *repo, const struct object_array *list) { unsigned int i; @@ -843,9 +652,10 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, /* load the gitmodules file for this rev */ if (recurse_submodules) { submodule_free(); - gitmodules_config_sha1(real_obj->oid.hash); + gitmodules_config_oid(&real_obj->oid); } - if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) { + if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path, + repo)) { hit = 1; if (opt->status_only) break; @@ -866,7 +676,7 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, if (exc_std) setup_standard_excludes(&dir); - fill_directory(&dir, pathspec); + fill_directory(&dir, &the_index, pathspec); for (i = 0; i < dir.nr; i++) { if (!dir_path_match(dir.entries[i], pathspec, 0, NULL)) continue; @@ -986,9 +796,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("ignore files specified via '.gitignore'"), 1), OPT_BOOL(0, "recurse-submodules", &recurse_submodules, N_("recursively search in each submodule")), - OPT_STRING(0, "parent-basename", &parent_basename, - N_("basename"), - N_("prepend parent project's basename to output")), OPT_GROUP(""), OPT_BOOL('v', "invert-match", &opt.invert, N_("show non-matching lines")), @@ -1032,8 +839,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOL('L', "files-without-match", &opt.unmatch_name_only, N_("show only the names of files without match")), - OPT_BOOL('z', "null", &opt.null_following_name, - N_("print NUL after filenames")), + OPT_BOOL_F('z', "null", &opt.null_following_name, + N_("print NUL after filenames"), + PARSE_OPT_NOCOMPLETE), OPT_BOOL('c', "count", &opt.count, N_("show the number of matches instead of matching lines")), OPT__COLOR(&opt.color, N_("highlight matches")), @@ -1084,16 +892,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_GROUP(""), { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, N_("pager"), N_("show matching files in the pager"), - PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager }, - OPT_BOOL(0, "ext-grep", &external_grep_allowed__ignored, - N_("allow calling of grep(1) (ignored by this build)")), + PARSE_OPT_OPTARG | PARSE_OPT_NOCOMPLETE, + NULL, (intptr_t)default_pager }, + OPT_BOOL_F(0, "ext-grep", &external_grep_allowed__ignored, + N_("allow calling of grep(1) (ignored by this build)"), + PARSE_OPT_NOCOMPLETE), OPT_END() }; init_grep_defaults(); git_config(grep_cmd_config, NULL); grep_init(&opt, prefix); - super_prefix = get_super_prefix(); /* * If there is no -- then the paths must exist in the working @@ -1151,10 +960,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!opt.pattern_list) die(_("no pattern given.")); - if (!opt.fixed && opt.ignore_case) - opt.regflags |= REG_ICASE; - - compile_grep_patterns(&opt); /* * We have to find "--" in a separate pass, because its presence @@ -1190,14 +995,14 @@ int cmd_grep(int argc, const char **argv, const char *prefix) break; } - if (get_sha1_with_context(arg, GET_SHA1_RECORD_PATH, - oid.hash, &oc)) { + if (get_oid_with_context(arg, GET_OID_RECORD_PATH, + &oid, &oc)) { if (seen_dashdash) die(_("unable to resolve revision: %s"), arg); break; } - object = parse_object_or_die(oid.hash, arg); + object = parse_object_or_die(&oid, arg); if (!seen_dashdash) verify_non_filename(prefix, arg); add_object_array_with_path(object, arg, &list, oc.mode, oc.path); @@ -1220,6 +1025,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) prefix, argv + i); pathspec.max_depth = opt.max_depth; pathspec.recursive = 1; + pathspec.recurse_submodules = !!recurse_submodules; #ifndef NO_PTHREADS if (list.nr || cached || show_in_pager) @@ -1228,10 +1034,23 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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) + warning(_("no threads support, ignoring --threads")); num_threads = 0; #endif + if (!num_threads) + /* + * The compiled patterns on the main path are only + * used when not using threading. Otherwise + * start_threads() below 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) @@ -1242,13 +1061,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } #endif - if (recurse_submodules) { - gitmodules_config(); - compile_submodule_options(&opt, argv + i, cached, untracked, - opt_exclude, use_index, - pattern_type_arg); - } - if (show_in_pager && (cached || list.nr)) die(_("--open-files-in-pager only works on the worktree")); @@ -1290,11 +1102,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!cached) setup_work_tree(); - hit = grep_cache(&opt, &pathspec, cached); + hit = grep_cache(&opt, the_repository, &pathspec, cached); } else { if (cached) die(_("both --cached and trees are given.")); - hit = grep_objects(&opt, &pathspec, &list); + + hit = grep_objects(&opt, &pathspec, the_repository, &list); } if (num_threads) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index bbeaf20bcc..526da5c185 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -5,6 +5,7 @@ * Copyright (C) Junio C Hamano, 2005 */ #include "builtin.h" +#include "config.h" #include "blob.h" #include "quote.h" #include "parse-options.h" @@ -15,7 +16,7 @@ * needs to bypass the data conversion performed by, and the type * limitation imposed by, index_fd() and its callees. */ -static int hash_literally(unsigned char *sha1, int fd, const char *type, unsigned flags) +static int hash_literally(struct object_id *oid, int fd, const char *type, unsigned flags) { struct strbuf buf = STRBUF_INIT; int ret; @@ -23,7 +24,8 @@ static int hash_literally(unsigned char *sha1, int fd, const char *type, unsigne if (strbuf_read(&buf, fd, 4096) < 0) ret = -1; else - ret = hash_sha1_file_literally(buf.buf, buf.len, type, sha1, flags); + ret = hash_object_file_literally(buf.buf, buf.len, type, oid, + flags); strbuf_release(&buf); return ret; } @@ -32,16 +34,16 @@ static void hash_fd(int fd, const char *type, const char *path, unsigned flags, int literally) { struct stat st; - unsigned char sha1[20]; + struct object_id oid; if (fstat(fd, &st) < 0 || (literally - ? hash_literally(sha1, fd, type, flags) - : index_fd(sha1, fd, &st, type_from_string(type), path, flags))) + ? hash_literally(&oid, fd, type, flags) + : index_fd(&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); - printf("%s\n", sha1_to_hex(sha1)); + printf("%s\n", oid_to_hex(&oid)); maybe_flush_or_die(stdout, "hash to stdout"); } diff --git a/builtin/help.c b/builtin/help.c index 49f7a07f85..598867cfea 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -2,6 +2,7 @@ * Builtin help command */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "exec_cmd.h" #include "parse-options.h" @@ -130,6 +131,7 @@ static void exec_woman_emacs(const char *path, const char *page) strbuf_addf(&man_page, "(woman \"%s\")", page); execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL); warning_errno(_("failed to exec '%s'"), path); + strbuf_release(&man_page); } } @@ -151,6 +153,7 @@ static void exec_man_konqueror(const char *path, const char *page) strbuf_addf(&man_page, "man:%s(1)", page); execlp(path, filename, "newTab", man_page.buf, (char *)NULL); warning_errno(_("failed to exec '%s'"), path); + strbuf_release(&man_page); } } @@ -168,6 +171,7 @@ static void exec_man_cmd(const char *cmd, const char *page) strbuf_addf(&shell_cmd, "%s %s", cmd, page); execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL); warning(_("failed to exec '%s'"), cmd); + strbuf_release(&shell_cmd); } static void add_man_viewer(const char *name) @@ -190,11 +194,11 @@ static void do_add_man_viewer_info(const char *name, size_t len, const char *value) { - struct man_viewer_info_list *new; - FLEX_ALLOC_MEM(new, name, name, len); - new->info = xstrdup(value); - new->next = man_viewer_info_list; - man_viewer_info_list = new; + struct man_viewer_info_list *new_man_viewer; + FLEX_ALLOC_MEM(new_man_viewer, name, name, len); + new_man_viewer->info = xstrdup(value); + new_man_viewer->next = man_viewer_info_list; + man_viewer_info_list = new_man_viewer; } static int add_man_viewer_path(const char *name, @@ -437,7 +441,7 @@ static const char *check_git_cmd(const char* cmd) alias = alias_lookup(cmd); if (alias) { - printf_ln(_("`git %s' is aliased to `%s'"), cmd, alias); + printf_ln(_("'%s' is aliased to '%s'"), cmd, alias); free(alias); exit(0); } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 4ff567db47..bda84a92ef 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "delta.h" #include "pack.h" #include "csum-file.h" @@ -11,6 +12,7 @@ #include "exec_cmd.h" #include "streaming.h" #include "thread-utils.h" +#include "packfile.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>])"; @@ -47,6 +49,7 @@ struct thread_local { int pack_fd; }; +/* Remember to update object flag allocation in object.h */ #define FLAG_LINK (1u<<20) #define FLAG_CHECKED (1u<<21) @@ -89,7 +92,7 @@ static unsigned int input_offset, input_len; static off_t consumed_bytes; static off_t max_input_size; static unsigned deepest_delta; -static git_SHA_CTX input_ctx; +static git_hash_ctx input_ctx; static uint32_t input_crc32; static int input_fd, output_fd; static const char *curr_pack; @@ -226,7 +229,7 @@ static unsigned check_object(struct object *obj) if (type != obj->type) die(_("object %s: expected type %s, found %s"), oid_to_hex(&obj->oid), - typename(obj->type), typename(type)); + type_name(obj->type), type_name(type)); obj->flags |= FLAG_CHECKED; return 1; } @@ -251,7 +254,7 @@ static void flush(void) if (input_offset) { if (output_fd >= 0) write_or_die(output_fd, input_buffer, input_offset); - git_SHA1_Update(&input_ctx, input_buffer, input_offset); + the_hash_algo->update_fn(&input_ctx, input_buffer, input_offset); memmove(input_buffer, input_buffer + input_offset, input_len); input_offset = 0; } @@ -324,7 +327,7 @@ static const char *open_pack_file(const char *pack_name) output_fd = -1; nothread_data.pack_fd = input_fd; } - git_SHA1_Init(&input_ctx); + the_hash_algo->init_fn(&input_ctx); return pack_name; } @@ -388,8 +391,7 @@ static struct base_data *alloc_base_data(void) static void free_base_data(struct base_data *c) { if (c->data) { - free(c->data); - c->data = NULL; + FREE_AND_NULL(c->data); get_thread_data()->base_cache_used -= c->size; } } @@ -436,22 +438,22 @@ static int is_delta_type(enum object_type type) } static void *unpack_entry_data(off_t offset, unsigned long size, - enum object_type type, unsigned char *sha1) + enum object_type type, struct object_id *oid) { static char fixed_buf[8192]; int status; git_zstream stream; void *buf; - git_SHA_CTX c; + git_hash_ctx c; char hdr[32]; int hdrlen; if (!is_delta_type(type)) { - hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), size) + 1; - git_SHA1_Init(&c); - git_SHA1_Update(&c, hdr, hdrlen); + hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", type_name(type), size) + 1; + the_hash_algo->init_fn(&c); + the_hash_algo->update_fn(&c, hdr, hdrlen); } else - sha1 = NULL; + oid = NULL; if (type == OBJ_BLOB && size > big_file_threshold) buf = fixed_buf; else @@ -468,8 +470,8 @@ static void *unpack_entry_data(off_t offset, unsigned long size, stream.avail_in = input_len; status = git_inflate(&stream, 0); use(input_len - stream.avail_in); - if (sha1) - git_SHA1_Update(&c, last_out, stream.next_out - last_out); + if (oid) + the_hash_algo->update_fn(&c, last_out, stream.next_out - last_out); if (buf == fixed_buf) { stream.next_out = buf; stream.avail_out = sizeof(fixed_buf); @@ -478,15 +480,15 @@ static void *unpack_entry_data(off_t offset, unsigned long size, if (stream.total_out != size || status != Z_STREAM_END) bad_object(offset, _("inflate returned %d"), status); git_inflate_end(&stream); - if (sha1) - git_SHA1_Final(sha1, &c); + if (oid) + the_hash_algo->final_fn(oid->hash, &c); return buf == fixed_buf ? NULL : buf; } static void *unpack_raw_entry(struct object_entry *obj, off_t *ofs_offset, - unsigned char *ref_sha1, - unsigned char *sha1) + struct object_id *ref_oid, + struct object_id *oid) { unsigned char *p; unsigned long size, c; @@ -514,8 +516,8 @@ static void *unpack_raw_entry(struct object_entry *obj, switch (obj->type) { case OBJ_REF_DELTA: - hashcpy(ref_sha1, fill(20)); - use(20); + hashcpy(ref_oid->hash, fill(the_hash_algo->rawsz)); + use(the_hash_algo->rawsz); break; case OBJ_OFS_DELTA: p = fill(1); @@ -545,7 +547,7 @@ static void *unpack_raw_entry(struct object_entry *obj, } obj->hdr_size = consumed_bytes - obj->idx.offset; - data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, sha1); + data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, oid); obj->idx.crc32 = input_crc32; return data; } @@ -605,8 +607,7 @@ static void *unpack_data(struct object_entry *obj, git_inflate_end(&stream); free(inbuf); if (consume) { - free(data); - data = NULL; + FREE_AND_NULL(data); } return data; } @@ -633,7 +634,7 @@ static int find_ofs_delta(const off_t offset, enum object_type type) int first = 0, last = nr_ofs_deltas; while (first < last) { - int next = (first + last) / 2; + int next = first + (last - first) / 2; struct ofs_delta_entry *delta = &ofs_deltas[next]; int cmp; @@ -687,7 +688,7 @@ static int find_ref_delta(const unsigned char *sha1, enum object_type type) int first = 0, last = nr_ref_deltas; while (first < last) { - int next = (first + last) / 2; + int next = first + (last - first) / 2; struct ref_delta_entry *delta = &ref_deltas[next]; int cmp; @@ -747,13 +748,13 @@ static int compare_objects(const unsigned char *buf, unsigned long size, ssize_t len = read_istream(data->st, data->buf, size); if (len == 0) die(_("SHA1 COLLISION FOUND WITH %s !"), - sha1_to_hex(data->entry->idx.sha1)); + oid_to_hex(&data->entry->idx.oid)); if (len < 0) die(_("unable to read %s"), - sha1_to_hex(data->entry->idx.sha1)); + oid_to_hex(&data->entry->idx.oid)); if (memcmp(buf, data->buf, len)) die(_("SHA1 COLLISION FOUND WITH %s !"), - sha1_to_hex(data->entry->idx.sha1)); + oid_to_hex(&data->entry->idx.oid)); size -= len; buf += len; } @@ -771,12 +772,12 @@ static int check_collison(struct object_entry *entry) memset(&data, 0, sizeof(data)); data.entry = entry; - data.st = open_istream(entry->idx.sha1, &type, &size, NULL); + data.st = open_istream(entry->idx.oid.hash, &type, &size, NULL); if (!data.st) return -1; if (size != entry->size || type != entry->type) die(_("SHA1 COLLISION FOUND WITH %s !"), - sha1_to_hex(entry->idx.sha1)); + oid_to_hex(&entry->idx.oid)); unpack_data(entry, compare_objects, &data); close_istream(data.st); free(data.buf); @@ -785,7 +786,7 @@ static int check_collison(struct object_entry *entry) static void sha1_object(const void *data, struct object_entry *obj_entry, unsigned long size, enum object_type type, - const unsigned char *sha1) + const struct object_id *oid) { void *new_data = NULL; int collision_test_needed = 0; @@ -794,7 +795,8 @@ 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(sha1, HAS_SHA1_QUICK); + collision_test_needed = + has_sha1_file_with_flags(oid->hash, OBJECT_INFO_QUICK); read_unlock(); } @@ -809,31 +811,31 @@ 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 = sha1_object_info(sha1, &has_size); + has_type = sha1_object_info(oid->hash, &has_size); if (has_type < 0) - die(_("cannot read existing object info %s"), sha1_to_hex(sha1)); + die(_("cannot read existing object info %s"), oid_to_hex(oid)); if (has_type != type || has_size != size) - die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1)); - has_data = read_sha1_file(sha1, &has_type, &has_size); + die(_("SHA1 COLLISION FOUND WITH %s !"), oid_to_hex(oid)); + has_data = read_sha1_file(oid->hash, &has_type, &has_size); read_unlock(); if (!data) data = new_data = get_data_from_pack(obj_entry); if (!has_data) - die(_("cannot read existing object %s"), sha1_to_hex(sha1)); + die(_("cannot read existing object %s"), oid_to_hex(oid)); if (size != has_size || type != has_type || memcmp(data, has_data, size) != 0) - die(_("SHA1 COLLISION FOUND WITH %s !"), sha1_to_hex(sha1)); + die(_("SHA1 COLLISION FOUND WITH %s !"), oid_to_hex(oid)); free(has_data); } - if (strict) { + if (strict || do_fsck_object) { read_lock(); if (type == OBJ_BLOB) { - struct blob *blob = lookup_blob(sha1); + struct blob *blob = lookup_blob(oid); if (blob) blob->object.flags |= FLAG_CHECKED; else - die(_("invalid blob object %s"), sha1_to_hex(sha1)); + die(_("invalid blob object %s"), oid_to_hex(oid)); } else { struct object *obj; int eaten; @@ -845,13 +847,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(sha1, type, size, buf, &eaten); + obj = parse_object_buffer(oid, type, size, buf, + &eaten); if (!obj) - die(_("invalid %s"), typename(type)); + die(_("invalid %s"), type_name(type)); if (do_fsck_object && fsck_object(obj, buf, size, &fsck_options)) die(_("Error in object")); - if (fsck_walk(obj, NULL, &fsck_options)) + if (strict && fsck_walk(obj, NULL, &fsck_options)) die(_("Not all child objects of %s are reachable"), oid_to_hex(&obj->oid)); if (obj->type == OBJ_TREE) { @@ -956,10 +959,10 @@ 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_sha1_file(result->data, result->size, - typename(delta_obj->real_type), delta_obj->idx.sha1); + hash_object_file(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.sha1); + &delta_obj->idx.oid); counter_lock(); nr_resolved_deltas++; counter_unlock(); @@ -989,7 +992,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, struct base_data *prev_base) { if (base->ref_last == -1 && base->ofs_last == -1) { - find_ref_delta_children(base->obj->idx.sha1, + find_ref_delta_children(base->obj->idx.oid.hash, &base->ref_first, &base->ref_last, OBJ_REF_DELTA); @@ -1116,11 +1119,11 @@ static void *threaded_second_pass(void *data) * - calculate SHA1 of all non-delta objects; * - remember base (SHA1 or offset) for all deltas. */ -static void parse_pack_objects(unsigned char *sha1) +static void parse_pack_objects(unsigned char *hash) { int i, nr_delays = 0; struct ofs_delta_entry *ofs_delta = ofs_deltas; - unsigned char ref_delta_sha1[20]; + struct object_id ref_delta_oid; struct stat st; if (verbose) @@ -1130,7 +1133,8 @@ static void parse_pack_objects(unsigned char *sha1) for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; void *data = unpack_raw_entry(obj, &ofs_delta->offset, - ref_delta_sha1, obj->idx.sha1); + &ref_delta_oid, + &obj->idx.oid); obj->real_type = obj->type; if (obj->type == OBJ_OFS_DELTA) { nr_ofs_deltas++; @@ -1138,7 +1142,7 @@ static void parse_pack_objects(unsigned char *sha1) ofs_delta++; } else if (obj->type == OBJ_REF_DELTA) { ALLOC_GROW(ref_deltas, nr_ref_deltas + 1, ref_deltas_alloc); - hashcpy(ref_deltas[nr_ref_deltas].sha1, ref_delta_sha1); + hashcpy(ref_deltas[nr_ref_deltas].sha1, ref_delta_oid.hash); ref_deltas[nr_ref_deltas].obj_no = i; nr_ref_deltas++; } else if (!data) { @@ -1146,7 +1150,8 @@ static void parse_pack_objects(unsigned char *sha1) obj->real_type = OBJ_BAD; nr_delays++; } else - sha1_object(data, NULL, obj->size, obj->type, obj->idx.sha1); + sha1_object(data, NULL, obj->size, obj->type, + &obj->idx.oid); free(data); display_progress(progress, i+1); } @@ -1155,10 +1160,10 @@ static void parse_pack_objects(unsigned char *sha1) /* Check pack integrity */ flush(); - git_SHA1_Final(sha1, &input_ctx); - if (hashcmp(fill(20), sha1)) + the_hash_algo->final_fn(hash, &input_ctx); + if (hashcmp(fill(the_hash_algo->rawsz), hash)) die(_("pack is corrupted (SHA1 mismatch)")); - use(20); + use(the_hash_algo->rawsz); /* If input_fd is a file, we should have reached its end now. */ if (fstat(input_fd, &st)) @@ -1172,7 +1177,8 @@ static void parse_pack_objects(unsigned char *sha1) if (obj->real_type != OBJ_BAD) continue; obj->real_type = obj->type; - sha1_object(NULL, obj, obj->size, obj->type, obj->idx.sha1); + sha1_object(NULL, obj, obj->size, obj->type, + &obj->idx.oid); nr_delays--; } if (nr_delays) @@ -1233,21 +1239,21 @@ static void resolve_deltas(void) /* * Third pass: * - append objects to convert thin pack to full pack if required - * - write the final 20-byte SHA-1 + * - write the final pack hash */ -static void fix_unresolved_deltas(struct sha1file *f); -static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1) +static void fix_unresolved_deltas(struct hashfile *f); +static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_hash) { if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) { stop_progress(&progress); - /* Flush remaining pack final 20-byte SHA1. */ + /* Flush remaining pack final hash. */ flush(); return; } if (fix_thin_pack) { - struct sha1file *f; - unsigned char read_sha1[20], tail_sha1[20]; + struct hashfile *f; + unsigned char read_hash[GIT_MAX_RAWSZ], tail_hash[GIT_MAX_RAWSZ]; struct strbuf msg = STRBUF_INIT; int nr_unresolved = nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas; int nr_objects_initial = nr_objects; @@ -1256,7 +1262,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha REALLOC_ARRAY(objects, nr_objects + nr_unresolved + 1); memset(objects + nr_objects + 1, 0, nr_unresolved * sizeof(*objects)); - f = sha1fd(output_fd, curr_pack); + f = hashfd(output_fd, curr_pack); fix_unresolved_deltas(f); strbuf_addf(&msg, Q_("completed with %d local object", "completed with %d local objects", @@ -1264,12 +1270,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); - sha1close(f, tail_sha1, 0); - hashcpy(read_sha1, pack_sha1); - fixup_pack_header_footer(output_fd, pack_sha1, + hashclose(f, tail_hash, 0); + hashcpy(read_hash, pack_hash); + fixup_pack_header_footer(output_fd, pack_hash, curr_pack, nr_objects, - read_sha1, consumed_bytes-20); - if (hashcmp(read_sha1, tail_sha1) != 0) + read_hash, consumed_bytes-the_hash_algo->rawsz); + if (hashcmp(read_hash, tail_hash) != 0) die(_("Unexpected tail checksum for %s " "(disk corruption?)"), curr_pack); } @@ -1280,7 +1286,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas); } -static int write_compressed(struct sha1file *f, void *in, unsigned int size) +static int write_compressed(struct hashfile *f, void *in, unsigned int size) { git_zstream stream; int status; @@ -1294,7 +1300,7 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size) stream.next_out = outbuf; stream.avail_out = sizeof(outbuf); status = git_deflate(&stream, Z_FINISH); - sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out); + hashwrite(f, outbuf, sizeof(outbuf) - stream.avail_out); } while (status == Z_OK); if (status != Z_STREAM_END) @@ -1304,7 +1310,7 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size) return size; } -static struct object_entry *append_obj_to_pack(struct sha1file *f, +static struct object_entry *append_obj_to_pack(struct hashfile *f, const unsigned char *sha1, void *buf, unsigned long size, enum object_type type) { @@ -1321,7 +1327,7 @@ static struct object_entry *append_obj_to_pack(struct sha1file *f, } header[n++] = c; crc32_begin(f); - sha1write(f, header, n); + hashwrite(f, header, n); obj[0].size = size; obj[0].hdr_size = n; obj[0].type = type; @@ -1329,8 +1335,8 @@ static struct object_entry *append_obj_to_pack(struct sha1file *f, obj[1].idx.offset = obj[0].idx.offset + n; obj[1].idx.offset += write_compressed(f, buf, size); obj[0].idx.crc32 = crc32_end(f); - sha1flush(f); - hashcpy(obj->idx.sha1, sha1); + hashflush(f); + hashcpy(obj->idx.oid.hash, sha1); return obj; } @@ -1341,7 +1347,7 @@ static int delta_pos_compare(const void *_a, const void *_b) return a->obj_no - b->obj_no; } -static void fix_unresolved_deltas(struct sha1file *f) +static void fix_unresolved_deltas(struct hashfile *f) { struct ref_delta_entry **sorted_by_pos; int i; @@ -1373,7 +1379,7 @@ static void fix_unresolved_deltas(struct sha1file *f) continue; if (check_sha1_signature(d->sha1, base_obj->data, - base_obj->size, typename(type))) + base_obj->size, type_name(type))) die(_("local object %s is corrupt"), sha1_to_hex(d->sha1)); base_obj->obj = append_obj_to_pack(f, d->sha1, base_obj->data, base_obj->size, type); @@ -1383,15 +1389,60 @@ static void fix_unresolved_deltas(struct sha1file *f) free(sorted_by_pos); } +static const char *derive_filename(const char *pack_name, const char *suffix, + struct strbuf *buf) +{ + size_t len; + if (!strip_suffix(pack_name, ".pack", &len)) + die(_("packfile name '%s' does not end with '.pack'"), + pack_name); + strbuf_add(buf, pack_name, len); + strbuf_addch(buf, '.'); + strbuf_addstr(buf, suffix); + return buf->buf; +} + +static void write_special_file(const char *suffix, const char *msg, + const char *pack_name, const unsigned char *hash, + const char **report) +{ + struct strbuf name_buf = STRBUF_INIT; + const char *filename; + int fd; + int msg_len = strlen(msg); + + if (pack_name) + filename = derive_filename(pack_name, suffix, &name_buf); + else + filename = odb_pack_name(&name_buf, hash, suffix); + + fd = odb_pack_keep(filename); + if (fd < 0) { + if (errno != EEXIST) + die_errno(_("cannot write %s file '%s'"), + suffix, filename); + } else { + if (msg_len > 0) { + write_or_die(fd, msg, msg_len); + write_or_die(fd, "\n", 1); + } + if (close(fd) != 0) + die_errno(_("cannot close written %s file '%s'"), + suffix, filename); + if (report) + *report = suffix; + } + strbuf_release(&name_buf); +} + static void final(const char *final_pack_name, const char *curr_pack_name, const char *final_index_name, const char *curr_index_name, - const char *keep_name, const char *keep_msg, - unsigned char *sha1) + const char *keep_msg, const char *promisor_msg, + unsigned char *hash) { const char *report = "pack"; struct strbuf pack_name = STRBUF_INIT; struct strbuf index_name = STRBUF_INIT; - struct strbuf keep_name_buf = STRBUF_INIT; int err; if (!from_stdin) { @@ -1403,32 +1454,16 @@ static void final(const char *final_pack_name, const char *curr_pack_name, die_errno(_("error while closing pack file")); } - if (keep_msg) { - int keep_fd, keep_msg_len = strlen(keep_msg); - - if (!keep_name) - keep_name = odb_pack_name(&keep_name_buf, sha1, "keep"); - - keep_fd = odb_pack_keep(keep_name); - if (keep_fd < 0) { - if (errno != EEXIST) - die_errno(_("cannot write keep file '%s'"), - keep_name); - } else { - if (keep_msg_len > 0) { - write_or_die(keep_fd, keep_msg, keep_msg_len); - write_or_die(keep_fd, "\n", 1); - } - if (close(keep_fd) != 0) - die_errno(_("cannot close written keep file '%s'"), - keep_name); - report = "keep"; - } - } + if (keep_msg) + write_special_file("keep", keep_msg, final_pack_name, hash, + &report); + if (promisor_msg) + write_special_file("promisor", promisor_msg, final_pack_name, + hash, NULL); if (final_pack_name != curr_pack_name) { if (!final_pack_name) - final_pack_name = odb_pack_name(&pack_name, sha1, "pack"); + final_pack_name = odb_pack_name(&pack_name, hash, "pack"); if (finalize_object_file(curr_pack_name, final_pack_name)) die(_("cannot store pack file")); } else if (from_stdin) @@ -1436,18 +1471,18 @@ static void final(const char *final_pack_name, const char *curr_pack_name, if (final_index_name != curr_index_name) { if (!final_index_name) - final_index_name = odb_pack_name(&index_name, sha1, "idx"); + final_index_name = odb_pack_name(&index_name, hash, "idx"); if (finalize_object_file(curr_index_name, final_index_name)) die(_("cannot store index file")); } else chmod(final_index_name, 0444); if (!from_stdin) { - printf("%s\n", sha1_to_hex(sha1)); + printf("%s\n", sha1_to_hex(hash)); } else { struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s\t%s\n", report, sha1_to_hex(sha1)); + strbuf_addf(&buf, "%s\t%s\n", report, sha1_to_hex(hash)); write_or_die(1, buf.buf, buf.len); strbuf_release(&buf); @@ -1466,7 +1501,6 @@ static void final(const char *final_pack_name, const char *curr_pack_name, strbuf_release(&index_name); strbuf_release(&pack_name); - strbuf_release(&keep_name_buf); } static int git_index_pack_config(const char *k, const char *v, void *cb) @@ -1581,13 +1615,14 @@ static void show_pack_info(int stat_only) if (stat_only) continue; printf("%s %-6s %lu %lu %"PRIuMAX, - sha1_to_hex(obj->idx.sha1), - typename(obj->real_type), obj->size, + oid_to_hex(&obj->idx.oid), + type_name(obj->real_type), obj->size, (unsigned long)(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]; - printf(" %u %s", obj_stat[i].delta_depth, sha1_to_hex(bobj->idx.sha1)); + printf(" %u %s", obj_stat[i].delta_depth, + oid_to_hex(&bobj->idx.oid)); } putchar('\n'); } @@ -1608,32 +1643,26 @@ static void show_pack_info(int stat_only) } } -static const char *derive_filename(const char *pack_name, const char *suffix, - struct strbuf *buf) -{ - size_t len; - if (!strip_suffix(pack_name, ".pack", &len)) - die(_("packfile name '%s' does not end with '.pack'"), - pack_name); - strbuf_add(buf, pack_name, len); - strbuf_addstr(buf, suffix); - return buf->buf; -} - int cmd_index_pack(int argc, const char **argv, const char *prefix) { int i, fix_thin_pack = 0, verify = 0, stat_only = 0; const char *curr_index; const char *index_name = NULL, *pack_name = NULL; - const char *keep_name = NULL, *keep_msg = NULL; - struct strbuf index_name_buf = STRBUF_INIT, - keep_name_buf = STRBUF_INIT; + const char *keep_msg = NULL; + const char *promisor_msg = NULL; + struct strbuf index_name_buf = STRBUF_INIT; struct pack_idx_entry **idx_objects; struct pack_idx_option opts; - unsigned char pack_sha1[20]; + unsigned char pack_hash[GIT_MAX_RAWSZ]; unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */ 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 + */ + fetch_if_missing = 0; + if (argc == 2 && !strcmp(argv[1], "-h")) usage(index_pack_usage); @@ -1653,16 +1682,15 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) from_stdin = 1; } else if (!strcmp(arg, "--fix-thin")) { fix_thin_pack = 1; - } else if (!strcmp(arg, "--strict")) { - strict = 1; - do_fsck_object = 1; - } else if (skip_prefix(arg, "--strict=", &arg)) { + } else if (skip_to_optional_arg(arg, "--strict", &arg)) { strict = 1; do_fsck_object = 1; fsck_set_msg_types(&fsck_options, arg); } else if (!strcmp(arg, "--check-self-contained-and-connected")) { strict = 1; check_self_contained_and_connected = 1; + } else if (!strcmp(arg, "--fsck-objects")) { + do_fsck_object = 1; } else if (!strcmp(arg, "--verify")) { verify = 1; } else if (!strcmp(arg, "--verify-stat")) { @@ -1672,10 +1700,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) verify = 1; show_stat = 1; stat_only = 1; - } else if (!strcmp(arg, "--keep")) { - keep_msg = ""; - } else if (starts_with(arg, "--keep=")) { - keep_msg = arg + 7; + } else if (skip_to_optional_arg(arg, "--keep", &keep_msg)) { + ; /* nothing to do */ + } else if (skip_to_optional_arg(arg, "--promisor", &promisor_msg)) { + ; /* already parsed */ } else if (starts_with(arg, "--threads=")) { char *end; nr_threads = strtoul(arg+10, &end, 0); @@ -1738,9 +1766,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (from_stdin && !startup_info->have_repository) die(_("--stdin requires a git repository")); if (!index_name && pack_name) - index_name = derive_filename(pack_name, ".idx", &index_name_buf); - if (keep_msg && !keep_name && pack_name) - keep_name = derive_filename(pack_name, ".keep", &keep_name_buf); + index_name = derive_filename(pack_name, "idx", &index_name_buf); if (verify) { if (!index_name) @@ -1766,11 +1792,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (show_stat) obj_stat = xcalloc(st_add(nr_objects, 1), sizeof(struct object_stat)); ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry)); - parse_pack_objects(pack_sha1); + parse_pack_objects(pack_hash); if (report_end_of_input) write_in_full(2, "\0", 1); resolve_deltas(); - conclude_pack(fix_thin_pack, curr_pack, pack_sha1); + conclude_pack(fix_thin_pack, curr_pack, pack_hash); free(ofs_deltas); free(ref_deltas); if (strict) @@ -1782,19 +1808,18 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) ALLOC_ARRAY(idx_objects, nr_objects); for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; - curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1); + curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_hash); free(idx_objects); if (!verify) final(pack_name, curr_pack, index_name, curr_index, - keep_name, keep_msg, - pack_sha1); + keep_msg, promisor_msg, + pack_hash); else close(input_fd); free(objects); strbuf_release(&index_name_buf); - strbuf_release(&keep_name_buf); if (pack_name == NULL) free((void *) curr_pack); if (index_name == NULL) diff --git a/builtin/init-db.c b/builtin/init-db.c index 8a6acb0ec6..68ff4ad75a 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "refs.h" #include "builtin.h" #include "exec_cmd.h" @@ -23,11 +24,11 @@ static int init_is_bare_repository = 0; static int init_shared_repository = -1; static const char *init_db_template_dir; -static void copy_templates_1(struct strbuf *path, struct strbuf *template, +static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, DIR *dir) { size_t path_baselen = path->len; - size_t template_baselen = template->len; + size_t template_baselen = template_path->len; struct dirent *de; /* Note: if ".git/hooks" file exists in the repository being @@ -43,12 +44,12 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template, int exists = 0; strbuf_setlen(path, path_baselen); - strbuf_setlen(template, template_baselen); + strbuf_setlen(template_path, template_baselen); if (de->d_name[0] == '.') continue; strbuf_addstr(path, de->d_name); - strbuf_addstr(template, de->d_name); + strbuf_addstr(template_path, de->d_name); if (lstat(path->buf, &st_git)) { if (errno != ENOENT) die_errno(_("cannot stat '%s'"), path->buf); @@ -56,36 +57,36 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template, else exists = 1; - if (lstat(template->buf, &st_template)) - die_errno(_("cannot stat template '%s'"), template->buf); + if (lstat(template_path->buf, &st_template)) + die_errno(_("cannot stat template '%s'"), template_path->buf); if (S_ISDIR(st_template.st_mode)) { - DIR *subdir = opendir(template->buf); + DIR *subdir = opendir(template_path->buf); if (!subdir) - die_errno(_("cannot opendir '%s'"), template->buf); + die_errno(_("cannot opendir '%s'"), template_path->buf); strbuf_addch(path, '/'); - strbuf_addch(template, '/'); - copy_templates_1(path, template, subdir); + strbuf_addch(template_path, '/'); + copy_templates_1(path, template_path, subdir); closedir(subdir); } else if (exists) continue; else if (S_ISLNK(st_template.st_mode)) { struct strbuf lnk = STRBUF_INIT; - if (strbuf_readlink(&lnk, template->buf, 0) < 0) - die_errno(_("cannot readlink '%s'"), template->buf); + if (strbuf_readlink(&lnk, template_path->buf, 0) < 0) + die_errno(_("cannot readlink '%s'"), template_path->buf); if (symlink(lnk.buf, path->buf)) die_errno(_("cannot symlink '%s' '%s'"), lnk.buf, path->buf); strbuf_release(&lnk); } else if (S_ISREG(st_template.st_mode)) { - if (copy_file(path->buf, template->buf, st_template.st_mode)) + if (copy_file(path->buf, template_path->buf, st_template.st_mode)) die_errno(_("cannot copy '%s' to '%s'"), - template->buf, path->buf); + template_path->buf, path->buf); } else - error(_("ignoring template %s"), template->buf); + error(_("ignoring template %s"), template_path->buf); } } @@ -578,6 +579,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) set_git_work_tree(work_tree); } + UNLEAK(real_git_dir); + flags |= INIT_DB_EXIST_OK; return init_db(git_dir, real_git_dir, template_dir, flags); } diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c index 175f14797b..b742539d4d 100644 --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c @@ -16,34 +16,119 @@ static const char * const git_interpret_trailers_usage[] = { NULL }; +static enum trailer_where where; +static enum trailer_if_exists if_exists; +static enum trailer_if_missing if_missing; + +static int option_parse_where(const struct option *opt, + const char *arg, int unset) +{ + return trailer_set_where(&where, arg); +} + +static int option_parse_if_exists(const struct option *opt, + const char *arg, int unset) +{ + return trailer_set_if_exists(&if_exists, arg); +} + +static int option_parse_if_missing(const struct option *opt, + const char *arg, int unset) +{ + return trailer_set_if_missing(&if_missing, arg); +} + +static void new_trailers_clear(struct list_head *trailers) +{ + struct list_head *pos, *tmp; + struct new_trailer_item *item; + + list_for_each_safe(pos, tmp, trailers) { + item = list_entry(pos, struct new_trailer_item, list); + list_del(pos); + free(item); + } +} + +static int option_parse_trailer(const struct option *opt, + const char *arg, int unset) +{ + struct list_head *trailers = opt->value; + struct new_trailer_item *item; + + if (unset) { + new_trailers_clear(trailers); + return 0; + } + + if (!arg) + return -1; + + item = xmalloc(sizeof(*item)); + item->text = arg; + item->where = where; + item->if_exists = if_exists; + item->if_missing = if_missing; + list_add_tail(&item->list, trailers); + return 0; +} + +static int parse_opt_parse(const struct option *opt, const char *arg, + int unset) +{ + struct process_trailer_options *v = opt->value; + v->only_trailers = 1; + v->only_input = 1; + v->unfold = 1; + return 0; +} + int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) { - int in_place = 0; - int trim_empty = 0; - struct string_list trailers = STRING_LIST_INIT_NODUP; + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; + LIST_HEAD(trailers); struct option options[] = { - OPT_BOOL(0, "in-place", &in_place, N_("edit files in place")), - OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")), - OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"), - N_("trailer(s) to add")), + OPT_BOOL(0, "in-place", &opts.in_place, N_("edit files in place")), + OPT_BOOL(0, "trim-empty", &opts.trim_empty, N_("trim empty trailers")), + + OPT_CALLBACK(0, "where", NULL, N_("action"), + N_("where to place the new trailer"), option_parse_where), + OPT_CALLBACK(0, "if-exists", NULL, N_("action"), + N_("action if trailer already exists"), option_parse_if_exists), + OPT_CALLBACK(0, "if-missing", NULL, N_("action"), + N_("action if trailer is missing"), option_parse_if_missing), + + 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(0, "trailer", &trailers, N_("trailer"), + N_("trailer(s) to add"), option_parse_trailer), OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_interpret_trailers_usage, 0); + if (opts.only_input && !list_empty(&trailers)) + usage_msg_opt( + _("--trailer with --only-input does not make sense"), + git_interpret_trailers_usage, + options); + if (argc) { int i; for (i = 0; i < argc; i++) - process_trailers(argv[i], in_place, trim_empty, &trailers); + process_trailers(argv[i], &opts, &trailers); } else { - if (in_place) + if (opts.in_place) die(_("no input file given for in-place editing")); - process_trailers(NULL, in_place, trim_empty, &trailers); + process_trailers(NULL, &opts, &trailers); } - string_list_clear(&trailers, 0); + new_trailers_clear(&trailers); return 0; } diff --git a/builtin/log.c b/builtin/log.c index 57ce470f50..94ee177d56 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -5,6 +5,7 @@ * 2006 Junio Hamano */ #include "cache.h" +#include "config.h" #include "refs.h" #include "color.h" #include "commit.h" @@ -26,6 +27,9 @@ #include "version.h" #include "mailmap.h" #include "gpg-interface.h" +#include "progress.h" + +#define MAIL_DEFAULT_WRAP 72 /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -57,9 +61,9 @@ static int auto_decoration_style(void) return (isatty(1) || pager_in_use()) ? DECORATE_SHORT_REFS : 0; } -static int parse_decoration_style(const char *var, const char *value) +static int parse_decoration_style(const char *value) { - switch (git_config_maybe_bool(var, value)) { + switch (git_parse_maybe_bool(value)) { case 1: return DECORATE_SHORT_REFS; case 0: @@ -81,7 +85,7 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse if (unset) decoration_style = 0; else if (arg) - decoration_style = parse_decoration_style("command line", arg); + decoration_style = parse_decoration_style(arg); else decoration_style = DECORATE_SHORT_REFS; @@ -119,20 +123,19 @@ static void cmd_log_init_defaults(struct rev_info *rev) if (fmt_pretty) get_commit_format(fmt_pretty, rev); if (default_follow) - DIFF_OPT_SET(&rev->diffopt, DEFAULT_FOLLOW_RENAMES); + rev->diffopt.flags.default_follow_renames = 1; rev->verbose_header = 1; - DIFF_OPT_SET(&rev->diffopt, RECURSIVE); + rev->diffopt.flags.recursive = 1; rev->diffopt.stat_width = -1; /* use full terminal width */ rev->diffopt.stat_graph_width = -1; /* respect statGraphWidth config */ rev->abbrev_commit = default_abbrev_commit; rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; rev->show_signature = default_show_signature; - DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV); + rev->diffopt.flags.allow_textconv = 1; if (default_date_mode) parse_date_format(default_date_mode, &rev->date_mode); - rev->diffopt.touched_flags = 0; } static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, @@ -141,11 +144,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct userformat_want w; int quiet = 0, source = 0, mailmap = 0; 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_include = STRING_LIST_INIT_NODUP; + struct decoration_filter decoration_filter = {&decorate_refs_include, + &decorate_refs_exclude}; 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_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('L', NULL, &line_cb, "n,m:file", @@ -179,8 +190,8 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (rev->show_notes) init_display_notes(&rev->notes_opt); - if (rev->diffopt.pickaxe || rev->diffopt.filter || - DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) + if ((rev->diffopt.pickaxe_opts & DIFF_PICKAXE_KINDS_MASK) || + rev->diffopt.filter || rev->diffopt.flags.follow_renames) rev->always_show_header = 0; if (source) @@ -204,7 +215,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (decoration_style) { rev->show_decorations = 1; - load_ref_decorations(decoration_style); + load_ref_decorations(&decoration_filter, decoration_style); } if (rev->line_level_traverse) @@ -371,11 +382,14 @@ static int cmd_log_walk(struct rev_info *rev) */ rev->max_count++; if (!rev->reflog_info) { - /* we allow cycles in reflog ancestry */ + /* + * We may show a given commit multiple times when + * walking the reflogs. + */ free_commit_buffer(commit); + free_commit_list(commit->parents); + commit->parents = NULL; } - free_commit_list(commit->parents); - commit->parents = NULL; if (saved_nrl < rev->diffopt.needed_rename_limit) saved_nrl = rev->diffopt.needed_rename_limit; if (rev->diffopt.degraded_cc_to_c) @@ -387,7 +401,7 @@ static int cmd_log_walk(struct rev_info *rev) fclose(rev->diffopt.file); if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && - DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { + rev->diffopt.flags.check_failed) { return 02; } return diff_result_code(&rev->diffopt, 0); @@ -408,7 +422,7 @@ static int git_log_config(const char *var, const char *value, void *cb) if (!strcmp(var, "log.date")) return git_config_string(&default_date_mode, var, value); if (!strcmp(var, "log.decorate")) { - decoration_style = parse_decoration_style(var, value); + decoration_style = parse_decoration_style(value); if (decoration_style < 0) decoration_style = 0; /* maybe warn? */ return 0; @@ -479,12 +493,12 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c unsigned long size; fflush(rev->diffopt.file); - if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) || - !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV)) + if (!rev->diffopt.flags.textconv_set_via_cmdline || + !rev->diffopt.flags.allow_textconv) return stream_blob_to_fd(1, oid, NULL, 0); - if (get_sha1_with_context(obj_name, GET_SHA1_RECORD_PATH, - oidc.hash, &obj_context)) + if (get_oid_with_context(obj_name, GET_OID_RECORD_PATH, + &oidc, &obj_context)) 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)) { @@ -600,7 +614,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.shown_one = 1; if (ret) break; - o = parse_object(t->tagged->oid.hash); + o = parse_object(&t->tagged->oid); if (!o) ret = error(_("Could not read object %s"), oid_to_hex(&t->tagged->oid)); @@ -662,9 +676,9 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) static void log_setup_revisions_tweak(struct rev_info *rev, struct setup_revision_opt *opt) { - if (DIFF_OPT_TST(&rev->diffopt, DEFAULT_FOLLOW_RENAMES) && + if (rev->diffopt.flags.default_follow_renames && rev->prune_data.nr == 1) - DIFF_OPT_SET(&rev->diffopt, FOLLOW_RENAMES); + rev->diffopt.flags.follow_renames = 1; /* Turn --cc/-c into -p --cc/-c when -p was not given */ if (!rev->diffopt.output_format && rev->combine_merges) @@ -820,7 +834,7 @@ static int git_format_config(const char *var, const char *value, void *cb) return 0; } if (!strcmp(var, "format.from")) { - int b = git_config_maybe_bool(var, value); + int b = git_parse_maybe_bool(value); free(from); if (b < 0) from = xstrdup(value); @@ -846,8 +860,10 @@ static int open_next_file(struct commit *commit, const char *subject, if (output_directory) { strbuf_addstr(&filename, output_directory); if (filename.len >= - PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) + PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) { + strbuf_release(&filename); return error(_("name of output directory is too long")); + } strbuf_complete(&filename, '/'); } @@ -861,8 +877,11 @@ static int open_next_file(struct commit *commit, const char *subject, if (!quiet) printf("%s\n", filename.buf + outdir_offset); - if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) - return error(_("Cannot open patch file %s"), filename.buf); + if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) { + error_errno(_("Cannot open patch file %s"), filename.buf); + strbuf_release(&filename); + return -1; + } strbuf_release(&filename); return 0; @@ -882,8 +901,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) o2 = rev->pending.objects[1].item; flags1 = o1->flags; flags2 = o2->flags; - c1 = lookup_commit_reference(o1->oid.hash); - c2 = lookup_commit_reference(o2->oid.hash); + c1 = lookup_commit_reference(&o1->oid); + c2 = lookup_commit_reference(&o2->oid); if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) die(_("Not a range.")); @@ -914,8 +933,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) static void gen_message_id(struct rev_info *info, char *base) { struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s.%lu.git.%s", base, - (unsigned long) time(NULL), + strbuf_addf(&buf, "%s.%"PRItime".git.%s", base, + (timestamp_t) time(NULL), git_committer_info(IDENT_NO_NAME|IDENT_NO_DATE|IDENT_STRICT)); info->message_id = strbuf_detach(&buf, NULL); } @@ -965,7 +984,7 @@ static char *find_branch_name(struct rev_info *rev) return NULL; ref = rev->cmdline.rev[positive].name; tip_oid = &rev->cmdline.rev[positive].item->oid; - if (dwim_ref(ref, strlen(ref), branch_oid.hash, &full_ref) && + if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref) && skip_prefix(full_ref, "refs/heads/", &v) && !oidcmp(tip_oid, &branch_oid)) branch = xstrdup(v); @@ -1027,7 +1046,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, shortlog_init(&log); log.wrap_lines = 1; - log.wrap = 72; + log.wrap = MAIL_DEFAULT_WRAP; log.in1 = 2; log.in2 = 4; log.file = rev->diffopt.file; @@ -1044,12 +1063,13 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, 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); - diff_tree_sha1(origin->tree->object.oid.hash, - head->tree->object.oid.hash, - "", &opts); + diff_tree_oid(&origin->tree->object.oid, + &head->tree->object.oid, + "", &opts); diffcore_std(&opts); diff_flush(&opts); @@ -1267,7 +1287,7 @@ static struct commit *get_base_commit(const char *base_commit, if (get_oid(upstream, &oid)) die(_("Failed to resolve '%s' as a valid ref."), upstream); - commit = lookup_commit_or_die(oid.hash, "upstream base"); + 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) @@ -1302,7 +1322,7 @@ static struct commit *get_base_commit(const char *base_commit, if (rev_nr % 2) rev[i] = rev[2 * i]; - rev_nr = (rev_nr + 1) / 2; + rev_nr = DIV_ROUND_UP(rev_nr, 2); } if (!in_merge_bases(base, rev[0])) @@ -1331,7 +1351,7 @@ static void prepare_bases(struct base_tree_info *bases, return; diff_setup(&diffopt); - DIFF_OPT_SET(&diffopt, RECURSIVE); + diffopt.flags.recursive = 1; diff_setup_done(&diffopt); oidcpy(&bases->base_commit, &base->object.oid); @@ -1358,7 +1378,7 @@ static void prepare_bases(struct base_tree_info *bases, struct object_id *patch_id; if (commit->util) continue; - if (commit_patch_id(commit, &diffopt, oid.hash, 0)) + if (commit_patch_id(commit, &diffopt, &oid, 0)) 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; @@ -1413,6 +1433,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) char *branch_name = NULL; char *base_commit = NULL; struct base_tree_info bases; + int show_progress = 0; + struct progress *progress = NULL; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, @@ -1484,6 +1506,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) OPT_FILENAME(0, "signature-file", &signature_file, N_("add a signature from a file")), OPT__QUIET(&quiet, N_("don't print the patch filenames")), + OPT_BOOL(0, "progress", &show_progress, + N_("show progress while generating patches")), OPT_END() }; @@ -1498,7 +1522,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.verbose_header = 1; rev.diff = 1; rev.max_parents = 1; - DIFF_OPT_SET(&rev.diffopt, RECURSIVE); + rev.diffopt.flags.recursive = 1; rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.def = "HEAD"; @@ -1593,14 +1617,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) (!rev.diffopt.output_format || rev.diffopt.output_format == DIFF_FORMAT_PATCH)) rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY; + if (!rev.diffopt.stat_width) + rev.diffopt.stat_width = MAIL_DEFAULT_WRAP; /* Always generate a patch */ rev.diffopt.output_format |= DIFF_FORMAT_PATCH; rev.zero_commit = zero_commit; - if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) - DIFF_OPT_SET(&rev.diffopt, BINARY); + if (!rev.diffopt.flags.text && !no_binary_diff) + rev.diffopt.flags.binary = 1; if (rev.show_notes) init_display_notes(&rev.notes_opt); @@ -1646,10 +1672,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) check_head = 1; if (check_head) { - struct object_id oid; const char *ref, *v; ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - oid.hash, NULL); + NULL, NULL); if (ref && skip_prefix(ref, "refs/heads/", &v)) branch_name = xstrdup(v); else @@ -1743,8 +1768,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) start_number--; } rev.add_signoff = do_signoff; + + if (show_progress) + progress = start_delayed_progress(_("Generating patches"), total); while (0 <= --nr) { int shown; + display_progress(progress, total - nr); commit = list[nr]; rev.nr = total - nr + (start_number - 1); /* Make the second and subsequent mails replies to the first */ @@ -1809,6 +1838,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (!use_stdout) fclose(rev.diffopt.file); } + stop_progress(&progress); free(list); free(branch_name); string_list_clear(&extra_to, 0); @@ -1823,7 +1853,7 @@ 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.hash); + struct commit *commit = lookup_commit_reference(&oid); if (commit) { commit->object.flags |= flags; add_pending_object(revs, &commit->object, arg); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index a6c70dbe9e..2fc836e330 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -5,7 +5,10 @@ * * Copyright (C) Linus Torvalds, 2005 */ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "repository.h" +#include "config.h" #include "quote.h" #include "dir.h" #include "builtin.h" @@ -16,6 +19,7 @@ #include "pathspec.h" #include "run-command.h" #include "submodule.h" +#include "submodule-config.h" static int abbrev; static int show_deleted; @@ -27,14 +31,13 @@ static int show_resolve_undo; static int show_modified; static int show_killed; static int show_valid_bit; +static int show_fsmonitor_bit; static int line_terminator = '\n'; static int debug_mode; static int show_eol; static int recurse_submodules; -static struct argv_array submodule_options = ARGV_ARRAY_INIT; static const char *prefix; -static const char *super_prefix; static int max_prefix_len; static int prefix_len; static struct pathspec pathspec; @@ -53,17 +56,17 @@ static const char *tag_modified = ""; static const char *tag_skip_worktree = ""; static const char *tag_resolve_undo = ""; -static void write_eolinfo(const struct cache_entry *ce, const char *path) +static void write_eolinfo(const struct index_state *istate, + const struct cache_entry *ce, const char *path) { - if (!show_eol) - return; - else { + if (show_eol) { struct stat st; const char *i_txt = ""; const char *w_txt = ""; const char *a_txt = get_convert_attr_ascii(path); if (ce && S_ISREG(ce->ce_mode)) - i_txt = get_cached_convert_stats_ascii(ce->name); + i_txt = get_cached_convert_stats_ascii(istate, + ce->name); if (!lstat(path, &st) && S_ISREG(st.st_mode)) w_txt = get_wt_convert_stats_ascii(path); printf("i/%-5s w/%-5s attr/%-17s\t", i_txt, w_txt, a_txt); @@ -73,54 +76,81 @@ static void write_eolinfo(const struct cache_entry *ce, const char *path) static void write_name(const char *name) { /* - * Prepend the super_prefix to name to construct the full_name to be - * written. - */ - struct strbuf full_name = STRBUF_INIT; - if (super_prefix) { - strbuf_addstr(&full_name, super_prefix); - strbuf_addstr(&full_name, name); - name = full_name.buf; - } - - /* * With "--full-name", prefix_len=0; this caller needs to pass * an empty string in that case (a NULL is good for ""). */ write_name_quoted_relative(name, prefix_len ? prefix : NULL, stdout, line_terminator); +} + +static const char *get_tag(const struct cache_entry *ce, const char *tag) +{ + static char alttag[4]; - strbuf_release(&full_name); + if (tag && *tag && ((show_valid_bit && (ce->ce_flags & CE_VALID)) || + (show_fsmonitor_bit && (ce->ce_flags & CE_FSMONITOR_VALID)))) { + memcpy(alttag, tag, 3); + + if (isalpha(tag[0])) { + alttag[0] = tolower(tag[0]); + } else if (tag[0] == '?') { + alttag[0] = '!'; + } else { + alttag[0] = 'v'; + alttag[1] = tag[0]; + alttag[2] = ' '; + alttag[3] = 0; + } + + tag = alttag; + } + + return tag; +} + +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); + } } static void show_dir_entry(const char *tag, struct dir_entry *ent) { int len = max_prefix_len; - if (len >= ent->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; fputs(tag, stdout); - write_eolinfo(NULL, ent->name); + write_eolinfo(NULL, NULL, ent->name); write_name(ent->name); } -static void show_other_files(struct dir_struct *dir) +static void show_other_files(const struct index_state *istate, + const struct dir_struct *dir) { int i; for (i = 0; i < dir->nr; i++) { struct dir_entry *ent = dir->entries[i]; - if (!cache_name_is_other(ent->name, ent->len)) + if (!index_name_is_other(istate, ent->name, ent->len)) continue; show_dir_entry(tag_other, ent); } } -static void show_killed_files(struct dir_struct *dir) +static void show_killed_files(const struct index_state *istate, + const struct dir_struct *dir) { int i; for (i = 0; i < dir->nr; i++) { @@ -134,29 +164,29 @@ static void show_killed_files(struct dir_struct *dir) /* If ent->name is prefix of an entry in the * cache, it will be killed. */ - pos = cache_name_pos(ent->name, ent->len); + pos = index_name_pos(istate, ent->name, ent->len); if (0 <= pos) die("BUG: killed-file %.*s not found", ent->len, ent->name); pos = -pos - 1; - while (pos < active_nr && - ce_stage(active_cache[pos])) + while (pos < istate->cache_nr && + ce_stage(istate->cache[pos])) pos++; /* skip unmerged */ - if (active_nr <= pos) + if (istate->cache_nr <= pos) break; /* pos points at a name immediately after * ent->name in the cache. Does it expect * ent->name to be a directory? */ - len = ce_namelen(active_cache[pos]); + len = ce_namelen(istate->cache[pos]); if ((ent->len < len) && - !strncmp(active_cache[pos]->name, + !strncmp(istate->cache[pos]->name, ent->name, ent->len) && - active_cache[pos]->name[ent->len] == '/') + istate->cache[pos]->name[ent->len] == '/') killed = 1; break; } - if (0 <= cache_name_pos(ent->name, sp - ent->name)) { + if (0 <= index_name_pos(istate, ent->name, sp - ent->name)) { /* If any of the leading directories in * ent->name is registered in the cache, * ent->name will be killed. @@ -170,100 +200,39 @@ static void show_killed_files(struct dir_struct *dir) } } -/* - * Compile an argv_array with all of the options supported by --recurse_submodules - */ -static void compile_submodule_options(const char **argv, - const struct dir_struct *dir, - int show_tag) -{ - if (line_terminator == '\0') - argv_array_push(&submodule_options, "-z"); - if (show_tag) - argv_array_push(&submodule_options, "-t"); - if (show_valid_bit) - argv_array_push(&submodule_options, "-v"); - if (show_cached) - argv_array_push(&submodule_options, "--cached"); - if (show_eol) - argv_array_push(&submodule_options, "--eol"); - if (debug_mode) - argv_array_push(&submodule_options, "--debug"); - - /* Add Pathspecs */ - argv_array_push(&submodule_options, "--"); - for (; *argv; argv++) - argv_array_push(&submodule_options, *argv); -} +static void show_files(struct repository *repo, struct dir_struct *dir); -/** - * Recursively call ls-files on a submodule - */ -static void show_gitlink(const struct cache_entry *ce) +static void show_submodule(struct repository *superproject, + struct dir_struct *dir, const char *path) { - struct child_process cp = CHILD_PROCESS_INIT; - int status; - char *dir; - - prepare_submodule_repo_env(&cp.env_array); - argv_array_push(&cp.env_array, GIT_DIR_ENVIRONMENT); - - if (prefix_len) - argv_array_pushf(&cp.env_array, "%s=%s", - GIT_TOPLEVEL_PREFIX_ENVIRONMENT, - prefix); - argv_array_pushf(&cp.args, "--super-prefix=%s%s/", - super_prefix ? super_prefix : "", - ce->name); - argv_array_push(&cp.args, "ls-files"); - argv_array_push(&cp.args, "--recurse-submodules"); - - /* add supported options */ - argv_array_pushv(&cp.args, submodule_options.argv); - - cp.git_cmd = 1; - dir = mkpathdup("%s/%s", get_git_work_tree(), ce->name); - cp.dir = dir; - status = run_command(&cp); - free(dir); - if (status) - exit(status); + struct repository submodule; + + if (repo_submodule_init(&submodule, superproject, path)) + return; + + if (repo_read_index(&submodule) < 0) + die("index file corrupt"); + + show_files(&submodule, dir); + + repo_clear(&submodule); } -static void show_ce_entry(const char *tag, const struct cache_entry *ce) +static void show_ce(struct repository *repo, struct dir_struct *dir, + const struct cache_entry *ce, const char *fullname, + const char *tag) { - struct strbuf name = STRBUF_INIT; - int len = max_prefix_len; - if (super_prefix) - strbuf_addstr(&name, super_prefix); - strbuf_addstr(&name, ce->name); - - if (len >= ce_namelen(ce)) + if (max_prefix_len > strlen(fullname)) die("git ls-files: internal error - cache entry not superset of prefix"); if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && - submodule_path_match(&pathspec, name.buf, ps_matched)) { - show_gitlink(ce); - } else if (match_pathspec(&pathspec, name.buf, name.len, - len, ps_matched, + is_submodule_active(repo, ce->name)) { + show_submodule(repo, dir, ce->name); + } else if (match_pathspec(&pathspec, fullname, strlen(fullname), + max_prefix_len, ps_matched, S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode))) { - if (tag && *tag && show_valid_bit && - (ce->ce_flags & CE_VALID)) { - static char alttag[4]; - memcpy(alttag, tag, 3); - if (isalpha(tag[0])) - alttag[0] = tolower(tag[0]); - else if (tag[0] == '?') - alttag[0] = '!'; - else { - alttag[0] = 'v'; - alttag[1] = tag[0]; - alttag[2] = ' '; - alttag[3] = 0; - } - tag = alttag; - } + tag = get_tag(ce, tag); if (!show_stage) { fputs(tag, stdout); @@ -274,30 +243,20 @@ static void show_ce_entry(const char *tag, const struct cache_entry *ce) find_unique_abbrev(ce->oid.hash, abbrev), ce_stage(ce)); } - write_eolinfo(ce, ce->name); - write_name(ce->name); - 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); - } + write_eolinfo(repo->index, ce, fullname); + write_name(fullname); + print_debug(ce); } - - strbuf_release(&name); } -static void show_ru_info(void) +static void show_ru_info(const struct index_state *istate) { struct string_list_item *item; - if (!the_index.resolve_undo) + if (!istate->resolve_undo) return; - for_each_string_list_item(item, the_index.resolve_undo) { + for_each_string_list_item(item, istate->resolve_undo) { const char *path = item->string; struct resolve_undo_info *ui = item->util; int i, len; @@ -319,88 +278,128 @@ static void show_ru_info(void) } } -static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce) +static int ce_excluded(struct dir_struct *dir, struct index_state *istate, + const char *fullname, const struct cache_entry *ce) { int dtype = ce_to_dtype(ce); - return is_excluded(dir, ce->name, &dtype); + return is_excluded(dir, istate, fullname, &dtype); +} + +static void construct_fullname(struct strbuf *out, const struct repository *repo, + const struct cache_entry *ce) +{ + strbuf_reset(out); + if (repo->submodule_prefix) + strbuf_addstr(out, repo->submodule_prefix); + strbuf_addstr(out, ce->name); } -static void show_files(struct dir_struct *dir) +static void show_files(struct repository *repo, struct dir_struct *dir) { int i; + struct strbuf fullname = STRBUF_INIT; /* For cached/deleted files we don't need to even do the readdir */ if (show_others || show_killed) { if (!show_others) dir->flags |= DIR_COLLECT_KILLED_ONLY; - fill_directory(dir, &pathspec); + fill_directory(dir, repo->index, &pathspec); if (show_others) - show_other_files(dir); + show_other_files(repo->index, dir); if (show_killed) - show_killed_files(dir); + show_killed_files(repo->index, dir); } if (show_cached || show_stage) { - for (i = 0; i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; + for (i = 0; i < repo->index->cache_nr; i++) { + const struct cache_entry *ce = repo->index->cache[i]; + + construct_fullname(&fullname, repo, ce); + if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, ce)) + !ce_excluded(dir, repo->index, fullname.buf, ce)) continue; if (show_unmerged && !ce_stage(ce)) continue; if (ce->ce_flags & CE_UPDATE) continue; - show_ce_entry(ce_stage(ce) ? tag_unmerged : - (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce); + show_ce(repo, dir, ce, fullname.buf, + ce_stage(ce) ? tag_unmerged : + (ce_skip_worktree(ce) ? tag_skip_worktree : + tag_cached)); } } if (show_deleted || show_modified) { - for (i = 0; i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; + for (i = 0; i < repo->index->cache_nr; i++) { + const struct cache_entry *ce = repo->index->cache[i]; struct stat st; int err; + + construct_fullname(&fullname, repo, ce); + if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, ce)) + !ce_excluded(dir, repo->index, fullname.buf, ce)) continue; if (ce->ce_flags & CE_UPDATE) continue; if (ce_skip_worktree(ce)) continue; - err = lstat(ce->name, &st); + err = lstat(fullname.buf, &st); if (show_deleted && err) - show_ce_entry(tag_removed, ce); - if (show_modified && ce_modified(ce, &st, 0)) - show_ce_entry(tag_modified, ce); + show_ce(repo, dir, ce, fullname.buf, tag_removed); + if (show_modified && ie_modified(repo->index, ce, &st, 0)) + show_ce(repo, dir, ce, fullname.buf, tag_modified); } } + + strbuf_release(&fullname); } /* * Prune the index to only contain stuff starting with "prefix" */ -static void prune_cache(const char *prefix, size_t prefixlen) +static void prune_index(struct index_state *istate, + const char *prefix, size_t prefixlen) { int pos; unsigned int first, last; - if (!prefix) + if (!prefix || !istate->cache_nr) return; - pos = cache_name_pos(prefix, prefixlen); + pos = index_name_pos(istate, prefix, prefixlen); if (pos < 0) pos = -pos-1; first = pos; - last = active_nr; + last = istate->cache_nr; while (last > first) { int next = (last + first) >> 1; - const struct cache_entry *ce = active_cache[next]; + const struct cache_entry *ce = istate->cache[next]; if (!strncmp(ce->name, prefix, prefixlen)) { first = next+1; continue; } last = next; } - memmove(active_cache, active_cache + pos, - (last - pos) * sizeof(struct cache_entry *)); - active_nr = last - pos; + MOVE_ARRAY(istate->cache, istate->cache + pos, last - pos); + istate->cache_nr = last - pos; +} + +static int get_common_prefix_len(const char *common_prefix) +{ + int common_prefix_len; + + if (!common_prefix) + return 0; + + common_prefix_len = strlen(common_prefix); + + /* + * If the prefix has a trailing slash, strip it so that submodules wont + * be pruned from the index. + */ + if (common_prefix[common_prefix_len - 1] == '/') + common_prefix_len--; + + return common_prefix_len; } /* @@ -411,23 +410,24 @@ static void prune_cache(const char *prefix, size_t prefixlen) * that were given from the command line. We are not * going to write this index out. */ -void overlay_tree_on_cache(const char *tree_name, const char *prefix) +void overlay_tree_on_index(struct index_state *istate, + const char *tree_name, const char *prefix) { struct tree *tree; - unsigned char sha1[20]; + struct object_id oid; struct pathspec pathspec; struct cache_entry *last_stage0 = NULL; int i; - if (get_sha1(tree_name, sha1)) + if (get_oid(tree_name, &oid)) die("tree-ish %s not found.", tree_name); - tree = parse_tree_indirect(sha1); + tree = parse_tree_indirect(&oid); if (!tree) die("bad tree-ish %s", tree_name); /* Hoist the unmerged entries up to stage #3 to make room */ - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; if (!ce_stage(ce)) continue; ce->ce_flags |= CE_STAGEMASK; @@ -440,11 +440,11 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) PATHSPEC_PREFER_CWD, prefix, matchbuf); } else memset(&pathspec, 0, sizeof(pathspec)); - if (read_tree(tree, 1, &pathspec)) + if (read_tree(tree, 1, &pathspec, istate)) die("unable to read tree entries %s", tree_name); - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; switch (ce_stage(ce)) { case 0: last_stage0 = ce; @@ -517,6 +517,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("identify the file status with tags")), OPT_BOOL('v', NULL, &show_valid_bit, N_("use lowercase letters for 'assume unchanged' files")), + OPT_BOOL('f', NULL, &show_fsmonitor_bit, + N_("use lowercase letters for 'fsmonitor clean' files")), OPT_BOOL('c', "cached", &show_cached, N_("show cached files in the output (default)")), OPT_BOOL('d', "deleted", &show_deleted, @@ -575,10 +577,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) prefix = cmd_prefix; if (prefix) prefix_len = strlen(prefix); - super_prefix = get_super_prefix(); git_config(git_default_config, NULL); - if (read_cache() < 0) + if (repo_read_index(the_repository) < 0) die("index file corrupt"); argc = parse_options(argc, argv, prefix, builtin_ls_files_options, @@ -587,7 +588,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) for (i = 0; i < exclude_list.nr; i++) { add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args); } - if (show_tag || show_valid_bit) { + if (show_tag || show_valid_bit || show_fsmonitor_bit) { tag_cached = "H "; tag_unmerged = "M "; tag_removed = "R "; @@ -611,9 +612,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (require_work_tree && !is_inside_work_tree()) setup_work_tree(); - if (recurse_submodules) - compile_submodule_options(argv, &dir, show_tag); - if (recurse_submodules && (show_stage || show_deleted || show_others || show_unmerged || show_killed || show_modified || show_resolve_undo || with_tree)) @@ -624,20 +622,24 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) "--error-unmatch"); parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD | - PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + PATHSPEC_PREFER_CWD, prefix, argv); /* * Find common prefix for all pathspec's * This is used as a performance optimization which unfortunately cannot - * be done when recursing into submodules + * be done when recursing into submodules because when a pathspec is + * given which spans repository boundaries you can't simply remove the + * submodule entry because the pathspec may match something inside the + * submodule. */ if (recurse_submodules) max_prefix = NULL; else max_prefix = common_prefix(&pathspec); - max_prefix_len = max_prefix ? strlen(max_prefix) : 0; + max_prefix_len = get_common_prefix_len(max_prefix); + + prune_index(the_repository->index, max_prefix, max_prefix_len); /* Treat unmatching pathspec elements as errors */ if (pathspec.nr && error_unmatch) @@ -651,7 +653,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) show_killed || show_modified || show_resolve_undo)) show_cached = 1; - prune_cache(max_prefix, max_prefix_len); if (with_tree) { /* * Basic sanity check; show-stages and show-unmerged @@ -659,11 +660,13 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) */ if (show_stage || show_unmerged) die("ls-files --with-tree is incompatible with -s or -u"); - overlay_tree_on_cache(with_tree, max_prefix); + overlay_tree_on_index(the_repository->index, with_tree, max_prefix); } - show_files(&dir); + + show_files(the_repository, &dir); + if (show_resolve_undo) - show_ru_info(); + show_ru_info(the_repository->index); if (ps_matched) { int bad; @@ -674,5 +677,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) return bad ? 1 : 0; } + UNLEAK(dir); return 0; } diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index b2d7d5ce68..540d56429f 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -24,7 +24,7 @@ static int tail_match(const char **pattern, const char *path) pathbuf = xstrfmt("/%s", path); while ((p = *(pattern++)) != NULL) { - if (!wildmatch(p, pathbuf, 0, NULL)) { + if (!wildmatch(p, pathbuf, 0)) { free(pathbuf); return 1; } @@ -60,8 +60,9 @@ 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_SET_INT(0, "exit-code", &status, - N_("exit with exit code 2 if no matching refs are found"), 2), + 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_END() diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index d7ebeb4ce6..ef965408e8 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "blob.h" #include "tree.h" #include "commit.h" @@ -119,7 +120,7 @@ static int show_tree(const unsigned char *sha1, struct strbuf *base, int cmd_ls_tree(int argc, const char **argv, const char *prefix) { - unsigned char sha1[20]; + struct object_id oid; struct tree *tree; int i, full_tree = 0; const struct option ls_tree_options[] = { @@ -164,7 +165,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) if (argc < 1) usage_with_options(ls_tree_usage, ls_tree_options); - if (get_sha1(argv[0], sha1)) + if (get_oid(argv[0], &oid)) die("Not a valid object name %s", argv[0]); /* @@ -180,7 +181,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) for (i = 0; i < pathspec.nr; i++) pathspec.items[i].nowildcard_len = pathspec.items[i].len; pathspec.has_wildcard = 0; - tree = parse_tree_indirect(sha1); + tree = parse_tree_indirect(&oid); if (!tree) die("not a tree object"); return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); diff --git a/builtin/merge-base.c b/builtin/merge-base.c index cfe2a796f8..3b7600150b 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "commit.h" #include "refs.h" #include "diff.h" @@ -8,20 +9,20 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) { - struct commit_list *result; + struct commit_list *result, *r; result = get_merge_bases_many_dirty(rev[0], rev_nr - 1, rev + 1); if (!result) return 1; - while (result) { - printf("%s\n", oid_to_hex(&result->item->object.oid)); + for (r = result; r; r = r->next) { + printf("%s\n", oid_to_hex(&r->item->object.oid)); if (!show_all) - return 0; - result = result->next; + break; } + free_commit_list(result); return 0; } @@ -41,7 +42,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.hash); + r = lookup_commit_reference(&revkey); if (!r) die("Not a valid commit name %s", arg); @@ -50,45 +51,47 @@ static struct commit *get_commit_reference(const char *arg) static int handle_independent(int count, const char **args) { - struct commit_list *revs = NULL; - struct commit_list *result; + struct commit_list *revs = NULL, *rev; int i; for (i = count - 1; i >= 0; i--) commit_list_insert(get_commit_reference(args[i]), &revs); - result = reduce_heads(revs); - if (!result) + reduce_heads_replace(&revs); + + if (!revs) return 1; - while (result) { - printf("%s\n", oid_to_hex(&result->item->object.oid)); - result = result->next; - } + for (rev = revs; rev; rev = rev->next) + printf("%s\n", oid_to_hex(&rev->item->object.oid)); + + free_commit_list(revs); return 0; } static int handle_octopus(int count, const char **args, int show_all) { struct commit_list *revs = NULL; - struct commit_list *result; + struct commit_list *result, *rev; int i; for (i = count - 1; i >= 0; i--) commit_list_insert(get_commit_reference(args[i]), &revs); - result = reduce_heads(get_octopus_merge_bases(revs)); + result = get_octopus_merge_bases(revs); + free_commit_list(revs); + reduce_heads_replace(&result); if (!result) return 1; - while (result) { - printf("%s\n", oid_to_hex(&result->item->object.oid)); + for (rev = result; rev; rev = rev->next) { + printf("%s\n", oid_to_hex(&rev->item->object.oid)); if (!show_all) - return 0; - result = result->next; + break; } + free_commit_list(result); return 0; } @@ -120,7 +123,7 @@ static void add_one_commit(struct object_id *oid, struct rev_collect *revs) if (is_null_oid(oid)) return; - commit = lookup_commit(oid->hash); + commit = lookup_commit(oid); if (!commit || (commit->object.flags & TMP_MARK) || parse_commit(commit)) @@ -132,7 +135,7 @@ static void add_one_commit(struct object_id *oid, struct rev_collect *revs) } static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *ident, unsigned long timestamp, + const char *ident, timestamp_t timestamp, int tz, const char *message, void *cbdata) { struct rev_collect *revs = cbdata; @@ -155,7 +158,7 @@ static int handle_fork_point(int argc, const char **argv) struct commit_list *bases; int i, ret = 0; - switch (dwim_ref(argv[0], strlen(argv[0]), oid.hash, &refname)) { + switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) { case 0: die("No such ref: '%s'", argv[0]); case 1: @@ -168,7 +171,7 @@ static int handle_fork_point(int argc, const char **argv) if (get_oid(commitname, &oid)) die("Not a valid object name: '%s'", commitname); - derived = lookup_commit_reference(oid.hash); + derived = lookup_commit_reference(&oid); memset(&revs, 0, sizeof(revs)); revs.initial = 1; for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 47dde7c39c..b08803e611 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "xdiff/xdiff.h" #include "xdiff-interface.h" #include "parse-options.h" diff --git a/builtin/merge-ours.c b/builtin/merge-ours.c index 684411694f..c84c6e05e9 100644 --- a/builtin/merge-ours.c +++ b/builtin/merge-ours.c @@ -9,26 +9,24 @@ */ #include "git-compat-util.h" #include "builtin.h" +#include "diff.h" static const char builtin_merge_ours_usage[] = "git merge-ours <base>... -- HEAD <remote>..."; -static const char *diff_index_args[] = { - "diff-index", "--quiet", "--cached", "HEAD", "--", NULL -}; -#define NARGS (ARRAY_SIZE(diff_index_args) - 1) - int cmd_merge_ours(int argc, const char **argv, const char *prefix) { if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_merge_ours_usage); /* - * We need to exit with 2 if the index does not match our HEAD tree, - * because the current index is what we will be committing as the - * merge result. + * The contents of the current index becomes the tree we + * commit. The index must match HEAD, or this merge cannot go + * through. */ - if (cmd_diff_index(NARGS, diff_index_args, prefix)) + if (read_cache() < 0) + die_errno("read_cache failed"); + if (index_differs_from("HEAD", NULL, 0)) exit(2); exit(0); } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 5b7ab9b967..d01ddecf66 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -161,14 +161,14 @@ static int both_empty(struct name_entry *a, struct name_entry *b) return !(a->oid || b->oid); } -static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path) +static struct merge_list *create_entry(unsigned stage, unsigned mode, const struct object_id *oid, const char *path) { struct merge_list *res = xcalloc(1, sizeof(*res)); res->stage = stage; res->path = path; res->mode = mode; - res->blob = lookup_blob(sha1); + res->blob = lookup_blob(oid); return res; } @@ -188,8 +188,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->hash, path); - final = create_entry(0, result->mode, result->oid->hash, path); + orig = create_entry(2, ours->mode, ours->oid, path); + final = create_entry(0, result->mode, result->oid, path); final->link = orig; @@ -213,11 +213,11 @@ static void unresolved_directory(const struct traverse_info *info, newbase = traverse_path(info, p); -#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->oid->hash : NULL) - buf0 = fill_tree_descriptor(t+0, ENTRY_SHA1(n + 0)); - buf1 = fill_tree_descriptor(t+1, ENTRY_SHA1(n + 1)); - buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2)); -#undef ENTRY_SHA1 +#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)); +#undef ENTRY_OID merge_trees(t, newbase); @@ -239,7 +239,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->hash, path); + link = create_entry(stage, n->mode, n->oid, path); link->link = entry; return link; } @@ -347,12 +347,12 @@ static void merge_trees(struct tree_desc t[3], const char *base) static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) { - unsigned char sha1[20]; + struct object_id oid; void *buf; - if (get_sha1(rev, sha1)) + if (get_oid(rev, &oid)) die("unknown rev %s", rev); - buf = fill_tree_descriptor(desc, sha1); + buf = fill_tree_descriptor(desc, &oid); if (!buf) die("%s is not a tree", rev); return buf; diff --git a/builtin/merge.c b/builtin/merge.c index 703827f006..ee050a47f3 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -7,6 +7,7 @@ */ #include "cache.h" +#include "config.h" #include "parse-options.h" #include "builtin.h" #include "lockfile.h" @@ -31,6 +32,8 @@ #include "gpg-interface.h" #include "sequencer.h" #include "string-list.h" +#include "packfile.h" +#include "tag.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -69,7 +72,9 @@ 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 struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -232,6 +237,8 @@ static struct option builtin_merge_options[] = { { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, 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_END() }; @@ -249,6 +256,7 @@ static int save_state(struct object_id *stash) struct child_process cp = CHILD_PROCESS_INIT; struct strbuf buffer = STRBUF_INIT; const char *argv[] = {"stash", "create", NULL}; + int rc = -1; cp.argv = argv; cp.out = -1; @@ -262,11 +270,14 @@ static int save_state(struct object_id *stash) if (finish_command(&cp) || len < 0) die(_("stash failed")); else if (!len) /* no changes */ - return -1; + goto out; strbuf_setlen(&buffer, buffer.len-1); if (get_oid(buffer.buf, stash)) die(_("not a valid object: %s"), buffer.buf); - return 0; + rc = 0; +out: + strbuf_release(&buffer); + return rc; } static void read_empty(unsigned const char *sha1, int verbose) @@ -395,9 +406,8 @@ static void finish(struct commit *head_commit, 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->hash, head->hash, 0, - UPDATE_REFS_DIE_ON_ERR); + 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. @@ -415,7 +425,7 @@ static void finish(struct commit *head_commit, DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; opts.detect_rename = DIFF_DETECT_RENAME; diff_setup_done(&opts); - diff_tree_sha1(head->hash, new_head->hash, "", &opts); + diff_tree_oid(head, new_head, "", &opts); diffcore_std(&opts); diff_flush(&opts); } @@ -445,7 +455,7 @@ static void merge_name(const char *remote, struct strbuf *msg) if (!remote_head) die(_("'%s' does not point to a commit"), remote); - if (dwim_ref(remote, strlen(remote), branch_head.hash, &found_ref) > 0) { + if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref) > 0) { if (starts_with(found_ref, "refs/heads/")) { strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", oid_to_hex(&branch_head), remote); @@ -511,7 +521,7 @@ static void merge_name(const char *remote, struct strbuf *msg) if (desc && desc->obj && desc->obj->type == OBJ_TAG) { strbuf_addf(msg, "%s\t\t%s '%s'\n", oid_to_hex(&desc->obj->oid), - typename(desc->obj->type), + type_name(desc->obj->type), remote); goto cleanup; } @@ -536,7 +546,7 @@ static void parse_branch_merge_options(char *bmo) die(_("Bad branch.%s.mergeoptions string: %s"), branch, split_cmdline_strerror(argc)); REALLOC_ARRAY(argv, argc + 2); - memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); + MOVE_ARRAY(argv + 1, argv, argc + 1); argc++; argv[0] = "branch.*.mergeoptions"; parse_options(argc, argv, NULL, builtin_merge_options, @@ -558,6 +568,8 @@ static int git_merge_config(const char *k, const char *v, void *cb) if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) show_diffstat = git_config_bool(k, v); + else if (!strcmp(k, "merge.verifysignatures")) + verify_signatures = git_config_bool(k, v); else if (!strcmp(k, "pull.twohead")) return git_config_string(&pull_twohead, k, v); else if (!strcmp(k, "pull.octopus")) @@ -565,7 +577,7 @@ static int git_merge_config(const char *k, const char *v, void *cb) else if (!strcmp(k, "merge.renormalize")) option_renormalize = git_config_bool(k, v); else if (!strcmp(k, "merge.ff")) { - int boolval = git_config_maybe_bool(k, v); + int boolval = git_parse_maybe_bool(v); if (0 <= boolval) { fast_forward = boolval ? FF_ALLOW : FF_NO; } else if (v && !strcmp(v, "only")) { @@ -605,13 +617,13 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head, opts.verbose_update = 1; opts.trivial_merges_only = 1; opts.merge = 1; - trees[nr_trees] = parse_tree_indirect(common->hash); + trees[nr_trees] = parse_tree_indirect(common); if (!trees[nr_trees++]) return -1; - trees[nr_trees] = parse_tree_indirect(head->hash); + trees[nr_trees] = parse_tree_indirect(head); if (!trees[nr_trees++]) return -1; - trees[nr_trees] = parse_tree_indirect(one->hash); + trees[nr_trees] = parse_tree_indirect(one); if (!trees[nr_trees++]) return -1; opts.fn = threeway_merge; @@ -640,10 +652,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, hold_locked_index(&lock, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); - if (active_cache_changed && - write_locked_index(&the_index, &lock, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) return error(_("Unable to write index.")); - rollback_lock_file(&lock); if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { int clean, x; @@ -680,10 +691,9 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, remoteheads->item, reversed, &result); if (clean < 0) exit(128); - if (active_cache_changed && - write_locked_index(&the_index, &lock, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) die (_("unable to write %s"), get_index_file()); - rollback_lock_file(&lock); return clean ? 0 : 1; } else { return try_merge_command(strategy, xopts_nr, xopts, @@ -755,13 +765,19 @@ N_("Please enter a commit message to explain why this merge is necessary,\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; 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 (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); if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg", git_path_merge_msg(), "merge", NULL)) @@ -770,6 +786,12 @@ static void prepare_to_commit(struct commit_list *remoteheads) if (launch_editor(git_path_merge_msg(), NULL, NULL)) abort_commit(remoteheads, NULL); } + + if (verify_msg && run_commit_hook(0 < option_edit, get_index_file(), + "commit-msg", + git_path_merge_msg(), NULL)) + abort_commit(remoteheads, NULL); + read_merge_msg(&msg); strbuf_stripspace(&msg, 0 < option_edit); if (!msg.len) @@ -787,18 +809,17 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) hold_locked_index(&lock, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); - if (active_cache_changed && - write_locked_index(&the_index, &lock, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) return error(_("Unable to write index.")); - rollback_lock_file(&lock); write_tree_trivial(&result_tree); printf(_("Wonderful.\n")); pptr = commit_list_append(head, pptr); pptr = commit_list_append(remoteheads->item, pptr); prepare_to_commit(remoteheads); - if (commit_tree(merge_msg.buf, merge_msg.len, result_tree.hash, parents, - result_commit.hash, NULL, sign_commit)) + if (commit_tree(merge_msg.buf, merge_msg.len, &result_tree, parents, + &result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, &result_commit, "In-index merge"); drop_save(); @@ -822,8 +843,8 @@ static int finish_automerge(struct commit *head, 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->hash, parents, - result_commit.hash, NULL, sign_commit)) + if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents, + &result_commit, NULL, sign_commit)) die(_("failed to write commit object")); strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, remoteheads, &result_commit, buf.buf); @@ -839,9 +860,7 @@ static int suggest_conflicts(void) struct strbuf msgbuf = STRBUF_INIT; filename = git_path_merge_msg(); - fp = fopen(filename, "a"); - if (!fp) - die_errno(_("Could not open '%s' for writing"), filename); + fp = xfopen(filename, "a"); append_conflicts_hint(&msgbuf); fputs(msgbuf.buf, fp); @@ -905,7 +924,7 @@ static int setup_with_upstream(const char ***argv) return i; } -static void write_merge_state(struct commit_list *remoteheads) +static void write_merge_heads(struct commit_list *remoteheads) { struct commit_list *j; struct strbuf buf = STRBUF_INIT; @@ -921,13 +940,19 @@ static void write_merge_state(struct commit_list *remoteheads) strbuf_addf(&buf, "%s\n", oid_to_hex(oid)); } write_file_buf(git_path_merge_head(), buf.buf, buf.len); - strbuf_addch(&merge_msg, '\n'); - write_file_buf(git_path_merge_msg(), merge_msg.buf, merge_msg.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); + strbuf_release(&buf); +} + +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); } static int default_edit_option(void) @@ -941,7 +966,7 @@ static int default_edit_option(void) return 0; if (e) { - int v = git_config_maybe_bool(name, e); + int v = git_parse_maybe_bool(e); if (v < 0) die(_("Bad value '%s' in environment '%s'"), e, name); return v; @@ -973,6 +998,7 @@ static struct commit_list *reduce_parents(struct commit *head_commit, /* Find what parents to record by checking independent ones. */ parents = reduce_heads(remoteheads); + free_commit_list(remoteheads); remoteheads = NULL; remotes = &remoteheads; @@ -1097,6 +1123,43 @@ static struct commit_list *collect_parents(struct commit *head_commit, return remoteheads; } +static int merging_a_throwaway_tag(struct commit *commit) +{ + char *tag_ref; + struct object_id oid; + int is_throwaway_tag = 0; + + /* Are we merging a tag? */ + if (!merge_remote_util(commit) || + !merge_remote_util(commit)->obj || + merge_remote_util(commit)->obj->type != OBJ_TAG) + return is_throwaway_tag; + + /* + * Now we know we are merging a tag object. Are we downstream + * and following the tags from upstream? If so, we must have + * the tag object pointed at by "refs/tags/$T" where $T is the + * tagname recorded in the tag object. We want to allow such + * a "just to catch up" merge to fast-forward. + * + * Otherwise, we are playing an integrator's role, making a + * merge with a throw-away tag from a contributor with + * something like "git pull $contributor $signed_tag". + * We want to forbid such a merge from fast-forwarding + * by default; otherwise we would not keep the signature + * anywhere. + */ + 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)) + is_throwaway_tag = 0; + else + is_throwaway_tag = 1; + free(tag_ref); + return is_throwaway_tag; +} + int cmd_merge(int argc, const char **argv, const char *prefix) { struct object_id result_tree, stash, head_oid; @@ -1117,13 +1180,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = branch_to_free = resolve_refdup("HEAD", 0, head_oid.hash, NULL); - if (branch && starts_with(branch, "refs/heads/")) - branch += 11; + branch = branch_to_free = resolve_refdup("HEAD", 0, &head_oid, NULL); + if (branch) + skip_prefix(branch, "refs/heads/", &branch); if (!branch || is_null_oid(&head_oid)) head_commit = NULL; else - head_commit = lookup_commit_or_die(head_oid.hash, "HEAD"); + head_commit = lookup_commit_or_die(&head_oid, "HEAD"); init_diff_ui_defaults(); git_config(git_merge_config, NULL); @@ -1235,8 +1298,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("Can merge only exactly one commit into empty head")); remote_head_oid = &remoteheads->item->object.oid; read_empty(remote_head_oid->hash, 0); - update_ref("initial pull", "HEAD", remote_head_oid->hash, - NULL, 0, UPDATE_REFS_DIE_ON_ERR); + update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); goto done; } @@ -1294,10 +1357,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) oid_to_hex(&commit->object.oid)); setenv(buf.buf, merge_remote_util(commit)->name, 1); strbuf_reset(&buf); - if (fast_forward != FF_ONLY && - merge_remote_util(commit) && - merge_remote_util(commit)->obj && - merge_remote_util(commit)->obj->type == OBJ_TAG) + if (fast_forward != FF_ONLY && merging_a_throwaway_tag(commit)) fast_forward = FF_NO; } @@ -1331,8 +1391,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) free(list); } - update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.oid.hash, - NULL, 0, UPDATE_REFS_DIE_ON_ERR); + update_ref("updating ORIG_HEAD", "ORIG_HEAD", + &head_commit->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); if (remoteheads && !common) { /* No common ancestors found. */ @@ -1346,7 +1406,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * If head can reach all the merge then we are up to date. * but first the most common case of merging one remote. */ - finish_up_to_date(_("Already up-to-date.")); + finish_up_to_date(_("Already up to date.")); goto done; } else if (fast_forward != FF_NO && !remoteheads->next && !common->next && @@ -1372,8 +1432,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } - if (checkout_fast_forward(head_commit->object.oid.hash, - commit->object.oid.hash, + if (checkout_fast_forward(&head_commit->object.oid, + &commit->object.oid, overwrite_ignore)) { ret = 1; goto done; @@ -1429,7 +1489,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } } if (up_to_date) { - finish_up_to_date(_("Already up-to-date. Yeeah!")); + finish_up_to_date(_("Already up to date. Yeeah!")); goto done; } } diff --git a/builtin/mktag.c b/builtin/mktag.c index 031b750f06..beb552847b 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -151,7 +151,7 @@ static int verify_tag(char *buffer, unsigned long size) int cmd_mktag(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; - unsigned char result_sha1[20]; + struct object_id result; if (argc != 1) usage("git mktag"); @@ -165,10 +165,10 @@ int cmd_mktag(int argc, const char **argv, const char *prefix) if (verify_tag(buf.buf, buf.len) < 0) die("invalid tag signature file"); - if (write_sha1_file(buf.buf, buf.len, tag_type, result_sha1) < 0) + if (write_object_file(buf.buf, buf.len, tag_type, &result) < 0) die("unable to write tag file"); strbuf_release(&buf); - printf("%s\n", sha1_to_hex(result_sha1)); + printf("%s\n", oid_to_hex(&result)); return 0; } diff --git a/builtin/mktree.c b/builtin/mktree.c index da0fd8cd70..f5f3c0eea1 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -40,7 +40,7 @@ static int ent_compare(const void *a_, const void *b_) b->name, b->len, b->mode); } -static void write_tree(unsigned char *sha1) +static void write_tree(struct object_id *oid) { struct strbuf buf; size_t size; @@ -57,7 +57,7 @@ static void write_tree(unsigned char *sha1) strbuf_add(&buf, ent->sha1, 20); } - write_sha1_file(buf.buf, buf.len, tree_type, sha1); + write_object_file(buf.buf, buf.len, tree_type, oid); strbuf_release(&buf); } @@ -112,7 +112,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss mode_type = object_type(mode); if (mode_type != type_from_string(ptr)) { die("entry '%s' object type (%s) doesn't match mode type (%s)", - path, ptr, typename(mode_type)); + path, ptr, type_name(mode_type)); } /* Check the type of object identified by sha1 */ @@ -131,7 +131,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss * because the new tree entry will never be correct. */ die("entry '%s' object %s is a %s but specified type was (%s)", - path, sha1_to_hex(sha1), typename(obj_type), typename(mode_type)); + path, sha1_to_hex(sha1), type_name(obj_type), type_name(mode_type)); } } @@ -142,7 +142,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss int cmd_mktree(int ac, const char **av, const char *prefix) { struct strbuf sb = STRBUF_INIT; - unsigned char sha1[20]; + struct object_id oid; int nul_term_line = 0; int allow_missing = 0; int is_batch_mode = 0; @@ -181,8 +181,8 @@ int cmd_mktree(int ac, const char **av, const char *prefix) */ ; /* skip creating an empty tree */ } else { - write_tree(sha1); - puts(sha1_to_hex(sha1)); + write_tree(&oid); + puts(oid_to_hex(&oid)); fflush(stdout); } used=0; /* reset tree entry buffer for re-use in batch mode */ diff --git a/builtin/mv.c b/builtin/mv.c index 61d20037ad..6d141f7a53 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -4,6 +4,7 @@ * Copyright (C) 2006 Johannes Schindelin */ #include "builtin.h" +#include "config.h" #include "pathspec.h" #include "lockfile.h" #include "dir.h" @@ -80,7 +81,7 @@ static void prepare_move_submodule(const char *src, int first, struct strbuf submodule_dotgit = STRBUF_INIT; if (!S_ISGITLINK(active_cache[first]->ce_mode)) die(_("Directory %s is in index and no submodule?"), src); - if (!is_staging_gitmodules_ok()) + if (!is_staging_gitmodules_ok(&the_index)) die(_("Please stage your changes to .gitmodules or stash them to proceed")); strbuf_addf(&submodule_dotgit, "%s/.git", src); *submodule_gitfile = read_gitfile(submodule_dotgit.buf); @@ -121,7 +122,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) struct option builtin_mv_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), OPT__DRY_RUN(&show_only, N_("dry run")), - OPT__FORCE(&force, N_("force move/rename even if target exists")), + OPT__FORCE(&force, N_("force move/rename even if target exists"), + PARSE_OPT_NOCOMPLETE), OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")), OPT_END(), }; @@ -130,7 +132,6 @@ int cmd_mv(int argc, const char **argv, const char *prefix) struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; - gitmodules_config(); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, builtin_mv_options, @@ -286,15 +287,14 @@ int cmd_mv(int argc, const char **argv, const char *prefix) pos = cache_name_pos(src, strlen(src)); assert(pos >= 0); - if (!show_only) - rename_cache_entry_at(pos, dst); + rename_cache_entry_at(pos, dst); } if (gitmodules_modified) - stage_updated_gitmodules(); + stage_updated_gitmodules(&the_index); - if (active_cache_changed && - write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock_file, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); return 0; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 1767af750d..9e088ebd11 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "commit.h" #include "tag.h" #include "refs.h" @@ -10,20 +11,20 @@ typedef struct rev_name { const char *tip_name; - unsigned long taggerdate; + timestamp_t taggerdate; int generation; int distance; int from_tag; } rev_name; -static long cutoff = LONG_MAX; +static timestamp_t cutoff = TIME_MAX; /* How many generations are maximally preferred over _one_ merge traversal? */ #define MERGE_TRAVERSAL_WEIGHT 65535 static int is_better_name(struct rev_name *name, const char *tip_name, - unsigned long taggerdate, + timestamp_t taggerdate, int generation, int distance, int from_tag) @@ -60,7 +61,7 @@ static int is_better_name(struct rev_name *name, } static void name_rev(struct commit *commit, - const char *tip_name, unsigned long taggerdate, + const char *tip_name, timestamp_t taggerdate, int generation, int distance, int from_tag, int deref) { @@ -129,7 +130,7 @@ static int subpath_matches(const char *path, const char *filter) const char *subpath = path; while (subpath) { - if (!wildmatch(filter, subpath, 0, NULL)) + if (!wildmatch(filter, subpath, 0)) return subpath - path; subpath = strchr(subpath, '/'); if (subpath) @@ -158,7 +159,7 @@ struct name_ref_data { static struct tip_table { struct tip_table_entry { - unsigned char sha1[20]; + struct object_id oid; const char *refname; } *table; int nr; @@ -166,13 +167,13 @@ static struct tip_table { int sorted; } tip_table; -static void add_to_tip_table(const unsigned char *sha1, const char *refname, +static void add_to_tip_table(const struct object_id *oid, const char *refname, int shorten_unambiguous) { refname = name_ref_abbrev(refname, shorten_unambiguous); ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); - hashcpy(tip_table.table[tip_table.nr].sha1, sha1); + oidcpy(&tip_table.table[tip_table.nr].oid, oid); tip_table.table[tip_table.nr].refname = xstrdup(refname); tip_table.nr++; tip_table.sorted = 0; @@ -181,16 +182,16 @@ static void add_to_tip_table(const unsigned char *sha1, const char *refname, static int tipcmp(const void *a_, const void *b_) { const struct tip_table_entry *a = a_, *b = b_; - return hashcmp(a->sha1, b->sha1); + return oidcmp(&a->oid, &b->oid); } static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data) { - struct object *o = parse_object(oid->hash); + struct object *o = parse_object(oid); struct name_ref_data *data = cb_data; int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; - unsigned long taggerdate = ULONG_MAX; + timestamp_t taggerdate = TIME_MAX; if (data->tags_only && !starts_with(path, "refs/tags/")) return 0; @@ -238,13 +239,13 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo return 0; } - add_to_tip_table(oid->hash, path, can_abbreviate_output); + 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.hash); + o = parse_object(&t->tagged->oid); deref = 1; taggerdate = t->date; } @@ -252,7 +253,7 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo struct commit *commit = (struct commit *)o; int from_tag = starts_with(path, "refs/tags/"); - if (taggerdate == ULONG_MAX) + 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, @@ -264,7 +265,7 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) { struct tip_table_entry *table = table_; - return table[ix].sha1; + return table[ix].oid.hash; } static const char *get_exact_ref_match(const struct object *o) @@ -349,9 +350,9 @@ static void name_rev_line(char *p, struct name_ref_data *data) #define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f')) if (!ishex(*p)) forty = 0; - else if (++forty == 40 && + else if (++forty == GIT_SHA1_HEXSZ && !ishex(*(p+1))) { - unsigned char sha1[40]; + struct object_id oid; const char *name = NULL; char c = *(p+1); int p_len = p - p_start + 1; @@ -359,9 +360,9 @@ static void name_rev_line(char *p, struct name_ref_data *data) forty = 0; *(p+1) = 0; - if (!get_sha1(p - 39, sha1)) { + if (!get_oid(p - (GIT_SHA1_HEXSZ - 1), &oid)) { struct object *o = - lookup_object(sha1); + lookup_object(oid.hash); if (o) name = get_rev_name(o, &buf); } @@ -371,7 +372,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) continue; if (data->name_only) - printf("%.*s%s", p_len - 40, p_start, name); + printf("%.*s%s", p_len - GIT_SHA1_HEXSZ, p_start, name); else printf("%.*s (%s)", p_len, p_start, name); p_start = p + 1; @@ -422,18 +423,18 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) cutoff = 0; for (; argc; argc--, argv++) { - unsigned char sha1[20]; + struct object_id oid; struct object *object; struct commit *commit; - if (get_sha1(*argv, sha1)) { + if (get_oid(*argv, &oid)) { fprintf(stderr, "Could not get sha1 for %s. Skipping.\n", *argv); continue; } commit = NULL; - object = parse_object(sha1); + object = parse_object(&oid); if (object) { struct object *peeled = deref_tag(object, *argv, 0); if (peeled && peeled->type == OBJ_COMMIT) @@ -493,5 +494,6 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) always, allow_undefined, data.name_only); } + UNLEAK(revs); return 0; } diff --git a/builtin/notes.c b/builtin/notes.c index fb856e53b6..6d2fda4a7d 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -8,10 +8,11 @@ */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "notes.h" #include "blob.h" -#include "commit.h" +#include "pretty.h" #include "refs.h" #include "exec_cmd.h" #include "run-command.h" @@ -32,7 +33,7 @@ static const char * const git_notes_usage[] = { N_("git notes merge --commit [-v | -q]"), N_("git notes merge --abort [-v | -q]"), N_("git notes [--ref <notes-ref>] remove [<object>...]"), - N_("git notes [--ref <notes-ref>] prune [-n | -v]"), + N_("git notes [--ref <notes-ref>] prune [-n] [-v]"), N_("git notes [--ref <notes-ref>] get-ref"), NULL }; @@ -109,11 +110,11 @@ static void free_note_data(struct note_data *d) strbuf_release(&d->buf); } -static int list_each_note(const unsigned char *object_sha1, - const unsigned char *note_sha1, char *note_path, +static int list_each_note(const struct object_id *object_oid, + const struct object_id *note_oid, char *note_path, void *cb_data) { - printf("%s %s\n", sha1_to_hex(note_sha1), sha1_to_hex(object_sha1)); + printf("%s %s\n", oid_to_hex(note_oid), oid_to_hex(object_oid)); return 0; } @@ -129,10 +130,10 @@ static void copy_obj_to_fd(int fd, const unsigned char *sha1) } } -static void write_commented_object(int fd, const unsigned char *object) +static void write_commented_object(int fd, const struct object_id *object) { const char *show_args[5] = - {"show", "--stat", "--no-notes", sha1_to_hex(object), NULL}; + {"show", "--stat", "--no-notes", oid_to_hex(object), NULL}; struct child_process show = CHILD_PROCESS_INIT; struct strbuf buf = STRBUF_INIT; struct strbuf cbuf = STRBUF_INIT; @@ -145,7 +146,7 @@ static void write_commented_object(int fd, const unsigned char *object) show.git_cmd = 1; if (start_command(&show)) die(_("unable to start 'show' for object '%s'"), - sha1_to_hex(object)); + oid_to_hex(object)); if (strbuf_read(&buf, show.out, 0) < 0) die_errno(_("could not read 'show' output")); @@ -157,10 +158,10 @@ static void write_commented_object(int fd, const unsigned char *object) if (finish_command(&show)) die(_("failed to finish 'show' for object '%s'"), - sha1_to_hex(object)); + oid_to_hex(object)); } -static void prepare_note_data(const unsigned char *object, struct note_data *d, +static void prepare_note_data(const struct object_id *object, struct note_data *d, const unsigned char *old_note) { if (d->use_editor || !d->given) { @@ -197,9 +198,9 @@ static void prepare_note_data(const unsigned char *object, struct note_data *d, } } -static void write_note_data(struct note_data *d, unsigned char *sha1) +static void write_note_data(struct note_data *d, struct object_id *oid) { - if (write_sha1_file(d->buf.buf, d->buf.len, blob_type, sha1)) { + if (write_object_file(d->buf.buf, d->buf.len, blob_type, oid)) { error(_("unable to write note object")); if (d->edit_path) error(_("the note contents have been left in %s"), @@ -243,16 +244,16 @@ static int parse_reuse_arg(const struct option *opt, const char *arg, int unset) { struct note_data *d = opt->value; char *buf; - unsigned char object[20]; + struct object_id object; enum object_type type; unsigned long len; if (d->buf.len) strbuf_addch(&d->buf, '\n'); - if (get_sha1(arg, object)) + if (get_oid(arg, &object)) die(_("failed to resolve '%s' as a valid ref."), arg); - if (!(buf = read_sha1_file(object, &type, &len))) { + if (!(buf = read_sha1_file(object.hash, &type, &len))) { free(buf); die(_("failed to read object '%s'."), arg); } @@ -292,7 +293,7 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) } while (strbuf_getline_lf(&buf, stdin) != EOF) { - unsigned char from_obj[20], to_obj[20]; + struct object_id from_obj, to_obj; struct strbuf **split; int err; @@ -301,15 +302,15 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) die(_("malformed input line: '%s'."), buf.buf); strbuf_rtrim(split[0]); strbuf_rtrim(split[1]); - if (get_sha1(split[0]->buf, from_obj)) + if (get_oid(split[0]->buf, &from_obj)) die(_("failed to resolve '%s' as a valid ref."), split[0]->buf); - if (get_sha1(split[1]->buf, to_obj)) + if (get_oid(split[1]->buf, &to_obj)) die(_("failed to resolve '%s' as a valid ref."), split[1]->buf); if (rewrite_cmd) - err = copy_note_for_rewrite(c, from_obj, to_obj); + err = copy_note_for_rewrite(c, &from_obj, &to_obj); else - err = copy_note(t, from_obj, to_obj, force, + err = copy_note(t, &from_obj, &to_obj, force, combine_notes_overwrite); if (err) { @@ -327,6 +328,7 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) } else { finish_copy_notes_for_rewrite(c, msg); } + strbuf_release(&buf); return ret; } @@ -352,8 +354,8 @@ static struct notes_tree *init_notes_check(const char *subcommand, static int list(int argc, const char **argv, const char *prefix) { struct notes_tree *t; - unsigned char object[20]; - const unsigned char *note; + struct object_id object; + const struct object_id *note; int retval = -1; struct option options[] = { OPT_END() @@ -370,15 +372,15 @@ static int list(int argc, const char **argv, const char *prefix) t = init_notes_check("list", 0); if (argc) { - if (get_sha1(argv[0], object)) + if (get_oid(argv[0], &object)) die(_("failed to resolve '%s' as a valid ref."), argv[0]); - note = get_note(t, object); + note = get_note(t, &object); if (note) { - puts(sha1_to_hex(note)); + puts(oid_to_hex(note)); retval = 0; } else retval = error(_("no note found for object %s."), - sha1_to_hex(object)); + oid_to_hex(&object)); } else retval = for_each_note(t, 0, list_each_note, NULL); @@ -393,8 +395,8 @@ static int add(int argc, const char **argv, const char *prefix) int force = 0, allow_empty = 0; const char *object_ref; struct notes_tree *t; - unsigned char object[20], new_note[20]; - const unsigned char *note; + struct object_id object, new_note; + const struct object_id *note; struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct option options[] = { { OPTION_CALLBACK, 'm', "message", &d, N_("message"), @@ -411,7 +413,7 @@ static int add(int argc, const char **argv, const char *prefix) parse_reuse_arg}, OPT_BOOL(0, "allow-empty", &allow_empty, N_("allow storing empty note")), - OPT__FORCE(&force, N_("replace existing notes")), + OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE), OPT_END() }; @@ -425,11 +427,11 @@ static int add(int argc, const char **argv, const char *prefix) object_ref = argc > 1 ? argv[1] : "HEAD"; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die(_("failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("add", NOTES_INIT_WRITABLE); - note = get_note(t, object); + note = get_note(t, &object); if (note) { if (!force) { @@ -439,7 +441,7 @@ static int add(int argc, const char **argv, const char *prefix) return error(_("Cannot add notes. " "Found existing notes for object %s. " "Use '-f' to overwrite existing notes"), - sha1_to_hex(object)); + oid_to_hex(&object)); } /* * Redirect to "edit" subcommand. @@ -452,19 +454,19 @@ static int add(int argc, const char **argv, const char *prefix) return append_edit(argc, argv, prefix); } fprintf(stderr, _("Overwriting existing notes for object %s\n"), - sha1_to_hex(object)); + oid_to_hex(&object)); } - prepare_note_data(object, &d, note); + prepare_note_data(&object, &d, note ? note->hash : NULL); if (d.buf.len || allow_empty) { - write_note_data(&d, new_note); - if (add_note(t, object, new_note, combine_notes_overwrite)) + 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'"); } else { fprintf(stderr, _("Removing note for object %s\n"), - sha1_to_hex(object)); - remove_note(t, object); + oid_to_hex(&object)); + remove_note(t, object.hash); commit_notes(t, "Notes removed by 'git notes add'"); } @@ -476,13 +478,13 @@ static int add(int argc, const char **argv, const char *prefix) static int copy(int argc, const char **argv, const char *prefix) { int retval = 0, force = 0, from_stdin = 0; - const unsigned char *from_note, *note; + const struct object_id *from_note, *note; const char *object_ref; - unsigned char object[20], from_obj[20]; + struct object_id object, from_obj; struct notes_tree *t; const char *rewrite_cmd = NULL; struct option options[] = { - OPT__FORCE(&force, N_("replace existing notes")), + OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE), OPT_BOOL(0, "stdin", &from_stdin, N_("read objects from stdin")), OPT_STRING(0, "for-rewrite", &rewrite_cmd, N_("command"), N_("load rewriting config for <command> (implies " @@ -511,37 +513,37 @@ static int copy(int argc, const char **argv, const char *prefix) usage_with_options(git_notes_copy_usage, options); } - if (get_sha1(argv[0], from_obj)) + if (get_oid(argv[0], &from_obj)) die(_("failed to resolve '%s' as a valid ref."), argv[0]); object_ref = 1 < argc ? argv[1] : "HEAD"; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die(_("failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("copy", NOTES_INIT_WRITABLE); - note = get_note(t, object); + note = get_note(t, &object); if (note) { if (!force) { retval = error(_("Cannot copy notes. Found existing " "notes for object %s. Use '-f' to " "overwrite existing notes"), - sha1_to_hex(object)); + oid_to_hex(&object)); goto out; } fprintf(stderr, _("Overwriting existing notes for object %s\n"), - sha1_to_hex(object)); + oid_to_hex(&object)); } - from_note = get_note(t, from_obj); + from_note = get_note(t, &from_obj); if (!from_note) { retval = error(_("missing notes on source object %s. Cannot " - "copy."), sha1_to_hex(from_obj)); + "copy."), oid_to_hex(&from_obj)); goto out; } - if (add_note(t, object, from_note, combine_notes_overwrite)) + 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'"); out: @@ -554,8 +556,8 @@ static int append_edit(int argc, const char **argv, const char *prefix) int allow_empty = 0; const char *object_ref; struct notes_tree *t; - unsigned char object[20], new_note[20]; - const unsigned char *note; + struct object_id object, new_note; + const struct object_id *note; char *logmsg; const char * const *usage; struct note_data d = { 0, 0, NULL, STRBUF_INIT }; @@ -594,19 +596,19 @@ static int append_edit(int argc, const char **argv, const char *prefix) object_ref = 1 < argc ? argv[1] : "HEAD"; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die(_("failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check(argv[0], NOTES_INIT_WRITABLE); - note = get_note(t, object); + note = get_note(t, &object); - prepare_note_data(object, &d, edit ? note : NULL); + prepare_note_data(&object, &d, edit && note ? note->hash : NULL); if (note && !edit) { /* Append buf to previous note contents */ unsigned long size; enum object_type type; - char *prev_buf = read_sha1_file(note, &type, &size); + char *prev_buf = read_sha1_file(note->hash, &type, &size); strbuf_grow(&d.buf, size + 1); if (d.buf.len && prev_buf && size) @@ -617,14 +619,14 @@ 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)) + write_note_data(&d, &new_note); + if (add_note(t, &object, &new_note, combine_notes_overwrite)) die("BUG: combine_notes_overwrite failed"); logmsg = xstrfmt("Notes added by 'git notes %s'", argv[0]); } else { fprintf(stderr, _("Removing note for object %s\n"), - sha1_to_hex(object)); - remove_note(t, object); + oid_to_hex(&object)); + remove_note(t, object.hash); logmsg = xstrfmt("Notes removed by 'git notes %s'", argv[0]); } commit_notes(t, logmsg); @@ -639,8 +641,8 @@ static int show(int argc, const char **argv, const char *prefix) { const char *object_ref; struct notes_tree *t; - unsigned char object[20]; - const unsigned char *note; + struct object_id object; + const struct object_id *note; int retval; struct option options[] = { OPT_END() @@ -656,17 +658,17 @@ static int show(int argc, const char **argv, const char *prefix) object_ref = argc ? argv[0] : "HEAD"; - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die(_("failed to resolve '%s' as a valid ref."), object_ref); t = init_notes_check("show", 0); - note = get_note(t, object); + note = get_note(t, &object); if (!note) retval = error(_("no note found for object %s."), - sha1_to_hex(object)); + oid_to_hex(&object)); else { - const char *show_args[3] = {"show", sha1_to_hex(note), NULL}; + const char *show_args[3] = {"show", oid_to_hex(note), NULL}; retval = execv_git_cmd(show_args); } free_notes(t); @@ -684,7 +686,7 @@ static int merge_abort(struct notes_merge_options *o) if (delete_ref(NULL, "NOTES_MERGE_PARTIAL", NULL, 0)) ret += error(_("failed to delete ref NOTES_MERGE_PARTIAL")); - if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NODEREF)) + if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NO_DEREF)) ret += error(_("failed to delete ref NOTES_MERGE_REF")); if (notes_merge_abort(o)) ret += error(_("failed to remove 'git notes merge' worktree")); @@ -708,7 +710,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.hash))) + else if (!(partial = lookup_commit_reference(&oid))) die(_("could not find commit from NOTES_MERGE_PARTIAL.")); else if (parse_commit(partial)) die(_("could not parse commit from NOTES_MERGE_PARTIAL.")); @@ -722,11 +724,11 @@ static int merge_commit(struct notes_merge_options *o) init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); o->local_ref = local_ref_to_free = - resolve_refdup("NOTES_MERGE_REF", 0, oid.hash, NULL); + resolve_refdup("NOTES_MERGE_REF", 0, &oid, NULL); if (!o->local_ref) die(_("failed to resolve NOTES_MERGE_REF")); - if (notes_merge_commit(o, t, partial, oid.hash)) + if (notes_merge_commit(o, t, partial, &oid)) die(_("failed to finalize notes merge")); /* Reuse existing commit message in reflog message */ @@ -734,8 +736,8 @@ static int merge_commit(struct notes_merge_options *o) format_commit_message(partial, "%s", &msg, &pretty_ctx); strbuf_trim(&msg); strbuf_insert(&msg, 0, "notes: ", 7); - update_ref(msg.buf, o->local_ref, oid.hash, - is_null_oid(&parent_oid) ? NULL : parent_oid.hash, + update_ref(msg.buf, o->local_ref, &oid, + is_null_oid(&parent_oid) ? NULL : &parent_oid, 0, UPDATE_REFS_DIE_ON_ERR); free_notes(t); @@ -762,7 +764,7 @@ static int git_config_get_notes_strategy(const char *key, static int merge(int argc, const char **argv, const char *prefix) { struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; - unsigned char result_sha1[20]; + struct object_id result_oid; struct notes_tree *t; struct notes_merge_options o; int do_merge = 0, do_commit = 0, do_abort = 0; @@ -844,16 +846,16 @@ static int merge(int argc, const char **argv, const char *prefix) remote_ref.buf, default_notes_ref()); strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */ - result = notes_merge(&o, t, result_sha1); + result = notes_merge(&o, t, &result_oid); - if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ + if (result >= 0) /* Merge resulted (trivially) in result_oid */ /* Update default notes ref with new commit */ - update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, - 0, UPDATE_REFS_DIE_ON_ERR); + update_ref(msg.buf, default_notes_ref(), &result_oid, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); else { /* Merge has unresolved conflicts */ const struct worktree *wt; /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ - update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, + update_ref(msg.buf, "NOTES_MERGE_PARTIAL", &result_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); @@ -863,10 +865,10 @@ static int merge(int argc, const char **argv, const char *prefix) if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die(_("failed to store link to current notes ref (%s)"), default_notes_ref()); - printf(_("Automatic notes merge failed. Fix conflicts in %s and " - "commit the result with 'git notes merge --commit', or " - "abort the merge with 'git notes merge --abort'.\n"), - git_path(NOTES_MERGE_WORKTREE)); + fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s " + "and commit the result with 'git notes merge --commit', " + "or abort the merge with 'git notes merge --abort'.\n"), + git_path(NOTES_MERGE_WORKTREE)); } free_notes(t); @@ -880,10 +882,10 @@ static int merge(int argc, const char **argv, const char *prefix) static int remove_one_note(struct notes_tree *t, const char *name, unsigned flag) { int status; - unsigned char sha1[20]; - if (get_sha1(name, sha1)) + struct object_id oid; + if (get_oid(name, &oid)) return error(_("Failed to resolve '%s' as a valid ref."), name); - status = remove_note(t, sha1); + status = remove_note(t, oid.hash); if (status) fprintf(stderr, _("Object %s has no note\n"), name); else diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 50e01aa80e..e9d3cfb9e3 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "attr.h" #include "object.h" #include "blob.h" @@ -14,6 +15,8 @@ #include "diff.h" #include "revision.h" #include "list-objects.h" +#include "list-objects-filter.h" +#include "list-objects-filter-options.h" #include "pack-objects.h" #include "progress.h" #include "refs.h" @@ -23,7 +26,8 @@ #include "reachable.h" #include "sha1-array.h" #include "argv-array.h" -#include "mru.h" +#include "list.h" +#include "packfile.h" static const char *pack_usage[] = { N_("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]"), @@ -44,7 +48,7 @@ static uint32_t nr_result, nr_written; static int non_empty; static int reuse_delta = 1, reuse_object = 1; static int keep_unreachable, unpack_unreachable, include_tag; -static unsigned long unpack_unreachable_expiration; +static timestamp_t unpack_unreachable_expiration; static int pack_loose_unreachable; static int local; static int have_non_local_packs; @@ -71,12 +75,24 @@ static int use_bitmap_index = -1; static int write_bitmap_index; static uint16_t write_bitmap_options; +static int exclude_promisor_objects; + static unsigned long delta_cache_size = 0; static unsigned long max_delta_cache_size = 256 * 1024 * 1024; static unsigned long cache_max_small_delta_size = 1000; static unsigned long window_memory_limit = 0; +static struct list_objects_filter_options filter_options; + +enum missing_action { + MA_ERROR = 0, /* fail if any missing objects are encountered */ + MA_ALLOW_ANY, /* silently allow ALL missing objects */ + MA_ALLOW_PROMISOR, /* silently allow all missing PROMISOR objects */ +}; +static enum missing_action arg_missing_action; +static show_object_fn fn_show_object; + /* * stats */ @@ -106,12 +122,14 @@ static void *get_delta(struct object_entry *entry) void *buf, *base_buf, *delta_buf; enum object_type type; - buf = read_sha1_file(entry->idx.sha1, &type, &size); + buf = read_sha1_file(entry->idx.oid.hash, &type, &size); if (!buf) - die("unable to read %s", sha1_to_hex(entry->idx.sha1)); - base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size); + die("unable to read %s", oid_to_hex(&entry->idx.oid)); + base_buf = read_sha1_file(entry->delta->idx.oid.hash, &type, + &base_size); if (!base_buf) - die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1)); + die("unable to read %s", + oid_to_hex(&entry->delta->idx.oid)); delta_buf = diff_delta(base_buf, base_size, buf, size, &delta_size, 0); if (!delta_buf || delta_size != entry->delta_size) @@ -146,8 +164,8 @@ static unsigned long do_compress(void **pptr, unsigned long size) return stream.total_out; } -static unsigned long write_large_blob_data(struct git_istream *st, struct sha1file *f, - const unsigned char *sha1) +static unsigned long write_large_blob_data(struct git_istream *st, struct hashfile *f, + const struct object_id *oid) { git_zstream stream; unsigned char ibuf[1024 * 16]; @@ -161,7 +179,7 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct sha1fi int zret = Z_OK; readlen = read_istream(st, ibuf, sizeof(ibuf)); if (readlen == -1) - die(_("unable to read %s"), sha1_to_hex(sha1)); + die(_("unable to read %s"), oid_to_hex(oid)); stream.next_in = ibuf; stream.avail_in = readlen; @@ -170,7 +188,7 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct sha1fi stream.next_out = obuf; stream.avail_out = sizeof(obuf); zret = git_deflate(&stream, readlen ? 0 : Z_FINISH); - sha1write(f, obuf, stream.next_out - obuf); + hashwrite(f, obuf, stream.next_out - obuf); olen += stream.next_out - obuf; } if (stream.avail_in) @@ -215,7 +233,7 @@ static int check_pack_inflate(struct packed_git *p, stream.total_in == len) ? 0 : -1; } -static void copy_pack_data(struct sha1file *f, +static void copy_pack_data(struct hashfile *f, struct packed_git *p, struct pack_window **w_curs, off_t offset, @@ -228,14 +246,14 @@ static void copy_pack_data(struct sha1file *f, in = use_pack(p, w_curs, offset, &avail); if (avail > len) avail = (unsigned long)len; - sha1write(f, in, avail); + hashwrite(f, in, avail); offset += avail; len -= avail; } } /* Return 0 if we will bust the pack-size limit */ -static unsigned long write_no_reuse_object(struct sha1file *f, struct object_entry *entry, +static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entry *entry, unsigned long limit, int usable_delta) { unsigned long size, datalen; @@ -249,19 +267,20 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent if (!usable_delta) { if (entry->type == OBJ_BLOB && entry->size > big_file_threshold && - (st = open_istream(entry->idx.sha1, &type, &size, NULL)) != NULL) + (st = open_istream(entry->idx.oid.hash, &type, &size, NULL)) != NULL) buf = NULL; else { - buf = read_sha1_file(entry->idx.sha1, &type, &size); + buf = read_sha1_file(entry->idx.oid.hash, &type, + &size); if (!buf) - die(_("unable to read %s"), sha1_to_hex(entry->idx.sha1)); + die(_("unable to read %s"), + oid_to_hex(&entry->idx.oid)); } /* * make sure no cached delta data remains from a * previous attempt before a pack split occurred. */ - free(entry->delta_data); - entry->delta_data = NULL; + FREE_AND_NULL(entry->delta_data); entry->z_delta_size = 0; } else if (entry->delta_data) { size = entry->delta_size; @@ -307,8 +326,8 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent free(buf); return 0; } - sha1write(f, header, hdrlen); - sha1write(f, dheader + pos, sizeof(dheader) - pos); + hashwrite(f, header, hdrlen); + hashwrite(f, dheader + pos, sizeof(dheader) - pos); hdrlen += sizeof(dheader) - pos; } else if (type == OBJ_REF_DELTA) { /* @@ -321,8 +340,8 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent free(buf); return 0; } - sha1write(f, header, hdrlen); - sha1write(f, entry->delta->idx.sha1, 20); + hashwrite(f, header, hdrlen); + hashwrite(f, entry->delta->idx.oid.hash, 20); hdrlen += 20; } else { if (limit && hdrlen + datalen + 20 >= limit) { @@ -331,13 +350,13 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent free(buf); return 0; } - sha1write(f, header, hdrlen); + hashwrite(f, header, hdrlen); } if (st) { - datalen = write_large_blob_data(st, f, entry->idx.sha1); + datalen = write_large_blob_data(st, f, &entry->idx.oid); close_istream(st); } else { - sha1write(f, buf, datalen); + hashwrite(f, buf, datalen); free(buf); } @@ -345,7 +364,7 @@ static unsigned long write_no_reuse_object(struct sha1file *f, struct object_ent } /* Return 0 if we will bust the pack-size limit */ -static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry, +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; @@ -369,7 +388,8 @@ static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry, 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", sha1_to_hex(entry->idx.sha1)); + 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); } @@ -379,7 +399,8 @@ static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry, if (!pack_to_stdout && p->index_version == 1 && check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) { - error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1)); + 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); } @@ -394,8 +415,8 @@ static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry, unuse_pack(&w_curs); return 0; } - sha1write(f, header, hdrlen); - sha1write(f, dheader + pos, sizeof(dheader) - pos); + hashwrite(f, header, hdrlen); + hashwrite(f, dheader + pos, sizeof(dheader) - pos); hdrlen += sizeof(dheader) - pos; reused_delta++; } else if (type == OBJ_REF_DELTA) { @@ -403,8 +424,8 @@ static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry, unuse_pack(&w_curs); return 0; } - sha1write(f, header, hdrlen); - sha1write(f, entry->delta->idx.sha1, 20); + hashwrite(f, header, hdrlen); + hashwrite(f, entry->delta->idx.oid.hash, 20); hdrlen += 20; reused_delta++; } else { @@ -412,7 +433,7 @@ static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry, unuse_pack(&w_curs); return 0; } - sha1write(f, header, hdrlen); + hashwrite(f, header, hdrlen); } copy_pack_data(f, p, &w_curs, offset, datalen); unuse_pack(&w_curs); @@ -421,7 +442,7 @@ static off_t write_reuse_object(struct sha1file *f, struct object_entry *entry, } /* Return 0 if we will bust the pack-size limit */ -static off_t write_object(struct sha1file *f, +static off_t write_object(struct hashfile *f, struct object_entry *entry, off_t write_offset) { @@ -494,7 +515,7 @@ enum write_one_status { WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */ }; -static enum write_one_status write_one(struct sha1file *f, +static enum write_one_status write_one(struct hashfile *f, struct object_entry *e, off_t *offset) { @@ -509,7 +530,7 @@ static enum write_one_status write_one(struct sha1file *f, recursing = (e->idx.offset == 1); if (recursing) { warning("recursive delta detected for object %s", - sha1_to_hex(e->idx.sha1)); + oid_to_hex(&e->idx.oid)); return WRITE_ONE_RECURSIVE; } else if (e->idx.offset || e->preferred_base) { /* offset is non zero if object is written already. */ @@ -550,13 +571,13 @@ static enum write_one_status write_one(struct sha1file *f, static int mark_tagged(const char *path, const struct object_id *oid, int flag, void *cb_data) { - unsigned char peeled[20]; + struct object_id peeled; struct object_entry *entry = packlist_find(&to_pack, oid->hash, NULL); if (entry) entry->tagged = 1; - if (!peel_ref(path, peeled)) { - entry = packlist_find(&to_pack, peeled, NULL); + if (!peel_ref(path, &peeled)) { + entry = packlist_find(&to_pack, peeled.hash, NULL); if (entry) entry->tagged = 1; } @@ -713,7 +734,7 @@ static struct object_entry **compute_write_order(void) return wo; } -static off_t write_reused_pack(struct sha1file *f) +static off_t write_reused_pack(struct hashfile *f) { unsigned char buffer[8192]; off_t to_write, total; @@ -744,7 +765,7 @@ static off_t write_reused_pack(struct sha1file *f) if (read_pack > to_write) read_pack = to_write; - sha1write(f, buffer, read_pack); + hashwrite(f, buffer, read_pack); to_write -= read_pack; /* @@ -773,7 +794,7 @@ static const char no_split_warning[] = N_( static void write_pack_file(void) { uint32_t i = 0, j; - struct sha1file *f; + struct hashfile *f; off_t offset; uint32_t nr_remaining = nr_result; time_t last_mtime = 0; @@ -785,11 +806,11 @@ static void write_pack_file(void) write_order = compute_write_order(); do { - unsigned char sha1[20]; + struct object_id oid; char *pack_tmp_name = NULL; if (pack_to_stdout) - f = sha1fd_throughput(1, "<stdout>", progress_state); + f = hashfd_throughput(1, "<stdout>", progress_state); else f = create_tmp_packfile(&pack_tmp_name); @@ -816,13 +837,13 @@ static void write_pack_file(void) * If so, rewrite it like in fast-import */ if (pack_to_stdout) { - sha1close(f, sha1, CSUM_CLOSE); + hashclose(f, oid.hash, CSUM_CLOSE); } else if (nr_written == nr_remaining) { - sha1close(f, sha1, CSUM_FSYNC); + hashclose(f, oid.hash, CSUM_FSYNC); } else { - int fd = sha1close(f, sha1, 0); - fixup_pack_header_footer(fd, sha1, pack_tmp_name, - nr_written, sha1, offset); + int fd = hashclose(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)); @@ -856,16 +877,16 @@ static void write_pack_file(void) strbuf_addf(&tmpname, "%s-", base_name); if (write_bitmap_index) { - bitmap_writer_set_checksum(sha1); + bitmap_writer_set_checksum(oid.hash); bitmap_writer_build_type_index(written_list, nr_written); } finish_tmp_packfile(&tmpname, pack_tmp_name, written_list, nr_written, - &pack_idx_opts, sha1); + &pack_idx_opts, oid.hash); if (write_bitmap_index) { - strbuf_addf(&tmpname, "%s.bitmap", sha1_to_hex(sha1)); + strbuf_addf(&tmpname, "%s.bitmap", oid_to_hex(&oid)); stop_progress(&progress_state); @@ -880,7 +901,7 @@ static void write_pack_file(void) strbuf_release(&tmpname); free(pack_tmp_name); - puts(sha1_to_hex(sha1)); + puts(oid_to_hex(&oid)); } /* mark written objects as written to previous pack */ @@ -921,13 +942,13 @@ static int no_try_delta(const char *path) * found the item, since that saves us from having to look it up again a * few lines later when we want to add the new entry. */ -static int have_duplicate_entry(const unsigned char *sha1, +static int have_duplicate_entry(const struct object_id *oid, int exclude, uint32_t *index_pos) { struct object_entry *entry; - entry = packlist_find(&to_pack, sha1, index_pos); + entry = packlist_find(&to_pack, oid->hash, index_pos); if (!entry) return 0; @@ -983,15 +1004,15 @@ static int want_found_object(int exclude, struct packed_git *p) * function finds if there is any pack that has the object and returns the pack * and its offset in these variables. */ -static int want_object_in_pack(const unsigned char *sha1, +static int want_object_in_pack(const struct object_id *oid, int exclude, struct packed_git **found_pack, off_t *found_offset) { - struct mru_entry *entry; int want; + struct list_head *pos; - if (!exclude && local && has_loose_object_nonlocal(sha1)) + if (!exclude && local && has_loose_object_nonlocal(oid->hash)) return 0; /* @@ -1005,14 +1026,14 @@ static int want_object_in_pack(const unsigned char *sha1, return want; } - for (entry = packed_git_mru->head; entry; entry = entry->next) { - struct packed_git *p = entry->item; + list_for_each(pos, &packed_git_mru) { + struct packed_git *p = list_entry(pos, struct packed_git, mru); off_t offset; if (p == *found_pack) offset = *found_offset; else - offset = find_pack_entry_one(sha1, p); + offset = find_pack_entry_one(oid->hash, p); if (offset) { if (!*found_pack) { @@ -1023,7 +1044,7 @@ static int want_object_in_pack(const unsigned char *sha1, } want = want_found_object(exclude, p); if (!exclude && want > 0) - mru_mark(packed_git_mru, entry); + list_move(&p->mru, &packed_git_mru); if (want != -1) return want; } @@ -1032,7 +1053,7 @@ static int want_object_in_pack(const unsigned char *sha1, return 1; } -static void create_object_entry(const unsigned char *sha1, +static void create_object_entry(const struct object_id *oid, enum object_type type, uint32_t hash, int exclude, @@ -1043,7 +1064,7 @@ static void create_object_entry(const unsigned char *sha1, { struct object_entry *entry; - entry = packlist_alloc(&to_pack, sha1, index_pos); + entry = packlist_alloc(&to_pack, oid->hash, index_pos); entry->hash = hash; if (type) entry->type = type; @@ -1063,17 +1084,17 @@ static const char no_closure_warning[] = N_( "disabling bitmap writing, as some objects are not being packed" ); -static int add_object_entry(const unsigned char *sha1, enum object_type type, +static int add_object_entry(const struct object_id *oid, enum object_type type, const char *name, int exclude) { struct packed_git *found_pack = NULL; off_t found_offset = 0; uint32_t index_pos; - if (have_duplicate_entry(sha1, exclude, &index_pos)) + if (have_duplicate_entry(oid, exclude, &index_pos)) return 0; - if (!want_object_in_pack(sha1, exclude, &found_pack, &found_offset)) { + 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)); @@ -1082,7 +1103,7 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type, return 0; } - create_object_entry(sha1, type, pack_name_hash(name), + create_object_entry(oid, type, pack_name_hash(name), exclude, name && no_try_delta(name), index_pos, found_pack, found_offset); @@ -1090,27 +1111,27 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type, return 1; } -static int add_object_entry_from_bitmap(const unsigned char *sha1, +static int add_object_entry_from_bitmap(const struct object_id *oid, enum object_type type, int flags, uint32_t name_hash, struct packed_git *pack, off_t offset) { uint32_t index_pos; - if (have_duplicate_entry(sha1, 0, &index_pos)) + if (have_duplicate_entry(oid, 0, &index_pos)) return 0; - if (!want_object_in_pack(sha1, 0, &pack, &offset)) + if (!want_object_in_pack(oid, 0, &pack, &offset)) return 0; - create_object_entry(sha1, type, name_hash, 0, 0, index_pos, pack, offset); + create_object_entry(oid, type, name_hash, 0, 0, index_pos, pack, offset); display_progress(progress_state, nr_result); return 1; } struct pbase_tree_cache { - unsigned char sha1[20]; + struct object_id oid; int ref; int temporary; void *tree_data; @@ -1118,9 +1139,9 @@ struct pbase_tree_cache { }; static struct pbase_tree_cache *(pbase_tree_cache[256]); -static int pbase_tree_cache_ix(const unsigned char *sha1) +static int pbase_tree_cache_ix(const struct object_id *oid) { - return sha1[0] % ARRAY_SIZE(pbase_tree_cache); + return oid->hash[0] % ARRAY_SIZE(pbase_tree_cache); } static int pbase_tree_cache_ix_incr(int ix) { @@ -1137,14 +1158,14 @@ static struct pbase_tree { struct pbase_tree_cache pcache; } *pbase_tree; -static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) +static struct pbase_tree_cache *pbase_tree_get(const struct object_id *oid) { struct pbase_tree_cache *ent, *nent; void *data; unsigned long size; enum object_type type; int neigh; - int my_ix = pbase_tree_cache_ix(sha1); + int my_ix = pbase_tree_cache_ix(oid); int available_ix = -1; /* pbase-tree-cache acts as a limited hashtable. @@ -1153,7 +1174,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) */ for (neigh = 0; neigh < 8; neigh++) { ent = pbase_tree_cache[my_ix]; - if (ent && !hashcmp(ent->sha1, sha1)) { + if (ent && !oidcmp(&ent->oid, oid)) { ent->ref++; return ent; } @@ -1169,7 +1190,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) /* Did not find one. Either we got a bogus request or * we need to read and perhaps cache. */ - data = read_sha1_file(sha1, &type, &size); + data = read_sha1_file(oid->hash, &type, &size); if (!data) return NULL; if (type != OBJ_TREE) { @@ -1195,7 +1216,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) free(ent->tree_data); nent = ent; } - hashcpy(nent->sha1, sha1); + oidcpy(&nent->oid, oid); nent->tree_data = data; nent->tree_size = size; nent->ref = 1; @@ -1240,7 +1261,7 @@ static void add_pbase_object(struct tree_desc *tree, if (cmp < 0) return; if (name[cmplen] != '/') { - add_object_entry(entry.oid->hash, + add_object_entry(entry.oid, object_type(entry.mode), fullname, 1); return; @@ -1251,7 +1272,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->hash); + tree = pbase_tree_get(entry.oid); if (!tree) return; init_tree_desc(&sub, tree->tree_data, tree->tree_size); @@ -1270,7 +1291,7 @@ static int done_pbase_path_pos(unsigned hash) int lo = 0; int hi = done_pbase_paths_num; while (lo < hi) { - int mi = (hi + lo) / 2; + int mi = lo + (hi - lo) / 2; if (done_pbase_paths[mi] == hash) return mi; if (done_pbase_paths[mi] < hash) @@ -1283,7 +1304,7 @@ static int done_pbase_path_pos(unsigned hash) static int check_pbase_path(unsigned hash) { - int pos = (!done_pbase_paths) ? -1 : done_pbase_path_pos(hash); + int pos = done_pbase_path_pos(hash); if (0 <= pos) return 1; pos = -pos - 1; @@ -1292,9 +1313,8 @@ static int check_pbase_path(unsigned hash) done_pbase_paths_alloc); done_pbase_paths_num++; if (pos < done_pbase_paths_num) - memmove(done_pbase_paths + pos + 1, - done_pbase_paths + pos, - (done_pbase_paths_num - pos - 1) * sizeof(unsigned)); + MOVE_ARRAY(done_pbase_paths + pos + 1, done_pbase_paths + pos, + done_pbase_paths_num - pos - 1); done_pbase_paths[pos] = hash; return 0; } @@ -1311,7 +1331,7 @@ static void add_preferred_base_object(const char *name) cmplen = name_cmp_len(name); for (it = pbase_tree; it; it = it->next) { if (cmplen == 0) { - add_object_entry(it->pcache.sha1, OBJ_TREE, NULL, 1); + add_object_entry(&it->pcache.oid, OBJ_TREE, NULL, 1); } else { struct tree_desc tree; @@ -1321,22 +1341,22 @@ static void add_preferred_base_object(const char *name) } } -static void add_preferred_base(unsigned char *sha1) +static void add_preferred_base(struct object_id *oid) { struct pbase_tree *it; void *data; unsigned long size; - unsigned char tree_sha1[20]; + struct object_id tree_oid; if (window <= num_preferred_base++) return; - data = read_object_with_reference(sha1, tree_type, &size, tree_sha1); + data = read_object_with_reference(oid->hash, tree_type, &size, tree_oid.hash); if (!data) return; for (it = pbase_tree; it; it = it->next) { - if (!hashcmp(it->pcache.sha1, tree_sha1)) { + if (!oidcmp(&it->pcache.oid, &tree_oid)) { free(data); return; } @@ -1346,7 +1366,7 @@ static void add_preferred_base(unsigned char *sha1) it->next = pbase_tree; pbase_tree = it; - hashcpy(it->pcache.sha1, tree_sha1); + oidcpy(&it->pcache.oid, &tree_oid); it->pcache.tree_data = data; it->pcache.tree_size = size; } @@ -1359,22 +1379,20 @@ static void cleanup_preferred_base(void) it = pbase_tree; pbase_tree = NULL; while (it) { - struct pbase_tree *this = it; - it = this->next; - free(this->pcache.tree_data); - free(this); + struct pbase_tree *tmp = it; + it = tmp->next; + free(tmp->pcache.tree_data); + free(tmp); } for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) { if (!pbase_tree_cache[i]) continue; free(pbase_tree_cache[i]->tree_data); - free(pbase_tree_cache[i]); - pbase_tree_cache[i] = NULL; + FREE_AND_NULL(pbase_tree_cache[i]); } - free(done_pbase_paths); - done_pbase_paths = NULL; + FREE_AND_NULL(done_pbase_paths); done_pbase_paths_num = done_pbase_paths_alloc = 0; } @@ -1432,7 +1450,7 @@ static void check_object(struct object_entry *entry) ofs += 1; if (!ofs || MSB(ofs, 7)) { error("delta base offset overflow in pack for %s", - sha1_to_hex(entry->idx.sha1)); + oid_to_hex(&entry->idx.oid)); goto give_up; } c = buf[used_0++]; @@ -1441,7 +1459,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", - sha1_to_hex(entry->idx.sha1)); + oid_to_hex(&entry->idx.oid)); goto give_up; } if (reuse_delta && !entry->preferred_base) { @@ -1498,7 +1516,7 @@ static void check_object(struct object_entry *entry) unuse_pack(&w_curs); } - entry->type = sha1_object_info(entry->idx.sha1, &entry->size); + entry->type = sha1_object_info(entry->idx.oid.hash, &entry->size); /* * The error condition is checked in prepare_pack(). This is * to permit a missing preferred base object to be ignored @@ -1514,7 +1532,7 @@ static int pack_offset_sort(const void *_a, const void *_b) /* avoid filesystem trashing with loose objects */ if (!a->in_pack && !b->in_pack) - return hashcmp(a->idx.sha1, b->idx.sha1); + return oidcmp(&a->idx.oid, &b->idx.oid); if (a->in_pack < b->in_pack) return -1; @@ -1560,7 +1578,8 @@ static void drop_reused_delta(struct object_entry *entry) * And if that fails, the error will be recorded in entry->type * and dealt with in prepare_pack(). */ - entry->type = sha1_object_info(entry->idx.sha1, &entry->size); + entry->type = sha1_object_info(entry->idx.oid.hash, + &entry->size); } } @@ -1852,26 +1871,29 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, /* Load data if not already done */ if (!trg->data) { read_lock(); - trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz); + trg->data = read_sha1_file(trg_entry->idx.oid.hash, &type, + &sz); read_unlock(); if (!trg->data) die("object %s cannot be read", - sha1_to_hex(trg_entry->idx.sha1)); + oid_to_hex(&trg_entry->idx.oid)); if (sz != trg_size) die("object %s inconsistent object length (%lu vs %lu)", - sha1_to_hex(trg_entry->idx.sha1), sz, trg_size); + oid_to_hex(&trg_entry->idx.oid), sz, + trg_size); *mem_usage += sz; } if (!src->data) { read_lock(); - src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz); + src->data = read_sha1_file(src_entry->idx.oid.hash, &type, + &sz); read_unlock(); if (!src->data) { if (src_entry->preferred_base) { static int warned = 0; if (!warned++) warning("object %s cannot be read", - sha1_to_hex(src_entry->idx.sha1)); + oid_to_hex(&src_entry->idx.oid)); /* * Those objects are not included in the * resulting pack. Be resilient and ignore @@ -1881,11 +1903,12 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, return 0; } die("object %s cannot be read", - sha1_to_hex(src_entry->idx.sha1)); + oid_to_hex(&src_entry->idx.oid)); } if (sz != src_size) die("object %s inconsistent object length (%lu vs %lu)", - sha1_to_hex(src_entry->idx.sha1), sz, src_size); + oid_to_hex(&src_entry->idx.oid), sz, + src_size); *mem_usage += sz; } if (!src->index) { @@ -1959,8 +1982,7 @@ static unsigned long free_unpacked(struct unpacked *n) n->index = NULL; if (n->data) { freed_mem += n->entry->size; - free(n->data); - n->data = NULL; + FREE_AND_NULL(n->data); } n->entry = NULL; n->depth = 0; @@ -2163,7 +2185,10 @@ static void *threaded_find_deltas(void *arg) { struct thread_params *me = arg; + progress_lock(); while (me->remaining) { + progress_unlock(); + find_deltas(me->list, &me->remaining, me->window, me->depth, me->processed); @@ -2185,7 +2210,10 @@ static void *threaded_find_deltas(void *arg) pthread_cond_wait(&me->cond, &me->mutex); me->data_ready = 0; pthread_mutex_unlock(&me->mutex); + + progress_lock(); } + progress_unlock(); /* leave ->working 1 so that this doesn't get more work assigned */ return NULL; } @@ -2337,13 +2365,13 @@ static void add_tag_chain(const struct object_id *oid) if (packlist_find(&to_pack, oid->hash, NULL)) return; - tag = lookup_tag(oid->hash); + tag = lookup_tag(oid); while (1) { if (!tag || parse_tag(tag) || !tag->tagged) die("unable to pack objects reachable from tag %s", oid_to_hex(oid)); - add_object_entry(tag->object.oid.hash, OBJ_TAG, NULL, 0); + add_object_entry(&tag->object.oid, OBJ_TAG, NULL, 0); if (tag->tagged->type != OBJ_TAG) return; @@ -2357,7 +2385,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag, struct object_id peeled; if (starts_with(path, "refs/tags/") && /* is a tag? */ - !peel_ref(path, peeled.hash) && /* peelable? */ + !peel_ref(path, &peeled) && /* peelable? */ packlist_find(&to_pack, peeled.hash, NULL)) /* object packed? */ add_tag_chain(oid); return 0; @@ -2406,7 +2434,7 @@ static void prepare_pack(int window, int depth) nr_deltas++; if (entry->type < 0) die("unable to get type of object %s", - sha1_to_hex(entry->idx.sha1)); + oid_to_hex(&entry->idx.oid)); } else { if (entry->type < 0) { /* @@ -2472,8 +2500,10 @@ static int git_pack_config(const char *k, const char *v, void *cb) die("invalid number of threads specified (%d)", delta_search_threads); #ifdef NO_PTHREADS - if (delta_search_threads != 1) + if (delta_search_threads != 1) { warning("no threads support, ignoring %s", k); + delta_search_threads = 0; + } #endif return 0; } @@ -2489,8 +2519,9 @@ static int git_pack_config(const char *k, const char *v, void *cb) static void read_object_list_from_stdin(void) { - char line[40 + 1 + PATH_MAX + 2]; - unsigned char sha1[20]; + char line[GIT_MAX_HEXSZ + 1 + PATH_MAX + 2]; + struct object_id oid; + const char *p; for (;;) { if (!fgets(line, sizeof(line), stdin)) { @@ -2504,25 +2535,26 @@ static void read_object_list_from_stdin(void) continue; } if (line[0] == '-') { - if (get_sha1_hex(line+1, sha1)) - die("expected edge sha1, got garbage:\n %s", + if (get_oid_hex(line+1, &oid)) + die("expected edge object ID, got garbage:\n %s", line); - add_preferred_base(sha1); + add_preferred_base(&oid); continue; } - if (get_sha1_hex(line, sha1)) - die("expected sha1, got garbage:\n %s", line); + if (parse_oid_hex(line, &oid, &p)) + die("expected object ID, got garbage:\n %s", line); - add_preferred_base_object(line+41); - add_object_entry(sha1, 0, line+41, 0); + add_preferred_base_object(p + 1); + add_object_entry(&oid, 0, p + 1, 0); } } +/* Remember to update object flag allocation in object.h */ #define OBJECT_ADDED (1u<<20) static void show_commit(struct commit *commit, void *data) { - add_object_entry(commit->object.oid.hash, OBJ_COMMIT, NULL, 0); + add_object_entry(&commit->object.oid, OBJ_COMMIT, NULL, 0); commit->object.flags |= OBJECT_ADDED; if (write_bitmap_index) @@ -2532,13 +2564,71 @@ static void show_commit(struct commit *commit, void *data) static void show_object(struct object *obj, const char *name, void *data) { add_preferred_base_object(name); - add_object_entry(obj->oid.hash, obj->type, name, 0); + add_object_entry(&obj->oid, obj->type, name, 0); obj->flags |= OBJECT_ADDED; } +static void show_object__ma_allow_any(struct object *obj, const char *name, void *data) +{ + assert(arg_missing_action == MA_ALLOW_ANY); + + /* + * Quietly ignore ALL missing objects. This avoids problems with + * staging them now and getting an odd error later. + */ + if (!has_object_file(&obj->oid)) + return; + + show_object(obj, name, data); +} + +static void show_object__ma_allow_promisor(struct object *obj, const char *name, void *data) +{ + assert(arg_missing_action == MA_ALLOW_PROMISOR); + + /* + * Quietly ignore EXPECTED missing objects. This avoids problems with + * staging them now and getting an odd error later. + */ + if (!has_object_file(&obj->oid) && is_promisor_object(&obj->oid)) + return; + + show_object(obj, name, data); +} + +static int option_parse_missing_action(const struct option *opt, + const char *arg, int unset) +{ + assert(arg); + assert(!unset); + + if (!strcmp(arg, "error")) { + arg_missing_action = MA_ERROR; + fn_show_object = show_object; + return 0; + } + + if (!strcmp(arg, "allow-any")) { + arg_missing_action = MA_ALLOW_ANY; + fetch_if_missing = 0; + fn_show_object = show_object__ma_allow_any; + return 0; + } + + if (!strcmp(arg, "allow-promisor")) { + arg_missing_action = MA_ALLOW_PROMISOR; + fetch_if_missing = 0; + fn_show_object = show_object__ma_allow_promisor; + return 0; + } + + die(_("invalid value for --missing")); + return 0; +} + static void show_edge(struct commit *commit) { - add_preferred_base(commit->object.oid.hash); + add_preferred_base(&commit->object.oid); } struct in_pack_object { @@ -2547,8 +2637,8 @@ struct in_pack_object { }; struct in_pack { - int alloc; - int nr; + unsigned int alloc; + unsigned int nr; struct in_pack_object *array; }; @@ -2585,7 +2675,7 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs) memset(&in_pack, 0, sizeof(in_pack)); for (p = packed_git; p; p = p->next) { - const unsigned char *sha1; + struct object_id oid; struct object *o; if (!p->pack_local || p->pack_keep) @@ -2598,8 +2688,8 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs) in_pack.alloc); for (i = 0; i < p->num_objects; i++) { - sha1 = nth_packed_object_sha1(p, i); - o = lookup_unknown_object(sha1); + nth_packed_object_oid(&oid, p, i); + o = lookup_unknown_object(oid.hash); if (!(o->flags & OBJECT_ADDED)) mark_in_pack_object(o, p, &in_pack); o->flags |= OBJECT_ADDED; @@ -2610,7 +2700,7 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs) QSORT(in_pack.array, in_pack.nr, ofscmp); for (i = 0; i < in_pack.nr; i++) { struct object *o = in_pack.array[i].object; - add_object_entry(o->oid.hash, o->type, "", 0); + add_object_entry(&o->oid, o->type, "", 0); } } free(in_pack.array); @@ -2626,7 +2716,7 @@ static int add_loose_object(const struct object_id *oid, const char *path, return 0; } - add_object_entry(oid->hash, type, "", 0); + add_object_entry(oid, type, "", 0); return 0; } @@ -2642,7 +2732,7 @@ static void add_unreachable_loose_objects(void) NULL, NULL, NULL); } -static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1) +static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid) { static struct packed_git *last_found = (void *)1; struct packed_git *p; @@ -2651,7 +2741,7 @@ static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1) while (p) { if ((!p->pack_local || p->pack_keep) && - find_pack_entry_one(sha1, p)) { + find_pack_entry_one(oid->hash, p)) { last_found = p; return 1; } @@ -2675,7 +2765,7 @@ static int has_sha1_pack_kept_or_nonlocal(const unsigned char *sha1) static struct oid_array recent_objects; static int loosened_object_can_be_discarded(const struct object_id *oid, - unsigned long mtime) + timestamp_t mtime) { if (!unpack_unreachable_expiration) return 0; @@ -2702,9 +2792,9 @@ static void loosen_unused_packed_objects(struct rev_info *revs) for (i = 0; i < p->num_objects; i++) { nth_packed_object_oid(&oid, p, i); if (!packlist_find(&to_pack, oid.hash, NULL) && - !has_sha1_pack_kept_or_nonlocal(oid.hash) && + !has_sha1_pack_kept_or_nonlocal(&oid) && !loosened_object_can_be_discarded(&oid, p->mtime)) - if (force_object_loose(oid.hash, p->mtime)) + if (force_object_loose(&oid, p->mtime)) die("unable to force loose object"); } } @@ -2781,10 +2871,10 @@ static void get_object_list(int ac, const char **av) continue; } if (starts_with(line, "--shallow ")) { - unsigned char sha1[20]; - if (get_sha1_hex(line + 10, sha1)) + struct object_id oid; + if (get_oid_hex(line + 10, &oid)) die("not an SHA-1 '%s'", line + 10); - register_shallow(sha1); + register_shallow(&oid); use_bitmap_index = 0; continue; } @@ -2800,7 +2890,12 @@ static void get_object_list(int ac, const char **av) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(&revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object, NULL); + + if (!fn_show_object) + fn_show_object = show_object; + traverse_commit_list_filtered(&filter_options, &revs, + show_commit, fn_show_object, NULL, + NULL); if (unpack_unreachable_expiration) { revs.ignore_missing_links = 1; @@ -2936,6 +3031,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) 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_PARSE_LIST_OBJECTS_FILTER(&filter_options), + { OPTION_CALLBACK, 0, "missing", NULL, N_("action"), + N_("handling for missing objects"), PARSE_OPT_NONEG, + option_parse_missing_action }, + OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects, + N_("do not pack objects in promisor packfiles")), OPT_END(), }; @@ -2981,6 +3082,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) argv_array_push(&rp, "--unpacked"); } + if (exclude_promisor_objects) { + use_internal_rev_list = 1; + fetch_if_missing = 0; + argv_array_push(&rp, "--exclude-promisor-objects"); + } + if (!reuse_object) reuse_delta = 0; if (pack_compression_level == -1) @@ -3012,6 +3119,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) 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; + } + /* * "soft" reasons not to use bitmaps - for on-disk repack by default we want * diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index cb1df1c761..991e1bb76f 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -7,6 +7,7 @@ */ #include "builtin.h" +#include "packfile.h" #define BLKSIZE 512 @@ -47,17 +48,17 @@ static inline void llist_item_put(struct llist_item *item) static inline struct llist_item *llist_item_get(void) { - struct llist_item *new; + struct llist_item *new_item; if ( free_nodes ) { - new = free_nodes; + new_item = free_nodes; free_nodes = free_nodes->next; } else { int i = 1; - ALLOC_ARRAY(new, BLKSIZE); + ALLOC_ARRAY(new_item, BLKSIZE); for (; i < BLKSIZE; i++) - llist_item_put(&new[i]); + llist_item_put(&new_item[i]); } - return new; + return new_item; } static void llist_free(struct llist *list) @@ -79,26 +80,26 @@ static inline void llist_init(struct llist **list) static struct llist * llist_copy(struct llist *list) { struct llist *ret; - struct llist_item *new, *old, *prev; + struct llist_item *new_item, *old_item, *prev; llist_init(&ret); if ((ret->size = list->size) == 0) return ret; - new = ret->front = llist_item_get(); - new->sha1 = list->front->sha1; + new_item = ret->front = llist_item_get(); + new_item->sha1 = list->front->sha1; - old = list->front->next; - while (old) { - prev = new; - new = llist_item_get(); - prev->next = new; - new->sha1 = old->sha1; - old = old->next; + 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; + old_item = old_item->next; } - new->next = NULL; - ret->back = new; + new_item->next = NULL; + ret->back = new_item; return ret; } @@ -107,24 +108,24 @@ static inline struct llist_item *llist_insert(struct llist *list, struct llist_item *after, const unsigned char *sha1) { - struct llist_item *new = llist_item_get(); - new->sha1 = sha1; - new->next = NULL; + struct llist_item *new_item = llist_item_get(); + new_item->sha1 = sha1; + new_item->next = NULL; if (after != NULL) { - new->next = after->next; - after->next = new; + new_item->next = after->next; + after->next = new_item; if (after == list->back) - list->back = new; + list->back = new_item; } else {/* insert in front */ if (list->size == 0) - list->back = new; + list->back = new_item; else - new->next = list->front; - list->front = new; + new_item->next = list->front; + list->front = new_item; } list->size++; - return new; + return new_item; } static inline struct llist_item *llist_insert_back(struct llist *list, diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 81552e02e4..970d0d30b4 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result) { diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index c026299e78..419238171d 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -2,6 +2,7 @@ #include "cache.h" #include "progress.h" #include "parse-options.h" +#include "packfile.h" static const char * const prune_packed_usage[] = { N_("git prune-packed [-n | --dry-run] [-q | --quiet]"), @@ -10,7 +11,7 @@ static const char * const prune_packed_usage[] = { static struct progress *progress; -static int prune_subdir(int nr, const char *path, void *data) +static int prune_subdir(unsigned int nr, const char *path, void *data) { int *opts = data; display_progress(progress, nr + 1); @@ -37,8 +38,7 @@ static int prune_object(const struct object_id *oid, const char *path, void prune_packed_objects(int opts) { if (opts & PRUNE_PACKED_VERBOSE) - progress = start_progress_delay(_("Removing duplicate objects"), - 256, 95, 2); + progress = start_delayed_progress(_("Removing duplicate objects"), 256); for_each_loose_file_in_objdir(get_object_directory(), prune_object, NULL, prune_subdir, &opts); diff --git a/builtin/prune.c b/builtin/prune.c index 42633e0c6e..4394d01c93 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -8,12 +8,12 @@ #include "progress.h" static const char * const prune_usage[] = { - N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"), + N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"), NULL }; static int show_only; static int verbose; -static unsigned long expire; +static timestamp_t expire; static int show_progress = -1; static int prune_tmp_file(const char *fullpath) @@ -52,7 +52,7 @@ static int prune_object(const struct object_id *oid, const char *fullpath, if (show_only || verbose) { enum object_type type = sha1_object_info(oid->hash, NULL); printf("%s %s\n", oid_to_hex(oid), - (type > 0) ? typename(type) : "unknown"); + (type > 0) ? type_name(type) : "unknown"); } if (!show_only) unlink_or_warn(fullpath); @@ -68,7 +68,7 @@ static int prune_cruft(const char *basename, const char *path, void *data) return 0; } -static int prune_subdir(int nr, const char *path, void *data) +static int prune_subdir(unsigned int nr, const char *path, void *data) { if (!show_only) rmdir(path); @@ -101,17 +101,20 @@ 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")), OPT__VERBOSE(&verbose, N_("report pruned objects")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), OPT_EXPIRY_DATE(0, "expire", &expire, N_("expire objects older than <time>")), + OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects, + N_("limit traversal to objects outside promisor packfiles")), OPT_END() }; char *s; - expire = ULONG_MAX; + expire = TIME_MAX; save_commit_buffer = 0; check_replace_refs = 0; ref_paranoia = 1; @@ -123,11 +126,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix) die(_("cannot prune in a precious-objects repo")); while (argc--) { - unsigned char sha1[20]; + struct object_id oid; const char *name = *argv++; - if (!get_sha1(name, sha1)) { - struct object *object = parse_object_or_die(sha1, name); + if (!get_oid(name, &oid)) { + struct object *object = parse_object_or_die(&oid, + name); add_pending_object(&revs, object, ""); } else @@ -137,7 +141,11 @@ int cmd_prune(int argc, const char **argv, const char *prefix) if (show_progress == -1) show_progress = isatty(2); if (show_progress) - progress = start_progress_delay(_("Checking connectivity"), 0, 0, 2); + 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); diff --git a/builtin/pull.c b/builtin/pull.c index 42f0560252..e32d6cd5b4 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -6,6 +6,7 @@ * Fetch one or more remote refs and merge it/them into the current HEAD. */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "parse-options.h" #include "exec_cmd.h" @@ -15,6 +16,8 @@ #include "dir.h" #include "refs.h" #include "revision.h" +#include "submodule.h" +#include "submodule-config.h" #include "tempfile.h" #include "lockfile.h" #include "wt-status.h" @@ -36,7 +39,7 @@ enum rebase_type { static enum rebase_type parse_config_rebase(const char *key, const char *value, int fatal) { - int v = git_config_maybe_bool("pull.rebase", value); + int v = git_parse_maybe_bool(value); if (!v) return REBASE_FALSE; @@ -77,11 +80,13 @@ static const char * const pull_usage[] = { /* Shared options */ static int opt_verbosity; static char *opt_progress; +static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; /* Options passed to git-merge or git-rebase */ static enum rebase_type opt_rebase = -1; static char *opt_diffstat; static char *opt_log; +static char *opt_signoff; static char *opt_squash; static char *opt_commit; static char *opt_edit; @@ -101,7 +106,6 @@ static char *opt_upload_pack; static int opt_force; static char *opt_tags; static char *opt_prune; -static char *opt_recurse_submodules; static char *max_children; static int opt_dry_run; static char *opt_keep; @@ -109,6 +113,8 @@ static char *opt_depth; static char *opt_unshallow; static char *opt_update_shallow; static char *opt_refmap; +static char *opt_ipv4; +static char *opt_ipv6; static struct option pull_options[] = { /* Shared options */ @@ -116,6 +122,10 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "progress", &opt_progress, NULL, N_("force progress reporting"), PARSE_OPT_NOARG), + { OPTION_CALLBACK, 0, "recurse-submodules", + &recurse_submodules, N_("on-demand"), + N_("control for recursive fetching of submodules"), + PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules }, /* Options passed to git-merge or git-rebase */ OPT_GROUP(N_("Options related to merging")), @@ -135,6 +145,9 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "log", &opt_log, N_("n"), N_("add (at most <n>) entries from shortlog to merge commit message"), PARSE_OPT_OPTARG), + OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL, + N_("add Signed-off-by:"), + PARSE_OPT_OPTARG), OPT_PASSTHRU(0, "squash", &opt_squash, NULL, N_("create a single commit instead of doing a merge"), PARSE_OPT_NOARG), @@ -180,17 +193,13 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"), N_("path to upload pack on remote end"), 0), - OPT__FORCE(&opt_force, N_("force overwrite of local branch")), + OPT__FORCE(&opt_force, N_("force overwrite of local branch"), 0), OPT_PASSTHRU('t', "tags", &opt_tags, NULL, N_("fetch all tags and associated objects"), PARSE_OPT_NOARG), OPT_PASSTHRU('p', "prune", &opt_prune, NULL, N_("prune remote-tracking branches no longer on remote"), PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "recurse-submodules", &opt_recurse_submodules, - N_("on-demand"), - N_("control recursive fetching of submodules"), - PARSE_OPT_OPTARG), OPT_PASSTHRU('j', "jobs", &max_children, N_("n"), N_("number of submodules pulled in parallel"), PARSE_OPT_OPTARG), @@ -211,6 +220,12 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), N_("specify fetch refmap"), PARSE_OPT_NONEG), + 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_END() }; @@ -271,7 +286,7 @@ static const char *config_get_ff(void) if (git_config_get_value("pull.ff", &value)) return NULL; - switch (git_config_maybe_bool("pull.ff", value)) { + switch (git_parse_maybe_bool(value)) { case 0: return "--no-ff"; case 1: @@ -322,6 +337,10 @@ static int git_pull_config(const char *var, const char *value, void *cb) if (!strcmp(var, "rebase.autostash")) { config_autostash = git_config_bool(var, value); return 0; + } else if (!strcmp(var, "submodule.recurse")) { + recurse_submodules = git_config_bool(var, value) ? + RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; + return 0; } return git_default_config(var, value, cb); } @@ -337,8 +356,7 @@ static void get_merge_heads(struct oid_array *merge_heads) struct strbuf sb = STRBUF_INIT; struct object_id oid; - if (!(fp = fopen(filename, "r"))) - die_errno(_("could not open '%s' for reading"), filename); + 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 */ @@ -484,8 +502,20 @@ static int run_fetch(const char *repo, const char **refspecs) argv_array_push(&args, opt_tags); if (opt_prune) argv_array_push(&args, opt_prune); - if (opt_recurse_submodules) - argv_array_push(&args, opt_recurse_submodules); + if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) + switch (recurse_submodules) { + case RECURSE_SUBMODULES_ON: + argv_array_push(&args, "--recurse-submodules=on"); + break; + case RECURSE_SUBMODULES_OFF: + argv_array_push(&args, "--recurse-submodules=no"); + break; + case RECURSE_SUBMODULES_ON_DEMAND: + argv_array_push(&args, "--recurse-submodules=on-demand"); + break; + default: + BUG("submodule recursion option not understood"); + } if (max_children) argv_array_push(&args, max_children); if (opt_dry_run) @@ -500,6 +530,10 @@ static int run_fetch(const char *repo, const char **refspecs) argv_array_push(&args, opt_update_shallow); if (opt_refmap) argv_array_push(&args, opt_refmap); + if (opt_ipv4) + argv_array_push(&args, opt_ipv4); + if (opt_ipv6) + argv_array_push(&args, opt_ipv6); if (repo) { argv_array_push(&args, repo); @@ -523,15 +557,41 @@ static int pull_into_void(const struct object_id *merge_head, * index/worktree changes that the user already made on the unborn * branch. */ - if (checkout_fast_forward(EMPTY_TREE_SHA1_BIN, merge_head->hash, 0)) + if (checkout_fast_forward(the_hash_algo->empty_tree, merge_head, 0)) return 1; - if (update_ref("initial pull", "HEAD", merge_head->hash, curr_head->hash, 0, UPDATE_REFS_DIE_ON_ERR)) + if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR)) return 1; return 0; } +static int rebase_submodules(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + cp.no_stdin = 1; + argv_array_pushl(&cp.args, "submodule", "update", + "--recursive", "--rebase", NULL); + argv_push_verbosity(&cp.args); + + return run_command(&cp); +} + +static int update_submodules(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + cp.no_stdin = 1; + argv_array_pushl(&cp.args, "submodule", "update", + "--recursive", "--checkout", NULL); + argv_push_verbosity(&cp.args); + + return run_command(&cp); +} + /** * Runs git-merge, returning its exit status. */ @@ -552,6 +612,8 @@ static int run_merge(void) argv_array_push(&args, opt_diffstat); if (opt_log) argv_array_push(&args, opt_log); + if (opt_signoff) + argv_array_push(&args, opt_signoff); if (opt_squash) argv_array_push(&args, opt_squash); if (opt_commit) @@ -698,17 +760,20 @@ 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->hash), &revs); - commit_list_insert(lookup_commit_reference(merge_head->hash), &revs); + commit_list_insert(lookup_commit_reference(curr_head), &revs); + commit_list_insert(lookup_commit_reference(merge_head), &revs); if (!is_null_oid(fork_point)) - commit_list_insert(lookup_commit_reference(fork_point->hash), &revs); + commit_list_insert(lookup_commit_reference(fork_point), &revs); - result = reduce_heads(get_octopus_merge_bases(revs)); + result = get_octopus_merge_bases(revs); free_commit_list(revs); + reduce_heads_replace(&result); + if (!result) return 1; oidcpy(merge_base, &result->item->object.oid); + free_commit_list(result); return 0; } @@ -777,6 +842,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); + git_config(git_pull_config, NULL); + argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0); parse_repo_refspecs(argc, argv, &repo, &refspecs); @@ -787,8 +854,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_rebase < 0) opt_rebase = config_get_rebase(); - git_config(git_pull_config, NULL); - if (read_cache_unmerged()) die_resolve_conflict("pull"); @@ -840,7 +905,7 @@ 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.hash, curr_head.hash, 0)) + if (checkout_fast_forward(&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" @@ -863,21 +928,36 @@ int cmd_pull(int argc, const char **argv, const char *prefix) die(_("Cannot rebase onto multiple branches.")); if (opt_rebase) { + int ret = 0; + if ((recurse_submodules == RECURSE_SUBMODULES_ON || + recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) && + submodule_touches_in_range(&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.hash); + head = lookup_commit_reference(&orig_head); commit_list_insert(head, &list); - merge_head = lookup_commit_reference(merge_heads.oid[0].hash); + merge_head = lookup_commit_reference(&merge_heads.oid[0]); if (is_descendant_of(merge_head, list)) { /* we can fast-forward this without invoking rebase */ opt_ff = "--ff-only"; - return run_merge(); + ret = run_merge(); } } - return run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); + ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); + + if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || + recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) + ret = rebase_submodules(); + + return ret; } else { - return run_merge(); + int ret = run_merge(); + if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || + recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) + ret = update_submodules(); + return ret; } } diff --git a/builtin/push.c b/builtin/push.c index a597759d8f..013c20d616 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -2,6 +2,7 @@ * "git push" */ #include "cache.h" +#include "config.h" #include "refs.h" #include "run-command.h" #include "builtin.h" @@ -31,6 +32,8 @@ static const char **refspec; static int refspec_nr; static int refspec_alloc; +static struct string_list push_options_config = STRING_LIST_INIT_DUP; + static void add_refspec(const char *ref) { refspec_nr++; @@ -480,7 +483,7 @@ static int git_push_config(const char *k, const char *v, void *cb) } else if (!strcmp(k, "push.gpgsign")) { const char *value; if (!git_config_get_value("push.gpgsign", &value)) { - switch (git_config_maybe_bool("push.gpgsign", value)) { + switch (git_parse_maybe_bool(value)) { case 0: set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_NEVER); break; @@ -498,6 +501,19 @@ static int git_push_config(const char *k, const char *v, void *cb) const char *value; if (!git_config_get_value("push.recursesubmodules", &value)) recurse_submodules = parse_push_recurse_submodules_arg(k, value); + } else if (!strcmp(k, "submodule.recurse")) { + int val = git_config_bool(k, v) ? + RECURSE_SUBMODULES_ON_DEMAND : RECURSE_SUBMODULES_OFF; + recurse_submodules = val; + } else if (!strcmp(k, "push.pushoption")) { + if (!v) + return config_error_nonbool(k); + else + if (!*v) + string_list_clear(&push_options_config, 0); + else + string_list_append(&push_options_config, v); + return 0; } return git_default_config(k, v, NULL); @@ -510,7 +526,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) int push_cert = -1; int rc; const char *repo = NULL; /* default repository */ - struct string_list push_options = STRING_LIST_INIT_DUP; + struct string_list push_options_cmdline = STRING_LIST_INIT_DUP; + struct string_list *push_options; const struct string_list_item *item; struct option options[] = { @@ -531,7 +548,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) { 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_BOOL( 0 , "thin", &thin, N_("use thin pack")), + 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")), OPT_BIT('u', "set-upstream", &flags, N_("set upstream for git pull/status"), @@ -546,7 +563,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) 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, N_("server-specific"), N_("option to transmit")), + 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"), TRANSPORT_FAMILY_IPV4), OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), @@ -557,6 +574,9 @@ int cmd_push(int argc, const char **argv, const char *prefix) packet_trace_identity("push"); git_config(git_push_config, &flags); argc = parse_options(argc, argv, prefix, options, push_usage, 0); + push_options = (push_options_cmdline.nr + ? &push_options_cmdline + : &push_options_config); set_push_cert_flags(&flags, push_cert); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) @@ -579,12 +599,13 @@ int cmd_push(int argc, const char **argv, const char *prefix) set_refspecs(argv + 1, argc - 1, repo); } - for_each_string_list_item(item, &push_options) + 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); - string_list_clear(&push_options, 0); + rc = do_push(repo, flags, push_options); + string_list_clear(&push_options_cmdline, 0); + string_list_clear(&push_options_config, 0); if (rc == -1) usage_with_options(push_usage, options); else diff --git a/builtin/read-tree.c b/builtin/read-tree.c index a52a9e11bb..bf87a2710b 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -5,6 +5,7 @@ */ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "object.h" #include "tree.h" @@ -21,15 +22,14 @@ static int nr_trees; static int read_empty; static struct tree *trees[MAX_UNPACK_TREES]; -static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; -static int list_tree(unsigned char *sha1) +static int list_tree(struct object_id *oid) { struct tree *tree; if (nr_trees >= MAX_UNPACK_TREES) die("I cannot read more than %d trees", MAX_UNPACK_TREES); - tree = parse_tree_indirect(sha1); + tree = parse_tree_indirect(oid); if (!tree) return -1; trees[nr_trees++] = tree; @@ -99,21 +99,12 @@ static int debug_merge(const struct cache_entry * const *stages, return 0; } -static int option_parse_recurse_submodules(const struct option *opt, - const char *arg, int unset) +static int git_read_tree_config(const char *var, const char *value, void *cb) { - if (unset) { - recurse_submodules = RECURSE_SUBMODULES_OFF; - return 0; - } - if (arg) - recurse_submodules = - parse_update_recurse_submodules_arg(opt->long_name, - arg); - else - recurse_submodules = RECURSE_SUBMODULES_ON; + if (!strcmp(var, "submodule.recurse")) + return git_default_submodule_config(var, value, cb); - return 0; + return git_default_config(var, value, cb); } static struct lock_file lock_file; @@ -121,7 +112,7 @@ static struct lock_file lock_file; int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { int i, stage = 0; - unsigned char sha1[20]; + struct object_id oid; struct tree_desc t[MAX_UNPACK_TREES]; struct unpack_trees_options opts; int prefix_set = 0; @@ -157,9 +148,9 @@ 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", &recurse_submodules, + { OPTION_CALLBACK, 0, "recurse-submodules", NULL, "checkout", "control recursive updating of submodules", - PARSE_OPT_OPTARG, option_parse_recurse_submodules }, + PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, OPT_END() }; @@ -168,19 +159,13 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) opts.src_index = &the_index; opts.dst_index = &the_index; - git_config(git_default_config, NULL); + git_config(git_read_tree_config, NULL); argc = parse_options(argc, argv, unused_prefix, read_tree_options, read_tree_usage, 0); hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); - if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) { - gitmodules_config(); - git_config(submodule_config, NULL); - set_config_update_recurse_submodules(RECURSE_SUBMODULES_ON); - } - prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); @@ -204,9 +189,9 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) for (i = 0; i < argc; i++) { const char *arg = argv[i]; - if (get_sha1(arg, sha1)) + if (get_oid(arg, &oid)) die("Not a valid object name %s", arg); - if (list_tree(sha1) < 0) + if (list_tree(&oid) < 0) die("failed to unpack tree object %s", arg); stage++; } diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index ca1ebb2fa1..ad074705bb 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "parse-options.h" #include "sequencer.h" @@ -11,19 +12,41 @@ static const char * const builtin_rebase_helper_usage[] = { 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 + 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() }; - git_config(git_default_config, NULL); + sequencer_init_config(&opts); + git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands); opts.action = REPLAY_INTERACTIVE_REBASE; opts.allow_ff = 1; @@ -32,9 +55,25 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) 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/receive-pack.c b/builtin/receive-pack.c index da9a3a2c9d..75e7f18ace 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "pack.h" #include "refs.h" @@ -22,6 +23,8 @@ #include "fsck.h" #include "tmp-objdir.h" #include "oidset.h" +#include "packfile.h" +#include "protocol.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -66,7 +69,7 @@ static int sent_capabilities; static int shallow_update; static const char *alt_shallow_file; static struct strbuf push_cert = STRBUF_INIT; -static unsigned char push_cert_sha1[20]; +static struct object_id push_cert_oid; static struct signature_check sigcheck; static const char *push_cert_nonce; static const char *cert_nonce_seed; @@ -78,7 +81,7 @@ static const char *NONCE_OK = "OK"; static const char *NONCE_SLOP = "SLOP"; static const char *nonce_status; static long nonce_stamp_slop; -static unsigned long nonce_stamp_slop_limit; +static timestamp_t nonce_stamp_slop_limit; static struct ref_transaction *transaction; static enum { @@ -454,17 +457,17 @@ static void hmac_sha1(unsigned char *out, git_SHA1_Final(out, &ctx); } -static char *prepare_push_cert_nonce(const char *path, unsigned long stamp) +static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp) { struct strbuf buf = STRBUF_INIT; unsigned char sha1[20]; - strbuf_addf(&buf, "%s:%lu", path, stamp); + strbuf_addf(&buf, "%s:%"PRItime, path, stamp); hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));; strbuf_release(&buf); /* RFC 2104 5. HMAC-SHA1-80 */ - strbuf_addf(&buf, "%lu-%.*s", stamp, 20, sha1_to_hex(sha1)); + strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, 20, sha1_to_hex(sha1)); return strbuf_detach(&buf, NULL); } @@ -499,7 +502,7 @@ static char *find_header(const char *msg, size_t len, const char *key, static const char *check_nonce(const char *buf, size_t len) { char *nonce = find_header(buf, len, "nonce", NULL); - unsigned long stamp, ostamp; + timestamp_t stamp, ostamp; char *bohmac, *expect = NULL; const char *retval = NONCE_BAD; @@ -537,7 +540,7 @@ static const char *check_nonce(const char *buf, size_t len) retval = NONCE_BAD; goto leave; } - stamp = strtoul(nonce, &bohmac, 10); + stamp = parse_timestamp(nonce, &bohmac, 10); if (bohmac == nonce || bohmac[0] != '-') { retval = NONCE_BAD; goto leave; @@ -555,7 +558,7 @@ static const char *check_nonce(const char *buf, size_t len) * would mean it was issued by another server with its clock * skewed in the future. */ - ostamp = strtoul(push_cert_nonce, NULL, 10); + ostamp = parse_timestamp(push_cert_nonce, NULL, 10); nonce_stamp_slop = (long)ostamp - (long)stamp; if (nonce_stamp_slop_limit && @@ -630,8 +633,9 @@ static void prepare_push_cert_sha1(struct child_process *proc) int bogs /* beginning_of_gpg_sig */; already_done = 1; - if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1)) - hashclr(push_cert_sha1); + if (write_object_file(push_cert.buf, push_cert.len, "blob", + &push_cert_oid)) + oidclr(&push_cert_oid); memset(&sigcheck, '\0', sizeof(sigcheck)); sigcheck.result = 'N'; @@ -652,9 +656,9 @@ static void prepare_push_cert_sha1(struct child_process *proc) strbuf_release(&gpg_status); nonce_status = check_nonce(push_cert.buf, bogs); } - if (!is_null_sha1(push_cert_sha1)) { + if (!is_null_oid(&push_cert_oid)) { argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT=%s", - sha1_to_hex(push_cert_sha1)); + oid_to_hex(&push_cert_oid)); argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s", sigcheck.signer ? sigcheck.signer : ""); argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s", @@ -741,7 +745,7 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, size_t n; if (feed(feed_state, &buf, &n)) break; - if (write_in_full(proc.in, buf, n) != n) + if (write_in_full(proc.in, buf, n) < 0) break; } close(proc.in); @@ -868,7 +872,7 @@ static void refuse_unconfigured_deny_delete_current(void) rp_error("%s", _(refuse_unconfigured_deny_delete_current_msg)); } -static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]); +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; @@ -900,7 +904,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) * not lose these new roots.. */ for (i = 0; i < extra.nr; i++) - register_shallow(extra.oid[i].hash); + register_shallow(&extra.oid[i]); si->shallow_ref[cmd->index] = 0; oid_array_clear(&extra); @@ -918,9 +922,9 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) */ static int head_has_history(void) { - unsigned char sha1[20]; + struct object_id oid; - return !get_sha1("HEAD", sha1); + return !get_oid("HEAD", &oid); } static const char *push_to_deploy(unsigned char *sha1, @@ -1102,8 +1106,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->hash); - new_object = parse_object(new_oid->hash); + old_object = parse_object(old_oid); + new_object = parse_object(new_oid); if (!old_object || !new_object || old_object->type != OBJ_COMMIT || @@ -1126,7 +1130,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) if (is_null_oid(new_oid)) { struct strbuf err = STRBUF_INIT; - if (!parse_object(old_oid->hash)) { + if (!parse_object(old_oid)) { old_oid = NULL; if (ref_exists(name)) { rp_warning("Allowing deletion of corrupt ref."); @@ -1137,7 +1141,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) } if (ref_transaction_delete(transaction, namespaced_name, - old_oid->hash, + old_oid, 0, "push", &err)) { rp_error("%s", err.buf); strbuf_release(&err); @@ -1154,7 +1158,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) if (ref_transaction_update(transaction, namespaced_name, - new_oid->hash, old_oid->hash, + new_oid, old_oid, 0, "push", &err)) { rp_error("%s", err.buf); @@ -1205,11 +1209,10 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) const char *dst_name; struct string_list_item *item; struct command *dst_cmd; - unsigned char sha1[GIT_MAX_RAWSZ]; int flag; strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name); - dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag); + dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag); strbuf_release(&buf); if (!(flag & REF_ISSYMREF)) @@ -1269,7 +1272,7 @@ static void check_aliased_updates(struct command *commands) string_list_clear(&ref_list, 0); } -static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]) +static int command_singleton_iterator(void *cb_data, struct object_id *oid) { struct command **cmd_list = cb_data; struct command *cmd = *cmd_list; @@ -1277,7 +1280,7 @@ static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]) if (!cmd || is_null_oid(&cmd->new_oid)) return -1; /* end of list */ *cmd_list = NULL; /* this returns only one */ - hashcpy(sha1, cmd->new_oid.hash); + oidcpy(oid, &cmd->new_oid); return 0; } @@ -1308,7 +1311,7 @@ struct iterate_data { struct shallow_info *si; }; -static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20]) +static int iterate_receive_command_list(void *cb_data, struct object_id *oid) { struct iterate_data *data = cb_data; struct command **cmd_list = &data->cmds; @@ -1319,7 +1322,7 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20]) /* to be checked in update_shallow_ref() */ continue; if (!is_null_oid(&cmd->new_oid) && !cmd->skip_update) { - hashcpy(sha1, cmd->new_oid.hash); + oidcpy(oid, &cmd->new_oid); *cmd_list = cmd->next; return 0; } @@ -1457,7 +1460,6 @@ static void execute_commands(struct command *commands, { struct check_connected_options opt = CHECK_CONNECTED_INIT; struct command *cmd; - struct object_id oid; struct iterate_data data; struct async muxer; int err_fd = 0; @@ -1514,7 +1516,7 @@ static void execute_commands(struct command *commands, check_aliased_updates(commands); free(head_name_to_free); - head_name = head_name_to_free = resolve_refdup("HEAD", 0, oid.hash, NULL); + head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL); if (use_atomic) execute_commands_atomic(commands, si); @@ -1805,7 +1807,7 @@ static const char *unpack_with_sideband(struct shallow_info *si) static void prepare_shallow_update(struct command *commands, struct shallow_info *si) { - int i, j, k, bitmap_size = (si->ref->nr + 31) / 32; + int i, j, k, bitmap_size = DIV_ROUND_UP(si->ref->nr, 32); ALLOC_ARRAY(si->used_shallow, si->shallow->nr); assign_shallow_commits_to_refs(si, si->used_shallow, NULL); @@ -1961,6 +1963,22 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) else if (0 <= receive_unpack_limit) unpack_limit = receive_unpack_limit; + switch (determine_protocol_version_server()) { + case protocol_v1: + /* + * v1 is just the original protocol with a version string, + * so just fall through after writing the version string. + */ + if (advertise_refs || !stateless_rpc) + packet_write_fmt(1, "version 1\n"); + + /* fallthrough */ + case protocol_v0: + break; + case protocol_unknown_version: + BUG("unknown protocol version"); + } + if (advertise_refs || !stateless_rpc) { write_head_info(); } diff --git a/builtin/reflog.c b/builtin/reflog.c index 7472775778..4719a5354c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "commit.h" #include "refs.h" @@ -16,14 +17,14 @@ static const char reflog_delete_usage[] = static const char reflog_exists_usage[] = "git reflog exists <ref>"; -static unsigned long default_reflog_expire; -static unsigned long default_reflog_expire_unreachable; +static timestamp_t default_reflog_expire; +static timestamp_t default_reflog_expire_unreachable; struct cmd_reflog_expire_cb { struct rev_info revs; int stalefix; - unsigned long expire_total; - unsigned long expire_unreachable; + timestamp_t expire_total; + timestamp_t expire_unreachable; int recno; }; @@ -41,7 +42,7 @@ struct expire_reflog_policy_cb { }; struct collected_reflog { - unsigned char sha1[20]; + struct object_id oid; char reflog[FLEX_ARRAY]; }; @@ -51,18 +52,19 @@ struct collect_reflog_cb { int nr; }; +/* Remember to update object flag allocation in object.h */ #define INCOMPLETE (1u<<10) #define STUDYING (1u<<11) #define REACHABLE (1u<<12) -static int tree_is_complete(const unsigned char *sha1) +static int tree_is_complete(const struct object_id *oid) { struct tree_desc desc; struct name_entry entry; int complete; struct tree *tree; - tree = lookup_tree(sha1); + tree = lookup_tree(oid); if (!tree) return 0; if (tree->object.flags & SEEN) @@ -73,7 +75,7 @@ static int tree_is_complete(const unsigned char *sha1) if (!tree->buffer) { enum object_type type; unsigned long size; - void *data = read_sha1_file(sha1, &type, &size); + void *data = read_sha1_file(oid->hash, &type, &size); if (!data) { tree->object.flags |= INCOMPLETE; return 0; @@ -85,7 +87,7 @@ static int tree_is_complete(const unsigned char *sha1) complete = 1; while (tree_entry(&desc, &entry)) { if (!has_sha1_file(entry.oid->hash) || - (S_ISDIR(entry.mode) && !tree_is_complete(entry.oid->hash))) { + (S_ISDIR(entry.mode) && !tree_is_complete(entry.oid))) { tree->object.flags |= INCOMPLETE; complete = 0; } @@ -125,8 +127,8 @@ static int commit_is_complete(struct commit *commit) struct commit *c; struct commit_list *parent; - c = (struct commit *)study.objects[--study.nr].item; - if (!c->object.parsed && !parse_object(c->object.oid.hash)) + c = (struct commit *)object_array_pop(&study); + if (!c->object.parsed && !parse_object(&c->object.oid)) c->object.flags |= INCOMPLETE; if (c->object.flags & INCOMPLETE) { @@ -152,7 +154,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.hash)) { + if (!tree_is_complete(&c->tree->object.oid)) { is_incomplete = 1; c->object.flags |= INCOMPLETE; } @@ -181,18 +183,18 @@ static int commit_is_complete(struct commit *commit) found.objects[i].item->flags |= SEEN; } /* free object arrays */ - free(study.objects); - free(found.objects); + object_array_clear(&study); + object_array_clear(&found); return !is_incomplete; } -static int keep_entry(struct commit **it, unsigned char *sha1) +static int keep_entry(struct commit **it, struct object_id *oid) { struct commit *commit; - if (is_null_sha1(sha1)) + if (is_null_oid(oid)) return 1; - commit = lookup_commit_reference_gently(sha1, 1); + commit = lookup_commit_reference_gently(oid, 1); if (!commit) return 0; @@ -219,7 +221,7 @@ static int keep_entry(struct commit **it, unsigned char *sha1) static void mark_reachable(struct expire_reflog_policy_cb *cb) { struct commit_list *pending; - unsigned long expire_limit = cb->mark_limit; + timestamp_t expire_limit = cb->mark_limit; struct commit_list *leftover = NULL; for (pending = cb->mark_list; pending; pending = pending->next) @@ -251,17 +253,17 @@ static void mark_reachable(struct expire_reflog_policy_cb *cb) cb->mark_list = leftover; } -static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, unsigned char *sha1) +static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid) { /* * We may or may not have the commit yet - if not, look it * up using the supplied sha1. */ if (!commit) { - if (is_null_sha1(sha1)) + if (is_null_oid(oid)) return 0; - commit = lookup_commit_reference_gently(sha1, 1); + commit = lookup_commit_reference_gently(oid, 1); /* Not a commit -- keep it */ if (!commit) @@ -283,25 +285,25 @@ static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit /* * Return true iff the specified reflog entry should be expired. */ -static int should_expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, - const char *email, unsigned long timestamp, int tz, +static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid, + const char *email, timestamp_t timestamp, int tz, const char *message, void *cb_data) { struct expire_reflog_policy_cb *cb = cb_data; - struct commit *old, *new; + struct commit *old_commit, *new_commit; if (timestamp < cb->cmd.expire_total) return 1; - old = new = NULL; + old_commit = new_commit = NULL; if (cb->cmd.stalefix && - (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))) + (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid))) return 1; if (timestamp < cb->cmd.expire_unreachable) { if (cb->unreachable_expire_kind == UE_ALWAYS) return 1; - if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1)) + if (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid)) return 1; } @@ -318,7 +320,7 @@ 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->hash, 1); + tip_commit = lookup_commit_reference_gently(oid, 1); if (!tip_commit) return 0; commit_list_insert(tip_commit, list); @@ -326,7 +328,7 @@ static int push_tip_to_list(const char *refname, const struct object_id *oid, } static void reflog_expiry_prepare(const char *refname, - const unsigned char *sha1, + const struct object_id *oid, void *cb_data) { struct expire_reflog_policy_cb *cb = cb_data; @@ -335,7 +337,7 @@ static void reflog_expiry_prepare(const char *refname, cb->tip_commit = NULL; cb->unreachable_expire_kind = UE_HEAD; } else { - cb->tip_commit = lookup_commit_reference_gently(sha1, 1); + cb->tip_commit = lookup_commit_reference_gently(oid, 1); if (!cb->tip_commit) cb->unreachable_expire_kind = UE_ALWAYS; else @@ -384,7 +386,7 @@ static int collect_reflog(const char *ref, const struct object_id *oid, int unus struct collect_reflog_cb *cb = cb_data; FLEX_ALLOC_STR(e, reflog, ref); - hashcpy(e->sha1, oid->hash); + oidcpy(&e->oid, oid); ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc); cb->e[cb->nr++] = e; return 0; @@ -392,8 +394,8 @@ static int collect_reflog(const char *ref, const struct object_id *oid, int unus static struct reflog_expire_cfg { struct reflog_expire_cfg *next; - unsigned long expire_total; - unsigned long expire_unreachable; + timestamp_t expire_total; + timestamp_t expire_unreachable; char pattern[FLEX_ARRAY]; } *reflog_expire_cfg, **reflog_expire_cfg_tail; @@ -415,16 +417,6 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len) return ent; } -static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire) -{ - if (!value) - return config_error_nonbool(var); - if (parse_expiry_date(value, expire)) - return error(_("'%s' for '%s' is not a valid timestamp"), - value, var); - return 0; -} - /* expiry timer slot */ #define EXPIRE_TOTAL 01 #define EXPIRE_UNREACH 02 @@ -433,7 +425,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb) { const char *pattern, *key; int pattern_len; - unsigned long expire; + timestamp_t expire; int slot; struct reflog_expire_cfg *ent; @@ -442,11 +434,11 @@ static int reflog_expire_config(const char *var, const char *value, void *cb) if (!strcmp(key, "reflogexpire")) { slot = EXPIRE_TOTAL; - if (parse_expire_cfg_value(var, value, &expire)) + if (git_config_expiry_date(&expire, var, value)) return -1; } else if (!strcmp(key, "reflogexpireunreachable")) { slot = EXPIRE_UNREACH; - if (parse_expire_cfg_value(var, value, &expire)) + if (git_config_expiry_date(&expire, var, value)) return -1; } else return git_default_config(var, value, cb); @@ -485,7 +477,7 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c return; /* both given explicitly -- nothing to tweak */ for (ent = reflog_expire_cfg; ent; ent = ent->next) { - if (!wildmatch(ent->pattern, ref, 0, NULL)) { + if (!wildmatch(ent->pattern, ref, 0)) { if (!(slot & EXPIRE_TOTAL)) cb->expire_total = ent->expire_total; if (!(slot & EXPIRE_UNREACH)) @@ -515,7 +507,7 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) { struct expire_reflog_policy_cb cb; - unsigned long now = time(NULL); + timestamp_t now = time(NULL); int i, status, do_all; int explicit_expiry = 0; unsigned int flags = 0; @@ -588,7 +580,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for (i = 0; i < collected.nr; i++) { struct collected_reflog *e = collected.e[i]; set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog); - status |= reflog_expire(e->reflog, e->sha1, flags, + status |= reflog_expire(e->reflog, &e->oid, flags, reflog_expiry_prepare, should_expire_reflog_ent, reflog_expiry_cleanup, @@ -600,13 +592,13 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for (; i < argc; i++) { char *ref; - unsigned char sha1[20]; - if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) { + struct object_id oid; + if (!dwim_log(argv[i], strlen(argv[i]), &oid, &ref)) { status |= error("%s points nowhere!", argv[i]); continue; } set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); - status |= reflog_expire(ref, sha1, flags, + status |= reflog_expire(ref, &oid, flags, reflog_expiry_prepare, should_expire_reflog_ent, reflog_expiry_cleanup, @@ -616,7 +608,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) } static int count_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, unsigned long timestamp, int tz, + const char *email, timestamp_t timestamp, int tz, const char *message, void *cb_data) { struct expire_reflog_policy_cb *cb = cb_data; @@ -658,7 +650,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) for ( ; i < argc; i++) { const char *spec = strstr(argv[i], "@{"); - unsigned char sha1[20]; + struct object_id oid; char *ep, *ref; int recno; @@ -667,7 +659,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) continue; } - if (!dwim_log(argv[i], spec - argv[i], sha1, &ref)) { + if (!dwim_log(argv[i], spec - argv[i], &oid, &ref)) { status |= error("no reflog for '%s'", argv[i]); continue; } @@ -682,7 +674,7 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) cb.cmd.expire_total = 0; } - status |= reflog_expire(ref, sha1, flags, + status |= reflog_expire(ref, &oid, flags, reflog_expiry_prepare, should_expire_reflog_ent, reflog_expiry_cleanup, diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index 11b48bfb41..6a9127a33c 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -3,6 +3,9 @@ #include "run-command.h" #include "pkt-line.h" +static const char usage_msg[] = + "git remote-ext <remote> <url>"; + /* * URL syntax: * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments. @@ -54,7 +57,7 @@ static char *strip_escapes(const char *str, const char *service, special = str[rpos]; if (rpos == 1) break; - /* Fall-through to error. */ + /* fallthrough */ default: die("Bad remote-ext placeholder '%%%c'.", str[rpos]); @@ -193,7 +196,7 @@ static int command_loop(const char *child) int cmd_remote_ext(int argc, const char **argv, const char *prefix) { if (argc != 3) - die("Expected two arguments"); + usage(usage_msg); return command_loop(argv[2]); } diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c index 08d7121b6d..91dfe07e06 100644 --- a/builtin/remote-fd.c +++ b/builtin/remote-fd.c @@ -1,6 +1,9 @@ #include "builtin.h" #include "transport.h" +static const char usage_msg[] = + "git remote-fd <remote> <url>"; + /* * URL syntax: * 'fd::<inoutfd>[/<anything>]' Read/write socket pair @@ -57,7 +60,7 @@ int cmd_remote_fd(int argc, const char **argv, const char *prefix) char *end; if (argc != 3) - die("Expected two arguments"); + usage(usage_msg); input_fd = (int)strtoul(argv[2], &end, 10); diff --git a/builtin/remote.c b/builtin/remote.c index 9054e2858e..805ffc05cd 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "parse-options.h" #include "transport.h" #include "remote.h" @@ -167,7 +168,7 @@ static int add(int argc, const char **argv) OPT_STRING('m', "master", &master, N_("branch"), N_("master branch")), { OPTION_CALLBACK, 0, "mirror", &mirror, N_("push|fetch"), N_("set up remote as a mirror to push to or fetch from"), - PARSE_OPT_OPTARG, parse_mirror_opt }, + PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt }, OPT_END() }; @@ -300,7 +301,7 @@ static int config_read_branches(const char *key, const char *value, void *cb) } string_list_append(&info->merge, xstrdup(value)); } else { - int v = git_config_maybe_bool(orig_key, value); + int v = git_parse_maybe_bool(value); if (v >= 0) info->rebase = v; else if (!strcmp(value, "preserve")) @@ -321,7 +322,7 @@ static void read_branches(void) struct ref_states { struct remote *remote; - struct string_list new, stale, tracked, heads, push; + struct string_list new_refs, stale, tracked, heads, push; int queried; }; @@ -336,12 +337,12 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat die(_("Could not get fetch map for refspec %s"), states->remote->fetch_refspec[i]); - states->new.strdup_strings = 1; + states->new_refs.strdup_strings = 1; states->tracked.strdup_strings = 1; states->stale.strdup_strings = 1; for (ref = fetch_map; ref; ref = ref->next) { if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) - string_list_append(&states->new, abbrev_branch(ref->name)); + string_list_append(&states->new_refs, abbrev_branch(ref->name)); else string_list_append(&states->tracked, abbrev_branch(ref->name)); } @@ -355,7 +356,7 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat free_refs(stale_refs); free_refs(fetch_map); - string_list_sort(&states->new); + string_list_sort(&states->new_refs); string_list_sort(&states->tracked); string_list_sort(&states->stale); @@ -545,8 +546,8 @@ static int add_branch_for_removal(const char *refname, } struct rename_info { - const char *old; - const char *new; + const char *old_name; + const char *new_name; struct string_list *remote_branches; }; @@ -557,19 +558,19 @@ static int read_remote_branches(const char *refname, struct strbuf buf = STRBUF_INIT; struct string_list_item *item; int flag; - struct object_id orig_oid; const char *symref; - strbuf_addf(&buf, "refs/remotes/%s/", rename->old); + strbuf_addf(&buf, "refs/remotes/%s/", rename->old_name); if (starts_with(refname, buf.buf)) { item = string_list_append(rename->remote_branches, xstrdup(refname)); symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING, - orig_oid.hash, &flag); - if (flag & REF_ISSYMREF) + NULL, &flag); + if (symref && (flag & REF_ISSYMREF)) item->util = xstrdup(symref); else item->util = NULL; } + strbuf_release(&buf); return 0; } @@ -594,6 +595,7 @@ static int migrate_file(struct remote *remote) unlink_or_warn(git_path("remotes/%s", remote->name)); else if (remote->origin == REMOTE_BRANCHES) unlink_or_warn(git_path("branches/%s", remote->name)); + strbuf_release(&buf); return 0; } @@ -613,36 +615,36 @@ static int mv(int argc, const char **argv) if (argc != 3) usage_with_options(builtin_remote_rename_usage, options); - rename.old = argv[1]; - rename.new = argv[2]; + rename.old_name = argv[1]; + rename.new_name = argv[2]; rename.remote_branches = &remote_branches; - oldremote = remote_get(rename.old); + oldremote = remote_get(rename.old_name); if (!remote_is_configured(oldremote, 1)) - die(_("No such remote: %s"), rename.old); + die(_("No such remote: %s"), rename.old_name); - if (!strcmp(rename.old, rename.new) && oldremote->origin != REMOTE_CONFIG) + if (!strcmp(rename.old_name, rename.new_name) && oldremote->origin != REMOTE_CONFIG) return migrate_file(oldremote); - newremote = remote_get(rename.new); + newremote = remote_get(rename.new_name); if (remote_is_configured(newremote, 1)) - die(_("remote %s already exists."), rename.new); + die(_("remote %s already exists."), rename.new_name); - strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new); + strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new_name); if (!valid_fetch_refspec(buf.buf)) - die(_("'%s' is not a valid remote name"), rename.new); + die(_("'%s' is not a valid remote name"), rename.new_name); strbuf_reset(&buf); - strbuf_addf(&buf, "remote.%s", rename.old); - strbuf_addf(&buf2, "remote.%s", rename.new); + strbuf_addf(&buf, "remote.%s", rename.old_name); + strbuf_addf(&buf2, "remote.%s", rename.new_name); if (git_config_rename_section(buf.buf, buf2.buf) < 1) return error(_("Could not rename config section '%s' to '%s'"), buf.buf, buf2.buf); strbuf_reset(&buf); - strbuf_addf(&buf, "remote.%s.fetch", rename.new); + 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); + strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name); for (i = 0; i < oldremote->fetch_refspec_nr; i++) { char *ptr; @@ -653,8 +655,8 @@ static int mv(int argc, const char **argv) refspec_updated = 1; strbuf_splice(&buf2, ptr-buf2.buf + strlen(":refs/remotes/"), - strlen(rename.old), rename.new, - strlen(rename.new)); + strlen(rename.old_name), rename.new_name, + strlen(rename.new_name)); } else warning(_("Not updating non-default fetch refspec\n" "\t%s\n" @@ -668,10 +670,10 @@ static int mv(int argc, const char **argv) for (i = 0; i < branch_list.nr; i++) { struct string_list_item *item = branch_list.items + i; struct branch_info *info = item->util; - if (info->remote_name && !strcmp(info->remote_name, rename.old)) { + if (info->remote_name && !strcmp(info->remote_name, rename.old_name)) { strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.remote", item->string); - git_config_set(buf.buf, rename.new); + git_config_set(buf.buf, rename.new_name); } } @@ -688,10 +690,10 @@ static int mv(int argc, const char **argv) int flag = 0; struct object_id oid; - read_ref_full(item->string, RESOLVE_REF_READING, oid.hash, &flag); + read_ref_full(item->string, RESOLVE_REF_READING, &oid, &flag); if (!(flag & REF_ISSYMREF)) continue; - if (delete_ref(NULL, item->string, NULL, REF_NODEREF)) + if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF)) die(_("deleting '%s' failed"), item->string); } for (i = 0; i < remote_branches.nr; i++) { @@ -701,8 +703,8 @@ static int mv(int argc, const char **argv) continue; strbuf_reset(&buf); strbuf_addstr(&buf, item->string); - strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old), - rename.new, strlen(rename.new)); + strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name), + rename.new_name, strlen(rename.new_name)); strbuf_reset(&buf2); strbuf_addf(&buf2, "remote: renamed %s to %s", item->string, buf.buf); @@ -716,12 +718,12 @@ static int mv(int argc, const char **argv) continue; strbuf_reset(&buf); strbuf_addstr(&buf, item->string); - strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old), - rename.new, strlen(rename.new)); + strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name), + rename.new_name, strlen(rename.new_name)); strbuf_reset(&buf2); strbuf_addstr(&buf2, item->util); - strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old), - rename.new, strlen(rename.new)); + strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old_name), + rename.new_name, strlen(rename.new_name)); strbuf_reset(&buf3); strbuf_addf(&buf3, "remote: renamed %s to %s", item->string, buf.buf); @@ -786,7 +788,7 @@ static int rm(int argc, const char **argv) strbuf_release(&buf); if (!result) - result = delete_refs(&branches, REF_NODEREF); + result = delete_refs("remote: remove", &branches, REF_NO_DEREF); string_list_clear(&branches, 0); if (skipped.nr) { @@ -820,7 +822,7 @@ static void clear_push_info(void *util, const char *string) static void free_remote_ref_states(struct ref_states *states) { - string_list_clear(&states->new, 0); + string_list_clear(&states->new_refs, 0); string_list_clear(&states->stale, 1); string_list_clear(&states->tracked, 0); string_list_clear(&states->heads, 0); @@ -905,7 +907,7 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data) if (states->queried) { const char *fmt = "%s"; const char *arg = ""; - if (string_list_has_string(&states->new, name)) { + if (string_list_has_string(&states->new_refs, name)) { fmt = _(" new (next fetch will store in remotes/%s)"); arg = states->remote->name; } else if (string_list_has_string(&states->tracked, name)) @@ -1174,7 +1176,7 @@ static int show(int argc, const char **argv) /* remote branch info */ info.width = 0; - for_each_string_list(&states.new, add_remote_to_show_info, &info); + for_each_string_list(&states.new_refs, add_remote_to_show_info, &info); for_each_string_list(&states.tracked, add_remote_to_show_info, &info); for_each_string_list(&states.stale, add_remote_to_show_info, &info); if (info.list->nr) @@ -1253,7 +1255,7 @@ static int set_head(int argc, const char **argv) head_name = xstrdup(states.heads.items[0].string); free_remote_ref_states(&states); } else if (opt_d && !opt_a && argc == 1) { - if (delete_ref(NULL, buf.buf, NULL, REF_NODEREF)) + if (delete_ref(NULL, buf.buf, NULL, REF_NO_DEREF)) result |= error(_("Could not delete %s"), buf.buf); } else usage_with_options(builtin_remote_sethead_usage, options); @@ -1304,7 +1306,7 @@ static int prune_remote(const char *remote, int dry_run) string_list_sort(&refs_to_prune); if (!dry_run) - result |= delete_refs(&refs_to_prune, 0); + result |= delete_refs("remote: prune", &refs_to_prune, 0); for_each_string_list_item(item, &states.stale) { const char *refname = item->util; @@ -1562,9 +1564,7 @@ static int set_url(int argc, const char **argv) "^$", 0); else git_config_set(name_buf.buf, newurl); - strbuf_release(&name_buf); - - return 0; + goto out; } /* Old URL specified. Demand that one matches. */ @@ -1587,6 +1587,8 @@ static int set_url(int argc, const char **argv) git_config_set_multivar(name_buf.buf, newurl, oldurl, 0); else git_config_set_multivar(name_buf.buf, NULL, oldurl, 1); +out: + strbuf_release(&name_buf); return 0; } diff --git a/builtin/repack.c b/builtin/repack.c index 677bc7c81a..7bdb40142f 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "dir.h" #include "parse-options.h" #include "run-command.h" @@ -82,7 +83,8 @@ static void remove_pack_on_signal(int signo) /* * Adds all packs hex strings to the fname list, which do not - * have a corresponding .keep file. + * have a corresponding .keep or .promisor 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) { @@ -100,7 +102,8 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list) fname = xmemdupz(e->d_name, len); - if (!file_exists(mkpath("%s/%s.keep", packdir, fname))) + if (!file_exists(mkpath("%s/%s.keep", packdir, fname)) && + !file_exists(mkpath("%s/%s.promisor", packdir, fname))) string_list_append_nodup(fname_list, fname); else free(fname); @@ -155,6 +158,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) 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; int no_update_server_info = 0; @@ -190,6 +194,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) N_("same as the above, but limit memory size instead of entries count")), OPT_STRING(0, "depth", &depth, N_("n"), N_("limits the maximum delta depth")), + OPT_STRING(0, "threads", &threads, N_("n"), + N_("limits the maximum number of threads")), OPT_STRING(0, "max-pack-size", &max_pack_size, N_("bytes"), N_("maximum size of each packfile")), OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects, @@ -228,12 +234,16 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argv_array_push(&cmd.args, "--all"); argv_array_push(&cmd.args, "--reflog"); argv_array_push(&cmd.args, "--indexed-objects"); + if (repository_format_partial_clone) + 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) diff --git a/builtin/replace.c b/builtin/replace.c index ab17668f43..482f12018f 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -9,6 +9,7 @@ */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "refs.h" #include "parse-options.h" @@ -40,7 +41,7 @@ static int show_reference(const char *refname, const struct object_id *oid, { struct show_data *data = cb_data; - if (!wildmatch(data->pattern, refname, 0, NULL)) { + if (!wildmatch(data->pattern, refname, 0)) { if (data->format == REPLACE_FORMAT_SHORT) printf("%s\n", refname); else if (data->format == REPLACE_FORMAT_MEDIUM) @@ -49,14 +50,14 @@ static int show_reference(const char *refname, const struct object_id *oid, struct object_id object; enum object_type obj_type, repl_type; - if (get_sha1(refname, object.hash)) + if (get_oid(refname, &object)) return error("Failed to resolve '%s' as a valid ref.", refname); obj_type = sha1_object_info(object.hash, NULL); repl_type = sha1_object_info(oid->hash, NULL); - printf("%s (%s) -> %s (%s)\n", refname, typename(obj_type), - oid_to_hex(oid), typename(repl_type)); + printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type), + oid_to_hex(oid), type_name(repl_type)); } } @@ -112,7 +113,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) strbuf_addstr(&ref, oid_to_hex(&oid)); full_hex = ref.buf + base_len; - if (read_ref(ref.buf, oid.hash)) { + if (read_ref(ref.buf, &oid)) { error("replace ref '%s' not found.", full_hex); had_error = 1; continue; @@ -127,7 +128,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn) static int delete_replace_ref(const char *name, const char *ref, const struct object_id *oid) { - if (delete_ref(NULL, ref, oid->hash, 0)) + if (delete_ref(NULL, ref, oid, 0)) return 1; printf("Deleted replace ref '%s'\n", name); return 0; @@ -143,7 +144,7 @@ static void check_ref_valid(struct object_id *object, if (check_refname_format(ref->buf, 0)) die("'%s' is not a valid ref name.", ref->buf); - if (read_ref(ref->buf, prev->hash)) + if (read_ref(ref->buf, prev)) oidclr(prev); else if (!force) die("replace ref '%s' already exists", ref->buf); @@ -167,14 +168,14 @@ static int replace_object_oid(const char *object_ref, 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, typename(obj_type), - replace_ref, typename(repl_type)); + object_ref, type_name(obj_type), + replace_ref, type_name(repl_type)); check_ref_valid(object, &prev, &ref, force); transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, ref.buf, repl->hash, prev.hash, + ref_transaction_update(transaction, ref.buf, repl, &prev, 0, NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); @@ -214,7 +215,7 @@ static void export_object(const struct object_id *oid, enum object_type type, argv_array_push(&cmd.args, "--no-replace-objects"); argv_array_push(&cmd.args, "cat-file"); if (raw) - argv_array_push(&cmd.args, typename(type)); + argv_array_push(&cmd.args, type_name(type)); else argv_array_push(&cmd.args, "-p"); argv_array_push(&cmd.args, oid_to_hex(oid)); @@ -268,7 +269,7 @@ static void import_object(struct object_id *oid, enum object_type type, if (fstat(fd, &st) < 0) die_errno("unable to fstat %s", filename); - if (index_fd(oid->hash, fd, &st, type, NULL, flags) < 0) + if (index_fd(oid, fd, &st, type, NULL, flags) < 0) die("unable to write object to database"); /* index_fd close()s fd for us */ } @@ -283,30 +284,30 @@ static int edit_and_replace(const char *object_ref, int force, int raw) { char *tmpfile = git_pathdup("REPLACE_EDITOBJ"); enum object_type type; - struct object_id old, new, prev; + struct object_id old_oid, new_oid, prev; struct strbuf ref = STRBUF_INIT; - if (get_oid(object_ref, &old) < 0) + if (get_oid(object_ref, &old_oid) < 0) die("Not a valid object name: '%s'", object_ref); - type = sha1_object_info(old.hash, NULL); + type = sha1_object_info(old_oid.hash, NULL); if (type < 0) - die("unable to get object type for %s", oid_to_hex(&old)); + die("unable to get object type for %s", oid_to_hex(&old_oid)); - check_ref_valid(&old, &prev, &ref, force); + check_ref_valid(&old_oid, &prev, &ref, force); strbuf_release(&ref); - export_object(&old, type, raw, tmpfile); + export_object(&old_oid, type, raw, tmpfile); if (launch_editor(tmpfile, NULL, NULL) < 0) die("editing object file failed"); - import_object(&new, type, raw, tmpfile); + import_object(&new_oid, type, raw, tmpfile); free(tmpfile); - if (!oidcmp(&old, &new)) - return error("new object is the same as the old one: '%s'", oid_to_hex(&old)); + if (!oidcmp(&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, "replacement", &new, force); + return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force); } static void replace_parents(struct strbuf *buf, int argc, const char **argv) @@ -328,7 +329,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv) 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.hash, argv[i]); + lookup_commit_or_die(&oid, argv[i]); strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&oid)); } @@ -354,8 +355,8 @@ static void check_one_mergetag(struct commit *commit, struct tag *tag; int i; - hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_oid.hash); - tag = lookup_tag(tag_oid.hash); + hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid); + tag = lookup_tag(&tag_oid); if (!tag) die(_("bad mergetag in commit '%s'"), ref); if (parse_tag_buffer(tag, extra->value, extra->len)) @@ -364,7 +365,7 @@ static void check_one_mergetag(struct commit *commit, /* iterate over new parents */ for (i = 1; i < mergetag_data->argc; i++) { struct object_id oid; - if (get_sha1(mergetag_data->argv[i], oid.hash) < 0) + 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 */ @@ -385,16 +386,16 @@ static void check_mergetags(struct commit *commit, int argc, const char **argv) static int create_graft(int argc, const char **argv, int force) { - struct object_id old, new; + struct object_id old_oid, new_oid; const char *old_ref = argv[0]; struct commit *commit; struct strbuf buf = STRBUF_INIT; const char *buffer; unsigned long size; - if (get_oid(old_ref, &old) < 0) + if (get_oid(old_ref, &old_oid) < 0) die(_("Not a valid object name: '%s'"), old_ref); - commit = lookup_commit_or_die(old.hash, old_ref); + commit = lookup_commit_or_die(&old_oid, old_ref); buffer = get_commit_buffer(commit, &size); strbuf_add(&buf, buffer, size); @@ -409,15 +410,15 @@ static int create_graft(int argc, const char **argv, int force) check_mergetags(commit, argc, argv); - if (write_sha1_file(buf.buf, buf.len, commit_type, new.hash)) + if (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) die(_("could not write replacement commit for: '%s'"), old_ref); strbuf_release(&buf); - if (!oidcmp(&old, &new)) - return error("new commit is the same as the old one: '%s'", oid_to_hex(&old)); + if (!oidcmp(&old_oid, &new_oid)) + return error("new commit is the same as the old one: '%s'", oid_to_hex(&old_oid)); - return replace_object_oid(old_ref, &old, "replacement", &new, force); + return replace_object_oid(old_ref, &old_oid, "replacement", &new_oid, force); } int cmd_replace(int argc, const char **argv, const char *prefix) @@ -438,7 +439,8 @@ 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_BOOL('f', "force", &force, N_("replace the ref if it exists")), + 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")), OPT_STRING(0, "format", &format, N_("format"), N_("use this format")), OPT_END() diff --git a/builtin/rerere.c b/builtin/rerere.c index 1bf72423bf..0bc40298c2 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "dir.h" #include "parse-options.h" #include "string-list.h" @@ -17,7 +18,7 @@ static int outf(void *dummy, mmbuffer_t *ptr, int nbuf) { int i; for (i = 0; i < nbuf; i++) - if (write_in_full(1, ptr[i].ptr, ptr[i].size) != ptr[i].size) + if (write_in_full(1, ptr[i].ptr, ptr[i].size) < 0) return -1; return 0; } diff --git a/builtin/reset.c b/builtin/reset.c index fc3b906c47..5da0f75de9 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -8,10 +8,11 @@ * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano */ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "tag.h" #include "object.h" -#include "commit.h" +#include "pretty.h" #include "run-command.h" #include "refs.h" #include "diff.h" @@ -21,6 +22,8 @@ #include "parse-options.h" #include "unpack-trees.h" #include "cache-tree.h" +#include "submodule.h" +#include "submodule-config.h" static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"), @@ -41,10 +44,11 @@ static inline int is_merge(void) static int reset_index(const struct object_id *oid, int reset_type, int quiet) { - int nr = 1; + int i, nr = 0; struct tree_desc desc[2]; struct tree *tree; struct unpack_trees_options opts; + int ret = -1; memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; @@ -72,45 +76,46 @@ 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, head_oid.hash)) + if (!fill_tree_descriptor(desc + nr, &head_oid)) return error(_("Failed to find tree of HEAD.")); nr++; opts.fn = twoway_merge; } - if (!fill_tree_descriptor(desc + nr - 1, oid->hash)) - return error(_("Failed to find tree of %s."), oid_to_hex(oid)); + if (!fill_tree_descriptor(desc + nr, oid)) { + error(_("Failed to find tree of %s."), oid_to_hex(oid)); + goto out; + } + nr++; + if (unpack_trees(nr, desc, &opts)) - return -1; + goto out; if (reset_type == MIXED || reset_type == HARD) { - tree = parse_tree_indirect(oid->hash); + tree = parse_tree_indirect(oid); prime_cache_tree(&the_index, tree); } - return 0; + ret = 0; + +out: + for (i = 0; i < nr; i++) + free((void *)desc[i].buffer); + return ret; } static void print_new_head_line(struct commit *commit) { - const char *hex, *body; - const char *msg; - - hex = find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV); - printf(_("HEAD is now at %s"), hex); - msg = logmsg_reencode(commit, NULL, get_log_output_encoding()); - body = strstr(msg, "\n\n"); - if (body) { - const char *eol; - size_t len; - body = skip_blank_lines(body + 2); - eol = strchr(body, '\n'); - len = eol ? eol - body : strlen(body); - printf(" %.*s\n", (int) len, body); - } - else - printf("\n"); - unuse_commit_buffer(commit, msg); + struct strbuf buf = STRBUF_INIT; + + printf(_("HEAD is now at %s"), + find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV)); + + pp_commit_easy(CMIT_FMT_ONELINE, commit, &buf); + if (buf.len > 0) + printf(" %s", buf.buf); + putchar('\n'); + strbuf_release(&buf); } static void update_index_from_diff(struct diff_queue_struct *q, @@ -153,8 +158,9 @@ static int read_from_tree(const struct pathspec *pathspec, opt.output_format = DIFF_FORMAT_CALLBACK; opt.format_callback = update_index_from_diff; opt.format_callback_data = &intent_to_add; + opt.flags.override_submodule_config = 1; - if (do_diff_cache(tree_oid->hash, &opt)) + if (do_diff_cache(tree_oid, &opt)) return 1; diffcore_std(&opt); diff_flush(&opt); @@ -216,8 +222,8 @@ static void parse_args(struct pathspec *pathspec, * has to be unambiguous. If there is a single argument, it * can not be a tree */ - else if ((!argv[1] && !get_sha1_committish(argv[0], unused.hash)) || - (argv[1] && !get_sha1_treeish(argv[0], unused.hash))) { + else if ((!argv[1] && !get_oid_committish(argv[0], &unused)) || + (argv[1] && !get_oid_treeish(argv[0], &unused))) { /* * Ok, argv[0] looks like a commit/tree; it should not * be a filename. @@ -236,7 +242,6 @@ static void parse_args(struct pathspec *pathspec, parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL | - PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP | (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0), prefix, argv); } @@ -253,17 +258,25 @@ static int reset_refs(const char *rev, const struct object_id *oid) if (!get_oid("HEAD", &oid_orig)) { orig = &oid_orig; set_reflog_message(&msg, "updating ORIG_HEAD", NULL); - update_ref_oid(msg.buf, "ORIG_HEAD", orig, old_orig, 0, + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) - delete_ref(NULL, "ORIG_HEAD", old_orig->hash, 0); + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); set_reflog_message(&msg, "updating HEAD", rev); - update_ref_status = update_ref_oid(msg.buf, "HEAD", oid, orig, 0, + update_ref_status = update_ref(msg.buf, "HEAD", oid, orig, 0, UPDATE_REFS_MSG_ON_ERR); strbuf_release(&msg); return update_ref_status; } +static int git_reset_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "submodule.recurse")) + return git_default_submodule_config(var, value, cb); + + return git_default_config(var, value, cb); +} + int cmd_reset(int argc, const char **argv, const char *prefix) { int reset_type = NONE, update_ref_status = 0, quiet = 0; @@ -283,35 +296,38 @@ 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, + "reset", "control recursive updating of submodules", + 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_END() }; - git_config(git_default_config, NULL); + git_config(git_reset_config, NULL); argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); parse_args(&pathspec, argv, prefix, patch_mode, &rev); - unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", oid.hash); + 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) { struct commit *commit; - if (get_sha1_committish(rev, oid.hash)) + if (get_oid_committish(rev, &oid)) die(_("Failed to resolve '%s' as a valid revision."), rev); - commit = lookup_commit_reference(oid.hash); + commit = lookup_commit_reference(&oid); if (!commit) die(_("Could not parse object '%s'."), rev); oidcpy(&oid, &commit->object.oid); } else { struct tree *tree; - if (get_sha1_treeish(rev, oid.hash)) + if (get_oid_treeish(rev, &oid)) die(_("Failed to resolve '%s' as a valid tree."), rev); - tree = parse_tree_indirect(oid.hash); + tree = parse_tree_indirect(&oid); if (!tree) die(_("Could not parse object '%s'."), rev); oidcpy(&oid, &tree->object.oid); @@ -353,8 +369,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die_if_unmerged_cache(reset_type); if (reset_type != SOFT) { - struct lock_file *lock = xcalloc(1, sizeof(*lock)); - hold_locked_index(lock, LOCK_DIE_ON_ERROR); + struct lock_file lock = LOCK_INIT; + hold_locked_index(&lock, LOCK_DIE_ON_ERROR); if (reset_type == MIXED) { int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, &oid, intent_to_add)) @@ -370,7 +386,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die(_("Could not reset index file to revision '%s'."), rev); } - if (write_locked_index(&the_index, lock, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock, COMMIT_LOCK)) die(_("Could not write new index file.")); } @@ -380,7 +396,7 @@ 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.hash)); + print_new_head_line(lookup_commit_reference(&oid)); } if (!pathspec.nr) remove_branch_state(); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index bcf77f0b8a..6f5b9b0847 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -1,8 +1,11 @@ #include "cache.h" +#include "config.h" #include "commit.h" #include "diff.h" #include "revision.h" #include "list-objects.h" +#include "list-objects-filter.h" +#include "list-objects-filter-options.h" #include "pack.h" #include "pack-bitmap.h" #include "builtin.h" @@ -10,6 +13,9 @@ #include "graph.h" #include "bisect.h" #include "progress.h" +#include "reflog-walk.h" +#include "oidset.h" +#include "packfile.h" static const char rev_list_usage[] = "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" @@ -53,6 +59,21 @@ static const char rev_list_usage[] = static struct progress *progress; static unsigned progress_counter; +static struct list_objects_filter_options filter_options; +static struct oidset omitted_objects; +static int arg_print_omitted; /* print objects omitted by filter */ + +static struct oidset missing_objects; +enum missing_action { + MA_ERROR = 0, /* fail if any missing objects are encountered */ + MA_ALLOW_ANY, /* silently allow ALL missing objects */ + MA_PRINT, /* print ALL missing objects in special section */ + MA_ALLOW_PROMISOR, /* silently allow all missing PROMISOR objects */ +}; +static enum missing_action arg_missing_action; + +#define DEFAULT_OIDSET_SIZE (16*1024) + static void finish_commit(struct commit *commit, void *data); static void show_commit(struct commit *commit, void *data) { @@ -80,7 +101,7 @@ static void show_commit(struct commit *commit, void *data) } if (info->show_timestamp) - printf("%lu ", commit->date); + printf("%"PRItime" ", commit->date); if (info->header_prefix) fputs(info->header_prefix, stdout); @@ -113,7 +134,7 @@ static void show_commit(struct commit *commit, void *data) else putchar('\n'); - if (revs->verbose_header && get_cached_commit_buffer(commit, NULL)) { + if (revs->verbose_header) { struct strbuf buf = STRBUF_INIT; struct pretty_print_context ctx = {0}; ctx.abbrev = revs->abbrev; @@ -121,6 +142,7 @@ static void show_commit(struct commit *commit, void *data) ctx.date_mode_explicit = revs->date_mode_explicit; ctx.fmt = revs->commit_format; ctx.output_encoding = get_log_output_encoding(); + ctx.color = revs->diffopt.use_color; pretty_print_commit(&ctx, commit, &buf); if (buf.len) { if (revs->commit_format != CMIT_FMT_ONELINE) @@ -175,19 +197,56 @@ static void finish_commit(struct commit *commit, void *data) free_commit_buffer(commit); } -static void finish_object(struct object *obj, const char *name, void *cb_data) +static inline void finish_object__ma(struct object *obj) { - struct rev_list_info *info = cb_data; - if (obj->type == OBJ_BLOB && !has_object_file(&obj->oid)) + /* + * Whether or not we try to dynamically fetch missing objects + * from the server, we currently DO NOT have the object. We + * can either print, allow (ignore), or conditionally allow + * (ignore) them. + */ + switch (arg_missing_action) { + case MA_ERROR: die("missing blob object '%s'", oid_to_hex(&obj->oid)); + return; + + case MA_ALLOW_ANY: + return; + + case MA_PRINT: + oidset_insert(&missing_objects, &obj->oid); + return; + + case MA_ALLOW_PROMISOR: + if (is_promisor_object(&obj->oid)) + return; + die("unexpected missing blob object '%s'", + oid_to_hex(&obj->oid)); + return; + + default: + BUG("unhandled missing_action"); + return; + } +} + +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)) { + finish_object__ma(obj); + return 1; + } if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT) - parse_object(obj->oid.hash); + parse_object(&obj->oid); + return 0; } static void show_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; - finish_object(obj, name, cb_data); + if (finish_object(obj, name, cb_data)) + return; display_progress(progress, ++progress_counter); if (info->flags & REV_LIST_QUIET) return; @@ -255,17 +314,45 @@ static int show_bisect_vars(struct rev_list_info *info, int reaches, int all) } static int show_object_fast( - const unsigned char *sha1, + const struct object_id *oid, enum object_type type, int exclude, uint32_t name_hash, struct packed_git *found_pack, off_t found_offset) { - fprintf(stdout, "%s\n", sha1_to_hex(sha1)); + fprintf(stdout, "%s\n", oid_to_hex(oid)); return 1; } +static inline int parse_missing_action_value(const char *value) +{ + if (!strcmp(value, "error")) { + arg_missing_action = MA_ERROR; + return 1; + } + + if (!strcmp(value, "allow-any")) { + arg_missing_action = MA_ALLOW_ANY; + fetch_if_missing = 0; + return 1; + } + + if (!strcmp(value, "print")) { + arg_missing_action = MA_PRINT; + fetch_if_missing = 0; + return 1; + } + + if (!strcmp(value, "allow-promisor")) { + arg_missing_action = MA_ALLOW_PROMISOR; + fetch_if_missing = 0; + return 1; + } + + return 0; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -277,10 +364,42 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int use_bitmap_index = 0; const char *show_progress = NULL; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(rev_list_usage); + git_config(git_default_config, NULL); init_revisions(&revs, prefix); revs.abbrev = DEFAULT_ABBREV; revs.commit_format = CMIT_FMT_UNSPECIFIED; + + /* + * Scan the argument list before invoking setup_revisions(), so that we + * know if fetch_if_missing needs to be set to 0. + * + * "--exclude-promisor-objects" acts as a pre-filter on missing objects + * by not crossing the boundary from realized objects to promisor + * objects. + * + * Let "--missing" to conditionally set fetch_if_missing. + */ + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(arg, "--exclude-promisor-objects")) { + fetch_if_missing = 0; + revs.exclude_promisor_objects = 1; + break; + } + } + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (skip_prefix(arg, "--missing=", &arg)) { + if (revs.exclude_promisor_objects) + die(_("cannot combine --exclude-promisor-objects and --missing")); + if (parse_missing_action_value(arg)) + break; + } + } + argc = setup_revisions(argc, argv, &revs, NULL); memset(&info, 0, sizeof(info)); @@ -288,7 +407,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (revs.bisect) bisect_list = 1; - if (DIFF_OPT_TST(&revs.diffopt, QUICK)) + if (revs.diffopt.flags.quick) info.flags |= REV_LIST_QUIET; for (i = 1 ; i < argc; i++) { const char *arg = argv[i]; @@ -329,6 +448,31 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) show_progress = arg; continue; } + + if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) { + 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))) { + list_objects_filter_set_no_filter(&filter_options); + continue; + } + if (!strcmp(arg, "--filter-print-omitted")) { + arg_print_omitted = 1; + continue; + } + + if (!strcmp(arg, "--exclude-promisor-objects")) + continue; /* already handled above */ + if (skip_prefix(arg, "--missing=", &arg)) + continue; /* already handled above */ + usage(rev_list_usage); } @@ -344,15 +488,19 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) /* Only --header was specified */ revs.commit_format = CMIT_FMT_RAW; - if ((!revs.commits && + if ((!revs.commits && reflog_walk_empty(revs.reflog_info) && (!(revs.tag_objects || revs.tree_objects || revs.blob_objects) && - !revs.pending.nr)) || + !revs.pending.nr) && + !revs.rev_input_given) || 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")); + save_commit_buffer = (revs.verbose_header || revs.grep_filter.pattern_list || revs.grep_filter.header_list); @@ -360,7 +508,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.limited = 1; if (show_progress) - progress = start_progress_delay(show_progress, 0, 0, 2); + progress = start_delayed_progress(show_progress, 0); if (use_bitmap_index && !revs.prune) { if (revs.count && !revs.left_right && !revs.cherry_mark) { @@ -388,16 +536,39 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) mark_edges_uninteresting(&revs, show_edge); if (bisect_list) { - int reaches = reaches, all = all; + int reaches, all; - revs.commits = find_bisection(revs.commits, &reaches, &all, - bisect_find_all); + find_bisection(&revs.commits, &reaches, &all, bisect_find_all); if (bisect_show_vars) return show_bisect_vars(&info, reaches, all); } - traverse_commit_list(&revs, show_commit, show_object, &info); + if (arg_print_omitted) + oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE); + if (arg_missing_action == MA_PRINT) + oidset_init(&missing_objects, DEFAULT_OIDSET_SIZE); + + traverse_commit_list_filtered( + &filter_options, &revs, show_commit, show_object, &info, + (arg_print_omitted ? &omitted_objects : NULL)); + + if (arg_print_omitted) { + struct oidset_iter iter; + struct object_id *oid; + oidset_iter_init(&omitted_objects, &iter); + while ((oid = oidset_iter_next(&iter))) + printf("~%s\n", oid_to_hex(oid)); + oidset_clear(&omitted_objects); + } + if (arg_missing_action == MA_PRINT) { + struct oidset_iter iter; + struct object_id *oid; + oidset_iter_init(&missing_objects, &iter); + while ((oid = oidset_iter_next(&iter))) + printf("?%s\n", oid_to_hex(oid)); + oidset_clear(&missing_objects); + } stop_progress(&progress); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 0513330910..a1e680b5e9 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "commit.h" #include "refs.h" #include "quote.h" @@ -121,7 +122,7 @@ static void show_with_type(int type, const char *arg) } /* Output a revision, only if filter allows it */ -static void show_rev(int type, const unsigned char *sha1, const char *name) +static void show_rev(int type, const struct object_id *oid, const char *name) { if (!(filter & DO_REVS)) return; @@ -129,10 +130,10 @@ static void show_rev(int type, const unsigned char *sha1, const char *name) if ((symbolic || abbrev_ref) && name) { if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) { - unsigned char discard[20]; + struct object_id discard; char *full; - switch (dwim_ref(name, strlen(name), discard, &full)) { + switch (dwim_ref(name, strlen(name), &discard, &full)) { case 0: /* * Not found -- not a ref. We could @@ -158,9 +159,9 @@ static void show_rev(int type, const unsigned char *sha1, const char *name) } } else if (abbrev) - show_with_type(type, find_unique_abbrev(sha1, abbrev)); + show_with_type(type, find_unique_abbrev(oid->hash, abbrev)); else - show_with_type(type, sha1_to_hex(sha1)); + show_with_type(type, oid_to_hex(oid)); } /* Output a flag, only if filter allows it. */ @@ -180,11 +181,11 @@ static int show_default(void) const char *s = def; if (s) { - unsigned char sha1[20]; + struct object_id oid; def = NULL; - if (!get_sha1(s, sha1)) { - show_rev(NORMAL, sha1, s); + if (!get_oid(s, &oid)) { + show_rev(NORMAL, &oid, s); return 1; } } @@ -195,19 +196,19 @@ static int show_reference(const char *refname, const struct object_id *oid, int { if (ref_excluded(ref_excludes, refname)) return 0; - show_rev(NORMAL, oid->hash, refname); + show_rev(NORMAL, oid, refname); return 0; } static int anti_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) { - show_rev(REVERSED, oid->hash, refname); + show_rev(REVERSED, oid, refname); return 0; } static int show_abbrev(const struct object_id *oid, void *cb_data) { - show_rev(NORMAL, oid->hash, NULL); + show_rev(NORMAL, oid, NULL); return 0; } @@ -218,7 +219,7 @@ static void show_datestring(const char *flag, const char *datestr) /* date handling requires both flags and revs */ if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS)) return; - buffer = xstrfmt("%s%lu", flag, approxidate(datestr)); + buffer = xstrfmt("%s%"PRItime, flag, approxidate(datestr)); show(buffer); free(buffer); } @@ -242,28 +243,28 @@ static int show_file(const char *arg, int output_prefix) static int try_difference(const char *arg) { char *dotdot; - unsigned char sha1[20]; - unsigned char end[20]; - const char *next; - const char *this; + struct object_id start_oid; + struct object_id end_oid; + const char *end; + const char *start; int symmetric; static const char head_by_default[] = "HEAD"; if (!(dotdot = strstr(arg, ".."))) return 0; - next = dotdot + 2; - this = arg; - symmetric = (*next == '.'); + end = dotdot + 2; + start = arg; + symmetric = (*end == '.'); *dotdot = 0; - next += symmetric; + end += symmetric; - if (!*next) - next = head_by_default; + if (!*end) + end = head_by_default; if (dotdot == arg) - this = head_by_default; + start = head_by_default; - if (this == head_by_default && next == head_by_default && + if (start == head_by_default && end == head_by_default && !symmetric) { /* * Just ".."? That is not a range but the @@ -273,18 +274,18 @@ static int try_difference(const char *arg) return 0; } - if (!get_sha1_committish(this, sha1) && !get_sha1_committish(next, end)) { - show_rev(NORMAL, end, next); - show_rev(symmetric ? NORMAL : REVERSED, sha1, this); + if (!get_oid_committish(start, &start_oid) && !get_oid_committish(end, &end_oid)) { + show_rev(NORMAL, &end_oid, end); + show_rev(symmetric ? NORMAL : REVERSED, &start_oid, start); if (symmetric) { struct commit_list *exclude; struct commit *a, *b; - a = lookup_commit_reference(sha1); - b = lookup_commit_reference(end); + a = lookup_commit_reference(&start_oid); + b = lookup_commit_reference(&end_oid); exclude = get_merge_bases(a, b); while (exclude) { struct commit *commit = pop_commit(&exclude); - show_rev(REVERSED, commit->object.oid.hash, NULL); + show_rev(REVERSED, &commit->object.oid, NULL); } } *dotdot = '.'; @@ -297,7 +298,7 @@ static int try_difference(const char *arg) static int try_parent_shorthands(const char *arg) { char *dotdot; - unsigned char sha1[20]; + struct object_id oid; struct commit *commit; struct commit_list *parents; int parent_number; @@ -327,12 +328,12 @@ static int try_parent_shorthands(const char *arg) return 0; *dotdot = 0; - if (get_sha1_committish(arg, sha1)) { + if (get_oid_committish(arg, &oid)) { *dotdot = '^'; return 0; } - commit = lookup_commit_reference(sha1); + commit = lookup_commit_reference(&oid); if (exclude_parent && exclude_parent > commit_list_count(commit->parents)) { *dotdot = '^'; @@ -340,7 +341,7 @@ static int try_parent_shorthands(const char *arg) } if (include_rev) - show_rev(NORMAL, sha1, arg); + show_rev(NORMAL, &oid, arg); for (parents = commit->parents, parent_number = 1; parents; parents = parents->next, parent_number++) { @@ -352,7 +353,7 @@ static int try_parent_shorthands(const char *arg) if (symbolic) name = xstrfmt("%s^%d", arg, parent_number); show_rev(include_parents ? NORMAL : REVERSED, - parents->item->object.oid.hash, name); + &parents->item->object.oid, name); free(name); } @@ -386,6 +387,14 @@ static const char *skipspaces(const char *s) return s; } +static char *findspace(const char *s) +{ + for (; *s; s++) + if (isspace(*s)) + return (char*)s; + return NULL; +} + static int cmd_parseopt(int argc, const char **argv, const char *prefix) { static int keep_dashdash = 0, stop_at_non_option = 0; @@ -433,7 +442,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) /* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */ while (strbuf_getline(&sb, stdin) != EOF) { const char *s; - const char *help; + char *help; struct option *o; if (!sb.len) @@ -443,15 +452,17 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) memset(opts + onb, 0, sizeof(opts[onb])); o = &opts[onb++]; - help = strchr(sb.buf, ' '); - if (!help || *sb.buf == ' ') { + help = findspace(sb.buf); + if (!help || sb.buf == help) { o->type = OPTION_GROUP; o->help = xstrdup(skipspaces(sb.buf)); continue; } + *help = '\0'; + o->type = OPTION_CALLBACK; - o->help = xstrdup(skipspaces(help)); + o->help = xstrdup(skipspaces(help+1)); o->value = &parsed; o->flags = PARSE_OPT_NOARG; o->callback = &parseopt_dump; @@ -505,7 +516,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) PARSE_OPT_SHELL_EVAL); strbuf_addstr(&parsed, " --"); - sq_quote_argv(&parsed, argv, 0); + sq_quote_argv(&parsed, argv); puts(parsed.buf); return 0; } @@ -515,7 +526,7 @@ static int cmd_sq_quote(int argc, const char **argv) struct strbuf buf = STRBUF_INIT; if (argc) - sq_quote_argv(&buf, argv, 0); + sq_quote_argv(&buf, argv); printf("%s\n", buf.buf); strbuf_release(&buf); @@ -571,7 +582,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) int did_repo_setup = 0; int has_dashdash = 0; int output_prefix = 0; - unsigned char sha1[20]; + struct object_id oid; unsigned int flags = 0; const char *name = NULL; struct object_context unused; @@ -701,7 +712,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) { quiet = 1; - flags |= GET_SHA1_QUIETLY; + flags |= GET_OID_QUIETLY; continue; } if (opt_with_value(arg, "--short", &arg)) { @@ -756,8 +767,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--bisect")) { - for_each_ref_in("refs/bisect/bad", show_reference, NULL); - for_each_ref_in("refs/bisect/good", anti_reference, NULL); + for_each_fullref_in("refs/bisect/bad", show_reference, NULL, 0); + for_each_fullref_in("refs/bisect/good", anti_reference, NULL, 0); continue; } if (opt_with_value(arg, "--branches", &arg)) { @@ -867,6 +878,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) : "false"); continue; } + if (!strcmp(arg, "--is-shallow-repository")) { + printf("%s\n", is_repository_shallow() ? "true" + : "false"); + continue; + } if (!strcmp(arg, "--shared-index-path")) { if (read_cache() < 0) die(_("Could not read the index")); @@ -910,11 +926,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) name++; type = REVERSED; } - if (!get_sha1_with_context(name, flags, sha1, &unused)) { + if (!get_oid_with_context(name, flags, &oid, &unused)) { if (verify) revs_count++; else - show_rev(type, sha1, name); + show_rev(type, &oid, name); continue; } if (verify) @@ -929,7 +945,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) strbuf_release(&buf); if (verify) { if (revs_count == 1) { - show_rev(type, sha1, name); + show_rev(type, &oid, name); return 0; } else if (revs_count == 0 && show_default()) return 0; diff --git a/builtin/revert.c b/builtin/revert.c index 345d9586a7..76f0a35b07 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "builtin.h" #include "parse-options.h" #include "diff.h" @@ -154,6 +155,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) "--strategy-option", opts->xopts ? 1 : 0, "-x", opts->record_origin, "--ff", opts->allow_ff, + "--rerere-autoupdate", opts->allow_rerere_auto == RERERE_AUTOUPDATE, + "--no-rerere-autoupdate", opts->allow_rerere_auto == RERERE_NOAUTOUPDATE, NULL); } @@ -205,7 +208,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) if (isatty(0)) opts.edit = 1; opts.action = REPLAY_REVERT; - git_config(git_default_config, NULL); + sequencer_init_config(&opts); res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("revert failed")); @@ -218,7 +221,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) int res; opts.action = REPLAY_PICK; - git_config(git_default_config, NULL); + sequencer_init_config(&opts); res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("cherry-pick failed")); diff --git a/builtin/rm.c b/builtin/rm.c index fb79dcab18..4447bb4d0f 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds 2006 */ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "dir.h" #include "cache-tree.h" @@ -129,7 +130,7 @@ static int check_local_mod(struct object_id *head, int index_only) ce = active_cache[pos]; if (lstat(ce->name, &st) < 0) { - if (errno != ENOENT && errno != ENOTDIR) + if (!is_missing_file_error(errno)) warning_errno(_("failed to stat '%s'"), ce->name); /* It already vanished from the working tree */ continue; @@ -241,7 +242,7 @@ static struct option builtin_rm_options[] = { OPT__DRY_RUN(&show_only, N_("dry run")), OPT__QUIET(&quiet, N_("do not list removed files")), OPT_BOOL( 0 , "cached", &index_only, N_("only remove from the index")), - OPT__FORCE(&force, N_("override the up-to-date check")), + OPT__FORCE(&force, N_("override the up-to-date check"), PARSE_OPT_NOCOMPLETE), 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")), @@ -254,7 +255,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) struct pathspec pathspec; char *seen; - gitmodules_config(); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, builtin_rm_options, @@ -271,8 +271,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) die(_("index file corrupt")); parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD | - PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + PATHSPEC_PREFER_CWD, prefix, argv); refresh_index(&the_index, REFRESH_QUIET, &pathspec, NULL, NULL); @@ -286,7 +285,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) 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()) + !is_staging_gitmodules_ok(&the_index)) die (_("Please stage your changes to .gitmodules or stash them to proceed")); } @@ -383,13 +382,12 @@ int cmd_rm(int argc, const char **argv, const char *prefix) } strbuf_release(&buf); if (gitmodules_modified) - stage_updated_gitmodules(); + stage_updated_gitmodules(&the_index); } - if (active_cache_changed) { - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) - die(_("Unable to write new index file")); - } + if (write_locked_index(&the_index, &lock_file, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) + die(_("Unable to write new index file")); return 0; } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index b8e2e74fe0..fc4f0bb5fb 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "commit.h" #include "refs.h" #include "pkt-line.h" @@ -104,7 +105,7 @@ static int send_pack_config(const char *k, const char *v, void *cb) if (!strcmp(k, "push.gpgsign")) { const char *value; if (!git_config_get_value("push.gpgsign", &value)) { - switch (git_config_maybe_bool("push.gpgsign", value)) { + switch (git_parse_maybe_bool(value)) { case 0: args.push_cert = SEND_PACK_PUSH_CERT_NEVER; break; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 7cff1839fc..e29875b843 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "commit.h" #include "diff.h" #include "string-list.h" @@ -51,26 +52,8 @@ static void insert_one_record(struct shortlog *log, const char *oneline) { struct string_list_item *item; - const char *mailbuf, *namebuf; - size_t namelen, maillen; - struct strbuf namemailbuf = STRBUF_INIT; - struct ident_split ident; - if (split_ident_line(&ident, author, strlen(author))) - return; - - namebuf = ident.name_begin; - mailbuf = ident.mail_begin; - namelen = ident.name_end - ident.name_begin; - maillen = ident.mail_end - ident.mail_begin; - - map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen); - strbuf_add(&namemailbuf, namebuf, namelen); - - if (log->email) - strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf); - - item = string_list_insert(&log->list, namemailbuf.buf); + item = string_list_insert(&log->list, author); if (log->summary) item->util = (void *)(UTIL_TO_INT(item) + 1); @@ -113,9 +96,33 @@ static void insert_one_record(struct shortlog *log, } } +static int parse_stdin_author(struct shortlog *log, + struct strbuf *out, const char *in) +{ + const char *mailbuf, *namebuf; + size_t namelen, maillen; + struct ident_split ident; + + if (split_ident_line(&ident, in, strlen(in))) + return -1; + + namebuf = ident.name_begin; + mailbuf = ident.mail_begin; + namelen = ident.name_end - ident.name_begin; + maillen = ident.mail_end - ident.mail_begin; + + map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen); + strbuf_add(out, namebuf, namelen); + if (log->email) + strbuf_addf(out, " <%.*s>", (int)maillen, mailbuf); + + return 0; +} + static void read_from_stdin(struct shortlog *log) { struct strbuf author = STRBUF_INIT; + struct strbuf mapped_author = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; static const char *author_match[2] = { "Author: ", "author " }; static const char *committer_match[2] = { "Commit: ", "committer " }; @@ -133,9 +140,15 @@ static void read_from_stdin(struct shortlog *log) while (strbuf_getline_lf(&oneline, stdin) != EOF && !oneline.len) ; /* discard blanks */ - insert_one_record(log, v, oneline.buf); + + strbuf_reset(&mapped_author); + if (parse_stdin_author(log, &mapped_author, v) < 0) + continue; + + insert_one_record(log, mapped_author.buf, oneline.buf); } strbuf_release(&author); + strbuf_release(&mapped_author); strbuf_release(&oneline); } @@ -152,7 +165,9 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); - fmt = log->committer ? "%cn <%ce>" : "%an <%ae>"; + fmt = log->committer ? + (log->email ? "%cN <%cE>" : "%cN") : + (log->email ? "%aN <%aE>" : "%aN"); format_commit_message(commit, fmt, &author, &ctx); if (!log->summary) { diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 19756595d5..e8a4aa40cb 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -1,10 +1,12 @@ #include "cache.h" -#include "commit.h" +#include "config.h" +#include "pretty.h" #include "refs.h" #include "builtin.h" #include "color.h" #include "argv-array.h" #include "parse-options.h" +#include "dir.h" static const char* show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" @@ -358,7 +360,7 @@ 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->hash, 1); + struct commit *commit = lookup_commit_reference_gently(oid, 1); int i; if (!commit) @@ -391,7 +393,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_sha1(refname + ofs, tmp.hash) || oidcmp(&tmp, oid)) + if (get_oid(refname + ofs, &tmp) || oidcmp(&tmp, oid)) ofs = 5; return append_ref(refname + ofs, oid, 0); } @@ -406,7 +408,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_sha1(refname + ofs, tmp.hash) || oidcmp(&tmp, oid)) + if (get_oid(refname + ofs, &tmp) || oidcmp(&tmp, oid)) ofs = 5; return append_ref(refname + ofs, oid, 0); } @@ -421,14 +423,6 @@ static int append_tag_ref(const char *refname, const struct object_id *oid, static const char *match_ref_pattern = NULL; static int match_ref_slash = 0; -static int count_slash(const char *s) -{ - int cnt = 0; - while (*s) - if (*s++ == '/') - cnt++; - return cnt; -} static int append_matching_ref(const char *refname, const struct object_id *oid, int flag, void *cb_data) @@ -438,13 +432,13 @@ static int append_matching_ref(const char *refname, const struct object_id *oid, * refs/tags/v0.99.9a and friends. */ const char *tail; - int slash = count_slash(refname); + int slash = count_slashes(refname); for (tail = refname; *tail && match_ref_slash < slash; ) if (*tail++ == '/') slash--; if (!*tail) return 0; - if (wildmatch(match_ref_pattern, tail, 0, NULL)) + if (wildmatch(match_ref_pattern, tail, 0)) return 0; if (starts_with(refname, "refs/heads/")) return append_head_ref(refname, oid, flag, cb_data); @@ -520,7 +514,7 @@ static int show_independent(struct commit **rev, static void append_one_rev(const char *av) { struct object_id revkey; - if (!get_sha1(av, revkey.hash)) { + if (!get_oid(av, &revkey)) { append_ref(av, &revkey, 0); return; } @@ -529,7 +523,7 @@ static void append_one_rev(const char *av) int saved_matches = ref_name_cnt; match_ref_pattern = av; - match_ref_slash = count_slash(av); + match_ref_slash = count_slashes(av); for_each_ref(append_matching_ref, NULL); if (saved_matches == ref_name_cnt && ref_name_cnt < MAX_REVS) @@ -711,8 +705,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) static const char *fake_av[2]; fake_av[0] = resolve_refdup("HEAD", - RESOLVE_REF_READING, - oid.hash, NULL); + RESOLVE_REF_READING, &oid, + NULL); fake_av[1] = NULL; av = fake_av; ac = 1; @@ -726,7 +720,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) die(Q_("only %d entry can be shown at one time.", "only %d entries can be shown at one time.", MAX_REVS), MAX_REVS); - if (!dwim_ref(*av, strlen(*av), oid.hash, &ref)) + if (!dwim_ref(*av, strlen(*av), &oid, &ref)) die(_("no such ref %s"), *av); /* Has the base been specified? */ @@ -735,9 +729,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) base = strtoul(reflog_base, &ep, 10); if (*ep) { /* Ah, that is a date spec... */ - unsigned long at; + timestamp_t at; at = approxidate(reflog_base); - read_ref_at(ref, flags, at, -1, oid.hash, NULL, + read_ref_at(ref, flags, at, -1, &oid, NULL, NULL, NULL, &base); } } @@ -746,10 +740,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) char *logmsg; char *nth_desc; const char *msg; - unsigned long timestamp; + timestamp_t timestamp; int tz; - if (read_ref_at(ref, flags, 0, base+i, oid.hash, &logmsg, + if (read_ref_at(ref, flags, 0, base + i, &oid, &logmsg, ×tamp, &tz, NULL)) { reflog = i; break; @@ -781,7 +775,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } head = resolve_refdup("HEAD", RESOLVE_REF_READING, - head_oid.hash, NULL); + &head_oid, NULL); if (with_current_branch && head) { int has_head = 0; @@ -814,9 +808,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) die(Q_("cannot handle more than %d rev.", "cannot handle more than %d revs.", MAX_REVS), MAX_REVS); - if (get_sha1(ref_name[num_rev], revkey.hash)) + if (get_oid(ref_name[num_rev], &revkey)) die(_("'%s' is not a valid ref."), ref_name[num_rev]); - commit = lookup_commit_reference(revkey.hash); + commit = lookup_commit_reference(&revkey); if (!commit) die(_("cannot find commit %s (%s)"), ref_name[num_rev], oid_to_hex(&revkey)); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 013d241abc..41e5e71cad 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -38,7 +38,7 @@ static void show_one(const char *refname, const struct object_id *oid) if (!deref_tags) return; - if (!peel_ref(refname, peeled.hash)) { + if (!peel_ref(refname, &peeled)) { hex = find_unique_abbrev(peeled.hash, abbrev); printf("%s %s^{}\n", hex, refname); } @@ -197,7 +197,7 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) struct object_id oid; if ((starts_with(*pattern, "refs/") || !strcmp(*pattern, "HEAD")) && - !read_ref(*pattern, oid.hash)) { + !read_ref(*pattern, &oid)) { show_one(*pattern, &oid); } else if (!quiet) diff --git a/builtin/stripspace.c b/builtin/stripspace.c index 1e62a008cb..bdf0328869 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "parse-options.h" #include "strbuf.h" diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index b4b4d29d82..4f35c98bb9 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1,5 +1,7 @@ #include "builtin.h" +#include "repository.h" #include "cache.h" +#include "config.h" #include "parse-options.h" #include "quote.h" #include "pathspec.h" @@ -11,13 +13,23 @@ #include "remote.h" #include "refs.h" #include "connect.h" +#include "revision.h" +#include "diffcore.h" +#include "diff.h" + +#define OPT_QUIET (1 << 0) +#define OPT_CACHED (1 << 1) +#define OPT_RECURSIVE (1 << 2) +#define OPT_FORCE (1 << 3) + +typedef void (*each_submodule_fn)(const struct cache_entry *list_item, + void *cb_data); static char *get_default_remote(void) { char *dest = NULL, *ret; - unsigned char sha1[20]; struct strbuf sb = STRBUF_INIT; - const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL); + const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); if (!refname) die(_("No such ref: %s"), "HEAD"); @@ -39,6 +51,20 @@ static char *get_default_remote(void) return ret; } +static int print_default_remote(int argc, const char **argv, const char *prefix) +{ + const char *remote; + + if (argc != 1) + die(_("submodule--helper print-default-remote takes no arguments")); + + remote = get_default_remote(); + if (remote) + printf("%s\n", remote); + + return 0; +} + static int starts_with_dot_slash(const char *str) { return str[0] == '.' && is_dir_sep(str[1]); @@ -218,6 +244,64 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr return 0; } +/* the result should be freed by the caller. */ +static char *get_submodule_displaypath(const char *path, const char *prefix) +{ + const char *super_prefix = get_super_prefix(); + + if (prefix && super_prefix) { + BUG("cannot have prefix '%s' and superprefix '%s'", + prefix, super_prefix); + } else if (prefix) { + struct strbuf sb = STRBUF_INIT; + char *displaypath = xstrdup(relative_path(path, prefix, &sb)); + strbuf_release(&sb); + return displaypath; + } else if (super_prefix) { + return xstrfmt("%s%s", super_prefix, path); + } else { + return xstrdup(path); + } +} + +static char *compute_rev_name(const char *sub_path, const char* object_id) +{ + struct strbuf sb = STRBUF_INIT; + const char ***d; + + static const char *describe_bare[] = { NULL }; + + static const char *describe_tags[] = { "--tags", NULL }; + + static const char *describe_contains[] = { "--contains", NULL }; + + static const char *describe_all_always[] = { "--all", "--always", NULL }; + + static const char **describe_argv[] = { describe_bare, describe_tags, + describe_contains, + describe_all_always, NULL }; + + for (d = describe_argv; *d; d++) { + struct child_process cp = CHILD_PROCESS_INIT; + prepare_submodule_repo_env(&cp.env_array); + cp.dir = sub_path; + cp.git_cmd = 1; + cp.no_stderr = 1; + + argv_array_push(&cp.args, "describe"); + argv_array_pushv(&cp.args, *d); + argv_array_push(&cp.args, object_id); + + if (!capture_command(&cp, &sb, 0)) { + strbuf_strip_suffix(&sb, "\n"); + return strbuf_detach(&sb, NULL); + } + } + + strbuf_release(&sb); + return NULL; +} + struct module_list { const struct cache_entry **entries; int alloc, nr; @@ -232,8 +316,7 @@ static int module_list_compute(int argc, const char **argv, int i, result = 0; char *ps_matched = NULL; parse_pathspec(pathspec, 0, - PATHSPEC_PREFER_FULL | - PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, + PATHSPEC_PREFER_FULL, prefix, argv); if (pathspec->nr) @@ -274,12 +357,10 @@ static void module_list_active(struct module_list *list) int i; struct module_list active_modules = MODULE_LIST_INIT; - gitmodules_config(); - for (i = 0; i < list->nr; i++) { const struct cache_entry *ce = list->entries[i]; - if (!is_submodule_initialized(ce->name)) + if (!is_submodule_active(the_repository, ce->name)) continue; ALLOC_GROW(active_modules.entries, @@ -292,6 +373,25 @@ static void module_list_active(struct module_list *list) *list = active_modules; } +static char *get_up_path(const char *path) +{ + int i; + struct strbuf sb = STRBUF_INIT; + + for (i = count_slashes(path); i; i--) + strbuf_addstr(&sb, "../"); + + /* + * Check if 'path' ends with slash or not + * for having the same output for dir/sub_dir + * and dir/sub_dir/ + */ + if (!is_dir_sep(path[strlen(path) - 1])) + strbuf_addstr(&sb, "../"); + + return strbuf_detach(&sb, NULL); +} + static int module_list(int argc, const char **argv, const char *prefix) { int i; @@ -330,26 +430,31 @@ static int module_list(int argc, const char **argv, const char *prefix) return 0; } -static void init_submodule(const char *path, const char *prefix, int quiet) +static void for_each_listed_submodule(const struct module_list *list, + each_submodule_fn fn, void *cb_data) +{ + int i; + for (i = 0; i < list->nr; i++) + fn(list->entries[i], cb_data); +} + +struct init_cb { + const char *prefix; + unsigned int flags; +}; + +#define INIT_CB_INIT { NULL, 0 } + +static void init_submodule(const char *path, const char *prefix, + unsigned int flags) { const struct submodule *sub; struct strbuf sb = STRBUF_INIT; char *upd = NULL, *url = NULL, *displaypath; - /* Only loads from .gitmodules, no overlay with .git/config */ - gitmodules_config(); - - if (prefix && get_super_prefix()) - die("BUG: cannot have prefix and superprefix"); - else if (prefix) - displaypath = xstrdup(relative_path(path, prefix, &sb)); - else if (get_super_prefix()) { - strbuf_addf(&sb, "%s%s", get_super_prefix(), path); - displaypath = strbuf_detach(&sb, NULL); - } else - displaypath = xstrdup(path); + displaypath = get_submodule_displaypath(path, prefix); - sub = submodule_from_path(null_sha1, path); + sub = submodule_from_path(&null_oid, path); if (!sub) die(_("No url found for submodule path '%s' in .gitmodules"), @@ -361,10 +466,10 @@ static void init_submodule(const char *path, const char *prefix, int quiet) * * Set active flag for the submodule being initialized */ - if (!is_submodule_initialized(path)) { - strbuf_reset(&sb); + if (!is_submodule_active(the_repository, path)) { strbuf_addf(&sb, "submodule.%s.active", sub->name); git_config_set_gently(sb.buf, "true"); + strbuf_reset(&sb); } /* @@ -372,7 +477,6 @@ static void init_submodule(const char *path, const char *prefix, int quiet) * To look up the url in .git/config, we must not fall back to * .gitmodules, so look it up directly. */ - strbuf_reset(&sb); strbuf_addf(&sb, "submodule.%s.url", sub->name); if (git_config_get_string(sb.buf, &url)) { if (!sub->url) @@ -404,14 +508,14 @@ static void init_submodule(const char *path, const char *prefix, int quiet) if (git_config_set_gently(sb.buf, url)) die(_("Failed to register url for submodule path '%s'"), displaypath); - if (!quiet) + if (!(flags & OPT_QUIET)) fprintf(stderr, _("Submodule '%s' (%s) registered for path '%s'\n"), sub->name, url, displaypath); } + strbuf_reset(&sb); /* Copy "update" setting when it is not set yet */ - strbuf_reset(&sb); strbuf_addf(&sb, "submodule.%s.update", sub->name); if (git_config_get_string(sb.buf, &upd) && sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { @@ -431,12 +535,18 @@ static void init_submodule(const char *path, const char *prefix, int quiet) free(upd); } +static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data) +{ + struct init_cb *info = cb_data; + init_submodule(list_item->name, info->prefix, info->flags); +} + static int module_init(int argc, const char **argv, const char *prefix) { + struct init_cb info = INIT_CB_INIT; struct pathspec pathspec; struct module_list list = MODULE_LIST_INIT; int quiet = 0; - int i; struct option module_init_options[] = { OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")), @@ -461,8 +571,165 @@ static int module_init(int argc, const char **argv, const char *prefix) if (!argc && git_config_get_value_multi("submodule.active")) module_list_active(&list); - for (i = 0; i < list.nr; i++) - init_submodule(list.entries[i]->name, prefix, quiet); + info.prefix = prefix; + if (quiet) + info.flags |= OPT_QUIET; + + for_each_listed_submodule(&list, init_submodule_cb, &info); + + return 0; +} + +struct status_cb { + const char *prefix; + unsigned int flags; +}; + +#define STATUS_CB_INIT { NULL, 0 } + +static void print_status(unsigned int flags, char state, const char *path, + const struct object_id *oid, const char *displaypath) +{ + if (flags & OPT_QUIET) + return; + + printf("%c%s %s", state, oid_to_hex(oid), displaypath); + + if (state == ' ' || state == '+') + printf(" (%s)", compute_rev_name(path, oid_to_hex(oid))); + + printf("\n"); +} + +static int handle_submodule_head_ref(const char *refname, + const struct object_id *oid, int flags, + void *cb_data) +{ + struct object_id *output = cb_data; + if (oid) + oidcpy(output, oid); + + return 0; +} + +static void status_submodule(const char *path, const struct object_id *ce_oid, + unsigned int ce_flags, const char *prefix, + unsigned int flags) +{ + char *displaypath; + struct argv_array diff_files_args = ARGV_ARRAY_INIT; + struct rev_info rev; + int diff_files_result; + + if (!submodule_from_path(&null_oid, path)) + die(_("no submodule mapping found in .gitmodules for path '%s'"), + path); + + displaypath = get_submodule_displaypath(path, prefix); + + if ((CE_STAGEMASK & ce_flags) >> CE_STAGESHIFT) { + print_status(flags, 'U', path, &null_oid, displaypath); + goto cleanup; + } + + if (!is_submodule_active(the_repository, path)) { + print_status(flags, '-', path, ce_oid, displaypath); + goto cleanup; + } + + 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); + rev.abbrev = 0; + diff_files_args.argc = setup_revisions(diff_files_args.argc, + diff_files_args.argv, + &rev, NULL); + diff_files_result = run_diff_files(&rev, 0); + + if (!diff_result_code(&rev.diffopt, diff_files_result)) { + print_status(flags, ' ', path, ce_oid, + displaypath); + } else if (!(flags & OPT_CACHED)) { + struct object_id oid; + + if (refs_head_ref(get_submodule_ref_store(path), + handle_submodule_head_ref, &oid)) + die(_("could not resolve HEAD ref inside the " + "submodule '%s'"), path); + + print_status(flags, '+', path, &oid, displaypath); + } else { + print_status(flags, '+', path, ce_oid, displaypath); + } + + if (flags & OPT_RECURSIVE) { + struct child_process cpr = CHILD_PROCESS_INIT; + + cpr.git_cmd = 1; + cpr.dir = path; + prepare_submodule_repo_env(&cpr.env_array); + + argv_array_push(&cpr.args, "--super-prefix"); + argv_array_pushf(&cpr.args, "%s/", displaypath); + argv_array_pushl(&cpr.args, "submodule--helper", "status", + "--recursive", NULL); + + if (flags & OPT_CACHED) + argv_array_push(&cpr.args, "--cached"); + + if (flags & OPT_QUIET) + argv_array_push(&cpr.args, "--quiet"); + + if (run_command(&cpr)) + die(_("failed to recurse into submodule '%s'"), path); + } + +cleanup: + argv_array_clear(&diff_files_args); + free(displaypath); +} + +static void status_submodule_cb(const struct cache_entry *list_item, + void *cb_data) +{ + struct status_cb *info = cb_data; + status_submodule(list_item->name, &list_item->oid, list_item->ce_flags, + info->prefix, info->flags); +} + +static int module_status(int argc, const char **argv, const char *prefix) +{ + struct status_cb info = STATUS_CB_INIT; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + int quiet = 0; + + struct option module_status_options[] = { + OPT__QUIET(&quiet, N_("Suppress submodule status output")), + OPT_BIT(0, "cached", &info.flags, N_("Use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED), + OPT_BIT(0, "recursive", &info.flags, N_("recurse into nested submodules"), OPT_RECURSIVE), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule status [--quiet] [--cached] [--recursive] [<path>...]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_status_options, + git_submodule_helper_usage, 0); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) + return 1; + + info.prefix = prefix; + if (quiet) + info.flags |= OPT_QUIET; + + for_each_listed_submodule(&list, status_submodule_cb, &info); return 0; } @@ -474,8 +741,7 @@ static int module_name(int argc, const char **argv, const char *prefix) if (argc != 2) usage(_("git submodule--helper name <path>")); - gitmodules_config(); - sub = submodule_from_path(null_sha1, argv[1]); + sub = submodule_from_path(&null_oid, argv[1]); if (!sub) die(_("no submodule mapping found in .gitmodules for path '%s'"), @@ -486,6 +752,309 @@ static int module_name(int argc, const char **argv, const char *prefix) return 0; } +struct sync_cb { + const char *prefix; + unsigned int flags; +}; + +#define SYNC_CB_INIT { NULL, 0 } + +static void sync_submodule(const char *path, const char *prefix, + unsigned int flags) +{ + const struct submodule *sub; + char *remote_key = NULL; + char *sub_origin_url, *super_config_url, *displaypath; + struct strbuf sb = STRBUF_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + char *sub_config_path = NULL; + + if (!is_submodule_active(the_repository, path)) + return; + + sub = submodule_from_path(&null_oid, path); + + if (sub && sub->url) { + if (starts_with_dot_dot_slash(sub->url) || + starts_with_dot_slash(sub->url)) { + char *remote_url, *up_path; + char *remote = get_default_remote(); + strbuf_addf(&sb, "remote.%s.url", remote); + + if (git_config_get_string(sb.buf, &remote_url)) + remote_url = xgetcwd(); + + up_path = get_up_path(path); + sub_origin_url = relative_url(remote_url, sub->url, up_path); + super_config_url = relative_url(remote_url, sub->url, NULL); + + free(remote); + free(up_path); + free(remote_url); + } else { + sub_origin_url = xstrdup(sub->url); + super_config_url = xstrdup(sub->url); + } + } else { + sub_origin_url = xstrdup(""); + super_config_url = xstrdup(""); + } + + displaypath = get_submodule_displaypath(path, prefix); + + if (!(flags & OPT_QUIET)) + printf(_("Synchronizing submodule url for '%s'\n"), + displaypath); + + strbuf_reset(&sb); + strbuf_addf(&sb, "submodule.%s.url", sub->name); + if (git_config_set_gently(sb.buf, super_config_url)) + die(_("failed to register url for submodule path '%s'"), + displaypath); + + if (!is_submodule_populated_gently(path, NULL)) + goto cleanup; + + prepare_submodule_repo_env(&cp.env_array); + cp.git_cmd = 1; + cp.dir = path; + argv_array_pushl(&cp.args, "submodule--helper", + "print-default-remote", NULL); + + strbuf_reset(&sb); + if (capture_command(&cp, &sb, 0)) + die(_("failed to get the default remote for submodule '%s'"), + path); + + strbuf_strip_suffix(&sb, "\n"); + remote_key = xstrfmt("remote.%s.url", sb.buf); + + strbuf_reset(&sb); + submodule_to_gitdir(&sb, path); + strbuf_addstr(&sb, "/config"); + + if (git_config_set_in_file_gently(sb.buf, remote_key, sub_origin_url)) + die(_("failed to update remote for submodule '%s'"), + path); + + if (flags & OPT_RECURSIVE) { + struct child_process cpr = CHILD_PROCESS_INIT; + + cpr.git_cmd = 1; + cpr.dir = path; + prepare_submodule_repo_env(&cpr.env_array); + + argv_array_push(&cpr.args, "--super-prefix"); + argv_array_pushf(&cpr.args, "%s/", displaypath); + argv_array_pushl(&cpr.args, "submodule--helper", "sync", + "--recursive", NULL); + + if (flags & OPT_QUIET) + argv_array_push(&cpr.args, "--quiet"); + + if (run_command(&cpr)) + die(_("failed to recurse into submodule '%s'"), + path); + } + +cleanup: + free(super_config_url); + free(sub_origin_url); + strbuf_release(&sb); + free(remote_key); + free(displaypath); + free(sub_config_path); +} + +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) +{ + struct sync_cb info = SYNC_CB_INIT; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + int quiet = 0; + int recursive = 0; + + struct option module_sync_options[] = { + OPT__QUIET(&quiet, N_("Suppress output of synchronizing submodule url")), + OPT_BOOL(0, "recursive", &recursive, + N_("Recurse into nested submodules")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper sync [--quiet] [--recursive] [<path>]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_sync_options, + git_submodule_helper_usage, 0); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) + return 1; + + info.prefix = prefix; + if (quiet) + info.flags |= OPT_QUIET; + if (recursive) + info.flags |= OPT_RECURSIVE; + + for_each_listed_submodule(&list, sync_submodule_cb, &info); + + return 0; +} + +struct deinit_cb { + const char *prefix; + unsigned int flags; +}; +#define DEINIT_CB_INIT { NULL, 0 } + +static void deinit_submodule(const char *path, const char *prefix, + unsigned int flags) +{ + const struct submodule *sub; + char *displaypath = NULL; + struct child_process cp_config = CHILD_PROCESS_INIT; + struct strbuf sb_config = STRBUF_INIT; + char *sub_git_dir = xstrfmt("%s/.git", path); + + sub = submodule_from_path(&null_oid, path); + + if (!sub || !sub->name) + goto cleanup; + + displaypath = get_submodule_displaypath(path, prefix); + + /* remove the submodule work tree (unless the user already did it) */ + if (is_directory(path)) { + struct strbuf sb_rm = STRBUF_INIT; + const char *format; + + /* + * protect submodules containing a .git directory + * NEEDSWORK: instead of dying, automatically call + * absorbgitdirs and (possibly) warn. + */ + if (is_directory(sub_git_dir)) + die(_("Submodule work tree '%s' contains a .git " + "directory (use 'rm -rf' if you really want " + "to remove it including all of its history)"), + displaypath); + + if (!(flags & OPT_FORCE)) { + struct child_process cp_rm = CHILD_PROCESS_INIT; + cp_rm.git_cmd = 1; + argv_array_pushl(&cp_rm.args, "rm", "-qn", + path, NULL); + + if (run_command(&cp_rm)) + die(_("Submodule work tree '%s' contains local " + "modifications; use '-f' to discard them"), + displaypath); + } + + strbuf_addstr(&sb_rm, path); + + if (!remove_dir_recursively(&sb_rm, 0)) + format = _("Cleared directory '%s'\n"); + else + format = _("Could not remove submodule work tree '%s'\n"); + + if (!(flags & OPT_QUIET)) + printf(format, displaypath); + + strbuf_release(&sb_rm); + } + + if (mkdir(path, 0777)) + printf(_("could not create empty submodule directory %s"), + displaypath); + + cp_config.git_cmd = 1; + argv_array_pushl(&cp_config.args, "config", "--get-regexp", NULL); + argv_array_pushf(&cp_config.args, "submodule.%s\\.", sub->name); + + /* remove the .git/config entries (unless the user already did it) */ + if (!capture_command(&cp_config, &sb_config, 0) && sb_config.len) { + char *sub_key = xstrfmt("submodule.%s", sub->name); + /* + * remove the whole section so we have a clean state when + * the user later decides to init this submodule again + */ + git_config_rename_section_in_file(NULL, sub_key, NULL); + if (!(flags & OPT_QUIET)) + printf(_("Submodule '%s' (%s) unregistered for path '%s'\n"), + sub->name, sub->url, displaypath); + free(sub_key); + } + +cleanup: + free(displaypath); + free(sub_git_dir); + strbuf_release(&sb_config); +} + +static void deinit_submodule_cb(const struct cache_entry *list_item, + void *cb_data) +{ + struct deinit_cb *info = cb_data; + deinit_submodule(list_item->name, info->prefix, info->flags); +} + +static int module_deinit(int argc, const char **argv, const char *prefix) +{ + struct deinit_cb info = DEINIT_CB_INIT; + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + int quiet = 0; + int force = 0; + int all = 0; + + struct option module_deinit_options[] = { + OPT__QUIET(&quiet, N_("Suppress submodule status output")), + OPT__FORCE(&force, N_("Remove submodule working trees even if they contain local changes"), 0), + OPT_BOOL(0, "all", &all, N_("Unregister all submodules")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule deinit [--quiet] [-f | --force] [--all | [--] [<path>...]]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_deinit_options, + git_submodule_helper_usage, 0); + + if (all && argc) { + error("pathspec and --all are incompatible"); + usage_with_options(git_submodule_helper_usage, + module_deinit_options); + } + + if (!argc && !all) + die(_("Use '--all' if you really want to deinitialize all submodules")); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) + return 1; + + info.prefix = prefix; + if (quiet) + info.flags |= OPT_QUIET; + if (force) + info.flags |= OPT_FORCE; + + for_each_listed_submodule(&list, deinit_submodule_cb, &info); + + return 0; +} + static int clone_submodule(const char *path, const char *gitdir, const char *url, const char *depth, struct string_list *reference, int quiet, int progress) @@ -779,6 +1348,10 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, struct strbuf *out) { const struct submodule *sub = NULL; + const char *url = NULL; + const char *update_string; + enum submodule_update_type update_type; + char *key; struct strbuf displaypath_sb = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; const char *displaypath = NULL; @@ -794,7 +1367,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, goto cleanup; } - sub = submodule_from_path(null_sha1, ce->name); + sub = submodule_from_path(&null_oid, ce->name); if (suc->recursive_prefix) displaypath = relative_path(suc->recursive_prefix, @@ -807,21 +1380,34 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, goto cleanup; } + key = xstrfmt("submodule.%s.update", sub->name); + if (!repo_config_get_string_const(the_repository, key, &update_string)) { + update_type = parse_submodule_update_type(update_string); + } else { + update_type = sub->update_strategy.type; + } + free(key); + if (suc->update.type == SM_UPDATE_NONE || (suc->update.type == SM_UPDATE_UNSPECIFIED - && sub->update_strategy.type == SM_UPDATE_NONE)) { + && update_type == SM_UPDATE_NONE)) { strbuf_addf(out, _("Skipping submodule '%s'"), displaypath); strbuf_addch(out, '\n'); goto cleanup; } /* Check if the submodule has been initialized. */ - if (!is_submodule_initialized(ce->name)) { + if (!is_submodule_active(the_repository, ce->name)) { next_submodule_warn_missing(suc, out, displaypath); goto cleanup; } 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; + + strbuf_reset(&sb); strbuf_addf(&sb, "%s/.git", ce->name); needs_cloning = !file_exists(sb.buf); @@ -850,7 +1436,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, argv_array_push(&child->args, "--depth=1"); argv_array_pushl(&child->args, "--path", sub->path, NULL); argv_array_pushl(&child->args, "--name", sub->name, NULL); - argv_array_pushl(&child->args, "--url", sub->url, NULL); + argv_array_pushl(&child->args, "--url", url, NULL); if (suc->references.nr) { struct string_list_item *item; for_each_string_list_item(item, &suc->references) @@ -929,7 +1515,7 @@ static int update_clone_task_finished(int result, const struct cache_entry *ce; struct submodule_update_clone *suc = suc_cb; - int *idxP = *(int**)idx_task_cb; + int *idxP = idx_task_cb; int idx = *idxP; free(idxP); @@ -959,10 +1545,19 @@ static int update_clone_task_finished(int result, return 0; } +static int gitmodules_update_clone_config(const char *var, const char *value, + void *cb) +{ + int *max_jobs = cb; + if (!strcmp(var, "submodule.fetchjobs")) + *max_jobs = parse_submodule_fetchjobs(var, value); + return 0; +} + static int update_clone(int argc, const char **argv, const char *prefix) { const char *update = NULL; - int max_jobs = -1; + int max_jobs = 1; struct string_list_item *item; struct pathspec pathspec; struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; @@ -999,6 +1594,9 @@ static int update_clone(int argc, const char **argv, const char *prefix) }; suc.prefix = prefix; + config_from_gitmodules(gitmodules_update_clone_config, &max_jobs); + git_config(gitmodules_update_clone_config, &max_jobs); + argc = parse_options(argc, argv, prefix, module_update_clone_options, git_submodule_helper_usage, 0); @@ -1012,13 +1610,6 @@ static int update_clone(int argc, const char **argv, const char *prefix) if (pathspec.nr) suc.warn_if_uninitialized = 1; - /* Overlay the parsed .gitmodules file with .git/config */ - gitmodules_config(); - git_config(submodule_config, NULL); - - if (max_jobs < 0) - max_jobs = parallel_submodules(); - run_processes_parallel(max_jobs, update_clone_get_next_task, update_clone_start_failure, @@ -1056,19 +1647,23 @@ static int resolve_relative_path(int argc, const char **argv, const char *prefix static const char *remote_submodule_branch(const char *path) { const struct submodule *sub; - gitmodules_config(); - git_config(submodule_config, NULL); + const char *branch = NULL; + char *key; - sub = submodule_from_path(null_sha1, path); + sub = submodule_from_path(&null_oid, path); if (!sub) return NULL; - if (!sub->branch) + key = xstrfmt("submodule.%s.branch", sub->name); + if (repo_config_get_string_const(the_repository, key, &branch)) + branch = sub->branch; + free(key); + + if (!branch) return "master"; - if (!strcmp(sub->branch, ".")) { - unsigned char sha1[20]; - const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL); + if (!strcmp(branch, ".")) { + const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); if (!refname) die(_("No such ref: %s"), "HEAD"); @@ -1084,7 +1679,7 @@ static const char *remote_submodule_branch(const char *path) return refname; } - return sub->branch; + return branch; } static int resolve_remote_submodule_branch(int argc, const char **argv, @@ -1107,9 +1702,28 @@ static int resolve_remote_submodule_branch(int argc, const char **argv, static int push_check(int argc, const char **argv, const char *prefix) { struct remote *remote; + const char *superproject_head; + char *head; + int detached_head = 0; + struct object_id head_oid; - if (argc < 2) - die("submodule--helper push-check requires at least 1 argument"); + if (argc < 3) + die("submodule--helper push-check requires at least 2 arguments"); + + /* + * superproject's resolved head ref. + * if HEAD then the superproject is in a detached head state, otherwise + * it will be the resolved head ref. + */ + superproject_head = argv[1]; + argv++; + argc--; + /* Get the submodule's head ref and determine if it is detached */ + head = resolve_refdup("HEAD", 0, &head_oid, NULL); + if (!head) + die(_("Failed to resolve HEAD as a valid ref.")); + if (!strcmp(head, "HEAD")) + detached_head = 1; /* * The remote must be configured. @@ -1132,18 +1746,31 @@ static int push_check(int argc, const char **argv, const char *prefix) if (rs->pattern || rs->matching) continue; - /* - * LHS must match a single ref - * NEEDSWORK: add logic to special case 'HEAD' once - * working with submodules in a detached head state - * ceases to be the norm. - */ - if (count_refspec_match(rs->src, local_refs, NULL) != 1) + /* LHS must match a single ref */ + switch (count_refspec_match(rs->src, local_refs, NULL)) { + case 1: + break; + case 0: + /* + * If LHS matches 'HEAD' then we need to ensure + * that it matches the same named branch + * checked out in the superproject. + */ + if (!strcmp(rs->src, "HEAD")) { + if (!detached_head && + !strcmp(head, superproject_head)) + break; + die("HEAD does not match the named branch in the superproject"); + } + /* fallthrough */ + default: die("src refspec '%s' must name a ref", rs->src); + } } free_refspec(refspec_nr, refspec); } + free(head); return 0; } @@ -1172,9 +1799,6 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, embed_gitdir_options, git_submodule_helper_usage, 0); - gitmodules_config(); - git_config(submodule_config, NULL); - if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) return 1; @@ -1190,9 +1814,7 @@ static int is_active(int argc, const char **argv, const char *prefix) if (argc != 2) die("submodule--helper is-active takes exactly 1 argument"); - gitmodules_config(); - - return !is_submodule_initialized(argv[1]); + return !is_submodule_active(the_repository, argv[1]); } /* @@ -1235,6 +1857,10 @@ static struct cmd_struct commands[] = { {"resolve-relative-url", resolve_relative_url, 0}, {"resolve-relative-url-test", resolve_relative_url_test, 0}, {"init", module_init, SUPPORT_SUPER_PREFIX}, + {"status", module_status, SUPPORT_SUPER_PREFIX}, + {"print-default-remote", print_default_remote, 0}, + {"sync", module_sync, SUPPORT_SUPER_PREFIX}, + {"deinit", module_deinit, 0}, {"remote-branch", resolve_remote_submodule_branch, 0}, {"push-check", push_check, 0}, {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, @@ -1245,9 +1871,8 @@ static struct cmd_struct commands[] = { int cmd_submodule__helper(int argc, const char **argv, const char *prefix) { int i; - if (argc < 2) - die(_("submodule--helper subcommand must be " - "called with a subcommand")); + if (argc < 2 || !strcmp(argv[1], "-h")) + usage("git submodule--helper <command>"); for (i = 0; i < ARRAY_SIZE(commands); i++) { if (!strcmp(argv[1], commands[i].cmd)) { diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 70addef158..80237f0df1 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "cache.h" #include "refs.h" #include "parse-options.h" @@ -11,9 +12,8 @@ static const char * const git_symbolic_ref_usage[] = { static int check_symref(const char *HEAD, int quiet, int shorten, int print) { - unsigned char sha1[20]; int flag; - const char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag); + const char *refname = resolve_ref_unsafe(HEAD, 0, NULL, &flag); if (!refname) die("No such ref: %s", HEAD); @@ -58,7 +58,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) die("Cannot delete %s, not a symbolic ref", argv[0]); if (!strcmp(argv[0], "HEAD")) die("deleting '%s' is not allowed", argv[0]); - return delete_ref(NULL, argv[0], NULL, REF_NODEREF); + return delete_ref(NULL, argv[0], NULL, REF_NO_DEREF); } switch (argc) { diff --git a/builtin/tag.c b/builtin/tag.c index bdf1e88e93..da186691ed 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -7,6 +7,7 @@ */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "refs.h" #include "tag.h" @@ -31,7 +32,8 @@ static const char * const git_tag_usage[] = { static unsigned int colopts; static int force_sign_annotate; -static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) +static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, + struct ref_format *format) { struct ref_array array; char *to_free = NULL; @@ -42,23 +44,24 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con if (filter->lines == -1) filter->lines = 0; - if (!format) { + if (!format->format) { if (filter->lines) { to_free = xstrfmt("%s %%(contents:lines=%d)", "%(align:15)%(refname:lstrip=2)%(end)", filter->lines); - format = to_free; + format->format = to_free; } else - format = "%(refname:lstrip=2)"; + format->format = "%(refname:lstrip=2)"; } - verify_ref_format(format); + if (verify_ref_format(format)) + die(_("unable to parse format string")); filter->with_commit_tag_algo = 1; filter_refs(&array, filter, FILTER_REFS_TAGS); ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) - show_ref_array_item(array.items[i], format, 0); + show_ref_array_item(array.items[i], format); ref_array_clear(&array); free(to_free); @@ -66,7 +69,7 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, con } typedef int (*each_tag_name_fn)(const char *name, const char *ref, - const unsigned char *sha1, const void *cb_data); + const struct object_id *oid, const void *cb_data); static int for_each_tag_name(const char **argv, each_tag_name_fn fn, const void *cb_data) @@ -74,17 +77,17 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn, const char **p; struct strbuf ref = STRBUF_INIT; int had_error = 0; - unsigned char sha1[20]; + struct object_id oid; for (p = argv; *p; p++) { strbuf_reset(&ref); strbuf_addf(&ref, "refs/tags/%s", *p); - if (read_ref(ref.buf, sha1)) { + if (read_ref(ref.buf, &oid)) { error(_("tag '%s' not found."), *p); had_error = 1; continue; } - if (fn(*p, ref.buf, sha1, cb_data)) + if (fn(*p, ref.buf, &oid, cb_data)) had_error = 1; } strbuf_release(&ref); @@ -92,29 +95,29 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn, } static int delete_tag(const char *name, const char *ref, - const unsigned char *sha1, const void *cb_data) + const struct object_id *oid, const void *cb_data) { - if (delete_ref(NULL, ref, sha1, 0)) + if (delete_ref(NULL, ref, oid, 0)) return 1; - printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV)); + printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(oid->hash, DEFAULT_ABBREV)); return 0; } static int verify_tag(const char *name, const char *ref, - const unsigned char *sha1, const void *cb_data) + const struct object_id *oid, const void *cb_data) { int flags; - const char *fmt_pretty = cb_data; + const struct ref_format *format = cb_data; flags = GPG_VERIFY_VERBOSE; - if (fmt_pretty) + if (format->format) flags = GPG_VERIFY_OMIT_STATUS; - if (gpg_verify_tag(sha1, name, flags)) + if (gpg_verify_tag(oid, name, flags)) return -1; - if (fmt_pretty) - pretty_print_ref(name, sha1, fmt_pretty); + if (format->format) + pretty_print_ref(name, oid->hash, format); return 0; } @@ -133,30 +136,6 @@ static const char tag_template_nocleanup[] = "Lines starting with '%c' will be kept; you may remove them" " yourself if you want to.\n"); -/* Parse arg given and add it the ref_sorting array */ -static int parse_sorting_string(const char *arg, struct ref_sorting **sorting_tail) -{ - struct ref_sorting *s; - int len; - - s = xcalloc(1, sizeof(*s)); - s->next = *sorting_tail; - *sorting_tail = s; - - if (*arg == '-') { - s->reverse = 1; - arg++; - } - if (skip_prefix(arg, "version:", &arg) || - skip_prefix(arg, "v:", &arg)) - s->version = 1; - - len = strlen(arg); - s->atom = parse_ref_filter_atom(arg, arg+len); - - return 0; -} - static int git_tag_config(const char *var, const char *value, void *cb) { int status; @@ -165,7 +144,7 @@ static int git_tag_config(const char *var, const char *value, void *cb) if (!strcmp(var, "tag.sort")) { if (!value) return config_error_nonbool(var); - parse_sorting_string(value, sorting_tail); + parse_ref_sorting(sorting_tail, value); return 0; } @@ -179,16 +158,16 @@ static int git_tag_config(const char *var, const char *value, void *cb) if (starts_with(var, "column.")) return git_column_config(var, value, "tag", &colopts); - return git_default_config(var, value, cb); + return git_color_default_config(var, value, cb); } -static void write_tag_body(int fd, const unsigned char *sha1) +static void write_tag_body(int fd, const struct object_id *oid) { unsigned long size; enum object_type type; char *buf, *sp; - buf = read_sha1_file(sha1, &type, &size); + buf = read_sha1_file(oid->hash, &type, &size); if (!buf) return; /* skip header */ @@ -204,17 +183,18 @@ static void write_tag_body(int fd, const unsigned char *sha1) free(buf); } -static int build_tag_object(struct strbuf *buf, int sign, unsigned char *result) +static int build_tag_object(struct strbuf *buf, int sign, struct object_id *result) { if (sign && do_sign(buf) < 0) return error(_("unable to sign the tag")); - if (write_sha1_file(buf->buf, buf->len, tag_type, result) < 0) + if (write_object_file(buf->buf, buf->len, tag_type, result) < 0) return error(_("unable to write tag file")); return 0; } struct create_tag_options { unsigned int message_given:1; + unsigned int use_editor:1; unsigned int sign; enum { CLEANUP_NONE, @@ -223,15 +203,15 @@ struct create_tag_options { } cleanup_mode; }; -static void create_tag(const unsigned char *object, const char *tag, +static void create_tag(const struct object_id *object, const char *tag, struct strbuf *buf, struct create_tag_options *opt, - unsigned char *prev, unsigned char *result) + struct object_id *prev, struct object_id *result) { enum object_type type; struct strbuf header = STRBUF_INIT; char *path = NULL; - type = sha1_object_info(object, NULL); + type = sha1_object_info(object->hash, NULL); if (type <= OBJ_NONE) die(_("bad object type.")); @@ -240,12 +220,12 @@ static void create_tag(const unsigned char *object, const char *tag, "type %s\n" "tag %s\n" "tagger %s\n\n", - sha1_to_hex(object), - typename(type), + oid_to_hex(object), + type_name(type), tag, git_committer_info(IDENT_STRICT)); - if (!opt->message_given) { + if (!opt->message_given || opt->use_editor) { int fd; /* write the template message before editing: */ @@ -254,7 +234,10 @@ static void create_tag(const unsigned char *object, const char *tag, if (fd < 0) die_errno(_("could not create file '%s'"), path); - if (!is_null_sha1(prev)) { + if (opt->message_given) { + write_or_die(fd, buf->buf, buf->len); + strbuf_reset(buf); + } else if (!is_null_oid(prev)) { write_tag_body(fd, prev); } else { struct strbuf buf = STRBUF_INIT; @@ -296,7 +279,7 @@ static void create_tag(const unsigned char *object, const char *tag, } } -static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb) +static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb) { enum object_type type; struct commit *c; @@ -310,17 +293,17 @@ static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb) strbuf_addstr(sb, rla); } else { strbuf_addstr(sb, "tag: tagging "); - strbuf_add_unique_abbrev(sb, sha1, DEFAULT_ABBREV); + strbuf_add_unique_abbrev(sb, oid->hash, DEFAULT_ABBREV); } strbuf_addstr(sb, " ("); - type = sha1_object_info(sha1, NULL); + type = sha1_object_info(oid->hash, NULL); switch (type) { default: strbuf_addstr(sb, "object of unknown type"); break; case OBJ_COMMIT: - if ((buf = read_sha1_file(sha1, &type, &size)) != NULL) { + if ((buf = read_sha1_file(oid->hash, &type, &size)) != NULL) { subject_len = find_commit_subject(buf, &subject_start); strbuf_insert(sb, sb->len, subject_start, subject_len); } else { @@ -328,7 +311,7 @@ static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb) } free(buf); - if ((c = lookup_commit_reference(sha1)) != NULL) + if ((c = lookup_commit_reference(oid)) != NULL) strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT))); break; case OBJ_TREE: @@ -378,7 +361,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; struct strbuf ref = STRBUF_INIT; struct strbuf reflog_msg = STRBUF_INIT; - unsigned char object[20], prev[20]; + struct object_id object, prev; const char *object_ref, *tag; struct create_tag_options opt; char *cleanup_arg = NULL; @@ -391,8 +374,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct strbuf err = STRBUF_INIT; struct ref_filter filter; static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; - const char *format = NULL; + struct ref_format format = REF_FORMAT_INIT; int icase = 0; + int edit_flag = 0; struct option options[] = { OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), { OPTION_INTEGER, 'n', NULL, &filter.lines, N_("n"), @@ -407,12 +391,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_CALLBACK('m', "message", &msg, N_("message"), N_("tag message"), 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_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")), + OPT__FORCE(&force, N_("replace the tag if exists"), 0), OPT_BOOL(0, "create-reflog", &create_reflog, N_("create a reflog")), OPT_GROUP(N_("Tag listing options")), @@ -430,7 +415,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT, parse_opt_object_name, (intptr_t) "HEAD" }, - OPT_STRING( 0 , "format", &format, N_("format"), N_("format to use for the output")), + 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_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_END() }; @@ -460,6 +447,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) cmdmode = 'l'; } + if (cmdmode == 'l') + setup_auto_pager("tag", 1); + if ((create_tag_object || force) && (cmdmode != 0)) usage_with_options(git_tag_usage, options); @@ -482,7 +472,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) run_column_filter(colopts, &copts); } filter.name_patterns = argv; - ret = list_tags(&filter, sorting, format); + ret = list_tags(&filter, sorting, &format); if (column_active(colopts)) stop_column_filter(); return ret; @@ -500,9 +490,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (cmdmode == 'd') return for_each_tag_name(argv, delete_tag, NULL); if (cmdmode == 'v') { - if (format) - verify_ref_format(format); - return for_each_tag_name(argv, verify_tag, format); + if (format.format && verify_ref_format(&format)) + usage_with_options(git_tag_usage, options); + return for_each_tag_name(argv, verify_tag, &format); } if (msg.given || msgfile) { @@ -528,18 +518,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (argc > 2) die(_("too many params")); - if (get_sha1(object_ref, object)) + if (get_oid(object_ref, &object)) die(_("Failed to resolve '%s' as a valid ref."), object_ref); if (strbuf_check_tag_ref(&ref, tag)) die(_("'%s' is not a valid tag name."), tag); - if (read_ref(ref.buf, prev)) - hashclr(prev); + if (read_ref(ref.buf, &prev)) + oidclr(&prev); else if (!force) die(_("tag '%s' already exists"), tag); opt.message_given = msg.given || msgfile; + opt.use_editor = edit_flag; if (!cleanup_arg || !strcmp(cleanup_arg, "strip")) opt.cleanup_mode = CLEANUP_ALL; @@ -550,28 +541,29 @@ int cmd_tag(int argc, const char **argv, const char *prefix) else die(_("Invalid cleanup mode %s"), cleanup_arg); - create_reflog_msg(object, &reflog_msg); + create_reflog_msg(&object, &reflog_msg); if (create_tag_object) { if (force_sign_annotate && !annotate) opt.sign = 1; - create_tag(object, tag, &buf, &opt, prev, object); + create_tag(&object, tag, &buf, &opt, &prev, &object); } transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, ref.buf, object, prev, + ref_transaction_update(transaction, ref.buf, &object, &prev, create_reflog ? REF_FORCE_CREATE_REFLOG : 0, reflog_msg.buf, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); - if (force && !is_null_sha1(prev) && hashcmp(prev, object)) - printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV)); - - strbuf_release(&err); - strbuf_release(&buf); - strbuf_release(&ref); - strbuf_release(&reflog_msg); + if (force && !is_null_oid(&prev) && oidcmp(&prev, &object)) + printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev.hash, DEFAULT_ABBREV)); + + UNLEAK(buf); + UNLEAK(ref); + UNLEAK(reflog_msg); + UNLEAK(msg); + UNLEAK(err); return 0; } diff --git a/builtin/unpack-file.c b/builtin/unpack-file.c index 6fc6bcdf7f..32e0155577 100644 --- a/builtin/unpack-file.c +++ b/builtin/unpack-file.c @@ -1,6 +1,7 @@ #include "builtin.h" +#include "config.h" -static char *create_temp_file(unsigned char *sha1) +static char *create_temp_file(struct object_id *oid) { static char path[50]; void *buf; @@ -8,13 +9,13 @@ static char *create_temp_file(unsigned char *sha1) unsigned long size; int fd; - buf = read_sha1_file(sha1, &type, &size); + buf = read_sha1_file(oid->hash, &type, &size); if (!buf || type != OBJ_BLOB) - die("unable to read blob object %s", sha1_to_hex(sha1)); + die("unable to read blob object %s", oid_to_hex(oid)); xsnprintf(path, sizeof(path), ".merge_file_XXXXXX"); fd = xmkstemp(path); - if (write_in_full(fd, buf, size) != size) + if (write_in_full(fd, buf, size) < 0) die_errno("unable to write temp-file"); close(fd); return path; @@ -22,15 +23,15 @@ static char *create_temp_file(unsigned char *sha1) int cmd_unpack_file(int argc, const char **argv, const char *prefix) { - unsigned char sha1[20]; + struct object_id oid; if (argc != 2 || !strcmp(argv[1], "-h")) usage("git unpack-file <sha1>"); - if (get_sha1(argv[1], sha1)) + if (get_oid(argv[1], &oid)) die("Not a valid object name %s", argv[1]); git_config(git_default_config, NULL); - puts(create_temp_file(sha1)); + puts(create_temp_file(&oid)); return 0; } diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 4532aa0831..6620feec68 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "object.h" #include "delta.h" #include "pack.h" @@ -20,7 +21,7 @@ static unsigned char buffer[4096]; static unsigned int offset, len; static off_t consumed_bytes; static off_t max_input_size; -static git_SHA_CTX ctx; +static git_hash_ctx ctx; static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; /* @@ -61,7 +62,7 @@ static void *fill(int min) if (min > sizeof(buffer)) die("cannot fill %d bytes", min); if (offset) { - git_SHA1_Update(&ctx, buffer, offset); + the_hash_algo->update_fn(&ctx, buffer, offset); memmove(buffer, buffer + offset, len); offset = 0; } @@ -112,8 +113,7 @@ static void *get_data(unsigned long size) break; if (ret != Z_OK) { error("inflate returned %d", ret); - free(buf); - buf = NULL; + FREE_AND_NULL(buf); if (!recover) exit(1); has_errors = 1; @@ -127,7 +127,7 @@ static void *get_data(unsigned long size) } struct delta_info { - unsigned char base_sha1[20]; + struct object_id base_oid; unsigned nr; off_t base_offset; unsigned long size; @@ -137,13 +137,13 @@ struct delta_info { static struct delta_info *delta_list; -static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1, +static void add_delta_to_list(unsigned nr, const struct object_id *base_oid, off_t base_offset, void *delta, unsigned long size) { struct delta_info *info = xmalloc(sizeof(*info)); - hashcpy(info->base_sha1, base_sha1); + oidcpy(&info->base_oid, base_oid); info->base_offset = base_offset; info->size = size; info->delta = delta; @@ -154,10 +154,11 @@ static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1, struct obj_info { off_t offset; - unsigned char sha1[20]; + struct object_id oid; struct object *obj; }; +/* Remember to update object flag allocation in object.h */ #define FLAG_OPEN (1u<<20) #define FLAG_WRITTEN (1u<<21) @@ -170,9 +171,10 @@ static unsigned nr_objects; */ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) { - unsigned char sha1[20]; + struct object_id oid; - if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0) + if (write_object_file(obj_buf->buffer, obj_buf->size, + type_name(obj->type), &oid) < 0) die("failed to write object %s", oid_to_hex(&obj->oid)); obj->flags |= FLAG_WRITTEN; } @@ -237,19 +239,21 @@ static void write_object(unsigned nr, enum object_type type, void *buf, unsigned long size) { if (!strict) { - if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0) + if (write_object_file(buf, size, type_name(type), + &obj_list[nr].oid) < 0) die("failed to write object"); added_object(nr, type, buf, size); free(buf); obj_list[nr].obj = NULL; } else if (type == OBJ_BLOB) { struct blob *blob; - if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0) + if (write_object_file(buf, size, type_name(type), + &obj_list[nr].oid) < 0) die("failed to write object"); added_object(nr, type, buf, size); free(buf); - blob = lookup_blob(obj_list[nr].sha1); + blob = lookup_blob(&obj_list[nr].oid); if (blob) blob->object.flags |= FLAG_WRITTEN; else @@ -258,11 +262,12 @@ static void write_object(unsigned nr, enum object_type type, } else { struct object *obj; int eaten; - hash_sha1_file(buf, size, typename(type), obj_list[nr].sha1); + hash_object_file(buf, size, type_name(type), &obj_list[nr].oid); added_object(nr, type, buf, size); - obj = parse_object_buffer(obj_list[nr].sha1, type, size, buf, &eaten); + obj = parse_object_buffer(&obj_list[nr].oid, type, size, buf, + &eaten); if (!obj) - die("invalid %s", typename(type)); + die("invalid %s", type_name(type)); add_object_buffer(obj, buf, size); obj->flags |= FLAG_OPEN; obj_list[nr].obj = obj; @@ -296,7 +301,7 @@ static void added_object(unsigned nr, enum object_type type, struct delta_info *info; while ((info = *p) != NULL) { - if (!hashcmp(info->base_sha1, obj_list[nr].sha1) || + if (!oidcmp(&info->base_oid, &obj_list[nr].oid) || info->base_offset == obj_list[nr].offset) { *p = info->next; p = &delta_list; @@ -320,12 +325,12 @@ static void unpack_non_delta_entry(enum object_type type, unsigned long size, free(buf); } -static int resolve_against_held(unsigned nr, const unsigned char *base, +static int resolve_against_held(unsigned nr, const struct object_id *base, void *delta_data, unsigned long delta_size) { struct object *obj; struct obj_buffer *obj_buffer; - obj = lookup_object(base); + obj = lookup_object(base->hash); if (!obj) return 0; obj_buffer = lookup_object_buffer(obj); @@ -341,25 +346,25 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, { void *delta_data, *base; unsigned long base_size; - unsigned char base_sha1[20]; + struct object_id base_oid; if (type == OBJ_REF_DELTA) { - hashcpy(base_sha1, fill(20)); - use(20); + hashcpy(base_oid.hash, fill(the_hash_algo->rawsz)); + use(the_hash_algo->rawsz); delta_data = get_data(delta_size); if (dry_run || !delta_data) { free(delta_data); return; } - if (has_sha1_file(base_sha1)) + if (has_object_file(&base_oid)) ; /* Ok we have this one */ - else if (resolve_against_held(nr, base_sha1, + else if (resolve_against_held(nr, &base_oid, delta_data, delta_size)) return; /* we are done */ else { /* cannot resolve yet --- queue it */ - hashclr(obj_list[nr].sha1); - add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size); + oidclr(&obj_list[nr].oid); + add_delta_to_list(nr, &base_oid, 0, delta_data, delta_size); return; } } else { @@ -393,14 +398,14 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, lo = 0; hi = nr; while (lo < hi) { - mid = (lo + hi)/2; + mid = lo + (hi - lo) / 2; if (base_offset < obj_list[mid].offset) { hi = mid; } else if (base_offset > obj_list[mid].offset) { lo = mid + 1; } else { - hashcpy(base_sha1, obj_list[mid].sha1); - base_found = !is_null_sha1(base_sha1); + oidcpy(&base_oid, &obj_list[mid].oid); + base_found = !is_null_oid(&base_oid); break; } } @@ -409,19 +414,19 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, * The delta base object is itself a delta that * has not been resolved yet. */ - hashclr(obj_list[nr].sha1); - add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size); + oidclr(&obj_list[nr].oid); + add_delta_to_list(nr, &null_oid, base_offset, delta_data, delta_size); return; } } - if (resolve_against_held(nr, base_sha1, delta_data, delta_size)) + if (resolve_against_held(nr, &base_oid, delta_data, delta_size)) return; - base = read_sha1_file(base_sha1, &type, &base_size); + base = read_sha1_file(base_oid.hash, &type, &base_size); if (!base) { error("failed to read delta-pack base object %s", - sha1_to_hex(base_sha1)); + oid_to_hex(&base_oid)); if (!recover) exit(1); has_errors = 1; @@ -505,7 +510,7 @@ static void unpack_all(void) int cmd_unpack_objects(int argc, const char **argv, const char *prefix) { int i; - unsigned char sha1[20]; + struct object_id oid; check_replace_refs = 0; @@ -563,15 +568,15 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) /* We don't take any non-flag arguments now.. Maybe some day */ usage(unpack_usage); } - git_SHA1_Init(&ctx); + the_hash_algo->init_fn(&ctx); unpack_all(); - git_SHA1_Update(&ctx, buffer, offset); - git_SHA1_Final(sha1, &ctx); + the_hash_algo->update_fn(&ctx, buffer, offset); + the_hash_algo->final_fn(oid.hash, &ctx); if (strict) write_rest(); - if (hashcmp(fill(20), sha1)) + if (hashcmp(fill(the_hash_algo->rawsz), oid.hash)) die("final sha1 did not match"); - use(20); + use(the_hash_algo->rawsz); /* Write the last part of the buffer to stdout */ while (len) { diff --git a/builtin/update-index.c b/builtin/update-index.c index 19216595fb..1af8a00b88 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -4,6 +4,7 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "config.h" #include "lockfile.h" #include "quote.h" #include "cache-tree.h" @@ -15,6 +16,7 @@ #include "pathspec.h" #include "dir.h" #include "split-index.h" +#include "fsmonitor.h" /* * Default to not allowing changes to the list of files. The @@ -31,6 +33,7 @@ static int force_remove; static int verbose; static int mark_valid_only; static int mark_skip_worktree_only; +static int mark_fsmonitor_only; #define MARK_FLAG 1 #define UNMARK_FLAG 2 static struct strbuf mtime_dir = STRBUF_INIT; @@ -227,6 +230,7 @@ static int mark_ce_flags(const char *path, int flag, int mark) int namelen = strlen(path); int pos = cache_name_pos(path, namelen); if (0 <= pos) { + mark_fsmonitor_invalid(&the_index, active_cache[pos]); if (mark) active_cache[pos]->ce_flags |= flag; else @@ -257,7 +261,7 @@ static int remove_one_path(const char *path) */ static int process_lstat_error(const char *path, int err) { - if (err == ENOENT || err == ENOTDIR) + if (is_missing_file_error(err)) return remove_one_path(path); return error("lstat(\"%s\"): %s", path, strerror(err)); } @@ -279,15 +283,17 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len fill_stat_cache_info(ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(ce->oid.hash, path, st, + if (index_path(&ce->oid, path, st, info_only ? 0 : HASH_WRITE_OBJECT)) { free(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)) + if (add_cache_entry(ce, option)) { + free(ce); return error("%s: cannot add to the index - missing --add option?", path); + } return 0; } @@ -325,7 +331,7 @@ static int process_directory(const char *path, int len, struct stat *st) if (S_ISGITLINK(ce->ce_mode)) { /* Do nothing to the index if there is no HEAD! */ - if (resolve_gitlink_ref(path, "HEAD", oid.hash) < 0) + if (resolve_gitlink_ref(path, "HEAD", &oid) < 0) return 0; return add_one_path(ce, path, len, st); @@ -351,7 +357,7 @@ static int process_directory(const char *path, int len, struct stat *st) } /* No match - should we add it as a gitlink? */ - if (!resolve_gitlink_ref(path, "HEAD", oid.hash)) + if (!resolve_gitlink_ref(path, "HEAD", &oid)) return add_one_path(NULL, path, len, st); /* Error out. */ @@ -445,7 +451,8 @@ static void update_one(const char *path) int stat_errno = 0; struct stat st; - if (mark_valid_only || mark_skip_worktree_only || force_remove) + 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; @@ -466,6 +473,11 @@ static void update_one(const char *path) die("Unable to mark file %s", path); return; } + if (mark_fsmonitor_only) { + if (mark_ce_flags(path, CE_FSMONITOR_VALID, mark_fsmonitor_only == MARK_FLAG)) + die("Unable to mark file %s", path); + return; + } if (force_remove) { if (remove_file_from_cache(path)) @@ -685,9 +697,9 @@ static int unresolve_one(const char *path) static void read_head_pointers(void) { - if (read_ref("HEAD", head_oid.hash)) + if (read_ref("HEAD", &head_oid)) die("No HEAD -- no initial commit yet?"); - if (read_ref("MERGE_HEAD", merge_head_oid.hash)) { + if (read_ref("MERGE_HEAD", &merge_head_oid)) { fprintf(stderr, "Not in the middle of a merge.\n"); exit(0); } @@ -727,7 +739,7 @@ static int do_reupdate(int ac, const char **av, PATHSPEC_PREFER_CWD, prefix, av + 1); - if (read_ref("HEAD", head_oid.hash)) + if (read_ref("HEAD", &head_oid)) /* If there is no HEAD, that means it is an initial * commit. Update everything in the index. */ @@ -923,7 +935,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) struct refresh_params refresh_args = {0, &has_errors}; int lock_error = 0; int split_index = -1; - struct lock_file *lock_file; + int force_write = 0; + int fsmonitor = -1; + struct lock_file lock_file = LOCK_INIT; struct parse_opt_ctx_t ctx; strbuf_getline_fn getline_fn; int parseopt_state = PARSE_OPT_UNKNOWN; @@ -1014,6 +1028,16 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) N_("test if the filesystem supports untracked cache"), UC_TEST), OPT_SET_INT(0, "force-untracked-cache", &untracked_cache, N_("enable untracked cache without testing the filesystem"), UC_FORCE), + OPT_SET_INT(0, "force-write-index", &force_write, + N_("write out the index even if is not flagged as changed"), 1), + OPT_BOOL(0, "fsmonitor", &fsmonitor, + N_("enable or disable file system monitor")), + {OPTION_SET_INT, 0, "fsmonitor-valid", &mark_fsmonitor_only, NULL, + N_("mark files as fsmonitor valid"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, + {OPTION_SET_INT, 0, "no-fsmonitor-valid", &mark_fsmonitor_only, NULL, + N_("clear fsmonitor valid bit"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, OPT_END() }; @@ -1022,11 +1046,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - /* We can't free this memory, it becomes part of a linked list parsed atexit() */ - lock_file = xcalloc(1, sizeof(struct lock_file)); - /* we will diagnose later if it turns out that we need to update it */ - newfd = hold_locked_index(lock_file, 0); + newfd = hold_locked_index(&lock_file, 0); if (newfd < 0) lock_error = errno; @@ -1155,17 +1176,33 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) die("BUG: bad untracked_cache value: %d", untracked_cache); } - if (active_cache_changed) { + if (fsmonitor > 0) { + if (git_config_get_fsmonitor() == 0) + warning(_("core.fsmonitor is unset; " + "set it if you really want to " + "enable fsmonitor")); + add_fsmonitor(&the_index); + report(_("fsmonitor enabled")); + } else if (!fsmonitor) { + if (git_config_get_fsmonitor() == 1) + warning(_("core.fsmonitor is set; " + "remove it if you really want to " + "disable fsmonitor")); + remove_fsmonitor(&the_index); + report(_("fsmonitor disabled")); + } + + if (active_cache_changed || force_write) { if (newfd < 0) { if (refresh_args.flags & REFRESH_QUIET) exit(128); unable_to_lock_die(get_index_file(), lock_error); } - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die("Unable to write new index file"); } - rollback_lock_file(lock_file); + rollback_lock_file(&lock_file); return has_errors ? 1 : 0; } diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 0b2ecf41ae..4b4714b3fd 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "refs.h" #include "builtin.h" #include "parse-options.h" @@ -93,10 +94,10 @@ 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_sha1(struct strbuf *input, const char **next, - unsigned char *sha1, - const char *command, const char *refname, - int flags) +static int parse_next_oid(struct strbuf *input, const char **next, + struct object_id *oid, + const char *command, const char *refname, + int flags) { struct strbuf arg = STRBUF_INIT; int ret = 0; @@ -114,11 +115,11 @@ static int parse_next_sha1(struct strbuf *input, const char **next, (*next)++; *next = parse_arg(*next, &arg); if (arg.len) { - if (get_sha1(arg.buf, sha1)) + if (get_oid(arg.buf, oid)) goto invalid; } else { /* Without -z, an empty value means all zeros: */ - hashclr(sha1); + oidclr(oid); } } else { /* With -z, read the next NUL-terminated line */ @@ -132,13 +133,13 @@ static int parse_next_sha1(struct strbuf *input, const char **next, *next += arg.len; if (arg.len) { - if (get_sha1(arg.buf, sha1)) + if (get_oid(arg.buf, oid)) goto invalid; } else if (flags & PARSE_SHA1_ALLOW_EMPTY) { /* With -z, treat an empty value as all zeros: */ warning("%s %s: missing <newvalue>, treating as zero", command, refname); - hashclr(sha1); + oidclr(oid); } else { /* * With -z, an empty non-required value means @@ -181,26 +182,25 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, { struct strbuf err = STRBUF_INIT; char *refname; - unsigned char new_sha1[20]; - unsigned char old_sha1[20]; + struct object_id new_oid, old_oid; int have_old; refname = parse_refname(input, &next); if (!refname) die("update: missing <ref>"); - if (parse_next_sha1(input, &next, new_sha1, "update", refname, - PARSE_SHA1_ALLOW_EMPTY)) + if (parse_next_oid(input, &next, &new_oid, "update", refname, + PARSE_SHA1_ALLOW_EMPTY)) die("update %s: missing <newvalue>", refname); - have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname, - PARSE_SHA1_OLD); + have_old = !parse_next_oid(input, &next, &old_oid, "update", refname, + PARSE_SHA1_OLD); if (*next != line_termination) die("update %s: extra input: %s", refname, next); if (ref_transaction_update(transaction, refname, - new_sha1, have_old ? old_sha1 : NULL, + &new_oid, have_old ? &old_oid : NULL, update_flags | create_reflog_flag, msg, &err)) die("%s", err.buf); @@ -217,22 +217,22 @@ static const char *parse_cmd_create(struct ref_transaction *transaction, { struct strbuf err = STRBUF_INIT; char *refname; - unsigned char new_sha1[20]; + struct object_id new_oid; refname = parse_refname(input, &next); if (!refname) die("create: missing <ref>"); - if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0)) + if (parse_next_oid(input, &next, &new_oid, "create", refname, 0)) die("create %s: missing <newvalue>", refname); - if (is_null_sha1(new_sha1)) + if (is_null_oid(&new_oid)) die("create %s: zero <newvalue>", refname); if (*next != line_termination) die("create %s: extra input: %s", refname, next); - if (ref_transaction_create(transaction, refname, new_sha1, + if (ref_transaction_create(transaction, refname, &new_oid, update_flags | create_reflog_flag, msg, &err)) die("%s", err.buf); @@ -249,18 +249,18 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction, { struct strbuf err = STRBUF_INIT; char *refname; - unsigned char old_sha1[20]; + struct object_id old_oid; int have_old; refname = parse_refname(input, &next); if (!refname) die("delete: missing <ref>"); - if (parse_next_sha1(input, &next, old_sha1, "delete", refname, - PARSE_SHA1_OLD)) { + if (parse_next_oid(input, &next, &old_oid, "delete", refname, + PARSE_SHA1_OLD)) { have_old = 0; } else { - if (is_null_sha1(old_sha1)) + if (is_null_oid(&old_oid)) die("delete %s: zero <oldvalue>", refname); have_old = 1; } @@ -269,7 +269,7 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction, die("delete %s: extra input: %s", refname, next); if (ref_transaction_delete(transaction, refname, - have_old ? old_sha1 : NULL, + have_old ? &old_oid : NULL, update_flags, msg, &err)) die("%s", err.buf); @@ -285,20 +285,20 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, { struct strbuf err = STRBUF_INIT; char *refname; - unsigned char old_sha1[20]; + struct object_id old_oid; refname = parse_refname(input, &next); if (!refname) die("verify: missing <ref>"); - if (parse_next_sha1(input, &next, old_sha1, "verify", refname, - PARSE_SHA1_OLD)) - hashclr(old_sha1); + if (parse_next_oid(input, &next, &old_oid, "verify", refname, + PARSE_SHA1_OLD)) + oidclr(&old_oid); if (*next != line_termination) die("verify %s: extra input: %s", refname, next); - if (ref_transaction_verify(transaction, refname, old_sha1, + if (ref_transaction_verify(transaction, refname, &old_oid, update_flags, &err)) die("%s", err.buf); @@ -312,7 +312,7 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, static const char *parse_cmd_option(struct strbuf *input, const char *next) { if (!strncmp(next, "no-deref", 8) && next[8] == line_termination) - update_flags |= REF_NODEREF; + update_flags |= REF_NO_DEREF; else die("option unknown: %s", next); return next + 8; @@ -354,7 +354,7 @@ static void update_refs_stdin(struct ref_transaction *transaction) int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname, *oldval; - unsigned char sha1[20], oldsha1[20]; + 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; @@ -411,7 +411,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) refname = argv[0]; value = argv[1]; oldval = argv[2]; - if (get_sha1(value, sha1)) + if (get_oid(value, &oid)) die("%s: not a valid SHA1", value); } @@ -421,23 +421,23 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) * The empty string implies that the reference * must not already exist: */ - hashclr(oldsha1); - else if (get_sha1(oldval, oldsha1)) + oidclr(&oldoid); + else if (get_oid(oldval, &oldoid)) die("%s: not a valid old SHA1", oldval); } if (no_deref) - flags = REF_NODEREF; + flags = REF_NO_DEREF; if (delete) /* * For purposes of backwards compatibility, we treat * NULL_SHA1 as "don't care" here: */ return delete_ref(msg, refname, - (oldval && !is_null_sha1(oldsha1)) ? oldsha1 : NULL, + (oldval && !is_null_oid(&oldoid)) ? &oldoid : NULL, flags); else - return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, + return update_ref(msg, refname, &oid, oldval ? &oldoid : NULL, flags | create_reflog_flag, UPDATE_REFS_DIE_ON_ERR); } diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index 6c8cc3edc1..4321a34456 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "config.h" #include "builtin.h" #include "parse-options.h" @@ -11,7 +12,7 @@ int cmd_update_server_info(int argc, const char **argv, const char *prefix) { int force = 0; struct option options[] = { - OPT__FORCE(&force, N_("update the info files from scratch")), + OPT__FORCE(&force, N_("update the info files from scratch"), 0), OPT_END() }; diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index cde06977b7..84532ae9a9 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -22,7 +22,7 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) struct argv_array sent_argv = ARGV_ARRAY_INIT; const char *arg_cmd = "argument "; - if (argc != 2) + if (argc != 2 || !strcmp(argv[1], "-h")) usage(upload_archive_usage); if (!enter_repo(argv[1], 0)) @@ -76,6 +76,9 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) { struct child_process writer = { argv }; + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(upload_archive_usage); + /* * Set up sideband subprocess. * diff --git a/builtin/var.c b/builtin/var.c index aedbb53a2d..6c6f46b4ae 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -4,6 +4,7 @@ * Copyright (C) Eric Biederman, 2005 */ #include "builtin.h" +#include "config.h" static const char var_usage[] = "git var (-l | <variable>)"; diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c index 38bedf8f9f..05315ea7c9 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -6,6 +6,7 @@ * Based on git-verify-tag */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "commit.h" #include "run-command.h" @@ -18,14 +19,14 @@ static const char * const verify_commit_usage[] = { NULL }; -static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned long size, unsigned flags) +static int run_gpg_verify(const struct object_id *oid, const char *buf, unsigned long size, unsigned flags) { struct signature_check signature_check; int ret; memset(&signature_check, 0, sizeof(signature_check)); - ret = check_commit_signature(lookup_commit(sha1), &signature_check); + ret = check_commit_signature(lookup_commit(oid), &signature_check); print_signature_buffer(&signature_check, flags); signature_check_clear(&signature_check); @@ -35,22 +36,22 @@ static int run_gpg_verify(const unsigned char *sha1, const char *buf, unsigned l static int verify_commit(const char *name, unsigned flags) { enum object_type type; - unsigned char sha1[20]; + struct object_id oid; char *buf; unsigned long size; int ret; - if (get_sha1(name, sha1)) + if (get_oid(name, &oid)) return error("commit '%s' not found.", name); - buf = read_sha1_file(sha1, &type, &size); + buf = read_sha1_file(oid.hash, &type, &size); if (!buf) return error("%s: unable to read file.", name); if (type != OBJ_COMMIT) return error("%s: cannot verify a non-commit object of type %s.", - name, typename(type)); + name, type_name(type)); - ret = run_gpg_verify(sha1, buf, size, flags); + ret = run_gpg_verify(&oid, buf, size, flags); free(buf); return ret; diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c index c94e156932..c2a1a5c504 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "config.h" #include "run-command.h" #include "parse-options.h" diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 5199553d91..ad7b79fa5c 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -6,6 +6,7 @@ * Based on git-verify-tag.sh */ #include "cache.h" +#include "config.h" #include "builtin.h" #include "tag.h" #include "run-command.h" @@ -31,11 +32,11 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; unsigned flags = 0; - char *fmt_pretty = NULL; + struct ref_format format = REF_FORMAT_INIT; const struct option verify_tag_options[] = { OPT__VERBOSE(&verbose, N_("print tag contents")), OPT_BIT(0, "raw", &flags, N_("print raw gpg status output"), GPG_VERIFY_RAW), - OPT_STRING( 0 , "format", &fmt_pretty, N_("format"), N_("format to use for the output")), + OPT_STRING(0, "format", &format.format, N_("format"), N_("format to use for the output")), OPT_END() }; @@ -49,26 +50,29 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (verbose) flags |= GPG_VERIFY_VERBOSE; - if (fmt_pretty) { - verify_ref_format(fmt_pretty); + if (format.format) { + if (verify_ref_format(&format)) + usage_with_options(verify_tag_usage, + verify_tag_options); flags |= GPG_VERIFY_OMIT_STATUS; } while (i < argc) { - unsigned char sha1[20]; + struct object_id oid; const char *name = argv[i++]; - if (get_sha1(name, sha1)) { + + if (get_oid(name, &oid)) { had_error = !!error("tag '%s' not found.", name); continue; } - if (gpg_verify_tag(sha1, name, flags)) { + if (gpg_verify_tag(&oid, name, flags)) { had_error = 1; continue; } - if (fmt_pretty) - pretty_print_ref(name, sha1, fmt_pretty); + if (format.format) + pretty_print_ref(name, oid.hash, &format); } return had_error; } diff --git a/builtin/worktree.c b/builtin/worktree.c index ff5dfd2b10..670555dedd 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -1,4 +1,6 @@ #include "cache.h" +#include "checkout.h" +#include "config.h" #include "builtin.h" #include "dir.h" #include "parse-options.h" @@ -12,10 +14,12 @@ #include "worktree.h" static const char * const worktree_usage[] = { - N_("git worktree add [<options>] <path> [<branch>]"), + N_("git worktree add [<options>] <path> [<commit-ish>]"), N_("git worktree list [<options>]"), N_("git worktree lock [<options>] <path>"), + N_("git worktree move <worktree> <new-path>"), N_("git worktree prune [<options>]"), + N_("git worktree remove [<options>] <worktree>"), N_("git worktree unlock <path>"), NULL }; @@ -31,13 +35,26 @@ struct add_opts { static int show_only; static int verbose; -static unsigned long expire; +static int guess_remote; +static timestamp_t expire; + +static int git_worktree_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "worktree.guessremote")) { + guess_remote = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} static int prune_worktree(const char *id, struct strbuf *reason) { struct stat st; char *path; - int fd, len; + int fd; + size_t len; + ssize_t read_result; if (!is_directory(git_path("worktrees/%s", id))) { strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); @@ -55,10 +72,26 @@ static int prune_worktree(const char *id, struct strbuf *reason) id, strerror(errno)); return 1; } - len = st.st_size; + len = xsize_t(st.st_size); path = xmallocz(len); - read_in_full(fd, path, len); + + read_result = read_in_full(fd, path, len); + if (read_result < 0) { + strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), + id, strerror(errno)); + close(fd); + free(path); + return 1; + } close(fd); + + if (read_result != len) { + strbuf_addf(reason, + _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), + id, (uintmax_t)len, (uintmax_t)read_result); + free(path); + return 1; + } while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) len--; if (!len) { @@ -131,7 +164,7 @@ static int prune(int ac, const char **av, const char *prefix) OPT_END() }; - expire = ULONG_MAX; + expire = TIME_MAX; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (ac) usage_with_options(worktree_usage, options); @@ -199,20 +232,21 @@ static int add_worktree(const char *path, const char *refname, int counter = 0, len, ret; struct strbuf symref = STRBUF_INIT; struct commit *commit = NULL; + int is_branch = 0; if (file_exists(path) && !is_empty_dir(path)) die(_("'%s' already exists"), path); /* is 'refname' a branch or commit? */ if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && - ref_exists(symref.buf)) { /* it's a branch */ + ref_exists(symref.buf)) { + is_branch = 1; if (!opts->force) die_if_checked_out(symref.buf, 0); - } else { /* must be a commit */ - commit = lookup_commit_reference_by_name(refname); - if (!commit) - die(_("invalid reference: %s"), refname); } + commit = lookup_commit_reference_by_name(refname); + if (!commit) + die(_("invalid reference: %s"), refname); name = worktree_basename(path, &len); git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name); @@ -277,7 +311,7 @@ static int add_worktree(const char *path, const char *refname, argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); cp.git_cmd = 1; - if (commit) + if (!is_branch) argv_array_pushl(&cp.args, "update-ref", "HEAD", oid_to_hex(&commit->object.oid), NULL); else @@ -299,10 +333,8 @@ static int add_worktree(const char *path, const char *refname, } is_junk = 0; - free(junk_work_tree); - free(junk_git_dir); - junk_work_tree = NULL; - junk_git_dir = NULL; + FREE_AND_NULL(junk_work_tree); + FREE_AND_NULL(junk_git_dir); done: if (ret || !opts->keep_locked) { @@ -310,6 +342,29 @@ done: strbuf_addf(&sb, "%s/locked", sb_repo.buf); unlink_or_warn(sb.buf); } + + /* + * Hook failure does not warrant worktree deletion, so run hook after + * is_junk is cleared, but do return appropriate code when hook fails. + */ + if (!ret && opts->checkout) { + const char *hook = find_hook("post-checkout"); + if (hook) { + const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL }; + cp.git_cmd = 0; + cp.no_stdin = 1; + cp.stdout_to_stderr = 1; + cp.dir = path; + cp.env = env; + cp.argv = NULL; + argv_array_pushl(&cp.args, absolute_path(hook), + oid_to_hex(&null_oid), + oid_to_hex(&commit->object.oid), + "1", NULL); + ret = run_command(&cp); + } + } + argv_array_clear(&child_env); strbuf_release(&sb); strbuf_release(&symref); @@ -324,8 +379,11 @@ static int add(int ac, const char **av, const char *prefix) const char *new_branch_force = NULL; char *path; const char *branch; + const char *opt_track = NULL; struct option options[] = { - OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")), + 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"), N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), @@ -333,6 +391,11 @@ static int add(int ac, const char **av, const char *prefix) 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_PASSTHRU(0, "track", &opt_track, NULL, + N_("set up tracking mode (see git-branch(1))"), + PARSE_OPT_NOARG | PARSE_OPT_OPTARG), + OPT_BOOL(0, "guess-remote", &guess_remote, + N_("try to match the new branch name with a remote-tracking branch")), OPT_END() }; @@ -367,6 +430,28 @@ static int add(int ac, const char **av, const char *prefix) 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 && !opts.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); + if (remote) { + opts.new_branch = branch; + branch = remote; + } + } } if (opts.new_branch) { @@ -377,11 +462,17 @@ static int add(int ac, const char **av, const char *prefix) argv_array_push(&cp.args, "--force"); argv_array_push(&cp.args, opts.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; + } else if (opt_track) { + die(_("--[no-]track can only be used if a new branch is created")); } + UNLEAK(path); + UNLEAK(opts); return add_worktree(path, branch, &opts); } @@ -391,7 +482,7 @@ static void show_worktree_porcelain(struct worktree *wt) if (wt->is_bare) printf("bare\n"); else { - printf("HEAD %s\n", sha1_to_hex(wt->head_sha1)); + printf("HEAD %s\n", oid_to_hex(&wt->head_oid)); if (wt->is_detached) printf("detached\n"); else if (wt->head_ref) @@ -411,7 +502,7 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) strbuf_addstr(&sb, "(bare)"); else { strbuf_addf(&sb, "%-*s ", abbrev_len, - find_unique_abbrev(wt->head_sha1, DEFAULT_ABBREV)); + find_unique_abbrev(wt->head_oid.hash, DEFAULT_ABBREV)); if (wt->is_detached) strbuf_addstr(&sb, "(detached HEAD)"); else if (wt->head_ref) { @@ -436,7 +527,7 @@ static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen) if (path_len > *maxlen) *maxlen = path_len; - sha1_len = strlen(find_unique_abbrev(wt[i]->head_sha1, *abbrev)); + sha1_len = strlen(find_unique_abbrev(wt[i]->head_oid.hash, *abbrev)); if (sha1_len > *abbrev) *abbrev = sha1_len; } @@ -532,13 +623,227 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) return ret; } +static void validate_no_submodules(const struct worktree *wt) +{ + struct index_state istate = { NULL }; + int i, found_submodules = 0; + + 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]; + + if (S_ISGITLINK(ce->ce_mode)) { + found_submodules = 1; + break; + } + } + } + discard_index(&istate); + + if (found_submodules) + die(_("working trees containing submodules cannot be moved or removed")); +} + +static int move_worktree(int ac, const char **av, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + struct worktree **worktrees, *wt; + struct strbuf dst = STRBUF_INIT; + struct strbuf errmsg = STRBUF_INIT; + const char *reason; + char *path; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 2) + usage_with_options(worktree_usage, options); + + path = prefix_filename(prefix, av[1]); + strbuf_addstr(&dst, path); + free(path); + + worktrees = get_worktrees(0); + wt = find_worktree(worktrees, prefix, av[0]); + if (!wt) + die(_("'%s' is not a working tree"), av[0]); + if (is_main_worktree(wt)) + die(_("'%s' is a main working tree"), av[0]); + if (is_directory(dst.buf)) { + const char *sep = find_last_dir_sep(wt->path); + + if (!sep) + die(_("could not figure out destination name from '%s'"), + wt->path); + strbuf_trim_trailing_dir_sep(&dst); + strbuf_addstr(&dst, sep); + } + if (file_exists(dst.buf)) + die(_("target '%s' already exists"), dst.buf); + + validate_no_submodules(wt); + + reason = is_worktree_locked(wt); + if (reason) { + if (*reason) + die(_("cannot move a locked working tree, lock reason: %s"), + reason); + die(_("cannot move a locked working tree")); + } + if (validate_worktree(wt, &errmsg, 0)) + die(_("validation failed, cannot move working tree: %s"), + errmsg.buf); + strbuf_release(&errmsg); + + if (rename(wt->path, dst.buf) == -1) + die_errno(_("failed to move '%s' to '%s'"), wt->path, dst.buf); + + update_worktree_location(wt, dst.buf); + + strbuf_release(&dst); + free_worktrees(worktrees); + return 0; +} + +/* + * Note, "git status --porcelain" is used to determine if it's safe to + * delete a whole worktree. "git status" does not ignore user + * configuration, so if a normal "git status" shows "clean" for the + * user, then it's ok to remove it. + * + * This assumption may be a bad one. We may want to ignore + * (potentially bad) user settings and only delete a worktree when + * it's absolutely safe to do so from _our_ point of view because we + * know better. + */ +static void check_clean_worktree(struct worktree *wt, + const char *original_path) +{ + struct argv_array child_env = ARGV_ARRAY_INIT; + struct child_process cp; + char buf[1]; + int ret; + + /* + * Until we sort this out, all submodules are "dirty" and + * will abort this function. + */ + validate_no_submodules(wt); + + argv_array_pushf(&child_env, "%s=%s/.git", + GIT_DIR_ENVIRONMENT, wt->path); + argv_array_pushf(&child_env, "%s=%s", + GIT_WORK_TREE_ENVIRONMENT, wt->path); + memset(&cp, 0, sizeof(cp)); + argv_array_pushl(&cp.args, "status", + "--porcelain", "--ignore-submodules=none", + NULL); + cp.env = child_env.argv; + cp.git_cmd = 1; + cp.dir = wt->path; + cp.out = -1; + ret = start_command(&cp); + if (ret) + die_errno(_("failed to run 'git status' on '%s'"), + original_path); + ret = xread(cp.out, buf, sizeof(buf)); + if (ret) + die(_("'%s' is dirty, use --force to delete it"), + original_path); + close(cp.out); + ret = finish_command(&cp); + if (ret) + die_errno(_("failed to run 'git status' on '%s', code %d"), + original_path, ret); +} + +static int delete_git_work_tree(struct worktree *wt) +{ + struct strbuf sb = STRBUF_INIT; + int ret = 0; + + strbuf_addstr(&sb, wt->path); + if (remove_dir_recursively(&sb, 0)) { + error_errno(_("failed to delete '%s'"), sb.buf); + ret = -1; + } + strbuf_release(&sb); + 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_END() + }; + struct worktree **worktrees, *wt; + struct strbuf errmsg = STRBUF_INIT; + const char *reason; + int ret = 0; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 1) + usage_with_options(worktree_usage, options); + + worktrees = get_worktrees(0); + wt = find_worktree(worktrees, prefix, av[0]); + if (!wt) + 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 (reason) { + if (*reason) + die(_("cannot remove a locked working tree, lock reason: %s"), + reason); + die(_("cannot remove a locked working tree")); + } + if (validate_worktree(wt, &errmsg, WT_VALIDATE_WORKTREE_MISSING_OK)) + die(_("validation failed, cannot remove working tree: %s"), + errmsg.buf); + strbuf_release(&errmsg); + + if (file_exists(wt->path)) { + if (!force) + check_clean_worktree(wt, av[0]); + + ret |= delete_git_work_tree(wt); + } + /* + * continue on even if ret is non-zero, there's no going back + * from here. + */ + ret |= delete_git_dir(wt); + + free_worktrees(worktrees); + return ret; +} + int cmd_worktree(int ac, const char **av, const char *prefix) { struct option options[] = { OPT_END() }; - git_config(git_default_config, NULL); + git_config(git_worktree_config, NULL); if (ac < 2) usage_with_options(worktree_usage, options); @@ -554,5 +859,9 @@ int cmd_worktree(int ac, const char **av, const char *prefix) return lock_worktree(ac - 1, av + 1, prefix); if (!strcmp(av[1], "unlock")) return unlock_worktree(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "move")) + return move_worktree(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "remove")) + return remove_worktree(ac - 1, av + 1, prefix); usage_with_options(worktree_usage, options); } diff --git a/builtin/write-tree.c b/builtin/write-tree.c index 084c0df783..bd0a78aa3c 100644 --- a/builtin/write-tree.c +++ b/builtin/write-tree.c @@ -5,6 +5,7 @@ */ #include "builtin.h" #include "cache.h" +#include "config.h" #include "tree.h" #include "cache-tree.h" #include "parse-options.h" |