diff options
Diffstat (limited to 'builtin')
47 files changed, 1724 insertions, 716 deletions
diff --git a/builtin/add.c b/builtin/add.c index 3390933d68..b2a5c57f0a 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -208,7 +208,8 @@ static int edit_patch(int argc, const char **argv, const char *prefix) if (run_diff_files(&rev, 0)) die(_("Could not write patch")); - launch_editor(file, NULL, NULL); + if (launch_editor(file, NULL, NULL)) + die(_("editing patch failed")); if (stat(file, &st)) die_errno(_("Could not stat '%s'"), file); @@ -374,7 +375,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (add_new_files) { int baselen; - struct pathspec empty_pathspec; /* Set up the default git porcelain excludes */ memset(&dir, 0, sizeof(dir)); @@ -383,7 +383,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) setup_standard_excludes(&dir); } - memset(&empty_pathspec, 0, sizeof(empty_pathspec)); /* This picks up the paths that are not tracked */ baselen = fill_directory(&dir, &pathspec); if (pathspec.nr) diff --git a/builtin/apply.c b/builtin/apply.c index 955cc79499..54aba4e351 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -51,6 +51,7 @@ static int apply_verbosely; static int allow_overlap; static int no_add; static int threeway; +static int unsafe_paths; static const char *fake_ancestor; static int line_termination = '\n'; static unsigned int p_context = UINT_MAX; @@ -207,7 +208,7 @@ struct patch { struct patch *next; /* three-way fallback result */ - unsigned char threeway_stage[3][20]; + struct object_id threeway_stage[3]; }; static void free_fragment_list(struct fragment *list) @@ -1600,6 +1601,9 @@ static int parse_fragment(const char *line, unsigned long size, if (!deleted && !added) leading++; trailing++; + if (!apply_in_reverse && + ws_error_action == correct_ws_error) + check_whitespace(line, len, patch->ws_rule); break; case '-': if (apply_in_reverse && @@ -1634,6 +1638,9 @@ static int parse_fragment(const char *line, unsigned long size, } if (oldlines || newlines) return -1; + if (!deleted && !added) + return -1; + fragment->leading = leading; fragment->trailing = trailing; @@ -2772,7 +2779,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, default: if (apply_verbosely) error(_("invalid start of line: '%c'"), first); - return -1; + applied_pos = -1; + goto out; } if (added_blank_line) { if (!new_blank_lines_at_end) @@ -2911,6 +2919,7 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, (int)(old - oldlines), oldlines); } +out: free(oldlines); strbuf_release(&newlines); free(preimage.line_allocated); @@ -3221,7 +3230,7 @@ static int load_patch_target(struct strbuf *buf, const char *name, unsigned expected_mode) { - if (cached) { + if (cached || check_index) { if (read_file_or_gitlink(ce, buf)) return error(_("read of %s failed"), name); } else if (name) { @@ -3230,6 +3239,8 @@ static int load_patch_target(struct strbuf *buf, return read_file_or_gitlink(ce, buf); else return SUBMODULE_PATCH_WITHOUT_INDEX; + } else if (has_symlink_leading_path(name, strlen(name))) { + return error(_("reading from '%s' beyond a symbolic link"), name); } else { if (read_old_data(st, name, buf)) return error(_("read of %s failed"), name); @@ -3418,11 +3429,11 @@ static int try_threeway(struct image *image, struct patch *patch, if (status) { patch->conflicted_threeway = 1; if (patch->is_new) - hashclr(patch->threeway_stage[0]); + oidclr(&patch->threeway_stage[0]); else - hashcpy(patch->threeway_stage[0], pre_sha1); - hashcpy(patch->threeway_stage[1], our_sha1); - hashcpy(patch->threeway_stage[2], post_sha1); + hashcpy(patch->threeway_stage[0].hash, pre_sha1); + hashcpy(patch->threeway_stage[1].hash, our_sha1); + hashcpy(patch->threeway_stage[2].hash, post_sha1); fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name); } else { fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name); @@ -3570,6 +3581,121 @@ static int check_to_create(const char *new_name, int ok_if_exists) } /* + * We need to keep track of how symlinks in the preimage are + * manipulated by the patches. A patch to add a/b/c where a/b + * is a symlink should not be allowed to affect the directory + * the symlink points at, but if the same patch removes a/b, + * it is perfectly fine, as the patch removes a/b to make room + * to create a directory a/b so that a/b/c can be created. + */ +static struct string_list symlink_changes; +#define SYMLINK_GOES_AWAY 01 +#define SYMLINK_IN_RESULT 02 + +static uintptr_t register_symlink_changes(const char *path, uintptr_t what) +{ + struct string_list_item *ent; + + ent = string_list_lookup(&symlink_changes, path); + if (!ent) { + ent = string_list_insert(&symlink_changes, path); + ent->util = (void *)0; + } + ent->util = (void *)(what | ((uintptr_t)ent->util)); + return (uintptr_t)ent->util; +} + +static uintptr_t check_symlink_changes(const char *path) +{ + struct string_list_item *ent; + + ent = string_list_lookup(&symlink_changes, path); + if (!ent) + return 0; + return (uintptr_t)ent->util; +} + +static void prepare_symlink_changes(struct patch *patch) +{ + for ( ; patch; patch = patch->next) { + if ((patch->old_name && S_ISLNK(patch->old_mode)) && + (patch->is_rename || patch->is_delete)) + /* the symlink at patch->old_name is removed */ + register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY); + + if (patch->new_name && S_ISLNK(patch->new_mode)) + /* the symlink at patch->new_name is created or remains */ + register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT); + } +} + +static int path_is_beyond_symlink_1(struct strbuf *name) +{ + do { + unsigned int change; + + while (--name->len && name->buf[name->len] != '/') + ; /* scan backwards */ + if (!name->len) + break; + name->buf[name->len] = '\0'; + change = check_symlink_changes(name->buf); + if (change & SYMLINK_IN_RESULT) + return 1; + if (change & SYMLINK_GOES_AWAY) + /* + * This cannot be "return 0", because we may + * see a new one created at a higher level. + */ + continue; + + /* otherwise, check the preimage */ + if (check_index) { + struct cache_entry *ce; + + ce = cache_file_exists(name->buf, name->len, ignore_case); + if (ce && S_ISLNK(ce->ce_mode)) + return 1; + } else { + struct stat st; + if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode)) + return 1; + } + } while (1); + return 0; +} + +static int path_is_beyond_symlink(const char *name_) +{ + int ret; + struct strbuf name = STRBUF_INIT; + + assert(*name_ != '\0'); + strbuf_addstr(&name, name_); + ret = path_is_beyond_symlink_1(&name); + strbuf_release(&name); + + return ret; +} + +static void die_on_unsafe_path(struct patch *patch) +{ + const char *old_name = NULL; + const char *new_name = NULL; + if (patch->is_delete) + old_name = patch->old_name; + else if (!patch->is_new && !patch->is_copy) + old_name = patch->old_name; + if (!patch->is_delete) + new_name = patch->new_name; + + if (old_name && !verify_path(old_name)) + die(_("invalid path '%s'"), old_name); + if (new_name && !verify_path(new_name)) + die(_("invalid path '%s'"), new_name); +} + +/* * Check and apply the patch in-core; leave the result in patch->result * for the caller to write it out to the final destination. */ @@ -3656,6 +3782,22 @@ static int check_patch(struct patch *patch) } } + if (!unsafe_paths) + die_on_unsafe_path(patch); + + /* + * An attempt to read from or delete a path that is beyond a + * symbolic link will be prevented by load_patch_target() that + * is called at the beginning of apply_data() so we do not + * have to worry about a patch marked with "is_delete" bit + * here. We however need to make sure that the patch result + * is not deposited to a path that is beyond a symbolic link + * here. + */ + if (!patch->is_delete && path_is_beyond_symlink(patch->new_name)) + return error(_("affected file '%s' is beyond a symbolic link"), + patch->new_name); + if (apply_data(patch, &st, ce) < 0) return error(_("%s: patch does not apply"), name); patch->rejected = 0; @@ -3666,6 +3808,7 @@ static int check_patch_list(struct patch *patch) { int err = 0; + prepare_symlink_changes(patch); prepare_fn_table(patch); while (patch) { if (apply_verbosely) @@ -4046,14 +4189,14 @@ static void add_conflicted_stages_file(struct patch *patch) remove_file_from_cache(patch->new_name); for (stage = 1; stage < 4; stage++) { - if (is_null_sha1(patch->threeway_stage[stage - 1])) + if (is_null_oid(&patch->threeway_stage[stage - 1])) continue; ce = xcalloc(1, ce_size); memcpy(ce->name, patch->new_name, namelen); ce->ce_mode = create_ce_mode(mode); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = namelen; - hashcpy(ce->sha1, patch->threeway_stage[stage - 1]); + hashcpy(ce->sha1, patch->threeway_stage[stage - 1].hash); if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0) die(_("unable to add cache entry for %s"), patch->new_name); } @@ -4404,6 +4547,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) N_("make sure the patch is applicable to the current index")), OPT_BOOL(0, "cached", &cached, N_("apply a patch without touching the working tree")), + OPT_BOOL(0, "unsafe-paths", &unsafe_paths, + N_("accept a patch that touches outside the working area")), OPT_BOOL(0, "apply", &force_apply, N_("also apply the patch (use with --stat/--summary/--check)")), OPT_BOOL('3', "3way", &threeway, @@ -4476,6 +4621,9 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) die(_("--cached outside a repository")); check_index = 1; } + if (check_index) + unsafe_paths = 0; + for (i = 0; i < argc; i++) { const char *arg = argv[i]; int fd; diff --git a/builtin/blame.c b/builtin/blame.c index 06484c2e0e..a22ac17407 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -26,8 +26,9 @@ #include "userdiff.h" #include "line-range.h" #include "line-log.h" +#include "dir.h" -static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] file"); +static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); static const char *blame_opt_usage[] = { blame_usage, @@ -2151,16 +2152,6 @@ static void sanity_check_refcnt(struct scoreboard *sb) } } -/* - * Used for the command line parsing; check if the path exists - * in the working tree. - */ -static int has_string_in_work_tree(const char *path) -{ - struct stat st; - return !lstat(path, &st); -} - static unsigned parse_score(const char *arg) { char *end; @@ -2185,6 +2176,14 @@ static int git_blame_config(const char *var, const char *value, void *cb) blank_boundary = git_config_bool(var, value); return 0; } + if (!strcmp(var, "blame.showemail")) { + int *output_option = cb; + if (git_config_bool(var, value)) + *output_option |= OUTPUT_SHOW_EMAIL; + else + *output_option &= ~OUTPUT_SHOW_EMAIL; + return 0; + } if (!strcmp(var, "blame.date")) { if (!value) return config_error_nonbool(var); @@ -2348,6 +2347,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, 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_sha1); @@ -2528,7 +2528,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) unsigned int range_i; long anchor; - git_config(git_blame_config, NULL); + git_config(git_blame_config, &output_option); init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); @@ -2655,14 +2655,14 @@ parse_done: if (argc < 2) usage_with_options(blame_opt_usage, options); path = add_prefix(prefix, argv[argc - 1]); - if (argc == 3 && !has_string_in_work_tree(path)) { /* (2b) */ + if (argc == 3 && !file_exists(path)) { /* (2b) */ path = add_prefix(prefix, argv[1]); argv[1] = argv[2]; } argv[argc - 1] = "--"; setup_work_tree(); - if (!has_string_in_work_tree(path)) + if (!file_exists(path)) die_errno("cannot stat path '%s'", path); } diff --git a/builtin/branch.c b/builtin/branch.c index 6a25957e9f..b42e5b6dbc 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -123,14 +123,12 @@ static int branch_merged(int kind, const char *name, if (kind == REF_LOCAL_BRANCH) { struct branch *branch = branch_get(name); + const char *upstream = branch_get_upstream(branch, NULL); unsigned char sha1[20]; - if (branch && - branch->merge && - branch->merge[0] && - branch->merge[0]->dst && + if (upstream && (reference_name = reference_name_to_free = - resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING, + resolve_refdup(upstream, RESOLVE_REF_READING, sha1, NULL)) != NULL) reference_rev = lookup_commit_reference(sha1); } @@ -242,7 +240,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, sha1, &flags); if (!target) { error(remote_branch - ? _("remote branch '%s' not found.") + ? _("remote-tracking branch '%s' not found.") : _("branch '%s' not found."), bname.buf); ret = 1; continue; @@ -257,7 +255,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, if (delete_ref(name, sha1, REF_NODEREF)) { error(remote_branch - ? _("Error deleting remote branch '%s'") + ? _("Error deleting remote-tracking branch '%s'") : _("Error deleting branch '%s'"), bname.buf); ret = 1; @@ -265,7 +263,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, } if (!quiet) { printf(remote_branch - ? _("Deleted remote branch %s (was %s).\n") + ? _("Deleted remote-tracking branch %s (was %s).\n") : _("Deleted branch %s (was %s).\n"), bname.buf, (flags & REF_ISBROKEN) ? "broken" @@ -328,7 +326,7 @@ static int match_patterns(const char **pattern, const char *refname) return 0; } -static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) +static int append_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data) { struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data); struct ref_list *ref_list = cb->ref_list; @@ -365,7 +363,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, commit = NULL; if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) { - commit = lookup_commit_reference_gently(sha1, 1); + commit = lookup_commit_reference_gently(oid->hash, 1); if (!commit) { cb->ret = error(_("branch '%s' does not point at a commit"), refname); return 0; @@ -427,25 +425,19 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, int ours, theirs; char *ref = NULL; struct branch *branch = branch_get(branch_name); + const char *upstream; struct strbuf fancy = STRBUF_INIT; int upstream_is_gone = 0; int added_decoration = 1; - switch (stat_tracking_info(branch, &ours, &theirs)) { - case 0: - /* no base */ - return; - case -1: - /* with "gone" base */ + if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) { + if (!upstream) + return; upstream_is_gone = 1; - break; - default: - /* with base */ - break; } if (show_upstream_ref) { - ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0); + ref = shorten_unambiguous_ref(upstream, 0); if (want_color(branch_use_color)) strbuf_addf(&fancy, "%s%s%s", branch_get_color(BRANCH_COLOR_UPSTREAM), @@ -589,9 +581,16 @@ static char *get_head_description(void) else if (state.bisect_in_progress) strbuf_addf(&desc, _("(no branch, bisect started on %s)"), state.branch); - else if (state.detached_from) - strbuf_addf(&desc, _("(detached from %s)"), - state.detached_from); + else if (state.detached_from) { + /* TRANSLATORS: make sure these match _("HEAD detached at ") + and _("HEAD detached from ") in wt-status.c */ + if (state.detached_at) + strbuf_addf(&desc, _("(HEAD detached at %s)"), + state.detached_from); + else + strbuf_addf(&desc, _("(HEAD detached from %s)"), + state.detached_from); + } else strbuf_addstr(&desc, _("(no branch)")); free(state.branch); @@ -764,7 +763,6 @@ static const char edit_description[] = "BRANCH_DESCRIPTION"; static int edit_branch_description(const char *branch_name) { - FILE *fp; int status; struct strbuf buf = STRBUF_INIT; struct strbuf name = STRBUF_INIT; @@ -777,8 +775,7 @@ static int edit_branch_description(const char *branch_name) " %s\n" "Lines starting with '%c' will be stripped.\n", branch_name, comment_line_char); - fp = fopen(git_path(edit_description), "w"); - if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) { + if (write_file(git_path(edit_description), 0, "%s", buf.buf)) { strbuf_release(&buf); return error(_("could not write branch description template: %s"), strerror(errno)); diff --git a/builtin/bundle.c b/builtin/bundle.c index 92a8a6026a..4883a435a9 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -42,6 +42,10 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) if (!strcmp(cmd, "verify")) { close(bundle_fd); + if (argc != 1) { + usage(builtin_bundle_usage); + return 1; + } if (verify_bundle(&header, 1)) return 1; fprintf(stderr, _("%s is okay\n"), bundle_file); @@ -52,6 +56,10 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) return !!list_bundle_refs(&header, argc, argv); } if (!strcmp(cmd, "create")) { + if (argc < 2) { + usage(builtin_bundle_usage); + return 1; + } if (!startup_info->have_repository) die(_("Need a repository to create a bundle.")); return !!create_bundle(&header, bundle_file, argc, argv); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index df99df4db1..049a95f1f1 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -8,14 +8,22 @@ #include "parse-options.h" #include "userdiff.h" #include "streaming.h" +#include "tree-walk.h" -static int cat_one_file(int opt, const char *exp_type, const char *obj_name) +static int cat_one_file(int opt, const char *exp_type, const char *obj_name, + int unknown_type) { unsigned char sha1[20]; enum object_type type; char *buf; unsigned long size; struct object_context obj_context; + struct object_info oi = {NULL}; + struct strbuf sb = STRBUF_INIT; + unsigned flags = LOOKUP_REPLACE_OBJECT; + + if (unknown_type) + flags |= LOOKUP_UNKNOWN_OBJECT; if (get_sha1_with_context(obj_name, 0, sha1, &obj_context)) die("Not a valid object name %s", obj_name); @@ -23,20 +31,22 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) buf = NULL; switch (opt) { case 't': - type = sha1_object_info(sha1, NULL); - if (type > 0) { - printf("%s\n", typename(type)); + oi.typename = &sb; + if (sha1_object_info_extended(sha1, &oi, flags) < 0) + die("git cat-file: could not get object info"); + if (sb.len) { + printf("%s\n", sb.buf); + strbuf_release(&sb); return 0; } break; case 's': - type = sha1_object_info(sha1, &size); - if (type > 0) { - printf("%lu\n", size); - return 0; - } - break; + oi.sizep = &size; + if (sha1_object_info_extended(sha1, &oi, flags) < 0) + die("git cat-file: could not get object info"); + printf("%lu\n", size); + return 0; case 'e': return !has_sha1_file(sha1); @@ -224,6 +234,7 @@ static void print_object_or_die(int fd, struct expand_data *data) struct batch_options { int enabled; + int follow_symlinks; int print_contents; const char *format; }; @@ -232,12 +243,44 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt, struct expand_data *data) { struct strbuf buf = STRBUF_INIT; + struct object_context ctx; + int flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0; + enum follow_symlinks_result result; if (!obj_name) return 1; - if (get_sha1(obj_name, data->sha1)) { - printf("%s missing\n", obj_name); + result = get_sha1_with_context(obj_name, flags, data->sha1, &ctx); + if (result != FOUND) { + switch (result) { + case MISSING_OBJECT: + printf("%s missing\n", obj_name); + break; + case DANGLING_SYMLINK: + printf("dangling %"PRIuMAX"\n%s\n", + (uintmax_t)strlen(obj_name), obj_name); + break; + case SYMLINK_LOOP: + printf("loop %"PRIuMAX"\n%s\n", + (uintmax_t)strlen(obj_name), obj_name); + break; + case NOT_DIR: + printf("notdir %"PRIuMAX"\n%s\n", + (uintmax_t)strlen(obj_name), obj_name); + break; + default: + die("BUG: unknown get_sha1_with_context result %d\n", + result); + break; + } + fflush(stdout); + return 0; + } + + if (ctx.mode == 0) { + printf("symlink %"PRIuMAX"\n%s\n", + (uintmax_t)ctx.symlink_path.len, + ctx.symlink_path.buf); fflush(stdout); return 0; } @@ -323,8 +366,8 @@ static int batch_objects(struct batch_options *opt) } static const char * const cat_file_usage[] = { - N_("git cat-file (-t | -s | -e | -p | <type> | --textconv) <object>"), - N_("git cat-file (--batch | --batch-check) < <list-of-objects>"), + N_("git cat-file (-t [--allow-unknown-type]|-s [--allow-unknown-type]|-e|-p|<type>|--textconv) <object>"), + N_("git cat-file (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>"), NULL }; @@ -342,9 +385,8 @@ static int batch_option_callback(const struct option *opt, { struct batch_options *bo = opt->value; - if (unset) { - memset(bo, 0, sizeof(*bo)); - return 0; + if (bo->enabled) { + return 1; } bo->enabled = 1; @@ -359,30 +401,32 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) int opt = 0; const char *exp_type = NULL, *obj_name = NULL; struct batch_options batch = {0}; + int unknown_type = 0; const struct option options[] = { OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")), - OPT_SET_INT('t', NULL, &opt, N_("show object type"), 't'), - OPT_SET_INT('s', NULL, &opt, N_("show object size"), 's'), - OPT_SET_INT('e', NULL, &opt, + OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'), + OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), + OPT_CMDMODE('e', NULL, &opt, N_("exit with zero when there's no error"), 'e'), - OPT_SET_INT('p', NULL, &opt, N_("pretty-print object's content"), 'p'), - OPT_SET_INT(0, "textconv", &opt, + OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'), + OPT_CMDMODE(0, "textconv", &opt, N_("for blob objects, run textconv on object's content"), 'c'), + OPT_BOOL( 0, "allow-unknown-type", &unknown_type, + N_("allow -s and -t to work with broken/corrupt objects")), { OPTION_CALLBACK, 0, "batch", &batch, "format", N_("show info and content of objects fed from the standard input"), PARSE_OPT_OPTARG, batch_option_callback }, { OPTION_CALLBACK, 0, "batch-check", &batch, "format", N_("show info about objects fed from the standard input"), PARSE_OPT_OPTARG, batch_option_callback }, + OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, + N_("follow in-tree symlinks (used with --batch or --batch-check)")), OPT_END() }; git_config(git_cat_file_config, NULL); - if (argc != 3 && argc != 2) - usage_with_options(cat_file_usage, options); - argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); if (opt) { @@ -402,8 +446,14 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) usage_with_options(cat_file_usage, options); } + if (batch.follow_symlinks && !batch.enabled) { + usage_with_options(cat_file_usage, options); + } + if (batch.enabled) return batch_objects(&batch); - return cat_one_file(opt, exp_type, obj_name); + if (unknown_type && opt != 't' && opt != 's') + die("git cat-file --allow-unknown-type: use with -s or -t"); + return cat_one_file(opt, exp_type, obj_name, unknown_type); } diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 9ca2da1583..8028c3768f 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -241,7 +241,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) /* Check out named files first */ for (i = 0; i < argc; i++) { const char *arg = argv[i]; - const char *p; + char *p; if (all) die("git checkout-index: don't mix '--all' and explicit filenames"); @@ -249,8 +249,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) die("git checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); checkout_file(p, prefix); - if (p < arg || p > arg + strlen(arg)) - free((char *)p); + free(p); } if (read_from_stdin) { @@ -260,7 +259,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) die("git checkout-index: don't mix '--all' and '--stdin'"); while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - const char *p; + char *p; if (line_termination && buf.buf[0] == '"') { strbuf_reset(&nbuf); if (unquote_c_style(&nbuf, buf.buf, NULL)) @@ -269,8 +268,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) } p = prefix_path(prefix, prefix_length, buf.buf); checkout_file(p, prefix); - if (p < buf.buf || p > buf.buf + buf.len) - free((char *)p); + free(p); } strbuf_release(&nbuf); strbuf_release(&buf); diff --git a/builtin/checkout.c b/builtin/checkout.c index 3e141fc149..f71844a23a 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -19,7 +19,6 @@ #include "ll-merge.h" #include "resolve-undo.h" #include "submodule.h" -#include "argv-array.h" static const char * const checkout_usage[] = { N_("git checkout [<options>] <branch>"), @@ -36,6 +35,7 @@ struct checkout_opts { int writeout_stage; int overwrite_ignore; int ignore_skipworktree; + int ignore_other_worktrees; const char *new_branch; const char *new_branch_force; @@ -48,6 +48,8 @@ struct checkout_opts { const char *prefix; struct pathspec pathspec; struct tree *source_tree; + + int new_worktree_mode; }; static int post_checkout_hook(struct commit *old, struct commit *new, @@ -441,6 +443,11 @@ struct branch_info { const char *name; /* The short name used */ const char *path; /* The full name of a real branch */ struct commit *commit; /* The named commit */ + /* + * if not null the branch is detached because it's already + * checked out in this checkout + */ + char *checkout; }; static void setup_branch_path(struct branch_info *branch) @@ -502,7 +509,7 @@ 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 ? + tree = parse_tree_indirect(old->commit && !opts->new_worktree_mode ? old->commit->object.sha1 : EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); @@ -606,18 +613,21 @@ static void update_refs_for_switch(const struct checkout_opts *opts, if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { int temp; - char log_file[PATH_MAX]; - char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); + struct strbuf log_file = STRBUF_INIT; + int ret; + const char *ref_name; + ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); temp = log_all_ref_updates; log_all_ref_updates = 1; - if (log_ref_setup(ref_name, log_file, sizeof(log_file))) { + ret = log_ref_setup(ref_name, &log_file); + log_all_ref_updates = temp; + strbuf_release(&log_file); + if (ret) { fprintf(stderr, _("Can not do reflog for '%s'\n"), opts->new_orphan_branch); - log_all_ref_updates = temp; return; } - log_all_ref_updates = temp; } } else @@ -685,10 +695,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts, } static int add_pending_uninteresting_ref(const char *refname, - const unsigned char *sha1, + const struct object_id *oid, int flags, void *cb_data) { - add_pending_sha1(cb_data, refname, sha1, UNINTERESTING); + add_pending_sha1(cb_data, refname, oid->hash, UNINTERESTING); return 0; } @@ -743,10 +753,17 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) if (advice_detached_head) fprintf(stderr, - _( + Q_( + /* The singular version */ + "If you want to keep it by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch <new-branch-name> %s\n\n", + /* The plural version */ "If you want to keep them by creating a new branch, " "this may be a good time\nto do so with:\n\n" - " git branch <new-branch-name> %s\n\n"), + " git branch <new-branch-name> %s\n\n", + /* Give ngettext() the count */ + lost), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); } @@ -815,7 +832,8 @@ static int switch_branches(const struct checkout_opts *opts, return ret; } - if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) + if (!opts->quiet && !old.path && old.commit && + new->commit != old.commit && !opts->new_worktree_mode) orphaned_commit_warning(old.commit, new->commit); update_refs_for_switch(opts, &old, new); @@ -880,13 +898,79 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1) return NULL; } +static void check_linked_checkout(struct branch_info *new, const char *id) +{ + struct strbuf sb = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; + const char *start, *end; + + if (id) + strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id); + else + strbuf_addf(&path, "%s/HEAD", get_git_common_dir()); + + if (strbuf_read_file(&sb, path.buf, 0) < 0 || + !skip_prefix(sb.buf, "ref:", &start)) + goto done; + while (isspace(*start)) + start++; + end = start; + while (*end && !isspace(*end)) + end++; + if (strncmp(start, new->path, end - start) || new->path[end - start] != '\0') + goto done; + if (id) { + strbuf_reset(&path); + strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id); + if (strbuf_read_file(&gitdir, path.buf, 0) <= 0) + goto done; + strbuf_rtrim(&gitdir); + } else + strbuf_addstr(&gitdir, get_git_common_dir()); + die(_("'%s' is already checked out at '%s'"), new->name, gitdir.buf); +done: + strbuf_release(&path); + strbuf_release(&sb); + strbuf_release(&gitdir); +} + +static void check_linked_checkouts(struct branch_info *new) +{ + struct strbuf path = STRBUF_INIT; + DIR *dir; + struct dirent *d; + + strbuf_addf(&path, "%s/worktrees", get_git_common_dir()); + if ((dir = opendir(path.buf)) == NULL) { + strbuf_release(&path); + return; + } + + /* + * $GIT_COMMON_DIR/HEAD is practically outside + * $GIT_DIR so resolve_ref_unsafe() won't work (it + * uses git_path). Parse the ref ourselves. + */ + check_linked_checkout(new, NULL); + + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + check_linked_checkout(new, d->d_name); + } + strbuf_release(&path); + closedir(dir); +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new, - struct tree **source_tree, - unsigned char rev[20], - const char **new_branch) + struct checkout_opts *opts, + unsigned char rev[20]) { + struct tree **source_tree = &opts->source_tree; + const char **new_branch = &opts->new_branch; int argcount = 0; unsigned char branch_rev[20]; const char *arg; @@ -1086,6 +1170,17 @@ static int checkout_branch(struct checkout_opts *opts, die(_("Cannot switch branch to a non-commit '%s'"), new->name); + if (new->path && !opts->force_detach && !opts->new_branch) { + unsigned char sha1[20]; + int flag; + char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); + if (head_ref && + (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path)) && + !opts->ignore_other_worktrees) + check_linked_checkouts(new); + free(head_ref); + } + if (!new->commit && opts->new_branch) { unsigned char rev[20]; int flag; @@ -1128,6 +1223,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) N_("do not limit pathspecs to sparse entries only")), OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch, N_("second guess 'git checkout <no-such-branch>'")), + OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, + N_("do not check if another worktree is holding the given ref")), OPT_END(), }; @@ -1144,6 +1241,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); + opts.new_worktree_mode = getenv("GIT_CHECKOUT_NEW_WORKTREE") != NULL; + if (conflict_style) { opts.merge = 1; /* implied */ git_xmerge_config("merge.conflictstyle", conflict_style, NULL); @@ -1197,8 +1296,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.source_tree, - rev, &opts.new_branch); + &new, &opts, rev); argv += n; argc -= n; } diff --git a/builtin/clean.c b/builtin/clean.c index 7e7fdcfe54..6dcb72e644 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -314,7 +314,6 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen) { struct string_list menu_list = STRING_LIST_INIT_DUP; struct strbuf menu = STRBUF_INIT; - struct strbuf buf = STRBUF_INIT; struct menu_item *menu_item; struct string_list_item *string_list_item; int i; @@ -363,7 +362,6 @@ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen) pretty_print_menus(&menu_list); strbuf_release(&menu); - strbuf_release(&buf); string_list_clear(&menu_list, 0); } @@ -754,7 +752,8 @@ static int ask_each_cmd(void) /* Ctrl-D should stop removing files */ if (!eof) { qname = quote_path_relative(item->string, NULL, &buf); - printf(_("remove %s? "), qname); + /* TRANSLATORS: Make sure to keep [y/N] as is */ + printf(_("Remove %s [y/N]? "), qname); if (strbuf_getline(&confirm, stdin, '\n') != EOF) { strbuf_trim(&confirm); } else { @@ -940,15 +939,15 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (!cache_name_is_other(ent->name, ent->len)) continue; - if (lstat(ent->name, &st)) - die_errno("Cannot lstat '%s'", ent->name); - if (pathspec.nr) matches = dir_path_match(ent, &pathspec, 0, NULL); if (pathspec.nr && !matches) continue; + if (lstat(ent->name, &st)) + die_errno("Cannot lstat '%s'", ent->name); + if (S_ISDIR(st.st_mode) && !remove_directories && matches != MATCHED_EXACTLY) continue; diff --git a/builtin/clone.c b/builtin/clone.c index 957246723e..a72ff7e009 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -51,15 +51,6 @@ static struct string_list option_config; static struct string_list option_reference; static int option_dissociate; -static int opt_parse_reference(const struct option *opt, const char *arg, int unset) -{ - struct string_list *option_reference = opt->value; - if (!arg) - return -1; - string_list_append(option_reference, arg); - return 0; -} - static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), OPT_BOOL(0, "progress", &option_progress, @@ -83,8 +74,10 @@ static struct option builtin_clone_options[] = { N_("initialize submodules in the clone")), OPT_STRING(0, "template", &option_template, N_("template-directory"), N_("directory from which templates will be used")), - OPT_CALLBACK(0 , "reference", &option_reference, N_("repo"), - N_("reference repository"), &opt_parse_reference), + OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"), + N_("reference repository")), + OPT_BOOL(0, "dissociate", &option_dissociate, + N_("use --reference only while cloning")), OPT_STRING('o', "origin", &option_origin, N_("name"), N_("use <name> instead of 'origin' to track upstream")), OPT_STRING('b', "branch", &option_branch, N_("branch"), @@ -95,8 +88,6 @@ static struct option builtin_clone_options[] = { N_("create a shallow clone of that depth")), OPT_BOOL(0, "single-branch", &option_single_branch, N_("clone only one branch, HEAD or --branch")), - OPT_BOOL(0, "dissociate", &option_dissociate, - N_("use --reference only while cloning")), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), OPT_STRING_LIST('c', "config", &option_config, N_("key=value"), @@ -156,6 +147,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) { const char *end = repo + strlen(repo), *start; + size_t len; char *dir; /* @@ -182,20 +174,12 @@ static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) /* * Strip .{bundle,git}. */ - if (is_bundle) { - if (end - start > 7 && !strncmp(end - 7, ".bundle", 7)) - end -= 7; - } else { - if (end - start > 4 && !strncmp(end - 4, ".git", 4)) - end -= 4; - } + strip_suffix(start, is_bundle ? ".bundle" : ".git" , &len); - if (is_bare) { - struct strbuf result = STRBUF_INIT; - strbuf_addf(&result, "%.*s.git", (int)(end - start), start); - dir = strbuf_detach(&result, NULL); - } else - dir = xstrndup(start, end - start); + if (is_bare) + dir = xstrfmt("%.*s.git", (int)len, start); + else + dir = xstrndup(start, len); /* * Replace sequences of 'control' characters and whitespace * with one ascii space, remove leading and trailing spaces. @@ -293,16 +277,17 @@ static void copy_alternates(struct strbuf *src, struct strbuf *dst, struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, in, '\n') != EOF) { - char *abs_path, abs_buf[PATH_MAX]; + char *abs_path; if (!line.len || line.buf[0] == '#') continue; if (is_absolute_path(line.buf)) { add_to_alternates_file(line.buf); continue; } - abs_path = mkpath("%s/objects/%s", src_repo, line.buf); - normalize_path_copy(abs_buf, abs_path); - add_to_alternates_file(abs_buf); + abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf); + normalize_path_copy(abs_path, abs_path); + add_to_alternates_file(abs_path); + free(abs_path); } strbuf_release(&line); fclose(in); @@ -842,20 +827,21 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_dir = mkpathdup("%s/.git", dir); } + atexit(remove_junk); + sigchain_push_common(remove_junk_on_signal); + if (!option_bare) { - junk_work_tree = work_tree; 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)) - die_errno(_("could not create work tree dir '%s'."), + 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 = git_dir; - atexit(remove_junk); - sigchain_push_common(remove_junk_on_signal); + junk_git_dir = git_dir; if (safe_create_leading_directories_const(git_dir) < 0) die(_("could not create leading directories of '%s'"), git_dir); @@ -905,6 +891,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote = remote_get(option_origin); transport = transport_get(remote, remote->url[0]); + transport_set_verbosity(transport, option_verbosity, option_progress); + path = get_repo_path(remote->url[0], &is_bundle); is_local = option_local != 0 && path && !is_bundle; if (is_local) { @@ -931,8 +919,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_single_branch) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); - transport_set_verbosity(transport, option_verbosity, option_progress); - if (option_upload_pack) transport_set_option(transport, TRANS_OPT_UPLOADPACK, option_upload_pack); diff --git a/builtin/commit.c b/builtin/commit.c index 6055c760f0..254477fd1d 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -170,7 +170,7 @@ static void determine_whence(struct wt_status *s) whence = FROM_MERGE; else if (file_exists(git_path("CHERRY_PICK_HEAD"))) { whence = FROM_CHERRY_PICK; - if (file_exists(git_path("sequencer"))) + if (file_exists(git_path(SEQ_DIR))) sequencer_in_use = 1; } else @@ -229,7 +229,7 @@ static int commit_index_files(void) static int list_paths(struct string_list *list, const char *with_tree, const char *prefix, const struct pathspec *pattern) { - int i; + int i, ret; char *m; if (!pattern->nr) @@ -256,7 +256,9 @@ static int list_paths(struct string_list *list, const char *with_tree, item->util = item; /* better a valid pointer than a fake one */ } - return report_path_error(m, pattern, prefix); + ret = report_path_error(m, pattern, prefix); + free(m); + return ret; } static void add_remove_files(struct string_list *list) @@ -1364,13 +1366,14 @@ int cmd_status(int argc, const char **argv, const char *prefix) refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL); fd = hold_locked_index(&index_lock, 0); - if (0 <= fd) - update_index_if_able(&the_index, &index_lock); s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0; s.ignore_submodule_arg = ignore_submodule_arg; wt_status_collect(&s); + if (0 <= fd) + update_index_if_able(&the_index, &index_lock); + if (s.relative_paths) s.prefix = prefix; @@ -1396,12 +1399,10 @@ int cmd_status(int argc, const char **argv, const char *prefix) static const char *implicit_ident_advice(void) { - char *user_config = NULL; - char *xdg_config = NULL; - int config_exists; + char *user_config = expand_user_path("~/.gitconfig"); + char *xdg_config = xdg_config_home("config"); + int config_exists = file_exists(user_config) || file_exists(xdg_config); - home_config_paths(&user_config, &xdg_config, "config"); - config_exists = file_exists(user_config) || file_exists(xdg_config); free(user_config); free(xdg_config); @@ -1766,8 +1767,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!transaction || ref_transaction_update(transaction, "HEAD", sha1, current_head - ? current_head->object.sha1 : NULL, - 0, !!current_head, sb.buf, &err) || + ? current_head->object.sha1 : null_sha1, + 0, sb.buf, &err) || ref_transaction_commit(transaction, &err)) { rollback_index_files(); die("%s", err.buf); diff --git a/builtin/config.c b/builtin/config.c index d32c5327e5..7188405f7e 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -193,7 +193,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)) { - fprintf(stderr, "Invalid key pattern: %s\n", key_); + error("invalid key pattern: %s", key_); free(key_regexp); key_regexp = NULL; ret = CONFIG_INVALID_PATTERN; @@ -214,7 +214,7 @@ static int get_value(const char *key_, const char *regex_) regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { - fprintf(stderr, "Invalid pattern: %s\n", regex_); + error("invalid pattern: %s", regex_); free(regexp); regexp = NULL; ret = CONFIG_INVALID_PATTERN; @@ -455,9 +455,9 @@ static char *default_user_config(void) struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, _("# This is Git's per-user configuration file.\n" - "[core]\n" + "[user]\n" "# Please adapt and uncomment the following lines:\n" - "# user = %s\n" + "# name = %s\n" "# email = %s\n"), ident_default_name(), ident_default_email()); @@ -488,10 +488,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) } if (use_global_config) { - char *user_config = NULL; - char *xdg_config = NULL; - - home_config_paths(&user_config, &xdg_config, "config"); + char *user_config = expand_user_path("~/.gitconfig"); + char *xdg_config = xdg_config_home("config"); if (!user_config) /* diff --git a/builtin/count-objects.c b/builtin/count-objects.c index e47ef0b1af..ad0c79954a 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -70,8 +70,10 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) /* we do not take arguments other than flags for now */ if (argc) usage_with_options(count_objects_usage, opts); - if (verbose) + if (verbose) { report_garbage = real_report_garbage; + report_linked_checkout_garbage(); + } for_each_loose_file_in_objdir(get_object_directory(), count_loose, count_cruft, NULL, NULL); diff --git a/builtin/describe.c b/builtin/describe.c index e00a75b121..a36c829e57 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -119,10 +119,10 @@ static void add_to_known_names(const char *path, } } -static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) +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/"); - unsigned char peeled[20]; + struct object_id peeled; int is_annotated, prio; /* Reject anything outside refs/tags/ unless --all */ @@ -134,10 +134,10 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void return 0; /* Is it annotated? */ - if (!peel_ref(path, peeled)) { - is_annotated = !!hashcmp(sha1, peeled); + if (!peel_ref(path, peeled.hash)) { + is_annotated = !!oidcmp(oid, &peeled); } else { - hashcpy(peeled, sha1); + oidcpy(&peeled, oid); is_annotated = 0; } @@ -154,7 +154,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void else prio = 0; - add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1); + add_to_known_names(all ? path + 5 : path + 10, peeled.hash, prio, oid->hash); return 0; } diff --git a/builtin/fetch.c b/builtin/fetch.c index 75a55e590b..8d5b2dba2b 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -179,13 +179,15 @@ static void add_merge_config(struct ref **head, } } -static int add_existing(const char *refname, const unsigned char *sha1, +static int add_existing(const char *refname, const struct object_id *oid, int flag, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; struct string_list_item *item = string_list_insert(list, refname); - item->util = xmalloc(20); - hashcpy(item->util, sha1); + struct object_id *old_oid = xmalloc(sizeof(*old_oid)); + + oidcpy(old_oid, oid); + item->util = old_oid; return 0; } @@ -415,8 +417,10 @@ static int s_update_ref(const char *action, transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, ref->name, ref->new_sha1, - ref->old_sha1, 0, check_old, msg, &err)) + ref_transaction_update(transaction, ref->name, + ref->new_sha1, + check_old ? ref->old_sha1 : NULL, + 0, msg, &err)) goto fail; ret = ref_transaction_commit(transaction, &err); @@ -586,7 +590,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; - char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); + char *url; + const char *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); int want_status; fp = fopen(filename, "a"); @@ -820,7 +825,7 @@ static void check_not_current_branch(struct ref *ref_map) static int truncate_fetch_head(void) { - char *filename = git_path("FETCH_HEAD"); + const char *filename = git_path("FETCH_HEAD"); FILE *fp = fopen(filename, "w"); if (!fp) @@ -910,9 +915,10 @@ static int do_fetch(struct transport *transport, struct string_list_item *peer_item = string_list_lookup(&existing_refs, rm->peer_ref->name); - if (peer_item) - hashcpy(rm->peer_ref->old_sha1, - peer_item->util); + if (peer_item) { + struct object_id *old_oid = peer_item->util; + hashcpy(rm->peer_ref->old_sha1, old_oid->hash); + } } } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 1d962dc569..05f4c26311 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -223,16 +223,14 @@ static void add_branch_desc(struct strbuf *out, const char *name) #define util_as_integral(elem) ((intptr_t)((elem)->util)) -static void record_person(int which, struct string_list *people, - struct commit *commit) +static void record_person_from_buf(int which, struct string_list *people, + const char *buffer) { - const char *buffer; char *name_buf, *name, *name_end; struct string_list_item *elem; const char *field; field = (which == 'a') ? "\nauthor " : "\ncommitter "; - buffer = get_commit_buffer(commit, NULL); name = strstr(buffer, field); if (!name) return; @@ -245,7 +243,6 @@ static void record_person(int which, struct string_list *people, if (name_end < name) return; name_buf = xmemdupz(name, name_end - name + 1); - unuse_commit_buffer(commit, buffer); elem = string_list_lookup(people, name_buf); if (!elem) { @@ -256,6 +253,15 @@ static void record_person(int which, struct string_list *people, free(name_buf); } + +static void record_person(int which, struct string_list *people, + struct commit *commit) +{ + const char *buffer = get_commit_buffer(commit, NULL); + record_person_from_buf(which, people, buffer); + unuse_commit_buffer(commit, buffer); +} + static int cmp_string_list_util_as_integral(const void *a_, const void *b_) { const struct string_list_item *a = a_, *b = b_; diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 19be78a943..cb7db230d3 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -74,6 +74,7 @@ static struct { { "contents:body" }, { "contents:signature" }, { "upstream" }, + { "push" }, { "symref" }, { "flag" }, { "HEAD" }, @@ -178,11 +179,10 @@ static const char *find_next(const char *cp) static int verify_format(const char *format) { const char *cp, *sp; - static const char color_reset[] = "color:reset"; need_color_reset_at_eol = 0; for (cp = format; *cp && (sp = find_next(cp)); ) { - const char *ep = strchr(sp, ')'); + const char *color, *ep = strchr(sp, ')'); int at; if (!ep) @@ -191,8 +191,8 @@ static int verify_format(const char *format) at = parse_atom(sp + 2, ep); cp = ep + 1; - if (starts_with(used_atom[at], "color:")) - need_color_reset_at_eol = !!strcmp(used_atom[at], color_reset); + if (skip_prefix(used_atom[at], "color:", &color)) + need_color_reset_at_eol = !!strcmp(color, "reset"); } return 0; } @@ -660,15 +660,26 @@ static void populate_value(struct refinfo *ref) else if (starts_with(name, "symref")) refname = ref->symref ? ref->symref : ""; else if (starts_with(name, "upstream")) { + const char *branch_name; /* only local branches may have an upstream */ - if (!starts_with(ref->refname, "refs/heads/")) + if (!skip_prefix(ref->refname, "refs/heads/", + &branch_name)) continue; - branch = branch_get(ref->refname + 11); + branch = branch_get(branch_name); - if (!branch || !branch->merge || !branch->merge[0] || - !branch->merge[0]->dst) + refname = branch_get_upstream(branch, NULL); + if (!refname) + continue; + } else if (starts_with(name, "push")) { + const char *branch_name; + if (!skip_prefix(ref->refname, "refs/heads/", + &branch_name)) + continue; + branch = branch_get(branch_name); + + refname = branch_get_push(branch, NULL); + if (!refname) continue; - refname = branch->merge[0]->dst; } else if (starts_with(name, "color:")) { char color[COLOR_MAXLEN] = ""; @@ -714,11 +725,12 @@ static void populate_value(struct refinfo *ref) refname = shorten_unambiguous_ref(refname, warn_ambiguous_refs); else if (!strcmp(formatp, "track") && - starts_with(name, "upstream")) { + (starts_with(name, "upstream") || + starts_with(name, "push"))) { char buf[40]; if (stat_tracking_info(branch, &num_ours, - &num_theirs) != 1) + &num_theirs, NULL)) continue; if (!num_ours && !num_theirs) @@ -736,11 +748,12 @@ static void populate_value(struct refinfo *ref) } continue; } else if (!strcmp(formatp, "trackshort") && - starts_with(name, "upstream")) { + (starts_with(name, "upstream") || + starts_with(name, "push"))) { assert(branch); if (stat_tracking_info(branch, &num_ours, - &num_theirs) != 1) + &num_theirs, NULL)) continue; if (!num_ours && !num_theirs) @@ -841,7 +854,8 @@ struct grab_ref_cbdata { * A call-back given to for_each_ref(). Filter refs and keep them for * later object processing. */ -static int grab_single_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +static int grab_single_ref(const char *refname, const struct object_id *oid, + int flag, void *cb_data) { struct grab_ref_cbdata *cb = cb_data; struct refinfo *ref; @@ -852,6 +866,11 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f return 0; } + if (flag & REF_ISBROKEN) { + warning("ignoring broken ref %s", refname); + return 0; + } + if (*cb->grab_pattern) { const char **pattern; int namelen = strlen(refname); @@ -879,7 +898,7 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f */ ref = xcalloc(1, sizeof(*ref)); ref->refname = xstrdup(refname); - hashcpy(ref->objectname, sha1); + hashcpy(ref->objectname, oid->hash); ref->flag = flag; cnt = cb->grab_cnt; diff --git a/builtin/fsck.c b/builtin/fsck.c index 0c757862e8..2679793049 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -25,7 +25,7 @@ static int include_reflogs = 1; static int check_full = 1; static int check_strict; static int keep_cache_objects; -static unsigned char head_sha1[20]; +static struct object_id head_oid; static const char *head_points_at; static int errors_found; static int write_lost_and_found; @@ -225,12 +225,12 @@ static void check_unreachable_object(struct object *obj) printf("dangling %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1)); if (write_lost_and_found) { - char *filename = git_path("lost-found/%s/%s", + const char *filename = git_path("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", sha1_to_hex(obj->sha1)); FILE *f; - if (safe_create_leading_directories(filename)) { + if (safe_create_leading_directories_const(filename)) { error("Could not create lost-found"); return; } @@ -451,44 +451,52 @@ static void fsck_dir(int i, char *path) static int default_refs; +static void fsck_handle_reflog_sha1(const char *refname, unsigned char *sha1) +{ + struct object *obj; + + if (!is_null_sha1(sha1)) { + obj = lookup_object(sha1); + if (obj) { + obj->used = 1; + mark_object_reachable(obj); + } else { + error("%s: invalid reflog entry %s", refname, sha1_to_hex(sha1)); + errors_found |= ERROR_REACHABLE; + } + } +} + static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { - struct object *obj; + const char *refname = cb_data; if (verbose) fprintf(stderr, "Checking reflog %s->%s\n", sha1_to_hex(osha1), sha1_to_hex(nsha1)); - if (!is_null_sha1(osha1)) { - obj = lookup_object(osha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } - } - obj = lookup_object(nsha1); - if (obj) { - obj->used = 1; - mark_object_reachable(obj); - } + fsck_handle_reflog_sha1(refname, osha1); + fsck_handle_reflog_sha1(refname, nsha1); return 0; } -static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data) +static int fsck_handle_reflog(const char *logname, const struct object_id *oid, + int flag, void *cb_data) { - for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL); + for_each_reflog_ent(logname, fsck_handle_reflog_ent, (void *)logname); return 0; } -static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +static int fsck_handle_ref(const char *refname, const struct object_id *oid, + int flag, void *cb_data) { struct object *obj; - obj = parse_object(sha1); + obj = parse_object(oid->hash); if (!obj) { - error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1)); + error("%s: invalid sha1 pointer %s", refname, oid_to_hex(oid)); errors_found |= ERROR_REACHABLE; /* We'll continue with the rest despite the error.. */ return 0; @@ -504,8 +512,8 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f static void get_default_heads(void) { - if (head_points_at && !is_null_sha1(head_sha1)) - fsck_handle_ref("HEAD", head_sha1, 0, NULL); + if (head_points_at && !is_null_oid(&head_oid)) + fsck_handle_ref("HEAD", &head_oid, 0, NULL); for_each_rawref(fsck_handle_ref, NULL); if (include_reflogs) for_each_reflog(fsck_handle_reflog, NULL); @@ -556,7 +564,7 @@ static int fsck_head_link(void) if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref_unsafe("HEAD", 0, head_sha1, &flag); + head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag); if (!head_points_at) return error("Invalid HEAD"); if (!strcmp(head_points_at, "HEAD")) @@ -565,7 +573,7 @@ static int fsck_head_link(void) else if (!starts_with(head_points_at, "refs/heads/")) return error("HEAD points to something strange (%s)", head_points_at); - if (is_null_sha1(head_sha1)) { + if (is_null_oid(&head_oid)) { if (null_is_error) return error("HEAD: detached HEAD points at nothing"); fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n", diff --git a/builtin/gc.c b/builtin/gc.c index 5c634afc00..4957c39032 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -33,11 +33,13 @@ static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; static int detach_auto = 1; static const char *prune_expire = "2.weeks.ago"; +static const char *prune_worktrees_expire = "3.months.ago"; static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT; static struct argv_array reflog = ARGV_ARRAY_INIT; static struct argv_array repack = ARGV_ARRAY_INIT; 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 char *pidfile; @@ -55,6 +57,17 @@ static void remove_pidfile_on_signal(int signo) raise(signo); } +static void git_config_date_string(const char *key, const char **output) +{ + if (git_config_get_string_const(key, output)) + return; + if (strcmp(*output, "now")) { + unsigned long now = approxidate("now"); + if (approxidate(*output) >= now) + git_die_config(key, _("Invalid %s: '%s'"), key, *output); + } +} + static void gc_config(void) { const char *value; @@ -71,16 +84,8 @@ static void gc_config(void) git_config_get_int("gc.auto", &gc_auto_threshold); git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); git_config_get_bool("gc.autodetach", &detach_auto); - - if (!git_config_get_string_const("gc.pruneexpire", &prune_expire)) { - if (strcmp(prune_expire, "now")) { - unsigned long now = approxidate("now"); - if (approxidate(prune_expire) >= now) { - git_die_config("gc.pruneexpire", _("Invalid gc.pruneexpire: '%s'"), - prune_expire); - } - } - } + git_config_date_string("gc.pruneexpire", &prune_expire); + git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire); git_config(git_default_config, NULL); } @@ -287,7 +292,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL); argv_array_pushl(&repack, "repack", "-d", "-l", NULL); - argv_array_pushl(&prune, "prune", "--expire", NULL ); + argv_array_pushl(&prune, "prune", "--expire", NULL); + argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL); argv_array_pushl(&rerere, "rerere", "gc", NULL); gc_config(); @@ -357,6 +363,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return error(FAILED_RUN, prune.argv[0]); } + if (prune_worktrees_expire) { + argv_array_push(&prune_worktrees, prune_worktrees_expire); + if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD)) + return error(FAILED_RUN, prune_worktrees.argv[0]); + } + if (run_command_v_opt(rerere.argv, RUN_GIT_CMD)) return error(FAILED_RUN, rerere.argv[0]); diff --git a/builtin/grep.c b/builtin/grep.c index 9262b73b6f..d04f4400d9 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -641,7 +641,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "untracked", &untracked, N_("search in both tracked and untracked files")), OPT_SET_INT(0, "exclude-standard", &opt_exclude, - N_("search also in ignored files"), 1), + N_("ignore files specified via '.gitignore'"), 1), OPT_GROUP(""), OPT_BOOL('v', "invert-match", &opt.invert, N_("show non-matching lines")), @@ -738,7 +738,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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)")), - { OPTION_CALLBACK, 0, "help-all", &options, NULL, N_("show usage"), + { OPTION_CALLBACK, 0, "help-all", NULL, NULL, N_("show usage"), PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; @@ -885,7 +885,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } } - if (!show_in_pager) + if (!show_in_pager && !opt.status_only) setup_pager(); if (!use_index && (untracked || cached)) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 207b90c7b1..07fef3cc6b 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -22,10 +22,8 @@ static int hash_literally(unsigned char *sha1, int fd, const char *type, unsigne if (strbuf_read(&buf, fd, 4096) < 0) ret = -1; - else if (flags & HASH_WRITE_OBJECT) - ret = write_sha1_file(buf.buf, buf.len, type, sha1); else - ret = hash_sha1_file(buf.buf, buf.len, type, sha1); + ret = hash_sha1_file_literally(buf.buf, buf.len, type, sha1, flags); strbuf_release(&buf); return ret; } diff --git a/builtin/help.c b/builtin/help.c index 6133fe496b..3422e73079 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -171,7 +171,7 @@ static void exec_man_cmd(const char *cmd, const char *page) { struct strbuf shell_cmd = STRBUF_INIT; strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL); + execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL); warning(_("failed to exec '%s': %s"), cmd, strerror(errno)); } @@ -456,7 +456,7 @@ static void list_common_guides_help(void) int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; - const char *alias; + char *alias; enum help_format parsed_help_format; argc = parse_options(argc, argv, prefix, builtin_help_options, @@ -499,6 +499,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) alias = alias_lookup(argv[0]); if (alias && !is_git_command(argv[0])) { printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias); + free(alias); return 0; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 4632117671..f07bc66ed6 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -18,16 +18,14 @@ static const char index_pack_usage[] = struct object_entry { struct pack_idx_entry idx; unsigned long size; - unsigned int hdr_size; - enum object_type type; - enum object_type real_type; - unsigned delta_depth; - int base_object_no; + unsigned char hdr_size; + signed char type; + signed char real_type; }; -union delta_base { - unsigned char sha1[20]; - off_t offset; +struct object_stat { + unsigned delta_depth; + int base_object_no; }; struct base_data { @@ -49,25 +47,28 @@ struct thread_local { int pack_fd; }; -/* - * Even if sizeof(union delta_base) == 24 on 64-bit archs, we really want - * to memcmp() only the first 20 bytes. - */ -#define UNION_BASE_SZ 20 - #define FLAG_LINK (1u<<20) #define FLAG_CHECKED (1u<<21) -struct delta_entry { - union delta_base base; +struct ofs_delta_entry { + off_t offset; + int obj_no; +}; + +struct ref_delta_entry { + unsigned char sha1[20]; int obj_no; }; static struct object_entry *objects; -static struct delta_entry *deltas; +static struct object_stat *obj_stat; +static struct ofs_delta_entry *ofs_deltas; +static struct ref_delta_entry *ref_deltas; static struct thread_local nothread_data; static int nr_objects; -static int nr_deltas; +static int nr_ofs_deltas; +static int nr_ref_deltas; +static int ref_deltas_alloc; static int nr_resolved_deltas; static int nr_threads; @@ -476,7 +477,8 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size, } static void *unpack_raw_entry(struct object_entry *obj, - union delta_base *delta_base, + off_t *ofs_offset, + unsigned char *ref_sha1, unsigned char *sha1) { unsigned char *p; @@ -505,11 +507,10 @@ static void *unpack_raw_entry(struct object_entry *obj, switch (obj->type) { case OBJ_REF_DELTA: - hashcpy(delta_base->sha1, fill(20)); + hashcpy(ref_sha1, fill(20)); use(20); break; case OBJ_OFS_DELTA: - memset(delta_base, 0, sizeof(*delta_base)); p = fill(1); c = *p; use(1); @@ -523,8 +524,8 @@ static void *unpack_raw_entry(struct object_entry *obj, use(1); base_offset = (base_offset << 7) + (c & 127); } - delta_base->offset = obj->idx.offset - base_offset; - if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset) + *ofs_offset = obj->idx.offset - base_offset; + if (*ofs_offset <= 0 || *ofs_offset >= obj->idx.offset) bad_object(obj->idx.offset, _("delta base offset is out of bound")); break; case OBJ_COMMIT: @@ -608,55 +609,110 @@ static void *get_data_from_pack(struct object_entry *obj) return unpack_data(obj, NULL, NULL); } -static int compare_delta_bases(const union delta_base *base1, - const union delta_base *base2, - enum object_type type1, - enum object_type type2) +static int compare_ofs_delta_bases(off_t offset1, off_t offset2, + enum object_type type1, + enum object_type type2) { int cmp = type1 - type2; if (cmp) return cmp; - return memcmp(base1, base2, UNION_BASE_SZ); + return offset1 < offset2 ? -1 : + offset1 > offset2 ? 1 : + 0; } -static int find_delta(const union delta_base *base, enum object_type type) +static int find_ofs_delta(const off_t offset, enum object_type type) { - int first = 0, last = nr_deltas; - - while (first < last) { - int next = (first + last) / 2; - struct delta_entry *delta = &deltas[next]; - int cmp; - - cmp = compare_delta_bases(base, &delta->base, - type, objects[delta->obj_no].type); - if (!cmp) - return next; - if (cmp < 0) { - last = next; - continue; - } - first = next+1; - } - return -first-1; + int first = 0, last = nr_ofs_deltas; + + while (first < last) { + int next = (first + last) / 2; + struct ofs_delta_entry *delta = &ofs_deltas[next]; + int cmp; + + cmp = compare_ofs_delta_bases(offset, delta->offset, + type, objects[delta->obj_no].type); + if (!cmp) + return next; + if (cmp < 0) { + last = next; + continue; + } + first = next+1; + } + return -first-1; } -static void find_delta_children(const union delta_base *base, - int *first_index, int *last_index, - enum object_type type) +static void find_ofs_delta_children(off_t offset, + int *first_index, int *last_index, + enum object_type type) { - int first = find_delta(base, type); + int first = find_ofs_delta(offset, type); int last = first; - int end = nr_deltas - 1; + int end = nr_ofs_deltas - 1; if (first < 0) { *first_index = 0; *last_index = -1; return; } - while (first > 0 && !memcmp(&deltas[first - 1].base, base, UNION_BASE_SZ)) + while (first > 0 && ofs_deltas[first - 1].offset == offset) --first; - while (last < end && !memcmp(&deltas[last + 1].base, base, UNION_BASE_SZ)) + while (last < end && ofs_deltas[last + 1].offset == offset) + ++last; + *first_index = first; + *last_index = last; +} + +static int compare_ref_delta_bases(const unsigned char *sha1, + const unsigned char *sha2, + enum object_type type1, + enum object_type type2) +{ + int cmp = type1 - type2; + if (cmp) + return cmp; + return hashcmp(sha1, sha2); +} + +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; + struct ref_delta_entry *delta = &ref_deltas[next]; + int cmp; + + cmp = compare_ref_delta_bases(sha1, delta->sha1, + type, objects[delta->obj_no].type); + if (!cmp) + return next; + if (cmp < 0) { + last = next; + continue; + } + first = next+1; + } + return -first-1; +} + +static void find_ref_delta_children(const unsigned char *sha1, + int *first_index, int *last_index, + enum object_type type) +{ + int first = find_ref_delta(sha1, type); + int last = first; + int end = nr_ref_deltas - 1; + + if (first < 0) { + *first_index = 0; + *last_index = -1; + return; + } + while (first > 0 && !hashcmp(ref_deltas[first - 1].sha1, sha1)) + --first; + while (last < end && !hashcmp(ref_deltas[last + 1].sha1, sha1)) ++last; *first_index = first; *last_index = last; @@ -730,7 +786,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, assert(data || obj_entry); read_lock(); - collision_test_needed = has_sha1_file(sha1); + collision_test_needed = has_sha1_file_with_flags(sha1, HAS_SHA1_QUICK); read_unlock(); if (collision_test_needed && !data) { @@ -873,13 +929,15 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data, *delta_data; if (show_stat) { - delta_obj->delta_depth = base->obj->delta_depth + 1; + int i = delta_obj - objects; + int j = base->obj - objects; + obj_stat[i].delta_depth = obj_stat[j].delta_depth + 1; deepest_delta_lock(); - if (deepest_delta < delta_obj->delta_depth) - deepest_delta = delta_obj->delta_depth; + if (deepest_delta < obj_stat[i].delta_depth) + deepest_delta = obj_stat[i].delta_depth; deepest_delta_unlock(); + obj_stat[i].base_object_no = j; } - delta_obj->base_object_no = base->obj - objects; delta_data = get_data_from_pack(delta_obj); base_data = get_base_data(base); result->obj = delta_obj; @@ -902,7 +960,7 @@ static void resolve_delta(struct object_entry *delta_obj, * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched * and return false. */ -static int compare_and_swap_type(enum object_type *type, +static int compare_and_swap_type(signed char *type, enum object_type want, enum object_type set) { @@ -921,16 +979,13 @@ 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) { - union delta_base base_spec; + find_ref_delta_children(base->obj->idx.sha1, + &base->ref_first, &base->ref_last, + OBJ_REF_DELTA); - hashcpy(base_spec.sha1, base->obj->idx.sha1); - find_delta_children(&base_spec, - &base->ref_first, &base->ref_last, OBJ_REF_DELTA); - - memset(&base_spec, 0, sizeof(base_spec)); - base_spec.offset = base->obj->idx.offset; - find_delta_children(&base_spec, - &base->ofs_first, &base->ofs_last, OBJ_OFS_DELTA); + find_ofs_delta_children(base->obj->idx.offset, + &base->ofs_first, &base->ofs_last, + OBJ_OFS_DELTA); if (base->ref_last == -1 && base->ofs_last == -1) { free(base->data); @@ -941,7 +996,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, } if (base->ref_first <= base->ref_last) { - struct object_entry *child = objects + deltas[base->ref_first].obj_no; + struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no; struct base_data *result = alloc_base_data(); if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA, @@ -957,7 +1012,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, } if (base->ofs_first <= base->ofs_last) { - struct object_entry *child = objects + deltas[base->ofs_first].obj_no; + struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no; struct base_data *result = alloc_base_data(); assert(child->real_type == OBJ_OFS_DELTA); @@ -993,15 +1048,22 @@ static void find_unresolved_deltas(struct base_data *base) } } -static int compare_delta_entry(const void *a, const void *b) +static int compare_ofs_delta_entry(const void *a, const void *b) +{ + const struct ofs_delta_entry *delta_a = a; + const struct ofs_delta_entry *delta_b = b; + + return delta_a->offset < delta_b->offset ? -1 : + delta_a->offset > delta_b->offset ? 1 : + 0; +} + +static int compare_ref_delta_entry(const void *a, const void *b) { - const struct delta_entry *delta_a = a; - const struct delta_entry *delta_b = b; + const struct ref_delta_entry *delta_a = a; + const struct ref_delta_entry *delta_b = b; - /* group by type (ref vs ofs) and then by value (sha-1 or offset) */ - return compare_delta_bases(&delta_a->base, &delta_b->base, - objects[delta_a->obj_no].type, - objects[delta_b->obj_no].type); + return hashcmp(delta_a->sha1, delta_b->sha1); } static void resolve_base(struct object_entry *obj) @@ -1047,7 +1109,8 @@ static void *threaded_second_pass(void *data) static void parse_pack_objects(unsigned char *sha1) { int i, nr_delays = 0; - struct delta_entry *delta = deltas; + struct ofs_delta_entry *ofs_delta = ofs_deltas; + unsigned char ref_delta_sha1[20]; struct stat st; if (verbose) @@ -1056,12 +1119,18 @@ static void parse_pack_objects(unsigned char *sha1) nr_objects); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; - void *data = unpack_raw_entry(obj, &delta->base, obj->idx.sha1); + void *data = unpack_raw_entry(obj, &ofs_delta->offset, + ref_delta_sha1, obj->idx.sha1); obj->real_type = obj->type; - if (is_delta_type(obj->type)) { - nr_deltas++; - delta->obj_no = i; - delta++; + if (obj->type == OBJ_OFS_DELTA) { + nr_ofs_deltas++; + ofs_delta->obj_no = i; + 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); + ref_deltas[nr_ref_deltas].obj_no = i; + nr_ref_deltas++; } else if (!data) { /* large blobs, check later */ obj->real_type = OBJ_BAD; @@ -1112,15 +1181,18 @@ static void resolve_deltas(void) { int i; - if (!nr_deltas) + if (!nr_ofs_deltas && !nr_ref_deltas) return; /* Sort deltas by base SHA1/offset for fast searching */ - qsort(deltas, nr_deltas, sizeof(struct delta_entry), - compare_delta_entry); + qsort(ofs_deltas, nr_ofs_deltas, sizeof(struct ofs_delta_entry), + compare_ofs_delta_entry); + qsort(ref_deltas, nr_ref_deltas, sizeof(struct ref_delta_entry), + compare_ref_delta_entry); if (verbose) - progress = start_progress(_("Resolving deltas"), nr_deltas); + progress = start_progress(_("Resolving deltas"), + nr_ref_deltas + nr_ofs_deltas); #ifndef NO_PTHREADS nr_dispatched = 0; @@ -1155,10 +1227,10 @@ static void resolve_deltas(void) * - append objects to convert thin pack to full pack if required * - write the final 20-byte SHA-1 */ -static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved); +static void fix_unresolved_deltas(struct sha1file *f); static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned char *pack_sha1) { - if (nr_deltas == nr_resolved_deltas) { + if (nr_ref_deltas + nr_ofs_deltas == nr_resolved_deltas) { stop_progress(&progress); /* Flush remaining pack final 20-byte SHA1. */ flush(); @@ -1169,7 +1241,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha struct sha1file *f; unsigned char read_sha1[20], tail_sha1[20]; struct strbuf msg = STRBUF_INIT; - int nr_unresolved = nr_deltas - nr_resolved_deltas; + int nr_unresolved = nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas; int nr_objects_initial = nr_objects; if (nr_unresolved <= 0) die(_("confusion beyond insanity")); @@ -1177,7 +1249,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha memset(objects + nr_objects + 1, 0, nr_unresolved * sizeof(*objects)); f = sha1fd(output_fd, curr_pack); - fix_unresolved_deltas(f, nr_unresolved); + fix_unresolved_deltas(f); strbuf_addf(&msg, _("completed with %d local objects"), nr_objects - nr_objects_initial); stop_progress_msg(&progress, msg.buf); @@ -1191,11 +1263,11 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha die(_("Unexpected tail checksum for %s " "(disk corruption?)"), curr_pack); } - if (nr_deltas != nr_resolved_deltas) + if (nr_ofs_deltas + nr_ref_deltas != nr_resolved_deltas) die(Q_("pack has %d unresolved delta", "pack has %d unresolved deltas", - nr_deltas - nr_resolved_deltas), - nr_deltas - nr_resolved_deltas); + nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas), + nr_ofs_deltas + nr_ref_deltas - nr_resolved_deltas); } static int write_compressed(struct sha1file *f, void *in, unsigned int size) @@ -1204,7 +1276,6 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size) int status; unsigned char outbuf[4096]; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, zlib_compression_level); stream.next_in = in; stream.avail_in = size; @@ -1255,15 +1326,15 @@ static struct object_entry *append_obj_to_pack(struct sha1file *f, static int delta_pos_compare(const void *_a, const void *_b) { - struct delta_entry *a = *(struct delta_entry **)_a; - struct delta_entry *b = *(struct delta_entry **)_b; + struct ref_delta_entry *a = *(struct ref_delta_entry **)_a; + struct ref_delta_entry *b = *(struct ref_delta_entry **)_b; return a->obj_no - b->obj_no; } -static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) +static void fix_unresolved_deltas(struct sha1file *f) { - struct delta_entry **sorted_by_pos; - int i, n = 0; + struct ref_delta_entry **sorted_by_pos; + int i; /* * Since many unresolved deltas may well be themselves base objects @@ -1275,29 +1346,26 @@ static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) * before deltas depending on them, a good heuristic is to start * resolving deltas in the same order as their position in the pack. */ - sorted_by_pos = xmalloc(nr_unresolved * sizeof(*sorted_by_pos)); - for (i = 0; i < nr_deltas; i++) { - if (objects[deltas[i].obj_no].real_type != OBJ_REF_DELTA) - continue; - sorted_by_pos[n++] = &deltas[i]; - } - qsort(sorted_by_pos, n, sizeof(*sorted_by_pos), delta_pos_compare); + sorted_by_pos = xmalloc(nr_ref_deltas * sizeof(*sorted_by_pos)); + for (i = 0; i < nr_ref_deltas; i++) + sorted_by_pos[i] = &ref_deltas[i]; + qsort(sorted_by_pos, nr_ref_deltas, sizeof(*sorted_by_pos), delta_pos_compare); - for (i = 0; i < n; i++) { - struct delta_entry *d = sorted_by_pos[i]; + for (i = 0; i < nr_ref_deltas; i++) { + struct ref_delta_entry *d = sorted_by_pos[i]; enum object_type type; struct base_data *base_obj = alloc_base_data(); if (objects[d->obj_no].real_type != OBJ_REF_DELTA) continue; - base_obj->data = read_sha1_file(d->base.sha1, &type, &base_obj->size); + base_obj->data = read_sha1_file(d->sha1, &type, &base_obj->size); if (!base_obj->data) continue; - if (check_sha1_signature(d->base.sha1, base_obj->data, + if (check_sha1_signature(d->sha1, base_obj->data, base_obj->size, typename(type))) - die(_("local object %s is corrupt"), sha1_to_hex(d->base.sha1)); - base_obj->obj = append_obj_to_pack(f, d->base.sha1, + 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); find_unresolved_deltas(base_obj); display_progress(progress, nr_resolved_deltas); @@ -1489,7 +1557,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) static void show_pack_info(int stat_only) { - int i, baseobjects = nr_objects - nr_deltas; + int i, baseobjects = nr_objects - nr_ref_deltas - nr_ofs_deltas; unsigned long *chain_histogram = NULL; if (deepest_delta) @@ -1499,7 +1567,7 @@ static void show_pack_info(int stat_only) struct object_entry *obj = &objects[i]; if (is_delta_type(obj->type)) - chain_histogram[obj->delta_depth - 1]++; + chain_histogram[obj_stat[i].delta_depth - 1]++; if (stat_only) continue; printf("%s %-6s %lu %lu %"PRIuMAX, @@ -1508,8 +1576,8 @@ static void show_pack_info(int stat_only) (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->base_object_no]; - printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1)); + 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)); } putchar('\n'); } @@ -1672,11 +1740,14 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) curr_pack = open_pack_file(pack_name); parse_pack_header(); objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); - deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); + if (show_stat) + obj_stat = xcalloc(nr_objects + 1, sizeof(struct object_stat)); + ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry)); parse_pack_objects(pack_sha1); resolve_deltas(); conclude_pack(fix_thin_pack, curr_pack, pack_sha1); - free(deltas); + free(ofs_deltas); + free(ref_deltas); if (strict) foreign_nr = check_objects(); diff --git a/builtin/init-db.c b/builtin/init-db.c index 6723d39c3b..4335738135 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -182,6 +182,20 @@ static int git_init_db_config(const char *k, const char *v, void *cb) return 0; } +/* + * If the git_dir is not directly inside the working tree, then git will not + * find it by default, and we need to set the worktree explicitly. + */ +static int needs_work_tree_config(const char *git_dir, const char *work_tree) +{ + if (!strcmp(work_tree, "/") && !strcmp(git_dir, "/.git")) + return 0; + if (skip_prefix(git_dir, work_tree, &git_dir) && + !strcmp(git_dir, "/.git")) + return 0; + return 1; +} + static int create_default_files(const char *template_path) { const char *git_dir = get_git_dir(); @@ -274,10 +288,8 @@ static int create_default_files(const char *template_path) /* allow template config file to override the default */ if (log_all_ref_updates == -1) git_config_set("core.logallrefupdates", "true"); - if (!starts_with(git_dir, work_tree) || - strcmp(git_dir + strlen(work_tree), "/.git")) { + if (needs_work_tree_config(git_dir, work_tree)) git_config_set("core.worktree", work_tree); - } } if (!reinit) { @@ -350,7 +362,6 @@ int set_git_dir_init(const char *git_dir, const char *real_git_dir, static void separate_git_dir(const char *git_dir) { struct stat st; - FILE *fp; if (!stat(git_link, &st)) { const char *src; @@ -366,11 +377,7 @@ static void separate_git_dir(const char *git_dir) die_errno(_("unable to move %s to %s"), src, git_dir); } - fp = fopen(git_link, "w"); - if (!fp) - die(_("Could not create git link %s"), git_link); - fprintf(fp, "gitdir: %s\n", git_dir); - fclose(fp); + write_file(git_link, 1, "gitdir: %s\n", git_dir); } int init_db(const char *template_dir, unsigned int flags) diff --git a/builtin/log.c b/builtin/log.c index dd8f3fcfc4..878104943f 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -38,7 +38,7 @@ static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; static const char * const builtin_log_usage[] = { - N_("git log [<options>] [<revision range>] [[--] <path>...]"), + N_("git log [<options>] [<revision-range>] [[--] <path>...]"), N_("git show [<options>] <object>..."), NULL }; @@ -795,7 +795,7 @@ static int reopen_stdout(struct commit *commit, const char *subject, static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) { struct rev_info check_rev; - struct commit *commit; + struct commit *commit, *c1, *c2; struct object *o1, *o2; unsigned flags1, flags2; @@ -803,9 +803,11 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) die(_("Need exactly one range.")); o1 = rev->pending.objects[0].item; - flags1 = o1->flags; o2 = rev->pending.objects[1].item; + flags1 = o1->flags; flags2 = o2->flags; + c1 = lookup_commit_reference(o1->sha1); + c2 = lookup_commit_reference(o2->sha1); if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING)) die(_("Not a range.")); @@ -827,10 +829,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids) } /* reset for next revision walk */ - clear_commit_marks((struct commit *)o1, - SEEN | UNINTERESTING | SHOWN | ADDED); - clear_commit_marks((struct commit *)o2, - SEEN | UNINTERESTING | SHOWN | ADDED); + clear_commit_marks(c1, SEEN | UNINTERESTING | SHOWN | ADDED); + clear_commit_marks(c2, SEEN | UNINTERESTING | SHOWN | ADDED); o1->flags = flags1; o2->flags = flags2; } @@ -1632,16 +1632,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) break; default: current_branch = branch_get(NULL); - if (!current_branch || !current_branch->merge - || !current_branch->merge[0] - || !current_branch->merge[0]->dst) { + upstream = branch_get_upstream(current_branch, NULL); + if (!upstream) { fprintf(stderr, _("Could not find a tracked" " remote branch, please" " specify <upstream> manually.\n")); usage_with_options(cherry_usage, options); } - - upstream = current_branch->merge[0]->dst; } init_revisions(&revs, prefix); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 914054d367..6fa2205734 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -354,49 +354,6 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) } } -int report_path_error(const char *ps_matched, - const struct pathspec *pathspec, - const char *prefix) -{ - /* - * Make sure all pathspec matched; otherwise it is an error. - */ - struct strbuf sb = STRBUF_INIT; - int num, errors = 0; - for (num = 0; num < pathspec->nr; num++) { - int other, found_dup; - - if (ps_matched[num]) - continue; - /* - * The caller might have fed identical pathspec - * twice. Do not barf on such a mistake. - * FIXME: parse_pathspec should have eliminated - * duplicate pathspec. - */ - for (found_dup = other = 0; - !found_dup && other < pathspec->nr; - other++) { - if (other == num || !ps_matched[other]) - continue; - if (!strcmp(pathspec->items[other].original, - pathspec->items[num].original)) - /* - * Ok, we have a match already. - */ - found_dup = 1; - } - if (found_dup) - continue; - - error("pathspec '%s' did not match any file(s) known to git.", - pathspec->items[num].original); - errors++; - } - strbuf_release(&sb); - return errors; -} - static const char * const ls_files_usage[] = { N_("git ls-files [<options>] [<file>...]"), NULL diff --git a/builtin/merge.c b/builtin/merge.c index 3b0f8f96d4..85c54dcd5a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -492,8 +492,7 @@ static void merge_name(const char *remote, struct strbuf *msg) } if (len) { struct strbuf truname = STRBUF_INIT; - strbuf_addstr(&truname, "refs/heads/"); - strbuf_addstr(&truname, remote); + strbuf_addf(&truname, "refs/heads/%s", remote); strbuf_setlen(&truname, truname.len - len); if (ref_exists(truname.buf)) { strbuf_addf(msg, @@ -504,28 +503,7 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_release(&truname); goto cleanup; } - } - - if (!strcmp(remote, "FETCH_HEAD") && - !access(git_path("FETCH_HEAD"), R_OK)) { - const char *filename; - FILE *fp; - struct strbuf line = STRBUF_INIT; - char *ptr; - - filename = git_path("FETCH_HEAD"); - fp = fopen(filename, "r"); - if (!fp) - die_errno(_("could not open '%s' for reading"), - filename); - strbuf_getline(&line, fp, '\n'); - fclose(fp); - ptr = strstr(line.buf, "\tnot-for-merge\t"); - if (ptr) - strbuf_remove(&line, ptr-line.buf+1, 13); - strbuf_addbuf(msg, &line); - strbuf_release(&line); - goto cleanup; + strbuf_release(&truname); } if (remote_head->util) { @@ -955,7 +933,7 @@ static int setup_with_upstream(const char ***argv) if (!branch) die(_("No current branch.")); - if (!branch->remote) + if (!branch->remote_name) die(_("No remote for the current branch.")); if (!branch->merge_nr) die(_("No default upstream defined for the current branch.")); @@ -1037,28 +1015,24 @@ static int default_edit_option(void) st_stdin.st_mode == st_stdout.st_mode); } -static struct commit_list *collect_parents(struct commit *head_commit, - int *head_subsumed, - int argc, const char **argv) +static struct commit_list *reduce_parents(struct commit *head_commit, + int *head_subsumed, + struct commit_list *remoteheads) { - int i; - struct commit_list *remoteheads = NULL, *parents, *next; - struct commit_list **remotes = &remoteheads; + struct commit_list *parents, *next, **remotes = &remoteheads; - if (head_commit) - remotes = &commit_list_insert(head_commit, remotes)->next; - for (i = 0; i < argc; i++) { - struct commit *commit = get_merge_parent(argv[i]); - if (!commit) - help_unknown_ref(argv[i], "merge", - "not something we can merge"); - remotes = &commit_list_insert(commit, remotes)->next; - } - *remotes = NULL; + /* + * Is the current HEAD reachable from another commit being + * merged? If so we do not want to record it as a parent of + * the resulting merge, unless --no-ff is given. We will flip + * this variable to 0 when we find HEAD among the independent + * tips being merged. + */ + *head_subsumed = 1; + /* Find what parents to record by checking independent ones. */ parents = reduce_heads(remoteheads); - *head_subsumed = 1; /* we will flip this to 0 when we find it */ for (remoteheads = NULL, remotes = &remoteheads; parents; parents = next) { @@ -1068,7 +1042,119 @@ static struct commit_list *collect_parents(struct commit *head_commit, *head_subsumed = 0; else remotes = &commit_list_insert(commit, remotes)->next; + free(parents); + } + return remoteheads; +} + +static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *merge_msg) +{ + struct fmt_merge_msg_opts opts; + + memset(&opts, 0, sizeof(opts)); + opts.add_title = !have_message; + opts.shortlog_len = shortlog_len; + opts.credit_people = (0 < option_edit); + + fmt_merge_msg(merge_names, merge_msg, &opts); + if (merge_msg->len) + strbuf_setlen(merge_msg, merge_msg->len - 1); +} + +static void handle_fetch_head(struct commit_list **remotes, struct strbuf *merge_names) +{ + const char *filename; + int fd, pos, npos; + struct strbuf fetch_head_file = STRBUF_INIT; + + if (!merge_names) + merge_names = &fetch_head_file; + + filename = git_path("FETCH_HEAD"); + fd = open(filename, O_RDONLY); + if (fd < 0) + die_errno(_("could not open '%s' for reading"), filename); + + if (strbuf_read(merge_names, fd, 0) < 0) + die_errno(_("could not read '%s'"), filename); + if (close(fd) < 0) + die_errno(_("could not close '%s'"), filename); + + for (pos = 0; pos < merge_names->len; pos = npos) { + unsigned char sha1[20]; + char *ptr; + struct commit *commit; + + ptr = strchr(merge_names->buf + pos, '\n'); + if (ptr) + npos = ptr - merge_names->buf + 1; + else + npos = merge_names->len; + + if (npos - pos < 40 + 2 || + get_sha1_hex(merge_names->buf + pos, sha1)) + commit = NULL; /* bad */ + else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2)) + continue; /* not-for-merge */ + else { + char saved = merge_names->buf[pos + 40]; + merge_names->buf[pos + 40] = '\0'; + commit = get_merge_parent(merge_names->buf + pos); + merge_names->buf[pos + 40] = saved; + } + if (!commit) { + if (ptr) + *ptr = '\0'; + die("not something we can merge in %s: %s", + filename, merge_names->buf + pos); + } + remotes = &commit_list_insert(commit, remotes)->next; + } + + if (merge_names == &fetch_head_file) + strbuf_release(&fetch_head_file); +} + +static struct commit_list *collect_parents(struct commit *head_commit, + int *head_subsumed, + int argc, const char **argv, + struct strbuf *merge_msg) +{ + int i; + struct commit_list *remoteheads = NULL; + struct commit_list **remotes = &remoteheads; + struct strbuf merge_names = STRBUF_INIT, *autogen = NULL; + + if (merge_msg && (!have_message || shortlog_len)) + autogen = &merge_names; + + if (head_commit) + remotes = &commit_list_insert(head_commit, remotes)->next; + + if (argc == 1 && !strcmp(argv[0], "FETCH_HEAD")) { + handle_fetch_head(remotes, autogen); + remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads); + } else { + for (i = 0; i < argc; i++) { + struct commit *commit = get_merge_parent(argv[i]); + if (!commit) + help_unknown_ref(argv[i], "merge", + "not something we can merge"); + remotes = &commit_list_insert(commit, remotes)->next; + } + remoteheads = reduce_parents(head_commit, head_subsumed, remoteheads); + if (autogen) { + struct commit_list *p; + for (p = remoteheads; p; p = p->next) + merge_name(merge_remote_util(p->item)->name, autogen); + } } + + if (autogen) { + prepare_merge_message(autogen, merge_msg); + strbuf_release(autogen); + } + return remoteheads; } @@ -1158,61 +1244,62 @@ int cmd_merge(int argc, const char **argv, const char *prefix) option_commit = 0; } - if (!abort_current_merge) { - if (!argc) { - if (default_to_upstream) - argc = setup_with_upstream(&argv); - else - die(_("No commit specified and merge.defaultToUpstream not set.")); - } else if (argc == 1 && !strcmp(argv[0], "-")) - argv[0] = "@{-1}"; + if (!argc) { + if (default_to_upstream) + argc = setup_with_upstream(&argv); + else + die(_("No commit specified and merge.defaultToUpstream not set.")); + } else if (argc == 1 && !strcmp(argv[0], "-")) { + argv[0] = "@{-1}"; } + if (!argc) usage_with_options(builtin_merge_usage, builtin_merge_options); - /* - * This could be traditional "merge <msg> HEAD <commit>..." and - * the way we can tell it is to see if the second token is HEAD, - * but some people might have misused the interface and used a - * commit-ish that is the same as HEAD there instead. - * Traditional format never would have "-m" so it is an - * additional safety measure to check for it. - */ - - if (!have_message && head_commit && - is_old_style_invocation(argc, argv, head_commit->object.sha1)) { - strbuf_addstr(&merge_msg, argv[0]); - head_arg = argv[1]; - argv += 2; - argc -= 2; - remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); - } else if (!head_commit) { + if (!head_commit) { struct commit *remote_head; /* * If the merged head is a valid one there is no reason * to forbid "git merge" into a branch yet to be born. * We do the same for "git pull". */ - if (argc != 1) - die(_("Can merge only exactly one commit into " - "empty head")); if (squash) die(_("Squash commit into empty head not supported yet")); if (fast_forward == FF_NO) die(_("Non-fast-forward commit does not make sense into " "an empty head")); - remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); + remoteheads = collect_parents(head_commit, &head_subsumed, + argc, argv, NULL); remote_head = remoteheads->item; if (!remote_head) die(_("%s - not something we can merge"), argv[0]); + if (remoteheads->next) + die(_("Can merge only exactly one commit into empty head")); read_empty(remote_head->object.sha1, 0); update_ref("initial pull", "HEAD", remote_head->object.sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; - } else { - struct strbuf merge_names = STRBUF_INIT; + } + /* + * This could be traditional "merge <msg> HEAD <commit>..." and + * the way we can tell it is to see if the second token is HEAD, + * but some people might have misused the interface and used a + * commit-ish that is the same as HEAD there instead. + * Traditional format never would have "-m" so it is an + * additional safety measure to check for it. + */ + if (!have_message && + is_old_style_invocation(argc, argv, head_commit->object.sha1)) { + warning("old-style 'git merge <msg> HEAD <commit>' is deprecated."); + strbuf_addstr(&merge_msg, argv[0]); + head_arg = argv[1]; + argv += 2; + argc -= 2; + remoteheads = collect_parents(head_commit, &head_subsumed, + argc, argv, NULL); + } else { /* We are invoked directly as the first-class UI. */ head_arg = "HEAD"; @@ -1221,21 +1308,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * the standard merge summary message to be appended * to the given message. */ - remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv); - for (p = remoteheads; p; p = p->next) - merge_name(merge_remote_util(p->item)->name, &merge_names); - - if (!have_message || shortlog_len) { - struct fmt_merge_msg_opts opts; - memset(&opts, 0, sizeof(opts)); - opts.add_title = !have_message; - opts.shortlog_len = shortlog_len; - opts.credit_people = (0 < option_edit); - - fmt_merge_msg(&merge_names, &merge_msg, &opts); - if (merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len - 1); - } + remoteheads = collect_parents(head_commit, &head_subsumed, + argc, argv, &merge_msg); } if (!head_commit || !argc) diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 9736d4452f..248a3eb260 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -138,9 +138,9 @@ static int tipcmp(const void *a_, const void *b_) return hashcmp(a->sha1, b->sha1); } -static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) +static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data) { - struct object *o = parse_object(sha1); + struct object *o = parse_object(oid->hash); struct name_ref_data *data = cb_data; int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; @@ -160,7 +160,7 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void } } - add_to_tip_table(sha1, path, can_abbreviate_output); + add_to_tip_table(oid->hash, path, can_abbreviate_output); while (o && o->type == OBJ_TAG) { struct tag *t = (struct tag *) o; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index d8165878e1..80fe8c7dc1 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -125,7 +125,6 @@ static unsigned long do_compress(void **pptr, unsigned long size) void *in, *out; unsigned long maxsize; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, pack_compression_level); maxsize = git_deflate_bound(&stream, size); @@ -153,7 +152,6 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct sha1fi unsigned char obuf[1024 * 16]; unsigned long olen = 0; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, pack_compression_level); for (;;) { @@ -542,11 +540,11 @@ static enum write_one_status write_one(struct sha1file *f, return WRITE_ONE_WRITTEN; } -static int mark_tagged(const char *path, const unsigned char *sha1, int flag, +static int mark_tagged(const char *path, const struct object_id *oid, int flag, void *cb_data) { unsigned char peeled[20]; - struct object_entry *entry = packlist_find(&to_pack, sha1, NULL); + struct object_entry *entry = packlist_find(&to_pack, oid->hash, NULL); if (entry) entry->tagged = 1; @@ -963,10 +961,8 @@ static int want_object_in_pack(const unsigned char *sha1, off_t offset = find_pack_entry_one(sha1, p); if (offset) { if (!*found_pack) { - if (!is_pack_valid(p)) { - warning("packfile %s cannot be accessed", p->pack_name); + if (!is_pack_valid(p)) continue; - } *found_offset = offset; *found_pack = p; } @@ -2101,14 +2097,14 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, #define ll_find_deltas(l, s, w, d, p) find_deltas(l, &s, w, d, p) #endif -static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, void *cb_data) +static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data) { - unsigned char peeled[20]; + struct object_id peeled; if (starts_with(path, "refs/tags/") && /* is a tag? */ - !peel_ref(path, peeled) && /* peelable? */ - packlist_find(&to_pack, peeled, NULL)) /* object packed? */ - add_object_entry(sha1, OBJ_TAG, NULL, 0); + !peel_ref(path, peeled.hash) && /* peelable? */ + packlist_find(&to_pack, peeled.hash, NULL)) /* object packed? */ + add_object_entry(oid->hash, OBJ_TAG, NULL, 0); return 0; } diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 77db8739b5..ba34dac4d2 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -1,14 +1,14 @@ #include "builtin.h" -static void flush_current_id(int patchlen, unsigned char *id, unsigned char *result) +static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result) { char name[50]; if (!patchlen) return; - memcpy(name, sha1_to_hex(id), 41); - printf("%s %s\n", sha1_to_hex(result), name); + memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1); + printf("%s %s\n", oid_to_hex(result), name); } static int remove_space(char *line) @@ -53,23 +53,23 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) return 1; } -static void flush_one_hunk(unsigned char *result, git_SHA_CTX *ctx) +static void flush_one_hunk(struct object_id *result, git_SHA_CTX *ctx) { - unsigned char hash[20]; + unsigned char hash[GIT_SHA1_RAWSZ]; unsigned short carry = 0; int i; git_SHA1_Final(hash, ctx); git_SHA1_Init(ctx); /* 20-byte sum, with carry */ - for (i = 0; i < 20; ++i) { - carry += result[i] + hash[i]; - result[i] = carry; + for (i = 0; i < GIT_SHA1_RAWSZ; ++i) { + carry += result->hash[i] + hash[i]; + result->hash[i] = carry; carry >>= 8; } } -static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, +static int get_one_patchid(struct object_id *next_oid, struct object_id *result, struct strbuf *line_buf, int stable) { int patchlen = 0, found_next = 0; @@ -77,7 +77,7 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, git_SHA_CTX ctx; git_SHA1_Init(&ctx); - hashclr(result); + oidclr(result); while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { char *line = line_buf->buf; @@ -93,7 +93,7 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, else if (!memcmp(line, "\\ ", 2) && 12 < strlen(line)) continue; - if (!get_sha1_hex(p, next_sha1)) { + if (!get_oid_hex(p, next_oid)) { found_next = 1; break; } @@ -143,7 +143,7 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, } if (!found_next) - hashclr(next_sha1); + oidclr(next_oid); flush_one_hunk(result, &ctx); @@ -152,15 +152,15 @@ static int get_one_patchid(unsigned char *next_sha1, unsigned char *result, static void generate_id_list(int stable) { - unsigned char sha1[20], n[20], result[20]; + struct object_id oid, n, result; int patchlen; struct strbuf line_buf = STRBUF_INIT; - hashclr(sha1); + oidclr(&oid); while (!feof(stdin)) { - patchlen = get_one_patchid(n, result, &line_buf, stable); - flush_current_id(patchlen, sha1, result); - hashcpy(sha1, n); + patchlen = get_one_patchid(&n, &result, &line_buf, stable); + flush_current_id(patchlen, &oid, &result); + oidcpy(&oid, &n); } strbuf_release(&line_buf); } diff --git a/builtin/prune.c b/builtin/prune.c index 04d3b12ae4..10b03d3e4c 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -6,7 +6,6 @@ #include "reachable.h" #include "parse-options.h" #include "progress.h" -#include "dir.h" static const char * const prune_usage[] = { N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"), @@ -115,9 +114,11 @@ int cmd_prune(int argc, const char **argv, const char *prefix) expire = ULONG_MAX; save_commit_buffer = 0; check_replace_refs = 0; + ref_paranoia = 1; init_revisions(&revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); + while (argc--) { unsigned char sha1[20]; const char *name = *argv++; diff --git a/builtin/push.c b/builtin/push.c index fc771a9f6f..57c138bd7b 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -473,13 +473,22 @@ static int option_parse_recurse_submodules(const struct option *opt, static int git_push_config(const char *k, const char *v, void *cb) { - struct wt_status *s = cb; + int *flags = cb; int status; status = git_gpg_config(k, v, NULL); if (status) return status; - return git_default_config(k, v, s); + + if (!strcmp(k, "push.followtags")) { + if (git_config_bool(k, v)) + *flags |= TRANSPORT_PUSH_FOLLOW_TAGS; + else + *flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS; + return 0; + } + + return git_default_config(k, v, NULL); } int cmd_push(int argc, const char **argv, const char *prefix) @@ -487,7 +496,6 @@ int cmd_push(int argc, const char **argv, const char *prefix) int flags = 0; int tags = 0; int rc; - int atomic = 0; const char *repo = NULL; /* default repository */ struct option options[] = { OPT__VERBOSITY(&verbosity), @@ -519,12 +527,12 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), TRANSPORT_PUSH_FOLLOW_TAGS), OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT), - OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")), + OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC), OPT_END() }; packet_trace_identity("push"); - git_config(git_push_config, NULL); + git_config(git_push_config, &flags); argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) @@ -535,9 +543,6 @@ int cmd_push(int argc, const char **argv, const char *prefix) if (tags) add_refspec("refs/tags/*"); - if (atomic) - flags |= TRANSPORT_PUSH_ATOMIC; - if (argc > 0) { repo = argv[0]; set_refspecs(argv + 1, argc - 1, repo); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index e0ce78e5a0..04cb5a1a06 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -197,7 +197,7 @@ static void show_ref(const char *path, const unsigned char *sha1) } } -static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused) +static int show_ref_cb(const char *path, const struct object_id *oid, int flag, void *unused) { path = strip_namespace(path); /* @@ -210,7 +210,7 @@ static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, vo */ if (!path) path = ".have"; - show_ref(path, sha1); + show_ref(path, oid->hash); return 0; } @@ -228,6 +228,7 @@ static void collect_one_alternate_ref(const struct ref *ref, void *data) static void write_head_info(void) { struct sha1_array sa = SHA1_ARRAY_INIT; + for_each_alternate_ref(collect_one_alternate_ref, &sa); sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL); sha1_array_clear(&sa); @@ -743,6 +744,22 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) return 0; } +/* + * NEEDSWORK: we should consolidate various implementions of "are we + * on an unborn branch?" test into one, and make the unified one more + * robust. !get_sha1() based check used here and elsewhere would not + * allow us to tell an unborn branch from corrupt ref, for example. + * For the purpose of fixing "deploy-to-update does not work when + * pushing into an empty repository" issue, this should suffice for + * now. + */ +static int head_has_history(void) +{ + unsigned char sha1[20]; + + return !get_sha1("HEAD", sha1); +} + static const char *push_to_deploy(unsigned char *sha1, struct argv_array *env, const char *work_tree) @@ -755,7 +772,7 @@ static const char *push_to_deploy(unsigned char *sha1, }; const char *diff_index[] = { "diff-index", "--quiet", "--cached", "--ignore-submodules", - "HEAD", "--", NULL + NULL, "--", NULL }; const char *read_tree[] = { "read-tree", "-u", "-m", NULL, NULL @@ -782,6 +799,9 @@ static const char *push_to_deploy(unsigned char *sha1, if (run_command(&child)) return "Working directory has unstaged changes"; + /* diff-index with either HEAD or an empty tree */ + diff_index[4] = head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX; + child_process_init(&child); child.argv = diff_index; child.env = env->argv; @@ -891,7 +911,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) return "deletion prohibited"; } - if (!strcmp(namespaced_name, head_name)) { + if (head_name && !strcmp(namespaced_name, head_name)) { switch (deny_delete_current) { case DENY_IGNORE: break; @@ -953,8 +973,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) if (ref_transaction_delete(transaction, namespaced_name, old_sha1, - 0, old_sha1 != NULL, - "push", &err)) { + 0, "push", &err)) { rp_error("%s", err.buf); strbuf_release(&err); return "failed to delete"; @@ -971,7 +990,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) if (ref_transaction_update(transaction, namespaced_name, new_sha1, old_sha1, - 0, 1, "push", + 0, "push", &err)) { rp_error("%s", err.buf); strbuf_release(&err); @@ -990,7 +1009,7 @@ static void run_update_post_hook(struct command *commands) int argc; const char **argv; struct child_process proc = CHILD_PROCESS_INIT; - char *hook; + const char *hook; hook = find_hook("post-update"); for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { diff --git a/builtin/reflog.c b/builtin/reflog.c index 49c64f96d8..c2eb8ff840 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -8,14 +8,11 @@ #include "revision.h" #include "reachable.h" -/* - * reflog expire - */ - +/* NEEDSWORK: switch to using parse_options */ static const char reflog_expire_usage[] = -"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; +"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>..."; static const char reflog_delete_usage[] = -"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>..."; +"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>..."; static unsigned long default_reflog_expire; static unsigned long default_reflog_expire_unreachable; @@ -316,14 +313,14 @@ static int should_expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, return 0; } -static int push_tip_to_list(const char *refname, const unsigned char *sha1, +static int push_tip_to_list(const char *refname, const struct object_id *oid, int flags, void *cb_data) { struct commit_list **list = cb_data; struct commit *tip_commit; if (flags & REF_ISSYMREF) return 0; - tip_commit = lookup_commit_reference_gently(sha1, 1); + tip_commit = lookup_commit_reference_gently(oid->hash, 1); if (!tip_commit) return 0; commit_list_insert(tip_commit, list); @@ -355,6 +352,7 @@ static void reflog_expiry_prepare(const char *refname, if (cb->unreachable_expire_kind != UE_ALWAYS) { if (cb->unreachable_expire_kind == UE_HEAD) { struct commit_list *elem; + for_each_ref(push_tip_to_list, &cb->tips); for (elem = cb->tips; elem; elem = elem->next) commit_list_insert(elem->item, &cb->mark_list); @@ -382,14 +380,14 @@ static void reflog_expiry_cleanup(void *cb_data) } } -static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) +static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data) { struct collected_reflog *e; struct collect_reflog_cb *cb = cb_data; size_t namelen = strlen(ref); e = xmalloc(sizeof(*e) + namelen + 1); - hashcpy(e->sha1, sha1); + hashcpy(e->sha1, oid->hash); memcpy(e->reflog, ref, namelen + 1); ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc); cb->e[cb->nr++] = e; diff --git a/builtin/remote.c b/builtin/remote.c index 5d3ab906bc..f4a6ec9f13 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -509,11 +509,10 @@ struct branches_for_remote { }; static int add_branch_for_removal(const char *refname, - const unsigned char *sha1, int flags, void *cb_data) + const struct object_id *oid, int flags, void *cb_data) { struct branches_for_remote *branches = cb_data; struct refspec refspec; - struct string_list_item *item; struct known_remote *kr; memset(&refspec, 0, sizeof(refspec)); @@ -543,9 +542,7 @@ static int add_branch_for_removal(const char *refname, if (flags & REF_ISSYMREF) return unlink(git_path("%s", refname)); - item = string_list_append(branches->branches, refname); - item->util = xmalloc(20); - hashcpy(item->util, sha1); + string_list_append(branches->branches, refname); return 0; } @@ -557,20 +554,20 @@ struct rename_info { }; static int read_remote_branches(const char *refname, - const unsigned char *sha1, int flags, void *cb_data) + const struct object_id *oid, int flags, void *cb_data) { struct rename_info *rename = cb_data; struct strbuf buf = STRBUF_INIT; struct string_list_item *item; int flag; - unsigned char orig_sha1[20]; + struct object_id orig_oid; const char *symref; strbuf_addf(&buf, "refs/remotes/%s/", rename->old); if (starts_with(refname, buf.buf)) { item = string_list_append(rename->remote_branches, xstrdup(refname)); symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING, - orig_sha1, &flag); + orig_oid.hash, &flag); if (flag & REF_ISSYMREF) item->util = xstrdup(symref); else @@ -584,7 +581,7 @@ static int migrate_file(struct remote *remote) { struct strbuf buf = STRBUF_INIT; int i; - char *path = NULL; + const char *path = NULL; strbuf_addf(&buf, "remote.%s.url", remote->name); for (i = 0; i < remote->url_nr; i++) @@ -704,9 +701,9 @@ static int mv(int argc, const char **argv) for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; int flag = 0; - unsigned char sha1[20]; + struct object_id oid; - read_ref_full(item->string, RESOLVE_REF_READING, sha1, &flag); + read_ref_full(item->string, RESOLVE_REF_READING, oid.hash, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(item->string, NULL, REF_NODEREF)) @@ -826,7 +823,7 @@ static int rm(int argc, const char **argv) if (!result) result = remove_branches(&branches); - string_list_clear(&branches, 1); + string_list_clear(&branches, 0); if (skipped.nr) { fprintf_ln(stderr, @@ -867,7 +864,7 @@ static void free_remote_ref_states(struct ref_states *states) } static int append_ref_to_tracked_list(const char *refname, - const unsigned char *sha1, int flags, void *cb_data) + const struct object_id *oid, int flags, void *cb_data) { struct ref_states *states = cb_data; struct refspec refspec; diff --git a/builtin/repack.c b/builtin/repack.c index 28fbc7099a..af7340c7ba 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -228,13 +228,17 @@ int cmd_repack(int argc, const char **argv, const char *prefix) get_non_kept_pack_filenames(&existing_packs); if (existing_packs.nr && delete_redundant) { - if (unpack_unreachable) + if (unpack_unreachable) { argv_array_pushf(&cmd.args, "--unpack-unreachable=%s", unpack_unreachable); - else if (pack_everything & LOOSEN_UNREACHABLE) + argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); + } else if (pack_everything & LOOSEN_UNREACHABLE) { argv_array_push(&cmd.args, "--unpack-unreachable"); + } else { + argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); + } } } else { argv_array_push(&cmd.args, "--unpacked"); @@ -281,7 +285,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) failed = 0; for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname, *fname_old; + const char *fname_old; + char *fname; fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); if (!file_exists(fname)) { @@ -309,7 +314,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (failed) { struct string_list rollback_failure = STRING_LIST_INIT_DUP; for_each_string_list_item(item, &rollback) { - char *fname, *fname_old; + const char *fname_old; + char *fname; fname = mkpathdup("%s/%s", packdir, item->string); fname_old = mkpath("%s/old-%s", packdir, item->string); if (rename(fname_old, fname)) @@ -362,7 +368,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) /* Remove the "old-" files */ for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname; + const char *fname; fname = mkpath("%s/old-%s%s", packdir, item->string, diff --git a/builtin/replace.c b/builtin/replace.c index 85d39b58d8..0d52e7fa1d 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -35,7 +35,7 @@ struct show_data { enum replace_format format; }; -static int show_reference(const char *refname, const unsigned char *sha1, +static int show_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) { struct show_data *data = cb_data; @@ -44,19 +44,19 @@ static int show_reference(const char *refname, const unsigned char *sha1, if (data->format == REPLACE_FORMAT_SHORT) printf("%s\n", refname); else if (data->format == REPLACE_FORMAT_MEDIUM) - printf("%s -> %s\n", refname, sha1_to_hex(sha1)); + printf("%s -> %s\n", refname, oid_to_hex(oid)); else { /* data->format == REPLACE_FORMAT_LONG */ - unsigned char object[20]; + struct object_id object; enum object_type obj_type, repl_type; - if (get_sha1(refname, object)) + if (get_sha1(refname, object.hash)) return error("Failed to resolve '%s' as a valid ref.", refname); - obj_type = sha1_object_info(object, NULL); - repl_type = sha1_object_info(sha1, NULL); + 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), - sha1_to_hex(sha1), typename(repl_type)); + oid_to_hex(oid), typename(repl_type)); } } @@ -82,7 +82,7 @@ static int list_replace_refs(const char *pattern, const char *format) "valid formats are 'short', 'medium' and 'long'\n", format); - for_each_replace_ref(show_reference, (void *) &data); + for_each_replace_ref(show_reference, (void *)&data); return 0; } @@ -172,7 +172,7 @@ static int replace_object_sha1(const char *object_ref, transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref, repl, prev, - 0, 1, NULL, &err) || + 0, NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index ff84a825ff..c0b4b53652 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -42,6 +42,7 @@ static const char rev_list_usage[] = " --abbrev=<n> | --no-abbrev\n" " --abbrev-commit\n" " --left-right\n" +" --count\n" " special purpose:\n" " --bisect\n" " --bisect-vars\n" @@ -355,7 +356,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_list) revs.limited = 1; - if (use_bitmap_index) { + if (use_bitmap_index && !revs.prune) { if (revs.count && !revs.left_right && !revs.cherry_mark) { uint32_t commit_count; if (!prepare_bitmap_walk(&revs)) { diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 3626c61da6..02d747dcb1 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -190,17 +190,17 @@ static int show_default(void) return 0; } -static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +static int show_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) { if (ref_excluded(ref_excludes, refname)) return 0; - show_rev(NORMAL, sha1, refname); + show_rev(NORMAL, oid->hash, refname); return 0; } -static int anti_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +static int anti_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) { - show_rev(REVERSED, sha1, refname); + show_rev(REVERSED, oid->hash, refname); return 0; } @@ -371,6 +371,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) N_("output in stuck long form")), OPT_END(), }; + static const char * const flag_chars = "*=?!"; struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT; const char **usage = NULL; @@ -400,7 +401,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, '\n') != EOF) { const char *s; - const char *end; + const char *help; struct option *o; if (!sb.len) @@ -410,54 +411,56 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) memset(opts + onb, 0, sizeof(opts[onb])); o = &opts[onb++]; - s = strchr(sb.buf, ' '); - if (!s || *sb.buf == ' ') { + help = strchr(sb.buf, ' '); + if (!help || *sb.buf == ' ') { o->type = OPTION_GROUP; o->help = xstrdup(skipspaces(sb.buf)); continue; } o->type = OPTION_CALLBACK; - o->help = xstrdup(skipspaces(s)); + o->help = xstrdup(skipspaces(help)); o->value = &parsed; o->flags = PARSE_OPT_NOARG; o->callback = &parseopt_dump; - /* Possible argument name hint */ - end = s; - while (s > sb.buf && strchr("*=?!", s[-1]) == NULL) - --s; - if (s != sb.buf && s != end) - o->argh = xmemdupz(s, end - s); - if (s == sb.buf) - s = end; - - while (s > sb.buf && strchr("*=?!", s[-1])) { - switch (*--s) { + /* name(s) */ + s = strpbrk(sb.buf, flag_chars); + if (s == NULL) + s = help; + + if (s - sb.buf == 1) /* short option only */ + o->short_name = *sb.buf; + else if (sb.buf[1] != ',') /* long option only */ + o->long_name = xmemdupz(sb.buf, s - sb.buf); + else { + o->short_name = *sb.buf; + o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2); + } + + /* flags */ + while (s < help) { + switch (*s++) { case '=': o->flags &= ~PARSE_OPT_NOARG; - break; + continue; case '?': o->flags &= ~PARSE_OPT_NOARG; o->flags |= PARSE_OPT_OPTARG; - break; + continue; case '!': o->flags |= PARSE_OPT_NONEG; - break; + continue; case '*': o->flags |= PARSE_OPT_HIDDEN; - break; + continue; } + s--; + break; } - if (s - sb.buf == 1) /* short option only */ - o->short_name = *sb.buf; - else if (sb.buf[1] != ',') /* long option only */ - o->long_name = xmemdupz(sb.buf, s - sb.buf); - else { - o->short_name = *sb.buf; - o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2); - } + if (s < help) + o->argh = xmemdupz(s, help - s); } strbuf_release(&sb); @@ -533,6 +536,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; + if (!strcmp(arg, "--git-path")) { + if (!argv[i + 1]) + die("--git-path requires an argument"); + puts(git_path("%s", argv[i + 1])); + i++; + continue; + } if (as_is) { if (show_file(arg, output_prefix) && as_is < 2) verify_filename(prefix, arg, 0); @@ -755,6 +765,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) free(cwd); continue; } + if (!strcmp(arg, "--git-common-dir")) { + puts(get_git_common_dir()); + continue; + } if (!strcmp(arg, "--resolve-git-dir")) { const char *gitdir = argv[++i]; if (!gitdir) diff --git a/builtin/rm.c b/builtin/rm.c index 3304bff42a..80b972f92f 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -84,7 +84,6 @@ static int check_submodules_use_gitfiles(void) const char *name = list.entry[i].name; int pos; const struct cache_entry *ce; - struct stat st; pos = cache_name_pos(name, strlen(name)); if (pos < 0) { @@ -95,7 +94,7 @@ static int check_submodules_use_gitfiles(void) ce = active_cache[pos]; if (!S_ISGITLINK(ce->ce_mode) || - (lstat(ce->name, &st) < 0) || + !file_exists(ce->name) || is_empty_dir(name)) continue; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index b961e5ae78..23b2962cb0 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -11,6 +11,7 @@ #include "transport.h" #include "version.h" #include "sha1-array.h" +#include "gpg-interface.h" static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n" @@ -113,6 +114,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int from_stdin = 0; struct push_cas_option cas = {0}; + git_config(git_gpg_config, NULL); + argv++; for (i = 1; i < argc; i++, argv++) { const char *arg = *argv; diff --git a/builtin/show-branch.c b/builtin/show-branch.c index f3fb5fb2bf..323f857463 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -369,10 +369,10 @@ static void sort_ref_range(int bottom, int top) compare_ref_name); } -static int append_ref(const char *refname, const unsigned char *sha1, +static int append_ref(const char *refname, const struct object_id *oid, int allow_dups) { - struct commit *commit = lookup_commit_reference_gently(sha1, 1); + struct commit *commit = lookup_commit_reference_gently(oid->hash, 1); int i; if (!commit) @@ -394,39 +394,42 @@ static int append_ref(const char *refname, const unsigned char *sha1, return 0; } -static int append_head_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +static int append_head_ref(const char *refname, const struct object_id *oid, + int flag, void *cb_data) { - unsigned char tmp[20]; + struct object_id tmp; int ofs = 11; if (!starts_with(refname, "refs/heads/")) return 0; /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. */ - if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) + if (get_sha1(refname + ofs, tmp.hash) || oidcmp(&tmp, oid)) ofs = 5; - return append_ref(refname + ofs, sha1, 0); + return append_ref(refname + ofs, oid, 0); } -static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +static int append_remote_ref(const char *refname, const struct object_id *oid, + int flag, void *cb_data) { - unsigned char tmp[20]; + struct object_id tmp; int ofs = 13; if (!starts_with(refname, "refs/remotes/")) return 0; /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. */ - if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) + if (get_sha1(refname + ofs, tmp.hash) || oidcmp(&tmp, oid)) ofs = 5; - return append_ref(refname + ofs, sha1, 0); + return append_ref(refname + ofs, oid, 0); } -static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +static int append_tag_ref(const char *refname, const struct object_id *oid, + int flag, void *cb_data) { if (!starts_with(refname, "refs/tags/")) return 0; - return append_ref(refname + 5, sha1, 0); + return append_ref(refname + 5, oid, 0); } static const char *match_ref_pattern = NULL; @@ -440,7 +443,8 @@ static int count_slash(const char *s) return cnt; } -static int append_matching_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +static int append_matching_ref(const char *refname, const struct object_id *oid, + int flag, void *cb_data) { /* we want to allow pattern hold/<asterisk> to show all * branches under refs/heads/hold/, and v0.99.9? to show @@ -456,21 +460,23 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, i if (wildmatch(match_ref_pattern, tail, 0, NULL)) return 0; if (starts_with(refname, "refs/heads/")) - return append_head_ref(refname, sha1, flag, cb_data); + return append_head_ref(refname, oid, flag, cb_data); if (starts_with(refname, "refs/tags/")) - return append_tag_ref(refname, sha1, flag, cb_data); - return append_ref(refname, sha1, 0); + return append_tag_ref(refname, oid, flag, cb_data); + return append_ref(refname, oid, 0); } static void snarf_refs(int head, int remotes) { if (head) { int orig_cnt = ref_name_cnt; + for_each_ref(append_head_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } if (remotes) { int orig_cnt = ref_name_cnt; + for_each_ref(append_remote_ref, NULL); sort_ref_range(orig_cnt, ref_name_cnt); } @@ -530,14 +536,15 @@ static int show_independent(struct commit **rev, static void append_one_rev(const char *av) { - unsigned char revkey[20]; - if (!get_sha1(av, revkey)) { - append_ref(av, revkey, 0); + struct object_id revkey; + if (!get_sha1(av, revkey.hash)) { + append_ref(av, &revkey, 0); return; } if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { /* glob style match */ int saved_matches = ref_name_cnt; + match_ref_pattern = av; match_ref_slash = count_slash(av); for_each_ref(append_matching_ref, NULL); @@ -636,7 +643,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) char head[128]; const char *head_p; int head_len; - unsigned char head_sha1[20]; + struct object_id head_oid; int merge_base = 0; int independent = 0; int no_name = 0; @@ -718,11 +725,11 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } /* If nothing is specified, show all branches by default */ - if (ac + all_heads + all_remotes == 0) + if (ac <= topics && all_heads + all_remotes == 0) all_heads = 1; if (reflog) { - unsigned char sha1[20]; + struct object_id oid; char nth_desc[256]; char *ref; int base = 0; @@ -733,7 +740,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) fake_av[0] = resolve_refdup("HEAD", RESOLVE_REF_READING, - sha1, NULL); + oid.hash, NULL); fake_av[1] = NULL; av = fake_av; ac = 1; @@ -744,7 +751,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (MAX_REVS < reflog) die("Only %d entries can be shown at one time.", MAX_REVS); - if (!dwim_ref(*av, strlen(*av), sha1, &ref)) + if (!dwim_ref(*av, strlen(*av), oid.hash, &ref)) die("No such ref %s", *av); /* Has the base been specified? */ @@ -755,7 +762,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) /* Ah, that is a date spec... */ unsigned long at; at = approxidate(reflog_base); - read_ref_at(ref, flags, at, -1, sha1, NULL, + read_ref_at(ref, flags, at, -1, oid.hash, NULL, NULL, NULL, &base); } } @@ -766,7 +773,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) unsigned long timestamp; int tz; - if (read_ref_at(ref, flags, 0, base+i, sha1, &logmsg, + if (read_ref_at(ref, flags, 0, base+i, oid.hash, &logmsg, ×tamp, &tz, NULL)) { reflog = i; break; @@ -781,21 +788,21 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) msg); free(logmsg); sprintf(nth_desc, "%s@{%d}", *av, base+i); - append_ref(nth_desc, sha1, 1); + append_ref(nth_desc, &oid, 1); } free(ref); } - else if (all_heads + all_remotes) - snarf_refs(all_heads, all_remotes); else { while (0 < ac) { append_one_rev(*av); ac--; av++; } + if (all_heads + all_remotes) + snarf_refs(all_heads, all_remotes); } head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - head_sha1, NULL); + head_oid.hash, NULL); if (head_p) { head_len = strlen(head_p); memcpy(head, head_p, head_len + 1); @@ -814,7 +821,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) if (rev_is_head(head, head_len, ref_name[i], - head_sha1, NULL)) + head_oid.hash, NULL)) has_head++; } if (!has_head) { @@ -829,17 +836,17 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) } for (num_rev = 0; ref_name[num_rev]; num_rev++) { - unsigned char revkey[20]; + struct object_id revkey; unsigned int flag = 1u << (num_rev + REV_SHIFT); if (MAX_REVS <= num_rev) die("cannot handle more than %d revs.", MAX_REVS); - if (get_sha1(ref_name[num_rev], revkey)) + if (get_sha1(ref_name[num_rev], revkey.hash)) die("'%s' is not a valid ref.", ref_name[num_rev]); - commit = lookup_commit_reference(revkey); + commit = lookup_commit_reference(revkey.hash); if (!commit) die("cannot find commit %s (%s)", - ref_name[num_rev], revkey); + ref_name[num_rev], oid_to_hex(&revkey)); parse_commit(commit); mark_seen(commit, &seen); @@ -873,7 +880,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int is_head = rev_is_head(head, head_len, ref_name[i], - head_sha1, + head_oid.hash, rev[i]->object.sha1); if (extra < 0) printf("%c [%s] ", diff --git a/builtin/show-ref.c b/builtin/show-ref.c index afb10309d6..dfbc314ac2 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -17,19 +17,20 @@ static int deref_tags, show_head, tags_only, heads_only, found_match, verify, static const char **pattern; static const char *exclude_existing_arg; -static void show_one(const char *refname, const unsigned char *sha1) +static void show_one(const char *refname, const struct object_id *oid) { - const char *hex = find_unique_abbrev(sha1, abbrev); + const char *hex = find_unique_abbrev(oid->hash, abbrev); if (hash_only) printf("%s\n", hex); else printf("%s %s\n", hex, refname); } -static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata) +static int show_ref(const char *refname, const struct object_id *oid, + int flag, void *cbdata) { const char *hex; - unsigned char peeled[20]; + struct object_id peeled; if (show_head && !strcmp(refname, "HEAD")) goto match; @@ -69,26 +70,27 @@ match: * detect and return error if the repository is corrupt and * ref points at a nonexistent object. */ - if (!has_sha1_file(sha1)) + if (!has_sha1_file(oid->hash)) die("git show-ref: bad ref %s (%s)", refname, - sha1_to_hex(sha1)); + oid_to_hex(oid)); if (quiet) return 0; - show_one(refname, sha1); + show_one(refname, oid); if (!deref_tags) return 0; - if (!peel_ref(refname, peeled)) { - hex = find_unique_abbrev(peeled, abbrev); + if (!peel_ref(refname, peeled.hash)) { + hex = find_unique_abbrev(peeled.hash, abbrev); printf("%s %s^{}\n", hex, refname); } return 0; } -static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) +static int add_existing(const char *refname, const struct object_id *oid, + int flag, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; string_list_insert(list, refname); @@ -208,12 +210,12 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) if (!pattern) die("--verify requires a reference"); while (*pattern) { - unsigned char sha1[20]; + struct object_id oid; if (starts_with(*pattern, "refs/") && - !read_ref(*pattern, sha1)) { + !read_ref(*pattern, oid.hash)) { if (!quiet) - show_one(*pattern, sha1); + show_one(*pattern, &oid); } else if (!quiet) die("'%s' - not a valid ref", *pattern); diff --git a/builtin/tag.c b/builtin/tag.c index 6dc85a9d5e..5f6cdc5a03 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -176,7 +176,7 @@ static enum contains_result contains(struct commit *candidate, return contains_test(candidate, want); } -static void show_tag_lines(const unsigned char *sha1, int lines) +static void show_tag_lines(const struct object_id *oid, int lines) { int i; unsigned long size; @@ -184,14 +184,14 @@ static void show_tag_lines(const unsigned char *sha1, int lines) char *buf, *sp, *eol; size_t len; - buf = read_sha1_file(sha1, &type, &size); + buf = read_sha1_file(oid->hash, &type, &size); if (!buf) - die_errno("unable to read object %s", sha1_to_hex(sha1)); + die_errno("unable to read object %s", oid_to_hex(oid)); if (type != OBJ_COMMIT && type != OBJ_TAG) goto free_return; if (!size) die("an empty %s object %s?", - typename(type), sha1_to_hex(sha1)); + typename(type), oid_to_hex(oid)); /* skip header */ sp = strstr(buf, "\n\n"); @@ -215,7 +215,7 @@ free_return: free(buf); } -static int show_reference(const char *refname, const unsigned char *sha1, +static int show_reference(const char *refname, const struct object_id *oid, int flag, void *cb_data) { struct tag_filter *filter = cb_data; @@ -224,14 +224,14 @@ static int show_reference(const char *refname, const unsigned char *sha1, if (filter->with_commit) { struct commit *commit; - commit = lookup_commit_reference_gently(sha1, 1); + commit = lookup_commit_reference_gently(oid->hash, 1); if (!commit) return 0; if (!contains(commit, filter->with_commit)) return 0; } - if (points_at.nr && !match_points_at(refname, sha1)) + if (points_at.nr && !match_points_at(refname, oid->hash)) return 0; if (!filter->lines) { @@ -242,7 +242,7 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } printf("%-15s ", refname); - show_tag_lines(sha1, filter->lines); + show_tag_lines(oid, filter->lines); putchar('\n'); } @@ -268,7 +268,7 @@ static int list_tags(const char **patterns, int lines, memset(&filter.tags, 0, sizeof(filter.tags)); filter.tags.strdup_strings = 1; - for_each_tag_ref(show_reference, (void *) &filter); + for_each_tag_ref(show_reference, (void *)&filter); if (sort) { int i; if ((sort & SORT_MASK) == VERCMP_SORT) @@ -605,13 +605,13 @@ int cmd_tag(int argc, const char **argv, const char *prefix) 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_GROUP(N_("Tag listing options")), OPT_COLUMN(0, "column", &colopts, N_("show tag list in columns")), { OPTION_CALLBACK, 0, "sort", &tag_sort, N_("type"), N_("sort tags"), PARSE_OPT_NONEG, parse_opt_sort }, - - OPT_GROUP(N_("Tag listing options")), { OPTION_CALLBACK, 0, "contains", &with_commit, N_("commit"), N_("print only tags that contain the commit"), @@ -733,7 +733,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, object, prev, - 0, 1, NULL, &err) || + 0, NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); diff --git a/builtin/update-index.c b/builtin/update-index.c index 587898624c..7431938fa6 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -33,6 +33,7 @@ static int mark_valid_only; static int mark_skip_worktree_only; #define MARK_FLAG 1 #define UNMARK_FLAG 2 +static struct strbuf mtime_dir = STRBUF_INIT; __attribute__((format (printf, 1, 2))) static void report(const char *fmt, ...) @@ -48,6 +49,166 @@ static void report(const char *fmt, ...) va_end(vp); } +static void remove_test_directory(void) +{ + if (mtime_dir.len) + remove_dir_recursively(&mtime_dir, 0); +} + +static const char *get_mtime_path(const char *path) +{ + static struct strbuf sb = STRBUF_INIT; + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/%s", mtime_dir.buf, path); + return sb.buf; +} + +static void xmkdir(const char *path) +{ + path = get_mtime_path(path); + if (mkdir(path, 0700)) + die_errno(_("failed to create directory %s"), path); +} + +static int xstat_mtime_dir(struct stat *st) +{ + if (stat(mtime_dir.buf, st)) + die_errno(_("failed to stat %s"), mtime_dir.buf); + return 0; +} + +static int create_file(const char *path) +{ + int fd; + path = get_mtime_path(path); + fd = open(path, O_CREAT | O_RDWR, 0644); + if (fd < 0) + die_errno(_("failed to create file %s"), path); + return fd; +} + +static void xunlink(const char *path) +{ + path = get_mtime_path(path); + if (unlink(path)) + die_errno(_("failed to delete file %s"), path); +} + +static void xrmdir(const char *path) +{ + path = get_mtime_path(path); + if (rmdir(path)) + die_errno(_("failed to delete directory %s"), path); +} + +static void avoid_racy(void) +{ + /* + * not use if we could usleep(10) if USE_NSEC is defined. The + * field nsec could be there, but the OS could choose to + * ignore it? + */ + sleep(1); +} + +static int test_if_untracked_cache_is_supported(void) +{ + struct stat st; + struct stat_data base; + int fd, ret = 0; + + strbuf_addstr(&mtime_dir, "mtime-test-XXXXXX"); + if (!mkdtemp(mtime_dir.buf)) + die_errno("Could not make temporary directory"); + + fprintf(stderr, _("Testing ")); + atexit(remove_test_directory); + xstat_mtime_dir(&st); + fill_stat_data(&base, &st); + fputc('.', stderr); + + avoid_racy(); + fd = create_file("newfile"); + xstat_mtime_dir(&st); + if (!match_stat_data(&base, &st)) { + close(fd); + fputc('\n', stderr); + fprintf_ln(stderr,_("directory stat info does not " + "change after adding a new file")); + goto done; + } + fill_stat_data(&base, &st); + fputc('.', stderr); + + avoid_racy(); + xmkdir("new-dir"); + xstat_mtime_dir(&st); + if (!match_stat_data(&base, &st)) { + close(fd); + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info does not change " + "after adding a new directory")); + goto done; + } + fill_stat_data(&base, &st); + fputc('.', stderr); + + avoid_racy(); + write_or_die(fd, "data", 4); + close(fd); + xstat_mtime_dir(&st); + if (match_stat_data(&base, &st)) { + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info changes " + "after updating a file")); + goto done; + } + fputc('.', stderr); + + avoid_racy(); + close(create_file("new-dir/new")); + xstat_mtime_dir(&st); + if (match_stat_data(&base, &st)) { + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info changes after " + "adding a file inside subdirectory")); + goto done; + } + fputc('.', stderr); + + avoid_racy(); + xunlink("newfile"); + xstat_mtime_dir(&st); + if (!match_stat_data(&base, &st)) { + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info does not " + "change after deleting a file")); + goto done; + } + fill_stat_data(&base, &st); + fputc('.', stderr); + + avoid_racy(); + xunlink("new-dir/new"); + xrmdir("new-dir"); + xstat_mtime_dir(&st); + if (!match_stat_data(&base, &st)) { + fputc('\n', stderr); + fprintf_ln(stderr, _("directory stat info does not " + "change after deleting a directory")); + goto done; + } + + if (rmdir(mtime_dir.buf)) + die_errno(_("failed to delete directory %s"), mtime_dir.buf); + fprintf_ln(stderr, _(" OK")); + ret = 1; + +done: + strbuf_release(&mtime_dir); + return ret; +} + static int mark_ce_flags(const char *path, int flag, int mark) { int namelen = strlen(path); @@ -532,10 +693,9 @@ static int do_unresolve(int ac, const char **av, for (i = 1; i < ac; i++) { const char *arg = av[i]; - const char *p = prefix_path(prefix, prefix_length, arg); + char *p = prefix_path(prefix, prefix_length, arg); err |= unresolve_one(p); - if (p < arg || p > arg + strlen(arg)) - free((char *)p); + free(p); } return err; } @@ -584,6 +744,7 @@ static int do_reupdate(int ac, const char **av, path = xstrdup(ce->name); update_one(path); free(path); + free(old); if (save_nr != active_nr) goto redo; } @@ -741,6 +902,7 @@ static int reupdate_callback(struct parse_opt_ctx_t *ctx, int cmd_update_index(int argc, const char **argv, const char *prefix) { int newfd, entries, has_errors = 0, line_termination = '\n'; + int untracked_cache = -1; int read_from_stdin = 0; int prefix_length = prefix ? strlen(prefix) : 0; int preferred_index_format = 0; @@ -832,6 +994,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) N_("write index in this format")), OPT_BOOL(0, "split-index", &split_index, N_("enable or disable split index")), + OPT_BOOL(0, "untracked-cache", &untracked_cache, + N_("enable/disable untracked cache")), + OPT_SET_INT(0, "force-untracked-cache", &untracked_cache, + N_("enable untracked cache without testing the filesystem"), 2), OPT_END() }; @@ -870,14 +1036,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) case PARSE_OPT_DONE: { const char *path = ctx.argv[0]; - const char *p; + char *p; setup_work_tree(); p = prefix_path(prefix, prefix_length, path); update_one(p); if (set_executable_bit) chmod_path(set_executable_bit, p); - free((char *)p); + free(p); ctx.argc--; ctx.argv++; break; @@ -908,7 +1074,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) setup_work_tree(); while (strbuf_getline(&buf, stdin, line_termination) != EOF) { - const char *p; + char *p; if (line_termination && buf.buf[0] == '"') { strbuf_reset(&nbuf); if (unquote_c_style(&nbuf, buf.buf, NULL)) @@ -919,7 +1085,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) update_one(p); if (set_executable_bit) chmod_path(set_executable_bit, p); - free((char *)p); + free(p); } strbuf_release(&nbuf); strbuf_release(&buf); @@ -938,6 +1104,28 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) the_index.split_index = NULL; the_index.cache_changed |= SOMETHING_CHANGED; } + if (untracked_cache > 0) { + struct untracked_cache *uc; + + if (untracked_cache < 2) { + setup_work_tree(); + if (!test_if_untracked_cache_is_supported()) + return 1; + } + if (!the_index.untracked) { + uc = xcalloc(1, sizeof(*uc)); + strbuf_init(&uc->ident, 100); + uc->exclude_per_dir = ".gitignore"; + /* should be the same flags used by git-status */ + uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; + the_index.untracked = uc; + } + add_untracked_ident(the_index.untracked); + the_index.cache_changed |= UNTRACKED_CHANGED; + } else if (!untracked_cache && the_index.untracked) { + the_index.untracked = NULL; + the_index.cache_changed |= UNTRACKED_CHANGED; + } if (active_cache_changed) { if (newfd < 0) { diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 2497ba4303..3d79a46b03 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -198,8 +198,9 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, if (*next != line_termination) die("update %s: extra input: %s", refname, next); - if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, - update_flags, have_old, msg, &err)) + if (ref_transaction_update(transaction, refname, + new_sha1, have_old ? old_sha1 : NULL, + update_flags, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -264,8 +265,9 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction, if (*next != line_termination) die("delete %s: extra input: %s", refname, next); - if (ref_transaction_delete(transaction, refname, old_sha1, - update_flags, have_old, msg, &err)) + if (ref_transaction_delete(transaction, refname, + have_old ? old_sha1 : NULL, + update_flags, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -280,7 +282,6 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, { struct strbuf err = STRBUF_INIT; char *refname; - unsigned char new_sha1[20]; unsigned char old_sha1[20]; refname = parse_refname(input, &next); @@ -291,13 +292,11 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, PARSE_SHA1_OLD)) hashclr(old_sha1); - hashcpy(new_sha1, old_sha1); - if (*next != line_termination) die("verify %s: extra input: %s", refname, next); - if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, - update_flags, 1, msg, &err)) + if (ref_transaction_verify(transaction, refname, old_sha1, + update_flags, &err)) die("%s", err.buf); update_flags = 0; @@ -353,7 +352,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname, *oldval; unsigned char sha1[20], oldsha1[20]; - int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0; + int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0; + unsigned int flags = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_BOOL('d', NULL, &delete, N_("delete the reference")), diff --git a/builtin/worktree.c b/builtin/worktree.c new file mode 100644 index 0000000000..6a264ee749 --- /dev/null +++ b/builtin/worktree.c @@ -0,0 +1,332 @@ +#include "cache.h" +#include "builtin.h" +#include "dir.h" +#include "parse-options.h" +#include "argv-array.h" +#include "run-command.h" +#include "sigchain.h" +#include "refs.h" + +static const char * const worktree_usage[] = { + N_("git worktree add [<options>] <path> <branch>"), + N_("git worktree prune [<options>]"), + NULL +}; + +static int show_only; +static int verbose; +static unsigned long expire; + +static int prune_worktree(const char *id, struct strbuf *reason) +{ + struct stat st; + char *path; + int fd, len; + + if (!is_directory(git_path("worktrees/%s", id))) { + strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); + return 1; + } + if (file_exists(git_path("worktrees/%s/locked", id))) + return 0; + if (stat(git_path("worktrees/%s/gitdir", id), &st)) { + strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id); + return 1; + } + fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); + if (fd < 0) { + strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), + id, strerror(errno)); + return 1; + } + len = st.st_size; + path = xmalloc(len + 1); + read_in_full(fd, path, len); + close(fd); + while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) + len--; + if (!len) { + strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); + free(path); + return 1; + } + path[len] = '\0'; + if (!file_exists(path)) { + struct stat st_link; + free(path); + /* + * the repo is moved manually and has not been + * accessed since? + */ + if (!stat(git_path("worktrees/%s/link", id), &st_link) && + st_link.st_nlink > 1) + return 0; + if (st.st_mtime <= expire) { + strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); + return 1; + } else { + return 0; + } + } + free(path); + return 0; +} + +static void prune_worktrees(void) +{ + struct strbuf reason = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + DIR *dir = opendir(git_path("worktrees")); + struct dirent *d; + int ret; + if (!dir) + return; + while ((d = readdir(dir)) != NULL) { + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + strbuf_reset(&reason); + if (!prune_worktree(d->d_name, &reason)) + continue; + if (show_only || verbose) + printf("%s\n", reason.buf); + if (show_only) + continue; + strbuf_reset(&path); + strbuf_addstr(&path, git_path("worktrees/%s", d->d_name)); + ret = remove_dir_recursively(&path, 0); + if (ret < 0 && errno == ENOTDIR) + ret = unlink(path.buf); + if (ret) + error(_("failed to remove: %s"), strerror(errno)); + } + closedir(dir); + if (!show_only) + rmdir(git_path("worktrees")); + strbuf_release(&reason); + strbuf_release(&path); +} + +static int prune(int ac, const char **av, const char *prefix) +{ + struct option options[] = { + OPT__DRY_RUN(&show_only, N_("do not remove, show only")), + OPT__VERBOSE(&verbose, N_("report pruned objects")), + OPT_EXPIRY_DATE(0, "expire", &expire, + N_("expire objects older than <time>")), + OPT_END() + }; + + expire = ULONG_MAX; + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac) + usage_with_options(worktree_usage, options); + prune_worktrees(); + return 0; +} + +static char *junk_work_tree; +static char *junk_git_dir; +static int is_junk; +static pid_t junk_pid; + +static void remove_junk(void) +{ + struct strbuf sb = STRBUF_INIT; + if (!is_junk || getpid() != junk_pid) + return; + if (junk_git_dir) { + strbuf_addstr(&sb, junk_git_dir); + remove_dir_recursively(&sb, 0); + strbuf_reset(&sb); + } + if (junk_work_tree) { + strbuf_addstr(&sb, junk_work_tree); + remove_dir_recursively(&sb, 0); + } + strbuf_release(&sb); +} + +static void remove_junk_on_signal(int signo) +{ + remove_junk(); + sigchain_pop(signo); + raise(signo); +} + +static const char *worktree_basename(const char *path, int *olen) +{ + const char *name; + int len; + + len = strlen(path); + while (len && is_dir_sep(path[len - 1])) + len--; + + for (name = path + len - 1; name > path; name--) + if (is_dir_sep(*name)) { + name++; + break; + } + + *olen = len; + return name; +} + +static int add_worktree(const char *path, const char **child_argv) +{ + struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *name; + struct stat st; + struct child_process cp; + int counter = 0, len, ret; + unsigned char rev[20]; + + if (file_exists(path) && !is_empty_dir(path)) + die(_("'%s' already exists"), path); + + name = worktree_basename(path, &len); + strbuf_addstr(&sb_repo, + git_path("worktrees/%.*s", (int)(path + len - name), name)); + len = sb_repo.len; + if (safe_create_leading_directories_const(sb_repo.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_repo.buf); + while (!stat(sb_repo.buf, &st)) { + counter++; + strbuf_setlen(&sb_repo, len); + strbuf_addf(&sb_repo, "%d", counter); + } + name = strrchr(sb_repo.buf, '/') + 1; + + junk_pid = getpid(); + atexit(remove_junk); + sigchain_push_common(remove_junk_on_signal); + + if (mkdir(sb_repo.buf, 0777)) + die_errno(_("could not create directory of '%s'"), sb_repo.buf); + junk_git_dir = xstrdup(sb_repo.buf); + is_junk = 1; + + /* + * lock the incomplete repo so prune won't delete it, unlock + * after the preparation is over. + */ + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + write_file(sb.buf, 1, "initializing\n"); + + strbuf_addf(&sb_git, "%s/.git", path); + if (safe_create_leading_directories_const(sb_git.buf)) + die_errno(_("could not create leading directories of '%s'"), + sb_git.buf); + junk_work_tree = xstrdup(path); + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", real_path(sb_git.buf)); + write_file(sb_git.buf, 1, "gitdir: %s/worktrees/%s\n", + real_path(get_git_common_dir()), name); + /* + * This is to keep resolve_ref() happy. We need a valid HEAD + * or is_git_directory() will reject the directory. Moreover, HEAD + * in the new worktree must resolve to the same value as HEAD in + * the current tree since the command invoked to populate the new + * worktree will be handed the branch/ref specified by the user. + * For instance, if the user asks for the new worktree to be based + * at HEAD~5, then the resolved HEAD~5 in the new worktree must + * match the resolved HEAD~5 in the current tree in order to match + * the user's expectation. + */ + if (!resolve_ref_unsafe("HEAD", 0, rev, NULL)) + die(_("unable to resolve HEAD")); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); + write_file(sb.buf, 1, "%s\n", sha1_to_hex(rev)); + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/commondir", sb_repo.buf); + write_file(sb.buf, 1, "../..\n"); + + fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name); + + setenv("GIT_CHECKOUT_NEW_WORKTREE", "1", 1); + setenv(GIT_DIR_ENVIRONMENT, sb_git.buf, 1); + setenv(GIT_WORK_TREE_ENVIRONMENT, path, 1); + memset(&cp, 0, sizeof(cp)); + cp.git_cmd = 1; + cp.argv = child_argv; + ret = run_command(&cp); + if (!ret) { + is_junk = 0; + free(junk_work_tree); + free(junk_git_dir); + junk_work_tree = NULL; + junk_git_dir = NULL; + } + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/locked", sb_repo.buf); + unlink_or_warn(sb.buf); + strbuf_release(&sb); + strbuf_release(&sb_repo); + strbuf_release(&sb_git); + return ret; +} + +static int add(int ac, const char **av, const char *prefix) +{ + int force = 0, detach = 0; + const char *new_branch = NULL, *new_branch_force = NULL; + const char *path, *branch; + struct argv_array cmd = ARGV_ARRAY_INIT; + struct option options[] = { + OPT__FORCE(&force, N_("checkout <branch> even if already checked out in other worktree")), + OPT_STRING('b', NULL, &new_branch, N_("branch"), + N_("create a new branch")), + OPT_STRING('B', NULL, &new_branch_force, N_("branch"), + N_("create or reset a branch")), + OPT_BOOL(0, "detach", &detach, N_("detach HEAD at named commit")), + OPT_END() + }; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (new_branch && new_branch_force) + die(_("-b and -B are mutually exclusive")); + if (ac < 1 || ac > 2) + usage_with_options(worktree_usage, options); + + path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0]; + branch = ac < 2 ? "HEAD" : av[1]; + + if (ac < 2 && !new_branch && !new_branch_force) { + int n; + const char *s = worktree_basename(path, &n); + new_branch = xstrndup(s, n); + } + + argv_array_push(&cmd, "checkout"); + if (force) + argv_array_push(&cmd, "--ignore-other-worktrees"); + if (new_branch) + argv_array_pushl(&cmd, "-b", new_branch, NULL); + if (new_branch_force) + argv_array_pushl(&cmd, "-B", new_branch_force, NULL); + if (detach) + argv_array_push(&cmd, "--detach"); + argv_array_push(&cmd, branch); + + return add_worktree(path, cmd.argv); +} + +int cmd_worktree(int ac, const char **av, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + + if (ac < 2) + usage_with_options(worktree_usage, options); + if (!strcmp(av[1], "add")) + return add(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "prune")) + return prune(ac - 1, av + 1, prefix); + usage_with_options(worktree_usage, options); +} |