From: Junio C Hamano Date: Fri, 20 Sep 2013 19:36:12 +0000 (-0700) Subject: Merge branch 'bk/refs-multi-update' X-Git-Tag: v1.8.5-rc0~94 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/9a86b899415c8a49cc33d6b43bcab8113ddca517?ds=inline;hp=-c Merge branch 'bk/refs-multi-update' Give "update-refs" a "--stdin" option to read multiple update requests and perform them in an all-or-none fashion. * bk/refs-multi-update: update-ref: add test cases covering --stdin signature update-ref: support multiple simultaneous updates refs: add update_refs for multiple simultaneous updates refs: add function to repack without multiple refs refs: factor delete_ref loose ref step into a helper refs: factor update_ref steps into helpers refs: report ref type from lock_any_ref_for_update reset: rename update_refs to reset_refs --- 9a86b899415c8a49cc33d6b43bcab8113ddca517 diff --combined branch.c index d31b354d48,f2d383fa77..9e6c68edaf --- a/branch.c +++ b/branch.c @@@ -203,7 -203,8 +203,7 @@@ static int check_tracking_branch(struc struct refspec query; memset(&query, 0, sizeof(struct refspec)); query.dst = tracking_branch; - return !(remote_find_tracking(remote, &query) || - prefixcmp(query.src, "refs/heads/")); + return !remote_find_tracking(remote, &query); } static int validate_remote_tracking_branch(char *ref) @@@ -290,7 -291,7 +290,7 @@@ void create_branch(const char *head hashcpy(sha1, commit->object.sha1); if (!dont_change_ref) { - lock = lock_any_ref_for_update(ref.buf, NULL, 0); + lock = lock_any_ref_for_update(ref.buf, NULL, 0, NULL); if (!lock) die_errno(_("Failed to lock ref for update")); } @@@ -306,7 -307,7 +306,7 @@@ start_name); if (real_ref && track) - setup_tracking(ref.buf+11, real_ref, track, quiet); + setup_tracking(ref.buf + 11, real_ref, track, quiet); if (!dont_change_ref) if (write_ref_sha1(lock, sha1, msg) < 0) diff --combined builtin/commit.c index d0d8bc1b30,be08f4153e..29d4225ff2 --- a/builtin/commit.c +++ b/builtin/commit.c @@@ -30,7 -30,6 +30,7 @@@ #include "column.h" #include "sequencer.h" #include "notes-utils.h" +#include "mailmap.h" static const char * const builtin_commit_usage[] = { N_("git commit [options] [--] ..."), @@@ -203,15 -202,17 +203,15 @@@ static int commit_index_files(void * and return the paths that match the given pattern in list. */ static int list_paths(struct string_list *list, const char *with_tree, - const char *prefix, const char **pattern) + const char *prefix, const struct pathspec *pattern) { int i; char *m; - if (!pattern) + if (!pattern->nr) return 0; - for (i = 0; pattern[i]; i++) - ; - m = xcalloc(1, i); + m = xcalloc(1, pattern->nr); if (with_tree) { char *max_prefix = common_prefix(pattern); @@@ -225,7 -226,7 +225,7 @@@ if (ce->ce_flags & CE_UPDATE) continue; - if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m)) + if (!match_pathspec_depth(pattern, ce->name, ce_namelen(ce), 0, m)) continue; item = string_list_insert(list, ce->name); if (ce_skip_worktree(ce)) @@@ -297,17 -298,17 +297,17 @@@ static char *prepare_index(int argc, co { int fd; struct string_list partial; - const char **pathspec = NULL; + struct pathspec pathspec; char *old_index_env = NULL; int refresh_flags = REFRESH_QUIET; if (is_status) refresh_flags |= REFRESH_UNMERGED; + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, argv); - if (*argv) - pathspec = get_pathspec(prefix, argv); - - if (read_cache_preload(pathspec) < 0) + if (read_cache_preload(&pathspec) < 0) die(_("index file corrupt")); if (interactive) { @@@ -349,9 -350,9 +349,9 @@@ * (A) if all goes well, commit the real index; * (B) on failure, rollback the real index. */ - if (all || (also && pathspec && *pathspec)) { + if (all || (also && pathspec.nr)) { fd = hold_locked_index(&index_lock, 1); - add_files_to_cache(also ? prefix : NULL, pathspec, 0); + add_files_to_cache(also ? prefix : NULL, &pathspec, 0); refresh_cache_or_die(refresh_flags); update_main_cache_tree(WRITE_TREE_SILENT); if (write_cache(fd, active_cache, active_nr) || @@@ -370,7 -371,7 +370,7 @@@ * and create commit from the_index. * We still need to refresh the index here. */ - if (!only && (!pathspec || !*pathspec)) { + if (!only && !pathspec.nr) { fd = hold_locked_index(&index_lock, 1); refresh_cache_or_die(refresh_flags); if (active_cache_changed) { @@@ -415,7 -416,7 +415,7 @@@ memset(&partial, 0, sizeof(partial)); partial.strdup_strings = 1; - if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec)) + if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec)) exit(1); discard_cache(); @@@ -598,7 -599,6 +598,7 @@@ static int prepare_to_commit(const cha const char *hook_arg2 = NULL; int ident_shown = 0; int clean_message_contents = (cleanup_mode != CLEANUP_NONE); + int old_display_comment_prefix; /* This checks and barfs if author is badly specified */ determine_author_info(author_ident); @@@ -696,10 -696,6 +696,10 @@@ if (s->fp == NULL) die_errno(_("could not open '%s'"), git_path(commit_editmsg)); + /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */ + old_display_comment_prefix = s->display_comment_prefix; + s->display_comment_prefix = 1; + if (clean_message_contents) stripspace(&sb, 0); @@@ -825,7 -821,6 +825,7 @@@ */ if (!commitable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { + s->display_comment_prefix = old_display_comment_prefix; run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); @@@ -940,7 -935,6 +940,7 @@@ static const char *find_author_by_nickn struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; + struct string_list mailmap = STRING_LIST_INIT_NODUP; const char *av[20]; int ac = 0; @@@ -951,17 -945,13 +951,17 @@@ av[++ac] = buf.buf; av[++ac] = NULL; setup_revisions(ac, av, &revs, NULL); + revs.mailmap = &mailmap; + read_mailmap(revs.mailmap, NULL); + prepare_revision_walk(&revs); commit = get_revision(&revs); if (commit) { struct pretty_print_context ctx = {0}; ctx.date_mode = DATE_NORMAL; strbuf_release(&buf); - format_commit_message(commit, "%an <%ae>", &buf, &ctx); + format_commit_message(commit, "%aN <%aE>", &buf, &ctx); + clear_mailmap(&mailmap); return strbuf_detach(&buf, NULL); } die(_("No existing author found with '%s'"), name); @@@ -1101,7 -1091,7 +1101,7 @@@ static int parse_and_validate_options(i if (patch_interactive) interactive = 1; - if (!!also + !!only + !!all + !!interactive > 1) + if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); if (argc == 0 && (also || (only && !amend))) die(_("No paths with --include/--only does not make sense.")); @@@ -1192,10 -1182,6 +1192,10 @@@ static int git_status_config(const cha s->use_color = git_config_colorbool(k, v); return 0; } + if (!strcmp(k, "status.displaycommentprefix")) { + s->display_comment_prefix = git_config_bool(k, v); + return 0; + } if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { int slot = parse_status_slot(k, 13); if (slot < 0) @@@ -1242,14 -1228,14 +1242,14 @@@ int cmd_status(int argc, const char **a OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), - OPT_BOOLEAN('z', "null", &s.null_termination, - N_("terminate entries with NUL")), + OPT_BOOL('z', "null", &s.null_termination, + N_("terminate entries with NUL")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, - OPT_BOOLEAN(0, "ignored", &show_ignored_in_status, - N_("show ignored files")), + OPT_BOOL(0, "ignored", &show_ignored_in_status, + N_("show ignored files")), { 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" }, @@@ -1273,12 -1259,11 +1273,12 @@@ handle_untracked_files_arg(&s); if (show_ignored_in_status) s.show_ignored_files = 1; - if (*argv) - s.pathspec = get_pathspec(prefix, argv); + parse_pathspec(&s.pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, argv); - read_cache_preload(s.pathspec); - refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL); + 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 (0 <= fd) @@@ -1449,24 -1434,24 +1449,24 @@@ int cmd_commit(int argc, const char **a OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")), OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), - OPT_BOOLEAN(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), - OPT_BOOLEAN('s', "signoff", &signoff, N_("add Signed-off-by:")), + OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), + OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")), - OPT_BOOLEAN(0, "status", &include_status, N_("include status in commit message template")), + OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, /* end commit message options */ OPT_GROUP(N_("Commit contents options")), - OPT_BOOLEAN('a', "all", &all, N_("commit all changed files")), - OPT_BOOLEAN('i', "include", &also, N_("add specified files to index for commit")), - OPT_BOOLEAN(0, "interactive", &interactive, N_("interactively add files")), - OPT_BOOLEAN('p', "patch", &patch_interactive, N_("interactively add changes")), - OPT_BOOLEAN('o', "only", &only, N_("commit only specified files")), - OPT_BOOLEAN('n', "no-verify", &no_verify, N_("bypass pre-commit hook")), - OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")), + OPT_BOOL('a', "all", &all, N_("commit all changed files")), + OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")), + OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")), + OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")), + OPT_BOOL('o', "only", &only, N_("commit only specified files")), + OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")), + OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")), 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")), @@@ -1475,17 -1460,19 +1475,17 @@@ OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), - OPT_BOOLEAN('z', "null", &s.null_termination, - N_("terminate entries with NUL")), - OPT_BOOLEAN(0, "amend", &amend, N_("amend previous commit")), - OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), + OPT_BOOL('z', "null", &s.null_termination, + N_("terminate entries with NUL")), + OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), + OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */ - { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL, - N_("ok to record an empty change"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, - { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL, - N_("ok to record a change with an empty message"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty, + N_("ok to record an empty change")), + OPT_HIDDEN_BOOL(0, "allow-empty-message", &allow_empty_message, + N_("ok to record a change with an empty message")), OPT_END() }; @@@ -1631,7 -1618,7 +1631,7 @@@ !current_head ? NULL : current_head->object.sha1, - 0); + 0, NULL); nl = strchr(sb.buf, '\n'); if (nl) diff --combined builtin/fetch.c index 9e654efa3b,28e40255be..bd7a10164f --- a/builtin/fetch.c +++ b/builtin/fetch.c @@@ -30,18 -30,13 +30,18 @@@ enum TAGS_SET = 2 }; -static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; +static int fetch_prune_config = -1; /* unspecified */ +static int prune = -1; /* unspecified */ +#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */ + +static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity; static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int tags = TAGS_DEFAULT, unshallow; static const char *depth; static const char *upload_pack; static struct strbuf default_rla = STRBUF_INIT; -static struct transport *transport; +static struct transport *gtransport; +static struct transport *gsecondary; static const char *submodule_prefix = ""; static const char *recurse_submodules_default; @@@ -59,39 -54,30 +59,39 @@@ static int option_parse_recurse_submodu return 0; } +static int git_fetch_config(const char *k, const char *v, void *cb) +{ + if (!strcmp(k, "fetch.prune")) { + fetch_prune_config = git_config_bool(k, v); + return 0; + } + return 0; +} + static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), - OPT_BOOLEAN(0, "all", &all, - N_("fetch from all remotes")), - OPT_BOOLEAN('a', "append", &append, - N_("append to .git/FETCH_HEAD instead of overwriting")), + OPT_BOOL(0, "all", &all, + N_("fetch from all remotes")), + OPT_BOOL('a', "append", &append, + N_("append to .git/FETCH_HEAD instead of overwriting")), OPT_STRING(0, "upload-pack", &upload_pack, N_("path"), N_("path to upload pack on remote end")), OPT__FORCE(&force, N_("force overwrite of local branch")), - OPT_BOOLEAN('m', "multiple", &multiple, - N_("fetch from multiple remotes")), + OPT_BOOL('m', "multiple", &multiple, + N_("fetch from multiple remotes")), OPT_SET_INT('t', "tags", &tags, N_("fetch all tags and associated objects"), TAGS_SET), OPT_SET_INT('n', NULL, &tags, N_("do not fetch all tags (--no-tags)"), TAGS_UNSET), - OPT_BOOLEAN('p', "prune", &prune, - N_("prune remote-tracking branches no longer on remote")), + OPT_BOOL('p', "prune", &prune, + N_("prune remote-tracking branches no longer on remote")), { OPTION_CALLBACK, 0, "recurse-submodules", NULL, N_("on-demand"), N_("control recursive fetching of submodules"), PARSE_OPT_OPTARG, option_parse_recurse_submodules }, - OPT_BOOLEAN(0, "dry-run", &dry_run, - N_("dry run")), - OPT_BOOLEAN('k', "keep", &keep, N_("keep downloaded pack")), - OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, + OPT_BOOL(0, "dry-run", &dry_run, + N_("dry run")), + OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")), + OPT_BOOL('u', "update-head-ok", &update_head_ok, N_("allow updating of HEAD ref")), OPT_BOOL(0, "progress", &progress, N_("force progress reporting")), OPT_STRING(0, "depth", &depth, N_("depth"), @@@ -109,10 -95,8 +109,10 @@@ static void unlock_pack(void) { - if (transport) - transport_unlock_pack(transport); + if (gtransport) + transport_unlock_pack(gtransport); + if (gsecondary) + transport_unlock_pack(gsecondary); } static void unlock_pack_on_signal(int signo) @@@ -262,7 -246,8 +262,8 @@@ static int s_update_ref(const char *act rla = default_rla.buf; snprintf(msg, sizeof(msg), "%s: %s", rla, action); lock = lock_any_ref_for_update(ref->name, - check_old ? ref->old_sha1 : NULL, 0); + check_old ? ref->old_sha1 : NULL, + 0, NULL); if (!lock) return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : STORE_REF_ERROR_OTHER; @@@ -736,48 -721,6 +737,48 @@@ static int truncate_fetch_head(void return 0; } +static void set_option(struct transport *transport, const char *name, const char *value) +{ + int r = transport_set_option(transport, name, value); + if (r < 0) + die(_("Option \"%s\" value \"%s\" is not valid for %s"), + name, value, transport->url); + if (r > 0) + warning(_("Option \"%s\" is ignored for %s\n"), + name, transport->url); +} + +static struct transport *prepare_transport(struct remote *remote) +{ + struct transport *transport; + transport = transport_get(remote, NULL); + transport_set_verbosity(transport, verbosity, progress); + if (upload_pack) + set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack); + if (keep) + set_option(transport, TRANS_OPT_KEEP, "yes"); + if (depth) + set_option(transport, TRANS_OPT_DEPTH, depth); + return transport; +} + +static void backfill_tags(struct transport *transport, struct ref *ref_map) +{ + if (transport->cannot_reuse) { + gsecondary = prepare_transport(transport->remote); + transport = gsecondary; + } + + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); + transport_set_option(transport, TRANS_OPT_DEPTH, "0"); + fetch_refs(transport, ref_map); + + if (gsecondary) { + transport_disconnect(gsecondary); + gsecondary = NULL; + } +} + static int do_fetch(struct transport *transport, struct refspec *refs, int ref_count) { @@@ -829,10 -772,7 +830,10 @@@ goto cleanup; } if (prune) { - /* If --tags was specified, pretend the user gave us the canonical tags refspec */ + /* + * If --tags was specified, pretend that the user gave us + * the canonical tags refspec + */ if (tags == TAGS_SET) { const char *tags_str = "refs/tags/*:refs/tags/*"; struct refspec *tags_refspec, *refspec; @@@ -864,8 -804,11 +865,8 @@@ struct ref **tail = &ref_map; ref_map = NULL; find_non_local_tags(transport, &ref_map, &tail); - if (ref_map) { - transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); - transport_set_option(transport, TRANS_OPT_DEPTH, "0"); - fetch_refs(transport, ref_map); - } + if (ref_map) + backfill_tags(transport, ref_map); free_refs(ref_map); } @@@ -874,6 -817,17 +875,6 @@@ return retcode; } -static void set_option(const char *name, const char *value) -{ - int r = transport_set_option(transport, name, value); - if (r < 0) - die(_("Option \"%s\" value \"%s\" is not valid for %s"), - name, value, transport->url); - if (r > 0) - warning(_("Option \"%s\" is ignored for %s\n"), - name, transport->url); -} - static int get_one_remote_for_fetch(struct remote *remote, void *priv) { struct string_list *list = priv; @@@ -929,7 -883,7 +930,7 @@@ static void add_options_to_argv(struct { if (dry_run) argv_array_push(argv, "--dry-run"); - if (prune) + if (prune > 0) argv_array_push(argv, "--prune"); if (update_head_ok) argv_array_push(argv, "--update-head-ok"); @@@ -996,17 -950,14 +997,17 @@@ static int fetch_one(struct remote *rem die(_("No remote repository specified. Please, specify either a URL or a\n" "remote name from which new revisions should be fetched.")); - transport = transport_get(remote, NULL); - transport_set_verbosity(transport, verbosity, progress); - if (upload_pack) - set_option(TRANS_OPT_UPLOADPACK, upload_pack); - if (keep) - set_option(TRANS_OPT_KEEP, "yes"); - if (depth) - set_option(TRANS_OPT_DEPTH, depth); + gtransport = prepare_transport(remote); + + if (prune < 0) { + /* no command line request */ + if (0 <= gtransport->remote->prune) + prune = gtransport->remote->prune; + else if (0 <= fetch_prune_config) + prune = fetch_prune_config; + else + prune = PRUNE_BY_DEFAULT; + } if (argc > 0) { int j = 0; @@@ -1033,10 -984,10 +1034,10 @@@ sigchain_push_common(unlock_pack_on_signal); atexit(unlock_pack); refspec = parse_fetch_refspec(ref_nr, refs); - exit_code = do_fetch(transport, refspec, ref_nr); + exit_code = do_fetch(gtransport, refspec, ref_nr); free_refspec(ref_nr, refspec); - transport_disconnect(transport); - transport = NULL; + transport_disconnect(gtransport); + gtransport = NULL; return exit_code; } @@@ -1057,8 -1008,6 +1058,8 @@@ int cmd_fetch(int argc, const char **ar for (i = 1; i < argc; i++) strbuf_addf(&default_rla, " %s", argv[i]); + git_config(git_fetch_config, NULL); + argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); diff --combined builtin/receive-pack.c index b7e71a04f6,a32307038e..67ce1ef105 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@@ -8,7 -8,6 +8,7 @@@ #include "commit.h" #include "object.h" #include "remote.h" +#include "connect.h" #include "transport.h" #include "string-list.h" #include "sha1-array.h" @@@ -39,7 -38,6 +39,7 @@@ static int quiet static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; +static int fix_thin = 1; static const char *head_name; static void *head_name_to_free; static int sent_capabilities; @@@ -526,7 -524,8 +526,8 @@@ static const char *update(struct comman return NULL; /* good */ } else { - lock = lock_any_ref_for_update(namespaced_name, old_sha1, 0); + lock = lock_any_ref_for_update(namespaced_name, old_sha1, + 0, NULL); if (!lock) { rp_error("failed to lock %s", name); return "failed to lock"; @@@ -871,8 -870,7 +872,8 @@@ static const char *unpack(int err_fd keeper[i++] = "--stdin"; if (fsck_objects) keeper[i++] = "--strict"; - keeper[i++] = "--fix-thin"; + if (fix_thin) + keeper[i++] = "--fix-thin"; keeper[i++] = hdr_arg; keeper[i++] = keep_arg; keeper[i++] = NULL; @@@ -978,10 -976,6 +979,10 @@@ int cmd_receive_pack(int argc, const ch stateless_rpc = 1; continue; } + if (!strcmp(arg, "--reject-thin-pack-for-testing")) { + fix_thin = 0; + continue; + } usage(receive_pack_usage); } diff --combined builtin/reflog.c index ba27f7cc61,28d756a418..6eb24c8da2 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@@ -94,7 -94,8 +94,7 @@@ static int tree_is_complete(const unsig complete = 0; } } - free(tree->buffer); - tree->buffer = NULL; + free_tree_buffer(tree); if (complete) tree->object.flags |= SEEN; @@@ -365,7 -366,7 +365,7 @@@ static int expire_reflog(const char *re * we take the lock for the ref itself to prevent it from * getting updated. */ - lock = lock_any_ref_for_update(ref, sha1, 0); + lock = lock_any_ref_for_update(ref, sha1, 0, NULL); if (!lock) return error("cannot lock ref '%s'", ref); log_file = git_pathdup("logs/%s", ref); diff --combined builtin/replace.c index 11b0a55ae7,1ecae8d076..301b45ce6a --- a/builtin/replace.c +++ b/builtin/replace.c @@@ -105,7 -105,7 +105,7 @@@ static int replace_object(const char *o else if (!force) die("replace ref '%s' already exists", ref); - lock = lock_any_ref_for_update(ref, prev, 0); + lock = lock_any_ref_for_update(ref, prev, 0, NULL); if (!lock) die("%s: cannot lock the ref", ref); if (write_ref_sha1(lock, repl, NULL) < 0) @@@ -118,9 -118,9 +118,9 @@@ int cmd_replace(int argc, const char ** { int list = 0, delete = 0, force = 0; struct option options[] = { - OPT_BOOLEAN('l', NULL, &list, N_("list replace refs")), - OPT_BOOLEAN('d', NULL, &delete, N_("delete replace refs")), - OPT_BOOLEAN('f', NULL, &force, N_("replace the ref if it exists")), + OPT_BOOL('l', NULL, &list, N_("list replace refs")), + OPT_BOOL('d', NULL, &delete, N_("delete replace refs")), + OPT_BOOL('f', NULL, &force, N_("replace the ref if it exists")), OPT_END() }; diff --combined builtin/reset.c index 088ccffba0,789ee489b7..1a53448772 --- a/builtin/reset.c +++ b/builtin/reset.c @@@ -133,13 -133,12 +133,13 @@@ static void update_index_from_diff(stru } } -static int read_from_tree(const char **pathspec, unsigned char *tree_sha1) +static int read_from_tree(const struct pathspec *pathspec, + unsigned char *tree_sha1) { struct diff_options opt; memset(&opt, 0, sizeof(opt)); - diff_tree_setup_paths(pathspec, &opt); + copy_pathspec(&opt.pathspec, pathspec); opt.output_format = DIFF_FORMAT_CALLBACK; opt.format_callback = update_index_from_diff; @@@ -148,7 -147,7 +148,7 @@@ return 1; diffcore_std(&opt); diff_flush(&opt); - diff_tree_release_paths(&opt); + free_pathspec(&opt.pathspec); return 0; } @@@ -175,10 -174,7 +175,10 @@@ static void die_if_unmerged_cache(int r } -static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret) +static void parse_args(struct pathspec *pathspec, + const char **argv, const char *prefix, + int patch_mode, + const char **rev_ret) { const char *rev = "HEAD"; unsigned char unused[20]; @@@ -220,13 -216,10 +220,13 @@@ } } *rev_ret = rev; - return argv[0] ? get_pathspec(prefix, argv) : NULL; + parse_pathspec(pathspec, 0, + PATHSPEC_PREFER_FULL | + (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0), + prefix, argv); } - static int update_refs(const char *rev, const unsigned char *sha1) + static int reset_refs(const char *rev, const unsigned char *sha1) { int update_ref_status; struct strbuf msg = STRBUF_INIT; @@@ -253,7 -246,7 +253,7 @@@ int cmd_reset(int argc, const char **ar int patch_mode = 0, unborn; const char *rev; unsigned char sha1[20]; - const char **pathspec = NULL; + struct pathspec pathspec; const struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_SET_INT(0, "mixed", &reset_type, @@@ -265,7 -258,7 +265,7 @@@ N_("reset HEAD, index and working tree"), MERGE), OPT_SET_INT(0, "keep", &reset_type, N_("reset HEAD but keep local changes"), KEEP), - OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")), + OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), OPT_END() }; @@@ -273,13 -266,13 +273,13 @@@ argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); - pathspec = parse_args(argv, prefix, &rev); + parse_args(&pathspec, argv, prefix, patch_mode, &rev); unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ hashcpy(sha1, EMPTY_TREE_SHA1_BIN); - } else if (!pathspec) { + } else if (!pathspec.nr) { struct commit *commit; if (get_sha1_committish(rev, sha1)) die(_("Failed to resolve '%s' as a valid revision."), rev); @@@ -300,13 -293,13 +300,13 @@@ if (patch_mode) { if (reset_type != NONE) die(_("--patch is incompatible with --{hard,mixed,soft}")); - return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec); + return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", &pathspec); } /* git reset tree [--] paths... can be used to * load chosen paths from the tree into the index without * affecting the working tree nor HEAD. */ - if (pathspec) { + if (pathspec.nr) { if (reset_type == MIXED) warning(_("--mixed with paths is deprecated; use 'git reset -- ' instead.")); else if (reset_type != NONE) @@@ -330,14 -323,11 +330,14 @@@ die_if_unmerged_cache(reset_type); if (reset_type != SOFT) { - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + struct lock_file *lock = xcalloc(1, sizeof(*lock)); int newfd = hold_locked_index(lock, 1); if (reset_type == MIXED) { - if (read_from_tree(pathspec, sha1)) + int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; + if (read_from_tree(&pathspec, sha1)) return 1; + refresh_index(&the_index, flags, NULL, NULL, + _("Unstaged changes after reset:")); } else { int err = reset_index(sha1, reset_type, quiet); if (reset_type == KEEP && !err) @@@ -346,20 -336,26 +346,20 @@@ die(_("Could not reset index file to revision '%s'."), rev); } - if (reset_type == MIXED) { /* Report what has not been updated. */ - int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; - refresh_index(&the_index, flags, NULL, NULL, - _("Unstaged changes after reset:")); - } - if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock)) die(_("Could not write new index file.")); } - if (!pathspec && !unborn) { + if (!pathspec.nr && !unborn) { /* Any resets without paths update HEAD to the head being * switched to, saving the previous head in ORIG_HEAD before. */ - update_ref_status = update_refs(rev, sha1); + update_ref_status = reset_refs(rev, sha1); if (reset_type == HARD && !update_ref_status && !quiet) print_new_head_line(lookup_commit_reference(sha1)); } - if (!pathspec) + if (!pathspec.nr) remove_branch_state(); return update_ref_status; diff --combined builtin/tag.c index b577af562a,2c867d2c09..ea55f1d1bd --- a/builtin/tag.c +++ b/builtin/tag.c @@@ -436,26 -436,26 +436,26 @@@ int cmd_tag(int argc, const char **argv struct ref_lock *lock; struct create_tag_options opt; char *cleanup_arg = NULL; - int annotate = 0, force = 0, lines = -1, list = 0, - delete = 0, verify = 0; + int annotate = 0, force = 0, lines = -1; + int cmdmode = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct commit_list *with_commit = NULL; struct option options[] = { - OPT_BOOLEAN('l', "list", &list, N_("list tag names")), + OPT_CMDMODE('l', "list", &cmdmode, N_("list tag names"), 'l'), { OPTION_INTEGER, 'n', NULL, &lines, N_("n"), N_("print lines of each tag message"), PARSE_OPT_OPTARG, NULL, 1 }, - OPT_BOOLEAN('d', "delete", &delete, N_("delete tags")), - OPT_BOOLEAN('v', "verify", &verify, N_("verify tags")), + OPT_CMDMODE('d', "delete", &cmdmode, N_("delete tags"), 'd'), + OPT_CMDMODE('v', "verify", &cmdmode, N_("verify tags"), 'v'), OPT_GROUP(N_("Tag creation options")), - OPT_BOOLEAN('a', "annotate", &annotate, + OPT_BOOL('a', "annotate", &annotate, N_("annotated tag, needs a message")), OPT_CALLBACK('m', "message", &msg, N_("message"), N_("tag message"), parse_msg_arg), OPT_FILENAME('F', "file", &msgfile, N_("read message from file")), - OPT_BOOLEAN('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")), + 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"), @@@ -489,19 -489,22 +489,19 @@@ } if (opt.sign) annotate = 1; - if (argc == 0 && !(delete || verify)) - list = 1; + if (argc == 0 && !cmdmode) + cmdmode = 'l'; - if ((annotate || msg.given || msgfile || force) && - (list || delete || verify)) + if ((annotate || msg.given || msgfile || force) && (cmdmode != 0)) usage_with_options(git_tag_usage, options); - if (list + delete + verify > 1) - usage_with_options(git_tag_usage, options); finalize_colopts(&colopts, -1); - if (list && lines != -1) { + if (cmdmode == 'l' && lines != -1) { if (explicitly_enable_column(colopts)) die(_("--column and -n are incompatible")); colopts = 0; } - if (list) { + if (cmdmode == 'l') { int ret; if (column_active(colopts)) { struct column_options copts; @@@ -520,9 -523,9 +520,9 @@@ die(_("--contains option is only allowed with -l.")); if (points_at.nr) die(_("--points-at option is only allowed with -l.")); - if (delete) + if (cmdmode == 'd') return for_each_tag_name(argv, delete_tag); - if (verify) + if (cmdmode == 'v') return for_each_tag_name(argv, verify_tag); if (msg.given || msgfile) { @@@ -574,7 -577,7 +574,7 @@@ if (annotate) create_tag(object, tag, &buf, &opt, prev, object); - lock = lock_any_ref_for_update(ref.buf, prev, 0); + lock = lock_any_ref_for_update(ref.buf, prev, 0, NULL); if (!lock) die(_("%s: cannot lock the ref"), ref.buf); if (write_ref_sha1(lock, object, NULL) < 0) diff --combined builtin/update-ref.c index 7484d36a65,894f16bc59..702e90db2a --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@@ -2,23 -2,261 +2,261 @@@ #include "refs.h" #include "builtin.h" #include "parse-options.h" + #include "quote.h" + #include "argv-array.h" static const char * const git_update_ref_usage[] = { N_("git update-ref [options] -d []"), N_("git update-ref [options] []"), + N_("git update-ref [options] --stdin [-z]"), NULL }; + static int updates_alloc; + static int updates_count; + static const struct ref_update **updates; + + static char line_termination = '\n'; + static int update_flags; + + static struct ref_update *update_alloc(void) + { + struct ref_update *update; + + /* Allocate and zero-init a struct ref_update */ + update = xcalloc(1, sizeof(*update)); + ALLOC_GROW(updates, updates_count + 1, updates_alloc); + updates[updates_count++] = update; + + /* Store and reset accumulated options */ + update->flags = update_flags; + update_flags = 0; + + return update; + } + + static void update_store_ref_name(struct ref_update *update, + const char *ref_name) + { + if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL)) + die("invalid ref format: %s", ref_name); + update->ref_name = xstrdup(ref_name); + } + + static void update_store_new_sha1(struct ref_update *update, + const char *newvalue) + { + if (*newvalue && get_sha1(newvalue, update->new_sha1)) + die("invalid new value for ref %s: %s", + update->ref_name, newvalue); + } + + static void update_store_old_sha1(struct ref_update *update, + const char *oldvalue) + { + if (*oldvalue && get_sha1(oldvalue, update->old_sha1)) + die("invalid old value for ref %s: %s", + update->ref_name, oldvalue); + + /* We have an old value if non-empty, or if empty without -z */ + update->have_old = *oldvalue || line_termination; + } + + static const char *parse_arg(const char *next, struct strbuf *arg) + { + /* Parse SP-terminated, possibly C-quoted argument */ + if (*next != '"') + while (*next && !isspace(*next)) + strbuf_addch(arg, *next++); + else if (unquote_c_style(arg, next, &next)) + die("badly quoted argument: %s", next); + + /* Return position after the argument */ + return next; + } + + static const char *parse_first_arg(const char *next, struct strbuf *arg) + { + /* Parse argument immediately after "command SP" */ + strbuf_reset(arg); + if (line_termination) { + /* Without -z, use the next argument */ + next = parse_arg(next, arg); + } else { + /* With -z, use rest of first NUL-terminated line */ + strbuf_addstr(arg, next); + next = next + arg->len; + } + return next; + } + + static const char *parse_next_arg(const char *next, struct strbuf *arg) + { + /* Parse next SP-terminated or NUL-terminated argument, if any */ + strbuf_reset(arg); + if (line_termination) { + /* Without -z, consume SP and use next argument */ + if (!*next) + return NULL; + if (*next != ' ') + die("expected SP but got: %s", next); + next = parse_arg(next + 1, arg); + } else { + /* With -z, read the next NUL-terminated line */ + if (*next) + die("expected NUL but got: %s", next); + if (strbuf_getline(arg, stdin, '\0') == EOF) + return NULL; + next = arg->buf + arg->len; + } + return next; + } + + static void parse_cmd_update(const char *next) + { + struct strbuf ref = STRBUF_INIT; + struct strbuf newvalue = STRBUF_INIT; + struct strbuf oldvalue = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("update line missing "); + + if ((next = parse_next_arg(next, &newvalue)) != NULL) + update_store_new_sha1(update, newvalue.buf); + else + die("update %s missing ", ref.buf); + + if ((next = parse_next_arg(next, &oldvalue)) != NULL) + update_store_old_sha1(update, oldvalue.buf); + else if(!line_termination) + die("update %s missing [] NUL", ref.buf); + + if (next && *next) + die("update %s has extra input: %s", ref.buf, next); + } + + static void parse_cmd_create(const char *next) + { + struct strbuf ref = STRBUF_INIT; + struct strbuf newvalue = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("create line missing "); + + if ((next = parse_next_arg(next, &newvalue)) != NULL) + update_store_new_sha1(update, newvalue.buf); + else + die("create %s missing ", ref.buf); + if (is_null_sha1(update->new_sha1)) + die("create %s given zero new value", ref.buf); + + if (next && *next) + die("create %s has extra input: %s", ref.buf, next); + } + + static void parse_cmd_delete(const char *next) + { + struct strbuf ref = STRBUF_INIT; + struct strbuf oldvalue = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("delete line missing "); + + if ((next = parse_next_arg(next, &oldvalue)) != NULL) + update_store_old_sha1(update, oldvalue.buf); + else if(!line_termination) + die("delete %s missing [] NUL", ref.buf); + if (update->have_old && is_null_sha1(update->old_sha1)) + die("delete %s given zero old value", ref.buf); + + if (next && *next) + die("delete %s has extra input: %s", ref.buf, next); + } + + static void parse_cmd_verify(const char *next) + { + struct strbuf ref = STRBUF_INIT; + struct strbuf value = STRBUF_INIT; + struct ref_update *update; + + update = update_alloc(); + + if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0]) + update_store_ref_name(update, ref.buf); + else + die("verify line missing "); + + if ((next = parse_next_arg(next, &value)) != NULL) { + update_store_old_sha1(update, value.buf); + update_store_new_sha1(update, value.buf); + } else if(!line_termination) + die("verify %s missing [] NUL", ref.buf); + + if (next && *next) + die("verify %s has extra input: %s", ref.buf, next); + } + + static void parse_cmd_option(const char *next) + { + if (!strcmp(next, "no-deref")) + update_flags |= REF_NODEREF; + else + die("option unknown: %s", next); + } + + static void update_refs_stdin(void) + { + struct strbuf cmd = STRBUF_INIT; + + /* Read each line dispatch its command */ + while (strbuf_getline(&cmd, stdin, line_termination) != EOF) + if (!cmd.buf[0]) + die("empty command in input"); + else if (isspace(*cmd.buf)) + die("whitespace before command: %s", cmd.buf); + else if (!prefixcmp(cmd.buf, "update ")) + parse_cmd_update(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "create ")) + parse_cmd_create(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "delete ")) + parse_cmd_delete(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "verify ")) + parse_cmd_verify(cmd.buf + 7); + else if (!prefixcmp(cmd.buf, "option ")) + parse_cmd_option(cmd.buf + 7); + else + die("unknown command: %s", cmd.buf); + + strbuf_release(&cmd); + } + int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname, *oldval, *msg = NULL; unsigned char sha1[20], oldsha1[20]; - int delete = 0, no_deref = 0, flags = 0; + int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), - OPT_BOOLEAN('d', NULL, &delete, N_("delete the reference")), - OPT_BOOLEAN('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")), - OPT_BOOLEAN( 0 , "no-deref", &no_deref, + OPT_BOOL('d', NULL, &delete, N_("delete the reference")), + OPT_BOOL( 0 , "no-deref", &no_deref, N_("update not the one it points to")), - OPT_BOOLEAN( 0 , "stdin", &read_stdin, N_("read updates from stdin")), ++ OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")), ++ OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")), OPT_END(), }; @@@ -28,6 -266,18 +266,18 @@@ if (msg && !*msg) die("Refusing to perform update with empty message."); + if (read_stdin) { + if (delete || no_deref || argc > 0) + usage_with_options(git_update_ref_usage, options); + if (end_null) + line_termination = '\0'; + update_refs_stdin(); + return update_refs(msg, updates, updates_count, DIE_ON_ERR); + } + + if (end_null) + usage_with_options(git_update_ref_usage, options); + if (delete) { if (argc < 1 || argc > 2) usage_with_options(git_update_ref_usage, options); diff --combined fast-import.c index 45c8764867,5c329f6009..f4d9969e5c --- a/fast-import.c +++ b/fast-import.c @@@ -22,8 -22,8 +22,8 @@@ Format of STDIN stream ('author' (sp name)? sp '<' email '>' sp when lf)? 'committer' (sp name)? sp '<' email '>' sp when lf commit_msg - ('from' sp committish lf)? - ('merge' sp committish lf)* + ('from' sp commit-ish lf)? + ('merge' sp commit-ish lf)* (file_change | ls)* lf?; commit_msg ::= data; @@@ -43,18 -43,18 +43,18 @@@ file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf; file_inm ::= 'M' sp mode sp 'inline' sp path_str lf data; - note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf; - note_inm ::= 'N' sp 'inline' sp committish lf + note_obm ::= 'N' sp (hexsha1 | idnum) sp commit-ish lf; + note_inm ::= 'N' sp 'inline' sp commit-ish lf data; new_tag ::= 'tag' sp tag_str lf - 'from' sp committish lf + 'from' sp commit-ish lf ('tagger' (sp name)? sp '<' email '>' sp when lf)? tag_msg; tag_msg ::= data; reset_branch ::= 'reset' sp ref_str lf - ('from' sp committish lf)? + ('from' sp commit-ish lf)? lf?; checkpoint ::= 'checkpoint' lf @@@ -93,7 -93,7 +93,7 @@@ # stream formatting is: \, " and LF. Otherwise these values # are UTF8. # - committish ::= (ref_str | hexsha1 | sha1exp_str | idnum); + commit-ish ::= (ref_str | hexsha1 | sha1exp_str | idnum); ref_str ::= ref; sha1exp_str ::= sha1exp; tag_str ::= tag; @@@ -1568,8 -1568,7 +1568,8 @@@ static int tree_content_set static int tree_content_remove( struct tree_entry *root, const char *p, - struct tree_entry *backup_leaf) + struct tree_entry *backup_leaf, + int allow_root) { struct tree_content *t; const char *slash1; @@@ -1584,12 -1583,6 +1584,12 @@@ if (!root->tree) load_tree(root); + + if (!*p && allow_root) { + e = root; + goto del_entry; + } + t = root->tree; for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; @@@ -1606,7 -1599,7 +1606,7 @@@ goto del_entry; if (!e->tree) load_tree(e); - if (tree_content_remove(e, slash1 + 1, backup_leaf)) { + if (tree_content_remove(e, slash1 + 1, backup_leaf, 0)) { for (n = 0; n < e->tree->entry_count; n++) { if (e->tree->entries[n]->versions[1].mode) { hashclr(root->versions[1].sha1); @@@ -1636,8 -1629,7 +1636,8 @@@ del_entry static int tree_content_get( struct tree_entry *root, const char *p, - struct tree_entry *leaf) + struct tree_entry *leaf, + int allow_root) { struct tree_content *t; const char *slash1; @@@ -1649,39 -1641,31 +1649,39 @@@ n = slash1 - p; else n = strlen(p); - if (!n) + if (!n && !allow_root) die("Empty path component found in input"); if (!root->tree) load_tree(root); + + if (!n) { + e = root; + goto found_entry; + } + t = root->tree; for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { - if (!slash1) { - memcpy(leaf, e, sizeof(*leaf)); - if (e->tree && is_null_sha1(e->versions[1].sha1)) - leaf->tree = dup_tree_content(e->tree); - else - leaf->tree = NULL; - return 1; - } + if (!slash1) + goto found_entry; if (!S_ISDIR(e->versions[1].mode)) return 0; if (!e->tree) load_tree(e); - return tree_content_get(e, slash1 + 1, leaf); + return tree_content_get(e, slash1 + 1, leaf, 0); } } return 0; + +found_entry: + memcpy(leaf, e, sizeof(*leaf)); + if (e->tree && is_null_sha1(e->versions[1].sha1)) + leaf->tree = dup_tree_content(e->tree); + else + leaf->tree = NULL; + return 1; } static int update_branch(struct branch *b) @@@ -1694,7 -1678,7 +1694,7 @@@ return 0; if (read_ref(b->name, old_sha1)) hashclr(old_sha1); - lock = lock_any_ref_for_update(b->name, old_sha1, 0); + lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL); if (!lock) return error("Unable to lock %s", b->name); if (!force_update && !is_null_sha1(old_sha1)) { @@@ -2195,7 -2179,7 +2195,7 @@@ static uintmax_t do_change_note_fanout } /* Rename fullpath to realpath */ - if (!tree_content_remove(orig_root, fullpath, &leaf)) + if (!tree_content_remove(orig_root, fullpath, &leaf, 0)) die("Failed to remove path %s", fullpath); tree_content_set(orig_root, realpath, leaf.versions[1].sha1, @@@ -2330,7 -2314,7 +2330,7 @@@ static void file_change_m(struct branc /* Git does not track empty, non-toplevel directories. */ if (S_ISDIR(mode) && !memcmp(sha1, EMPTY_TREE_SHA1_BIN, 20) && *p) { - tree_content_remove(&b->branch_tree, p, NULL); + tree_content_remove(&b->branch_tree, p, NULL, 0); return; } @@@ -2391,7 -2375,7 +2391,7 @@@ static void file_change_d(struct branc die("Garbage after path in: %s", command_buf.buf); p = uq.buf; } - tree_content_remove(&b->branch_tree, p, NULL); + tree_content_remove(&b->branch_tree, p, NULL, 1); } static void file_change_cr(struct branch *b, int rename) @@@ -2429,9 -2413,9 +2429,9 @@@ memset(&leaf, 0, sizeof(leaf)); if (rename) - tree_content_remove(&b->branch_tree, s, &leaf); + tree_content_remove(&b->branch_tree, s, &leaf, 1); else - tree_content_get(&b->branch_tree, s, &leaf); + tree_content_get(&b->branch_tree, s, &leaf, 1); if (!leaf.versions[1].mode) die("Path %s not in branch", s); if (!*d) { /* C "path/to/subdir" "" */ @@@ -2494,7 -2478,7 +2494,7 @@@ static void note_change_n(struct branc assert(*p == ' '); p++; /* skip space */ - /* */ + /* */ s = lookup_branch(p); if (s) { if (is_null_sha1(s->sha1)) @@@ -2537,7 -2521,7 +2537,7 @@@ } construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path); - if (tree_content_remove(&b->branch_tree, path, NULL)) + if (tree_content_remove(&b->branch_tree, path, NULL, 0)) b->num_notes--; if (is_null_sha1(sha1)) @@@ -2973,7 -2957,7 +2973,7 @@@ static struct object_entry *dereference case OBJ_TAG: break; default: - die("Not a treeish: %s", command_buf.buf); + die("Not a tree-ish: %s", command_buf.buf); } if (oe->pack_id != MAX_PACK_ID) { /* in a pack being written */ @@@ -3057,7 -3041,7 +3057,7 @@@ static void parse_ls(struct branch *b struct tree_entry *root = NULL; struct tree_entry leaf = {NULL}; - /* ls SP ( SP)? */ + /* ls SP ( SP)? */ p = command_buf.buf + strlen("ls "); if (*p == '"') { if (!b) @@@ -3067,8 -3051,6 +3067,8 @@@ struct object_entry *e = parse_treeish_dataref(&p); root = new_tree_entry(); hashcpy(root->versions[1].sha1, e->idx.sha1); + if (!is_null_sha1(root->versions[1].sha1)) + root->versions[1].mode = S_IFDIR; load_tree(root); if (*p++ != ' ') die("Missing space after tree-ish: %s", command_buf.buf); @@@ -3083,7 -3065,7 +3083,7 @@@ die("Garbage after path in: %s", command_buf.buf); p = uq.buf; } - tree_content_get(root, p, &leaf); + tree_content_get(root, p, &leaf, 1); /* * A directory in preparation would have a sha1 of zero * until it is saved. Save, for simplicity. diff --combined refs.c index d78860c46d,46177ad256..6383813627 --- a/refs.c +++ b/refs.c @@@ -2121,11 -2121,12 +2121,12 @@@ struct ref_lock *lock_ref_sha1(const ch } struct ref_lock *lock_any_ref_for_update(const char *refname, - const unsigned char *old_sha1, int flags) + const unsigned char *old_sha1, + int flags, int *type_p) { if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) return NULL; - return lock_ref_sha1_basic(refname, old_sha1, flags, NULL); + return lock_ref_sha1_basic(refname, old_sha1, flags, type_p); } /* @@@ -2413,60 -2414,82 +2414,82 @@@ static int curate_packed_ref_fn(struct return 0; } - static int repack_without_ref(const char *refname) + static int repack_without_refs(const char **refnames, int n) { struct ref_dir *packed; struct string_list refs_to_delete = STRING_LIST_INIT_DUP; struct string_list_item *ref_to_delete; + int i, removed = 0; + + /* Look for a packed ref */ + for (i = 0; i < n; i++) + if (get_packed_ref(refnames[i])) + break; - if (!get_packed_ref(refname)) - return 0; /* refname does not exist in packed refs */ + /* Avoid locking if we have nothing to do */ + if (i == n) + return 0; /* no refname exists in packed refs */ if (lock_packed_refs(0)) { unable_to_lock_error(git_path("packed-refs"), errno); - return error("cannot delete '%s' from packed refs", refname); + return error("cannot delete '%s' from packed refs", refnames[i]); } packed = get_packed_refs(&ref_cache); - /* Remove refname from the cache: */ - if (remove_entry(packed, refname) == -1) { + /* Remove refnames from the cache */ + for (i = 0; i < n; i++) + if (remove_entry(packed, refnames[i]) != -1) + removed = 1; + if (!removed) { /* - * The packed entry disappeared while we were + * All packed entries disappeared while we were * acquiring the lock. */ rollback_packed_refs(); return 0; } - /* Remove any other accumulated cruft: */ + /* Remove any other accumulated cruft */ do_for_each_entry_in_dir(packed, 0, curate_packed_ref_fn, &refs_to_delete); for_each_string_list_item(ref_to_delete, &refs_to_delete) { if (remove_entry(packed, ref_to_delete->string) == -1) die("internal error"); } - /* Write what remains: */ + /* Write what remains */ return commit_packed_refs(); } - int delete_ref(const char *refname, const unsigned char *sha1, int delopt) + static int repack_without_ref(const char *refname) { - struct ref_lock *lock; - int err, i = 0, ret = 0, flag = 0; + return repack_without_refs(&refname, 1); + } - lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag); - if (!lock) - return 1; + static int delete_ref_loose(struct ref_lock *lock, int flag) + { if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { /* loose */ - i = strlen(lock->lk->filename) - 5; /* .lock */ + int err, i = strlen(lock->lk->filename) - 5; /* .lock */ + lock->lk->filename[i] = 0; err = unlink_or_warn(lock->lk->filename); - if (err && errno != ENOENT) - ret = 1; - lock->lk->filename[i] = '.'; + if (err && errno != ENOENT) + return 1; } + return 0; + } + + int delete_ref(const char *refname, const unsigned char *sha1, int delopt) + { + struct ref_lock *lock; + int ret = 0, flag = 0; + + lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag); + if (!lock) + return 1; + ret |= delete_ref_loose(lock, flag); + /* removing the loose one could have resurrected an earlier * packed one. Also, if it was not loose we need to repack * without it. @@@ -3169,12 -3192,13 +3192,13 @@@ int for_each_reflog(each_ref_fn fn, voi return retval; } - int update_ref(const char *action, const char *refname, - const unsigned char *sha1, const unsigned char *oldval, - int flags, enum action_on_err onerr) + static struct ref_lock *update_ref_lock(const char *refname, + const unsigned char *oldval, + int flags, int *type_p, + enum action_on_err onerr) { - static struct ref_lock *lock; - lock = lock_any_ref_for_update(refname, oldval, flags); + struct ref_lock *lock; + lock = lock_any_ref_for_update(refname, oldval, flags, type_p); if (!lock) { const char *str = "Cannot lock the ref '%s'."; switch (onerr) { @@@ -3182,8 -3206,14 +3206,14 @@@ case DIE_ON_ERR: die(str, refname); break; case QUIET_ON_ERR: break; } - return 1; } + return lock; + } + + static int update_ref_write(const char *action, const char *refname, + const unsigned char *sha1, struct ref_lock *lock, + enum action_on_err onerr) + { if (write_ref_sha1(lock, sha1, action) < 0) { const char *str = "Cannot update the ref '%s'."; switch (onerr) { @@@ -3196,6 -3226,125 +3226,117 @@@ return 0; } + int update_ref(const char *action, const char *refname, + const unsigned char *sha1, const unsigned char *oldval, + int flags, enum action_on_err onerr) + { + struct ref_lock *lock; + lock = update_ref_lock(refname, oldval, flags, 0, onerr); + if (!lock) + return 1; + return update_ref_write(action, refname, sha1, lock, onerr); + } + + static int ref_update_compare(const void *r1, const void *r2) + { + const struct ref_update * const *u1 = r1; + const struct ref_update * const *u2 = r2; + return strcmp((*u1)->ref_name, (*u2)->ref_name); + } + + static int ref_update_reject_duplicates(struct ref_update **updates, int n, + enum action_on_err onerr) + { + int i; + for (i = 1; i < n; i++) + if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) { + const char *str = + "Multiple updates for ref '%s' not allowed."; + switch (onerr) { + case MSG_ON_ERR: + error(str, updates[i]->ref_name); break; + case DIE_ON_ERR: + die(str, updates[i]->ref_name); break; + case QUIET_ON_ERR: + break; + } + return 1; + } + return 0; + } + + int update_refs(const char *action, const struct ref_update **updates_orig, + int n, enum action_on_err onerr) + { + int ret = 0, delnum = 0, i; + struct ref_update **updates; + int *types; + struct ref_lock **locks; + const char **delnames; + + if (!updates_orig || !n) + return 0; + + /* Allocate work space */ + updates = xmalloc(sizeof(*updates) * n); + types = xmalloc(sizeof(*types) * n); + locks = xcalloc(n, sizeof(*locks)); + delnames = xmalloc(sizeof(*delnames) * n); + + /* Copy, sort, and reject duplicate refs */ + memcpy(updates, updates_orig, sizeof(*updates) * n); + qsort(updates, n, sizeof(*updates), ref_update_compare); + ret = ref_update_reject_duplicates(updates, n, onerr); + if (ret) + goto cleanup; + + /* Acquire all locks while verifying old values */ + for (i = 0; i < n; i++) { + locks[i] = update_ref_lock(updates[i]->ref_name, + (updates[i]->have_old ? + updates[i]->old_sha1 : NULL), + updates[i]->flags, + &types[i], onerr); + if (!locks[i]) { + ret = 1; + goto cleanup; + } + } + + /* Perform updates first so live commits remain referenced */ + for (i = 0; i < n; i++) + if (!is_null_sha1(updates[i]->new_sha1)) { + ret = update_ref_write(action, + updates[i]->ref_name, + updates[i]->new_sha1, + locks[i], onerr); + locks[i] = NULL; /* freed by update_ref_write */ + if (ret) + goto cleanup; + } + + /* Perform deletes now that updates are safely completed */ + for (i = 0; i < n; i++) + if (locks[i]) { + delnames[delnum++] = locks[i]->ref_name; + ret |= delete_ref_loose(locks[i], types[i]); + } + ret |= repack_without_refs(delnames, delnum); + for (i = 0; i < delnum; i++) + unlink_or_warn(git_path("logs/%s", delnames[i])); + clear_loose_ref_cache(&ref_cache); + + cleanup: + for (i = 0; i < n; i++) + if (locks[i]) + unlock_ref(locks[i]); + free(updates); + free(types); + free(locks); + free(delnames); + return ret; + } + -struct ref *find_ref_by_name(const struct ref *list, const char *name) -{ - for ( ; list; list = list->next) - if (!strcmp(list->name, name)) - return (struct ref *)list; - return NULL; -} - /* * generate a format suitable for scanf from a ref_rev_parse_rules * rule, that is replace the "%.*s" spec with a "%s" spec