diff options
Diffstat (limited to 'builtin')
| -rw-r--r-- | builtin/bisect--helper.c | 13 | ||||
| -rw-r--r-- | builtin/branch.c | 4 | ||||
| -rw-r--r-- | builtin/cat-file.c | 2 | ||||
| -rw-r--r-- | builtin/checkout.c | 6 | ||||
| -rw-r--r-- | builtin/clean.c | 49 | ||||
| -rw-r--r-- | builtin/clone.c | 35 | ||||
| -rw-r--r-- | builtin/commit-graph.c | 75 | ||||
| -rw-r--r-- | builtin/config.c | 2 | ||||
| -rw-r--r-- | builtin/diff-files.c | 7 | ||||
| -rw-r--r-- | builtin/diff.c | 147 | ||||
| -rw-r--r-- | builtin/fast-export.c | 162 | ||||
| -rw-r--r-- | builtin/fetch-pack.c | 19 | ||||
| -rw-r--r-- | builtin/fetch.c | 12 | ||||
| -rw-r--r-- | builtin/fsck.c | 4 | ||||
| -rw-r--r-- | builtin/index-pack.c | 14 | ||||
| -rw-r--r-- | builtin/init-db.c | 33 | ||||
| -rw-r--r-- | builtin/ls-remote.c | 4 | ||||
| -rw-r--r-- | builtin/merge.c | 2 | ||||
| -rw-r--r-- | builtin/pack-objects.c | 76 | ||||
| -rw-r--r-- | builtin/pull.c | 4 | ||||
| -rw-r--r-- | builtin/receive-pack.c | 10 | ||||
| -rw-r--r-- | builtin/reflog.c | 2 | ||||
| -rw-r--r-- | builtin/show-index.c | 29 | ||||
| -rw-r--r-- | builtin/sparse-checkout.c | 6 | ||||
| -rw-r--r-- | builtin/submodule--helper.c | 46 | ||||
| -rw-r--r-- | builtin/worktree.c | 158 |
26 files changed, 715 insertions, 206 deletions
diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index c1c40b516d..ec4996282e 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -455,9 +455,12 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, no_checkout = 1; } else if (!strcmp(arg, "--term-good") || !strcmp(arg, "--term-old")) { + i++; + if (argc <= i) + return error(_("'' is not a valid term")); must_write_terms = 1; free((void *) terms->term_good); - terms->term_good = xstrdup(argv[++i]); + terms->term_good = xstrdup(argv[i]); } else if (skip_prefix(arg, "--term-good=", &arg) || skip_prefix(arg, "--term-old=", &arg)) { must_write_terms = 1; @@ -465,16 +468,18 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, terms->term_good = xstrdup(arg); } else if (!strcmp(arg, "--term-bad") || !strcmp(arg, "--term-new")) { + i++; + if (argc <= i) + return error(_("'' is not a valid term")); must_write_terms = 1; free((void *) terms->term_bad); - terms->term_bad = xstrdup(argv[++i]); + terms->term_bad = xstrdup(argv[i]); } else if (skip_prefix(arg, "--term-bad=", &arg) || skip_prefix(arg, "--term-new=", &arg)) { must_write_terms = 1; free((void *) terms->term_bad); terms->term_bad = xstrdup(arg); - } else if (starts_with(arg, "--") && - !one_of(arg, "--term-good", "--term-bad", NULL)) { + } else if (starts_with(arg, "--")) { return error(_("unrecognized option: '%s'"), arg); } else { char *commit_id = xstrfmt("%s^{commit}", arg); diff --git a/builtin/branch.c b/builtin/branch.c index accb61b1aa..e82301fb1b 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -468,7 +468,7 @@ static void print_current_branch_name(void) static void reject_rebase_or_bisect_branch(const char *target) { - struct worktree **worktrees = get_worktrees(0); + struct worktree **worktrees = get_worktrees(); int i; for (i = 0; worktrees[i]; i++) { @@ -693,7 +693,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) list = 1; if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + - list + unset_upstream > 1) + list + edit_description + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); if (filter.abbrev == -1) diff --git a/builtin/cat-file.c b/builtin/cat-file.c index ae18e20a7c..5ebf13359e 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -596,7 +596,7 @@ static int batch_objects(struct batch_options *opt) static const char * const cat_file_usage[] = { N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"), - N_("git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]"), + N_("git cat-file (--batch[=<format>] | --batch-check[=<format>]) [--follow-symlinks] [--textconv | --filters]"), NULL }; diff --git a/builtin/checkout.c b/builtin/checkout.c index e9d111bb83..af849c644f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -621,9 +621,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, opts.src_index = &the_index; opts.dst_index = &the_index; init_checkout_metadata(&opts.meta, info->refname, - info->commit ? &info->commit->object.oid : - is_null_oid(&info->oid) ? &tree->object.oid : - &info->oid, + info->commit ? &info->commit->object.oid : &null_oid, NULL); parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); @@ -1689,7 +1687,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, * Try to give more helpful suggestion. * new_branch && argc > 1 will be caught later. */ - if (opts->new_branch && argc == 1) + if (opts->new_branch && argc == 1 && !new_branch_info.commit) die(_("'%s' is not a commit and a branch '%s' cannot be created from it"), argv[0], opts->new_branch); diff --git a/builtin/clean.c b/builtin/clean.c index 4ca12bc0c0..5a9c29a558 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -924,12 +924,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 0); memset(&dir, 0, sizeof(dir)); - if (ignored_only) - dir.flags |= DIR_SHOW_IGNORED; - - if (ignored && ignored_only) - die(_("-x and -X cannot be used together")); - if (!interactive && !dry_run && !force) { if (config_set) die(_("clean.requireForce set to true and neither -i, -n, nor -f given; " @@ -946,6 +940,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; + if (ignored && ignored_only) + die(_("-x and -X cannot be used together")); + if (!ignored) + setup_standard_excludes(&dir); + if (ignored_only) + dir.flags |= DIR_SHOW_IGNORED; + if (argc) { /* * Remaining args implies pathspecs specified, and we should @@ -954,15 +955,41 @@ int cmd_clean(int argc, const char **argv, const char *prefix) remove_directories = 1; } - if (remove_directories) - dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS; + if (remove_directories && !ignored_only) { + /* + * We need to know about ignored files too: + * + * If (ignored), then we will delete ignored files as well. + * + * If (!ignored), then even though we not are doing + * anything with ignored files, we need to know about them + * so that we can avoid deleting a directory of untracked + * files that also contains an ignored file within it. + * + * For the (!ignored) case, since we only need to avoid + * deleting ignored files, we can set + * DIR_SHOW_IGNORED_TOO_MODE_MATCHING in order to avoid + * recursing into a directory which is itself ignored. + */ + dir.flags |= DIR_SHOW_IGNORED_TOO; + if (!ignored) + dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING; + + /* + * Let the fill_directory() machinery know that we aren't + * just recursing to collect the ignored files; we want all + * the untracked ones so that we can delete them. (Note: + * we could also set DIR_KEEP_UNTRACKED_CONTENTS when + * ignored_only is true, since DIR_KEEP_UNTRACKED_CONTENTS + * only has effect in combination with DIR_SHOW_IGNORED_TOO. It makes + * the code clearer to exclude it, though. + */ + dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS; + } if (read_cache() < 0) die(_("index file corrupt")); - if (!ignored) - setup_standard_excludes(&dir); - pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option"); for (i = 0; i < exclude_list.nr; i++) add_pattern(exclude_list.items[i].string, "", 0, pl, -(i+1)); diff --git a/builtin/clone.c b/builtin/clone.c index cb48a291ca..bef70745c0 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -945,7 +945,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) { int is_bundle = 0, is_local; const char *repo_name, *repo, *work_tree, *git_dir; - char *path, *dir; + char *path, *dir, *display_repo = NULL; int dest_exists; const struct ref *refs, *remote_head; const struct ref *remote_head_points_at; @@ -1000,10 +1000,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix) path = get_repo_path(repo_name, &is_bundle); if (path) repo = absolute_pathdup(repo_name); - else if (!strchr(repo_name, ':')) - die(_("repository '%s' does not exist"), repo_name); - else + else if (strchr(repo_name, ':')) { repo = repo_name; + display_repo = transport_anonymize_url(repo); + } else + die(_("repository '%s' does not exist"), repo_name); /* no need to be strict, transport_set_option() will validate it again */ if (option_depth && atoi(option_depth) < 1) @@ -1020,7 +1021,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die(_("destination path '%s' already exists and is not " "an empty directory."), dir); - strbuf_addf(&reflog_msg, "clone: from %s", repo); + strbuf_addf(&reflog_msg, "clone: from %s", + display_repo ? display_repo : repo); + free(display_repo); if (option_bare) work_tree = NULL; @@ -1108,7 +1111,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } } - init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, INIT_DB_QUIET); + init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL, + INIT_DB_QUIET); if (real_git_dir) git_dir = real_git_dir; @@ -1217,6 +1221,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) refs = transport_get_remote_refs(transport, &ref_prefixes); if (refs) { + int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); + + /* + * Now that we know what algorithm the remote side is using, + * let's set ours to the same thing. + */ + initialize_repository_version(hash_algo); + repo_set_hash_algo(the_repository, hash_algo); + mapped_refs = wanted_peer_refs(refs, &remote->fetch); /* * transport_get_remote_refs() may return refs with null sha-1 @@ -1263,9 +1276,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head_points_at = NULL; remote_head = NULL; option_no_checkout = 1; - if (!option_bare) - install_branch_config(0, "master", option_origin, - "refs/heads/master"); + if (!option_bare) { + const char *branch = git_default_branch_name(); + char *ref = xstrfmt("refs/heads/%s", branch); + + install_branch_config(0, branch, option_origin, ref); + free(ref); + } } write_refspec_config(src_ref_prefix, our_head_points_at, diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 15fe60317c..f6797e2a9f 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -6,6 +6,8 @@ #include "repository.h" #include "commit-graph.h" #include "object-store.h" +#include "progress.h" +#include "tag.h" static char const * const builtin_commit_graph_usage[] = { N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), @@ -138,14 +140,37 @@ static int write_option_parse_split(const struct option *opt, const char *arg, return 0; } +static int read_one_commit(struct oidset *commits, struct progress *progress, + const char *hash) +{ + struct object *result; + struct object_id oid; + const char *end; + + if (parse_oid_hex(hash, &oid, &end)) + return error(_("unexpected non-hex object ID: %s"), hash); + + result = deref_tag(the_repository, parse_object(the_repository, &oid), + NULL, 0); + if (!result) + return error(_("invalid object: %s"), hash); + else if (object_as_type(result, OBJ_COMMIT, 1)) + oidset_insert(commits, &result->oid); + + display_progress(progress, oidset_size(commits)); + + return 0; +} + static int graph_write(int argc, const char **argv) { - struct string_list *pack_indexes = NULL; + struct string_list pack_indexes = STRING_LIST_INIT_NODUP; + struct strbuf buf = STRBUF_INIT; struct oidset commits = OIDSET_INIT; struct object_directory *odb = NULL; - struct string_list lines; int result = 0; enum commit_graph_write_flags flags = 0; + struct progress *progress = NULL; static struct option builtin_commit_graph_write_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, @@ -209,44 +234,38 @@ static int graph_write(int argc, const char **argv) return 0; } - string_list_init(&lines, 0); - if (opts.stdin_packs || opts.stdin_commits) { - struct strbuf buf = STRBUF_INIT; - + if (opts.stdin_packs) { while (strbuf_getline(&buf, stdin) != EOF) - string_list_append(&lines, strbuf_detach(&buf, NULL)); - - if (opts.stdin_packs) - pack_indexes = &lines; - if (opts.stdin_commits) { - struct string_list_item *item; - oidset_init(&commits, lines.nr); - for_each_string_list_item(item, &lines) { - struct object_id oid; - const char *end; - - if (parse_oid_hex(item->string, &oid, &end)) { - error(_("unexpected non-hex object ID: " - "%s"), item->string); - return 1; - } - - oidset_insert(&commits, &oid); + string_list_append(&pack_indexes, + strbuf_detach(&buf, NULL)); + } else if (opts.stdin_commits) { + oidset_init(&commits, 0); + if (opts.progress) + progress = start_delayed_progress( + _("Collecting commits from input"), 0); + + while (strbuf_getline(&buf, stdin) != EOF) { + if (read_one_commit(&commits, progress, buf.buf)) { + result = 1; + goto cleanup; } - flags |= COMMIT_GRAPH_WRITE_CHECK_OIDS; } - UNLEAK(buf); + } if (write_commit_graph(odb, - pack_indexes, + opts.stdin_packs ? &pack_indexes : NULL, opts.stdin_commits ? &commits : NULL, flags, &split_opts)) result = 1; - UNLEAK(lines); +cleanup: + string_list_clear(&pack_indexes, 0); + strbuf_release(&buf); + if (progress) + stop_progress(&progress); return result; } diff --git a/builtin/config.c b/builtin/config.c index ee4aef6a35..5e39f61885 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -672,7 +672,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) given_config_source.file = git_pathdup("config"); given_config_source.scope = CONFIG_SCOPE_LOCAL; } else if (use_worktree_config) { - struct worktree **worktrees = get_worktrees(0); + struct worktree **worktrees = get_worktrees(); if (repository_format_worktree_config) given_config_source.file = git_pathdup("config.worktree"); else if (worktrees[0] && worktrees[1]) diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 86ae474fbf..1e352dd8f7 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -28,6 +28,13 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; + + /* + * Consider "intent-to-add" files as new by default, unless + * explicitly specified in the command line or anywhere else. + */ + rev.diffopt.ita_invisible_in_index = 1; + precompose_argv(argc, argv); argc = setup_revisions(argc, argv, &rev, NULL); diff --git a/builtin/diff.c b/builtin/diff.c index 8537b17bd5..cb98811c21 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -6,6 +6,7 @@ #define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" +#include "ewah/ewok.h" #include "lockfile.h" #include "color.h" #include "commit.h" @@ -23,7 +24,13 @@ #define DIFF_NO_INDEX_IMPLICIT 2 static const char builtin_diff_usage[] = -"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]"; +"git diff [<options>] [<commit>] [--] [<path>...]\n" +" or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n" +" or: git diff [<options>] <commit> [<commit>...] <commit> [--] [<path>...]\n" +" or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n" +" or: git diff [<options>] <blob> <blob>]\n" +" or: git diff [<options>] --no-index [--] <path> <path>]\n" +COMMON_DIFF_OPTIONS_HELP; static const char *blob_path(struct object_array_entry *entry) { @@ -254,6 +261,108 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv return run_diff_files(revs, options); } +struct symdiff { + struct bitmap *skip; + int warn; + const char *base, *left, *right; +}; + +/* + * Check for symmetric-difference arguments, and if present, arrange + * everything we need to know to handle them correctly. As a bonus, + * weed out all bogus range-based revision specifications, e.g., + * "git diff A..B C..D" or "git diff A..B C" get rejected. + * + * For an actual symmetric diff, *symdiff is set this way: + * + * - its skip is non-NULL and marks *all* rev->pending.objects[i] + * indices that the caller should ignore (extra merge bases, of + * which there might be many, and A in A...B). Note that the + * chosen merge base and right side are NOT marked. + * - warn is set if there are multiple merge bases. + * - base, left, and right point to the names to use in a + * warning about multiple merge bases. + * + * If there is no symmetric diff argument, sym->skip is NULL and + * sym->warn is cleared. The remaining fields are not set. + */ +static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym) +{ + int i, is_symdiff = 0, basecount = 0, othercount = 0; + int lpos = -1, rpos = -1, basepos = -1; + struct bitmap *map = NULL; + + /* + * Use the whence fields to find merge bases and left and + * right parts of symmetric difference, so that we do not + * depend on the order that revisions are parsed. If there + * are any revs that aren't from these sources, we have a + * "git diff C A...B" or "git diff A...B C" case. Or we + * could even get "git diff A...B C...E", for instance. + * + * If we don't have just one merge base, we pick one + * at random. + * + * NB: REV_CMD_LEFT, REV_CMD_RIGHT are also used for A..B, + * so we must check for SYMMETRIC_LEFT too. The two arrays + * rev->pending.objects and rev->cmdline.rev are parallel. + */ + for (i = 0; i < rev->cmdline.nr; i++) { + struct object *obj = rev->pending.objects[i].item; + switch (rev->cmdline.rev[i].whence) { + case REV_CMD_MERGE_BASE: + if (basepos < 0) + basepos = i; + basecount++; + break; /* do mark all bases */ + case REV_CMD_LEFT: + if (lpos >= 0) + usage(builtin_diff_usage); + lpos = i; + if (obj->flags & SYMMETRIC_LEFT) { + is_symdiff = 1; + break; /* do mark A */ + } + continue; + case REV_CMD_RIGHT: + if (rpos >= 0) + usage(builtin_diff_usage); + rpos = i; + continue; /* don't mark B */ + case REV_CMD_PARENTS_ONLY: + case REV_CMD_REF: + case REV_CMD_REV: + othercount++; + continue; + } + if (map == NULL) + map = bitmap_new(); + bitmap_set(map, i); + } + + /* + * Forbid any additional revs for both A...B and A..B. + */ + if (lpos >= 0 && othercount > 0) + usage(builtin_diff_usage); + + if (!is_symdiff) { + bitmap_free(map); + sym->warn = 0; + sym->skip = NULL; + return; + } + + sym->left = rev->pending.objects[lpos].name; + sym->right = rev->pending.objects[rpos].name; + if (basecount == 0) + die(_("%s...%s: no merge base"), sym->left, sym->right); + sym->base = rev->pending.objects[basepos].name; + bitmap_unset(map, basepos); /* unmark the base we want */ + sym->warn = basecount > 1; + sym->skip = map; +} + int cmd_diff(int argc, const char **argv, const char *prefix) { int i; @@ -263,19 +372,29 @@ int cmd_diff(int argc, const char **argv, const char *prefix) struct object_array_entry *blob[2]; int nongit = 0, no_index = 0; int result = 0; + struct symdiff sdiff; /* * We could get N tree-ish in the rev.pending_objects list. - * Also there could be M blobs there, and P pathspecs. + * Also there could be M blobs there, and P pathspecs. --cached may + * also be present. * * N=0, M=0: - * cache vs files (diff-files) + * cache vs files (diff-files) + * + * N=0, M=0, --cached: + * HEAD vs cache (diff-index --cached) + * * N=0, M=2: * compare two random blobs. P must be zero. + * * N=0, M=1, P=1: - * compare a blob with a working tree file. + * compare a blob with a working tree file. * * N=1, M=0: + * tree vs files (diff-index) + * + * N=1, M=0, --cached: * tree vs cache (diff-index --cached) * * N=2, M=0: @@ -382,6 +501,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } } + symdiff_prepare(&rev, &sdiff); for (i = 0; i < rev.pending.nr; i++) { struct object_array_entry *entry = &rev.pending.objects[i]; struct object *obj = entry->item; @@ -396,6 +516,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) obj = &get_commit_tree(((struct commit *)obj))->object; if (obj->type == OBJ_TREE) { + if (sdiff.skip && bitmap_get(sdiff.skip, i)) + continue; obj->flags |= flags; add_object_array(obj, name, &ent); } else if (obj->type == OBJ_BLOB) { @@ -437,21 +559,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) usage(builtin_diff_usage); else if (ent.nr == 1) result = builtin_diff_index(&rev, argc, argv); - else if (ent.nr == 2) + else if (ent.nr == 2) { + if (sdiff.warn) + warning(_("%s...%s: multiple merge bases, using %s"), + sdiff.left, sdiff.right, sdiff.base); result = builtin_diff_tree(&rev, argc, argv, &ent.objects[0], &ent.objects[1]); - else if (ent.objects[0].item->flags & UNINTERESTING) { - /* - * diff A...B where there is at least one merge base - * between A and B. We have ent.objects[0] == - * merge-base, ent.objects[ents-2] == A, and - * ent.objects[ents-1] == B. Show diff between the - * base and B. Note that we pick one merge base at - * random if there are more than one. - */ - result = builtin_diff_tree(&rev, argc, argv, - &ent.objects[0], - &ent.objects[ent.nr-1]); } else result = builtin_diff_combined(&rev, argc, argv, ent.objects, ent.nr); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 85868162ee..9f37895d4c 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -45,6 +45,7 @@ static struct string_list extra_refs = STRING_LIST_INIT_NODUP; static struct string_list tag_refs = STRING_LIST_INIT_NODUP; static struct refspec refspecs = REFSPEC_INIT_FETCH; static int anonymize; +static struct hashmap anonymized_seeds; static struct revision_sources revision_sources; static int parse_opt_signed_tag_mode(const struct option *opt, @@ -120,24 +121,33 @@ static int has_unshown_parent(struct commit *commit) struct anonymized_entry { struct hashmap_entry hash; + const char *anon; + const char orig[FLEX_ARRAY]; +}; + +struct anonymized_entry_key { + struct hashmap_entry hash; const char *orig; size_t orig_len; - const char *anon; - size_t anon_len; }; static int anonymized_entry_cmp(const void *unused_cmp_data, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata) { const struct anonymized_entry *a, *b; a = container_of(eptr, const struct anonymized_entry, hash); - b = container_of(entry_or_key, const struct anonymized_entry, hash); + if (keydata) { + const struct anonymized_entry_key *key = keydata; + int equal = !strncmp(a->orig, key->orig, key->orig_len) && + !a->orig[key->orig_len]; + return !equal; + } - return a->orig_len != b->orig_len || - memcmp(a->orig, b->orig, a->orig_len); + b = container_of(entry_or_key, const struct anonymized_entry, hash); + return strcmp(a->orig, b->orig); } /* @@ -145,31 +155,39 @@ static int anonymized_entry_cmp(const void *unused_cmp_data, * the same anonymized string with another. The actual generation * is farmed out to the generate function. */ -static const void *anonymize_mem(struct hashmap *map, - void *(*generate)(const void *, size_t *), - const void *orig, size_t *len) +static const char *anonymize_str(struct hashmap *map, + char *(*generate)(void *), + const char *orig, size_t len, + void *data) { - struct anonymized_entry key, *ret; + struct anonymized_entry_key key; + struct anonymized_entry *ret; if (!map->cmpfn) hashmap_init(map, anonymized_entry_cmp, NULL, 0); - hashmap_entry_init(&key.hash, memhash(orig, *len)); + hashmap_entry_init(&key.hash, memhash(orig, len)); key.orig = orig; - key.orig_len = *len; - ret = hashmap_get_entry(map, &key, hash, NULL); + key.orig_len = len; + + /* First check if it's a token the user configured manually... */ + if (anonymized_seeds.cmpfn) + ret = hashmap_get_entry(&anonymized_seeds, &key, hash, &key); + else + ret = NULL; + + /* ...otherwise check if we've already seen it in this context... */ + if (!ret) + ret = hashmap_get_entry(map, &key, hash, &key); + /* ...and finally generate a new mapping if necessary */ if (!ret) { - ret = xmalloc(sizeof(*ret)); + FLEX_ALLOC_MEM(ret, orig, orig, len); hashmap_entry_init(&ret->hash, key.hash.hash); - ret->orig = xstrdup(orig); - ret->orig_len = *len; - ret->anon = generate(orig, len); - ret->anon_len = *len; + ret->anon = generate(data); hashmap_put(map, &ret->hash); } - *len = ret->anon_len; return ret->anon; } @@ -181,13 +199,13 @@ static const void *anonymize_mem(struct hashmap *map, */ static void anonymize_path(struct strbuf *out, const char *path, struct hashmap *map, - void *(*generate)(const void *, size_t *)) + char *(*generate)(void *)) { while (*path) { const char *end_of_component = strchrnul(path, '/'); size_t len = end_of_component - path; - const char *c = anonymize_mem(map, generate, path, &len); - strbuf_add(out, c, len); + const char *c = anonymize_str(map, generate, path, len, NULL); + strbuf_addstr(out, c); path = end_of_component; if (*path) strbuf_addch(out, *path++); @@ -361,12 +379,12 @@ static void print_path_1(const char *path) printf("%s", path); } -static void *anonymize_path_component(const void *path, size_t *len) +static char *anonymize_path_component(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "path%d", counter++); - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } static void print_path(const char *path) @@ -383,20 +401,23 @@ static void print_path(const char *path) } } -static void *generate_fake_oid(const void *old, size_t *len) +static char *generate_fake_oid(void *data) { static uint32_t counter = 1; /* avoid null oid */ const unsigned hashsz = the_hash_algo->rawsz; - unsigned char *out = xcalloc(hashsz, 1); + unsigned char out[GIT_MAX_RAWSZ]; + char *hex = xmallocz(GIT_MAX_HEXSZ); + + hashclr(out); put_be32(out + hashsz - 4, counter++); - return out; + return hash_to_hex_algop_r(hex, out, the_hash_algo); } -static const struct object_id *anonymize_oid(const struct object_id *oid) +static const char *anonymize_oid(const char *oid_hex) { static struct hashmap objs; - size_t len = the_hash_algo->rawsz; - return anonymize_mem(&objs, generate_fake_oid, oid, &len); + size_t len = strlen(oid_hex); + return anonymize_str(&objs, generate_fake_oid, oid_hex, len, NULL); } static void show_filemodify(struct diff_queue_struct *q, @@ -455,9 +476,9 @@ static void show_filemodify(struct diff_queue_struct *q, */ if (no_data || S_ISGITLINK(spec->mode)) printf("M %06o %s ", spec->mode, - oid_to_hex(anonymize ? - anonymize_oid(&spec->oid) : - &spec->oid)); + anonymize ? + anonymize_oid(oid_to_hex(&spec->oid)) : + oid_to_hex(&spec->oid)); else { struct object *object = lookup_object(the_repository, &spec->oid); @@ -493,12 +514,12 @@ static const char *find_encoding(const char *begin, const char *end) return bol; } -static void *anonymize_ref_component(const void *old, size_t *len) +static char *anonymize_ref_component(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "ref%d", counter++); - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } static const char *anonymize_refname(const char *refname) @@ -517,13 +538,6 @@ static const char *anonymize_refname(const char *refname) static struct strbuf anon = STRBUF_INIT; int i; - /* - * We also leave "master" as a special case, since it does not reveal - * anything interesting. - */ - if (!strcmp(refname, "refs/heads/master")) - return refname; - strbuf_reset(&anon); for (i = 0; i < ARRAY_SIZE(prefixes); i++) { if (skip_prefix(refname, prefixes[i], &refname)) { @@ -546,14 +560,13 @@ static char *anonymize_commit_message(const char *old) return xstrfmt("subject %d\n\nbody\n", counter++); } -static struct hashmap idents; -static void *anonymize_ident(const void *old, size_t *len) +static char *anonymize_ident(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "User %d <user%d@example.com>", counter, counter); counter++; - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } /* @@ -563,6 +576,7 @@ static void *anonymize_ident(const void *old, size_t *len) */ static void anonymize_ident_line(const char **beg, const char **end) { + static struct hashmap idents; static struct strbuf buffers[] = { STRBUF_INIT, STRBUF_INIT }; static unsigned which_buffer; @@ -588,9 +602,9 @@ static void anonymize_ident_line(const char **beg, const char **end) size_t len; len = split.mail_end - split.name_begin; - ident = anonymize_mem(&idents, anonymize_ident, - split.name_begin, &len); - strbuf_add(out, ident, len); + ident = anonymize_str(&idents, anonymize_ident, + split.name_begin, len, NULL); + strbuf_addstr(out, ident); strbuf_addch(out, ' '); strbuf_add(out, split.date_begin, split.tz_end - split.date_begin); } else { @@ -712,9 +726,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, if (mark) printf(":%d\n", mark); else - printf("%s\n", oid_to_hex(anonymize ? - anonymize_oid(&obj->oid) : - &obj->oid)); + printf("%s\n", + anonymize ? + anonymize_oid(oid_to_hex(&obj->oid)) : + oid_to_hex(&obj->oid)); i++; } @@ -729,12 +744,12 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, show_progress(); } -static void *anonymize_tag(const void *old, size_t *len) +static char *anonymize_tag(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "tag message %d", counter++); - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } static void handle_tail(struct object_array *commits, struct rev_info *revs, @@ -804,8 +819,8 @@ static void handle_tag(const char *name, struct tag *tag) name = anonymize_refname(name); if (message) { static struct hashmap tags; - message = anonymize_mem(&tags, anonymize_tag, - message, &message_size); + message = anonymize_str(&tags, anonymize_tag, + message, message_size, NULL); } } @@ -1136,6 +1151,37 @@ static void handle_deletes(void) } } +static char *anonymize_seed(void *data) +{ + return xstrdup(data); +} + +static int parse_opt_anonymize_map(const struct option *opt, + const char *arg, int unset) +{ + struct hashmap *map = opt->value; + const char *delim, *value; + size_t keylen; + + BUG_ON_OPT_NEG(unset); + + delim = strchr(arg, ':'); + if (delim) { + keylen = delim - arg; + value = delim + 1; + } else { + keylen = strlen(arg); + value = arg; + } + + if (!keylen || !*value) + return error(_("--anonymize-map token cannot be empty")); + + anonymize_str(map, anonymize_seed, arg, keylen, (void *)value); + + return 0; +} + int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1177,6 +1223,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), N_("Apply refspec to exported refs")), OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")), + OPT_CALLBACK_F(0, "anonymize-map", &anonymized_seeds, N_("from:to"), + N_("convert <from> to <to> in anonymized output"), + PARSE_OPT_NONEG, parse_opt_anonymize_map), OPT_BOOL(0, "reference-excluded-parents", &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")), OPT_BOOL(0, "show-original-ids", &show_original_ids, @@ -1204,6 +1253,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (argc > 1) usage_with_options (fast_export_usage, options); + if (anonymized_seeds.cmpfn && !anonymize) + die(_("--anonymize-map without --anonymize does not make sense")); + if (refspecs_list.nr) { int i; diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 4771100072..bbb5c96167 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -48,8 +48,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct ref **sought = NULL; int nr_sought = 0, alloc_sought = 0; int fd[2]; - char *pack_lockfile = NULL; - char **pack_lockfile_ptr = NULL; + struct string_list pack_lockfiles = STRING_LIST_INIT_DUP; + struct string_list *pack_lockfiles_ptr = NULL; struct child_process *conn; struct fetch_pack_args args; struct oid_array shallow = OID_ARRAY_INIT; @@ -134,7 +134,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) } if (!strcmp("--lock-pack", arg)) { args.lock_pack = 1; - pack_lockfile_ptr = &pack_lockfile; + pack_lockfiles_ptr = &pack_lockfiles; continue; } if (!strcmp("--check-self-contained-and-connected", arg)) { @@ -224,7 +224,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) version = discover_version(&reader); switch (version) { case protocol_v2: - get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL); + get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc); break; case protocol_v1: case protocol_v0: @@ -235,10 +235,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) } ref = fetch_pack(&args, fd, ref, sought, nr_sought, - &shallow, pack_lockfile_ptr, version); - if (pack_lockfile) { - printf("lock %s\n", pack_lockfile); + &shallow, pack_lockfiles_ptr, version); + if (pack_lockfiles.nr) { + int i; + + printf("lock %s\n", pack_lockfiles.items[0].string); fflush(stdout); + for (i = 1; i < pack_lockfiles.nr; i++) + warning(_("Lockfile created but not reported: %s"), + pack_lockfiles.items[i].string); } if (args.check_self_contained_and_connected && args.self_contained_and_connected) { diff --git a/builtin/fetch.c b/builtin/fetch.c index b5788c16bf..82ac4be8a5 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1758,8 +1758,13 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) /* Record the command line for the reflog */ strbuf_addstr(&default_rla, "fetch"); - for (i = 1; i < argc; i++) - strbuf_addf(&default_rla, " %s", argv[i]); + for (i = 1; i < argc; i++) { + /* This handles non-URLs gracefully */ + char *anon = transport_anonymize_url(argv[i]); + + strbuf_addf(&default_rla, " %s", anon); + free(anon); + } fetch_config_from_gitmodules(&submodule_fetch_jobs_config, &recurse_submodules); @@ -1790,9 +1795,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth || deepen_since || deepen_not.nr) deepen = 1; - if (filter_options.choice && !has_promisor_remote()) - die("--filter can only be used when extensions.partialClone is set"); - if (all) { if (argc == 1) die(_("fetch --all does not take a repository argument")); diff --git a/builtin/fsck.c b/builtin/fsck.c index f02cbdb439..37aa07da78 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -241,7 +241,7 @@ static void mark_unreachable_referents(const struct object_id *oid) enum object_type type = oid_object_info(the_repository, &obj->oid, NULL); if (type > 0) - object_as_type(the_repository, obj, type, 0); + object_as_type(obj, type, 0); } options.walk = mark_used; @@ -577,7 +577,7 @@ static void get_default_heads(void) for_each_rawref(fsck_handle_ref, NULL); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); for (p = worktrees; *p; p++) { struct worktree *wt = *p; struct strbuf ref = STRBUF_INIT; diff --git a/builtin/index-pack.c b/builtin/index-pack.c index f176dd28c8..f865666db9 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1555,13 +1555,9 @@ static void read_v2_anomalous_offsets(struct packed_git *p, { const uint32_t *idx1, *idx2; uint32_t i; - const uint32_t hashwords = the_hash_algo->rawsz / sizeof(uint32_t); /* The address of the 4-byte offset table */ - idx1 = (((const uint32_t *)p->index_data) - + 2 /* 8-byte header */ - + 256 /* fan out */ - + hashwords * p->num_objects /* object ID table */ + idx1 = (((const uint32_t *)((const uint8_t *)p->index_data + p->crc_offset)) + p->num_objects /* CRC32 table */ ); @@ -1671,6 +1667,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) unsigned char pack_hash[GIT_MAX_RAWSZ]; unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */ int report_end_of_input = 0; + int hash_algo = 0; /* * index-pack never needs to fetch missing objects except when @@ -1764,6 +1761,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) die(_("bad %s"), arg); } else if (skip_prefix(arg, "--max-input-size=", &arg)) { max_input_size = strtoumax(arg, NULL, 10); + } else if (skip_prefix(arg, "--object-format=", &arg)) { + hash_algo = hash_algo_by_name(arg); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), arg); + repo_set_hash_algo(the_repository, hash_algo); } else usage(index_pack_usage); continue; @@ -1780,6 +1782,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) die(_("--fix-thin cannot be used without --stdin")); if (from_stdin && !startup_info->have_repository) die(_("--stdin requires a git repository")); + if (from_stdin && hash_algo) + die(_("--object-format cannot be used with --stdin")); if (!index_name && pack_name) index_name = derive_filename(pack_name, "idx", &index_name_buf); diff --git a/builtin/init-db.c b/builtin/init-db.c index 0b7222e718..cee64823cb 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -203,6 +203,7 @@ void initialize_repository_version(int hash_algo) static int create_default_files(const char *template_path, const char *original_git_dir, + const char *initial_branch, const struct repository_format *fmt) { struct stat st1; @@ -258,15 +259,26 @@ static int create_default_files(const char *template_path, die("failed to set up refs db: %s", err.buf); /* - * Create the default symlink from ".git/HEAD" to the "master" - * branch, if it does not exist yet. + * Point the HEAD symref to the initial branch with if HEAD does + * not yet exist. */ path = git_path_buf(&buf, "HEAD"); reinit = (!access(path, R_OK) || readlink(path, junk, sizeof(junk)-1) != -1); if (!reinit) { - if (create_symref("HEAD", "refs/heads/master", NULL) < 0) + char *ref; + + if (!initial_branch) + initial_branch = git_default_branch_name(); + + ref = xstrfmt("refs/heads/%s", initial_branch); + if (check_refname_format(ref, 0) < 0) + die(_("invalid initial branch name: '%s'"), + initial_branch); + + if (create_symref("HEAD", ref, NULL) < 0) exit(1); + free(ref); } initialize_repository_version(fmt->hash_algo); @@ -383,7 +395,8 @@ static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash } int init_db(const char *git_dir, const char *real_git_dir, - const char *template_dir, int hash, unsigned int flags) + const char *template_dir, int hash, const char *initial_branch, + unsigned int flags) { int reinit; int exist_ok = flags & INIT_DB_EXIST_OK; @@ -425,7 +438,11 @@ int init_db(const char *git_dir, const char *real_git_dir, validate_hash_algorithm(&repo_fmt, hash); - reinit = create_default_files(template_dir, original_git_dir, &repo_fmt); + reinit = create_default_files(template_dir, original_git_dir, + initial_branch, &repo_fmt); + if (reinit && initial_branch) + warning(_("re-init: ignored --initial-branch=%s"), + initial_branch); create_object_directory(); @@ -528,6 +545,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *template_dir = NULL; unsigned int flags = 0; const char *object_format = NULL; + const char *initial_branch = NULL; int hash_algo = GIT_HASH_UNKNOWN; const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, N_("template-directory"), @@ -541,6 +559,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), + OPT_STRING('b', "initial-branch", &initial_branch, N_("name"), + N_("override the name of the initial branch")), OPT_STRING(0, "object-format", &object_format, N_("hash"), N_("specify the hash algorithm to use")), OPT_END() @@ -652,5 +672,6 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) UNLEAK(work_tree); flags |= INIT_DB_EXIST_OK; - return init_db(git_dir, real_git_dir, template_dir, hash_algo, flags); + return init_db(git_dir, real_git_dir, template_dir, hash_algo, + initial_branch, flags); } diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 6ef519514b..3a4dd12903 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -118,6 +118,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) transport->server_options = &server_options; ref = transport_get_remote_refs(transport, &ref_prefixes); + if (ref) { + int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); + repo_set_hash_algo(the_repository, hash_algo); + } if (transport_disconnect(transport)) { UNLEAK(sorting); return 1; diff --git a/builtin/merge.c b/builtin/merge.c index ca6a5dc4bf..7da707bf55 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -1656,7 +1656,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } merge_was_ok = 1; } - cnt = evaluate_result(); + cnt = (use_strategies_nr > 1) ? evaluate_result() : 0; if (best_cnt <= 0 || cnt <= best_cnt) { best_strategy = use_strategies[i]->name; best_cnt = cnt; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index c5b433a23f..7016b28485 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -117,6 +117,8 @@ static unsigned long window_memory_limit = 0; static struct list_objects_filter_options filter_options; +static struct string_list uri_protocols = STRING_LIST_INIT_NODUP; + enum missing_action { MA_ERROR = 0, /* fail if any missing objects are encountered */ MA_ALLOW_ANY, /* silently allow ALL missing objects */ @@ -125,6 +127,15 @@ enum missing_action { static enum missing_action arg_missing_action; static show_object_fn fn_show_object; +struct configured_exclusion { + struct oidmap_entry e; + char *pack_hash_hex; + char *uri; +}; +static struct oidmap configured_exclusions; + +static struct oidset excluded_by_config; + /* * stats */ @@ -969,6 +980,25 @@ static void write_reused_pack(struct hashfile *f) unuse_pack(&w_curs); } +static void write_excluded_by_configs(void) +{ + struct oidset_iter iter; + const struct object_id *oid; + + oidset_iter_init(&excluded_by_config, &iter); + while ((oid = oidset_iter_next(&iter))) { + struct configured_exclusion *ex = + oidmap_get(&configured_exclusions, oid); + + if (!ex) + BUG("configured exclusion wasn't configured"); + write_in_full(1, ex->pack_hash_hex, strlen(ex->pack_hash_hex)); + write_in_full(1, " ", 1); + write_in_full(1, ex->uri, strlen(ex->uri)); + write_in_full(1, "\n", 1); + } +} + static const char no_split_warning[] = N_( "disabling bitmap writing, packs are split due to pack.packSizeLimit" ); @@ -1266,6 +1296,25 @@ static int want_object_in_pack(const struct object_id *oid, } } + if (uri_protocols.nr) { + struct configured_exclusion *ex = + oidmap_get(&configured_exclusions, oid); + int i; + const char *p; + + if (ex) { + for (i = 0; i < uri_protocols.nr; i++) { + if (skip_prefix(ex->uri, + uri_protocols.items[i].string, + &p) && + *p == ':') { + oidset_insert(&excluded_by_config, oid); + return 0; + } + } + } + } + return 1; } @@ -2864,6 +2913,29 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_idx_opts.version); return 0; } + if (!strcmp(k, "uploadpack.blobpackfileuri")) { + struct configured_exclusion *ex = xmalloc(sizeof(*ex)); + const char *oid_end, *pack_end; + /* + * Stores the pack hash. This is not a true object ID, but is + * of the same form. + */ + struct object_id pack_hash; + + if (parse_oid_hex(v, &ex->e.oid, &oid_end) || + *oid_end != ' ' || + parse_oid_hex(oid_end + 1, &pack_hash, &pack_end) || + *pack_end != ' ') + die(_("value of uploadpack.blobpackfileuri must be " + "of the form '<object-hash> <pack-hash> <uri>' (got '%s')"), v); + if (oidmap_get(&configured_exclusions, &ex->e.oid)) + die(_("object already configured in another " + "uploadpack.blobpackfileuri (got '%s')"), v); + ex->pack_hash_hex = xcalloc(1, pack_end - oid_end); + memcpy(ex->pack_hash_hex, oid_end + 1, pack_end - oid_end - 1); + ex->uri = xstrdup(pack_end + 1); + oidmap_put(&configured_exclusions, ex); + } return git_default_config(k, v, cb); } @@ -3462,6 +3534,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) N_("do not pack objects in promisor packfiles")), OPT_BOOL(0, "delta-islands", &use_delta_islands, N_("respect islands during delta compression")), + OPT_STRING_LIST(0, "uri-protocol", &uri_protocols, + N_("protocol"), + N_("exclude any configured uploadpack.blobpackfileuri with this protocol")), OPT_END(), }; @@ -3650,6 +3725,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } trace2_region_enter("pack-objects", "write-pack-file", the_repository); + write_excluded_by_configs(); write_pack_file(); trace2_region_leave("pack-objects", "write-pack-file", the_repository); diff --git a/builtin/pull.c b/builtin/pull.c index 00e5857a8d..8159c5d7c9 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -1025,12 +1025,14 @@ int cmd_pull(int argc, const char **argv, const char *prefix) commit_list_insert(head, &list); merge_head = lookup_commit_reference(the_repository, &merge_heads.oid[0]); - if (is_descendant_of(merge_head, list)) { + if (repo_is_descendant_of(the_repository, + merge_head, list)) { /* we can fast-forward this without invoking rebase */ opt_ff = "--ff-only"; ran_ff = 1; ret = run_merge(); } + free_commit_list(list); } if (!ran_ff) ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index ea3d0f01af..d43663bb0a 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -249,6 +249,7 @@ static void show_ref(const char *path, const struct object_id *oid) strbuf_addf(&cap, " push-cert=%s", push_cert_nonce); if (advertise_push_options) strbuf_addstr(&cap, " push-options"); + strbuf_addf(&cap, " object-format=%s", the_hash_algo->name); strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); packet_write_fmt(1, "%s %s%c%s\n", oid_to_hex(oid), path, 0, cap.buf); @@ -1624,6 +1625,8 @@ static struct command *read_head_info(struct packet_reader *reader, linelen = strlen(reader->line); if (linelen < reader->pktlen) { const char *feature_list = reader->line + linelen + 1; + const char *hash = NULL; + int len = 0; if (parse_feature_request(feature_list, "report-status")) report_status = 1; if (parse_feature_request(feature_list, "side-band-64k")) @@ -1636,6 +1639,13 @@ static struct command *read_head_info(struct packet_reader *reader, if (advertise_push_options && parse_feature_request(feature_list, "push-options")) use_push_options = 1; + hash = parse_feature_value(feature_list, "object-format", &len, NULL); + if (!hash) { + hash = hash_algos[GIT_HASH_SHA1].name; + len = strlen(hash); + } + if (xstrncmpz(the_hash_algo->name, hash, len)) + die("error: unsupported object format '%s'", hash); } if (!strcmp(reader->line, "push-cert")) { diff --git a/builtin/reflog.c b/builtin/reflog.c index 52ecf6d43c..ca1d8079f3 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -615,7 +615,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) int i; memset(&collected, 0, sizeof(collected)); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); for (p = worktrees; *p; p++) { if (!all_worktrees && !(*p)->is_current) continue; diff --git a/builtin/show-index.c b/builtin/show-index.c index 0826f6a5a2..8106b03a6b 100644 --- a/builtin/show-index.c +++ b/builtin/show-index.c @@ -1,9 +1,12 @@ #include "builtin.h" #include "cache.h" #include "pack.h" +#include "parse-options.h" -static const char show_index_usage[] = -"git show-index"; +static const char *const show_index_usage[] = { + "git show-index [--object-format=<hash-algorithm>]", + NULL +}; int cmd_show_index(int argc, const char **argv, const char *prefix) { @@ -11,10 +14,26 @@ int cmd_show_index(int argc, const char **argv, const char *prefix) unsigned nr; unsigned int version; static unsigned int top_index[256]; - const unsigned hashsz = the_hash_algo->rawsz; + unsigned hashsz; + const char *hash_name = NULL; + int hash_algo; + const struct option show_index_options[] = { + OPT_STRING(0, "object-format", &hash_name, N_("hash-algorithm"), + N_("specify the hash algorithm to use")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, show_index_options, show_index_usage, 0); + + if (hash_name) { + hash_algo = hash_algo_by_name(hash_name); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("Unknown hash algorithm")); + repo_set_hash_algo(the_repository, hash_algo); + } + + hashsz = the_hash_algo->rawsz; - if (argc != 1) - usage(show_index_usage); if (fread(top_index, 2 * 4, 1, stdin) != 1) die("unable to read header"); if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) { diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 95d0882417..4003f4d13a 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -99,6 +99,10 @@ static int update_working_directory(struct pattern_list *pl) struct lock_file lock_file = LOCK_INIT; struct repository *r = the_repository; + /* If no branch has been checked out, there are no updates to make. */ + if (is_index_unborn(r->index)) + return UPDATE_SPARSITY_SUCCESS; + memset(&o, 0, sizeof(o)); o.verbose_update = isatty(2); o.update = 1; @@ -249,6 +253,8 @@ static int set_config(enum sparse_checkout_mode mode) { const char *config_path; + if (upgrade_repository_format(1) < 0) + die(_("unable to upgrade repository format to enable worktreeConfig")); if (git_config_set_gently("extensions.worktreeConfig", "true")) { error(_("failed to set extensions.worktreeConfig setting")); return 1; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 46c03d2a12..a1c75607c7 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1981,7 +1981,7 @@ static const char *remote_submodule_branch(const char *path) free(key); if (!branch) - return "master"; + return "HEAD"; if (!strcmp(branch, ".")) { const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); @@ -2277,6 +2277,49 @@ static int module_set_url(int argc, const char **argv, const char *prefix) return 0; } +static int module_set_branch(int argc, const char **argv, const char *prefix) +{ + int opt_default = 0, ret; + const char *opt_branch = NULL; + const char *path; + char *config_name; + + /* + * We accept the `quiet` option for uniformity across subcommands, + * though there is nothing to make less verbose in this subcommand. + */ + struct option options[] = { + OPT_NOOP_NOARG('q', "quiet"), + OPT_BOOL('d', "default", &opt_default, + N_("set the default tracking branch to master")), + OPT_STRING('b', "branch", &opt_branch, N_("branch"), + N_("set the default tracking branch")), + OPT_END() + }; + const char *const usage[] = { + N_("git submodule--helper set-branch [-q|--quiet] (-d|--default) <path>"), + N_("git submodule--helper set-branch [-q|--quiet] (-b|--branch) <branch> <path>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (!opt_branch && !opt_default) + die(_("--branch or --default required")); + + if (opt_branch && opt_default) + die(_("--branch and --default are mutually exclusive")); + + if (argc != 1 || !(path = argv[0])) + usage_with_options(usage, options); + + config_name = xstrfmt("submodule.%s.branch", path); + ret = config_set_in_gitmodules_file_gently(config_name, opt_branch); + + free(config_name); + return !!ret; +} + #define SUPPORT_SUPER_PREFIX (1<<0) struct cmd_struct { @@ -2308,6 +2351,7 @@ static struct cmd_struct commands[] = { {"check-name", check_name, 0}, {"config", module_config, 0}, {"set-url", module_set_url, 0}, + {"set-branch", module_set_branch, 0}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/builtin/worktree.c b/builtin/worktree.c index d99db35668..f0cbdef718 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -67,7 +67,12 @@ static void delete_worktrees_dir_if_empty(void) rmdir(git_path("worktrees")); /* ignore failed removal */ } -static int prune_worktree(const char *id, struct strbuf *reason) +/* + * Return true if worktree entry should be pruned, along with the reason for + * pruning. Otherwise, return false and the worktree's path, or NULL if it + * cannot be determined. Caller is responsible for freeing returned path. + */ +static int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath) { struct stat st; char *path; @@ -75,20 +80,21 @@ static int prune_worktree(const char *id, struct strbuf *reason) size_t len; ssize_t read_result; + *wtpath = NULL; if (!is_directory(git_path("worktrees/%s", id))) { - strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); + strbuf_addstr(reason, _("not a valid directory")); 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); + strbuf_addstr(reason, _("gitdir file does not exist")); 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)); + strbuf_addf(reason, _("unable to read gitdir file (%s)"), + strerror(errno)); return 1; } len = xsize_t(st.st_size); @@ -96,8 +102,8 @@ static int prune_worktree(const char *id, struct strbuf *reason) read_result = read_in_full(fd, path, len); if (read_result < 0) { - strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), - id, strerror(errno)); + strbuf_addf(reason, _("unable to read gitdir file (%s)"), + strerror(errno)); close(fd); free(path); return 1; @@ -106,53 +112,103 @@ static int prune_worktree(const char *id, struct strbuf *reason) if (read_result != len) { strbuf_addf(reason, - _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), - id, (uintmax_t)len, (uintmax_t)read_result); + _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), + (uintmax_t)len, (uintmax_t)read_result); free(path); return 1; } while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) len--; if (!len) { - strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); + strbuf_addstr(reason, _("invalid gitdir file")); free(path); return 1; } path[len] = '\0'; if (!file_exists(path)) { - free(path); if (stat(git_path("worktrees/%s/index", id), &st) || st.st_mtime <= expire) { - strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); + strbuf_addstr(reason, _("gitdir file points to non-existent location")); + free(path); return 1; } else { + *wtpath = path; return 0; } } - free(path); + *wtpath = path; return 0; } +static void prune_worktree(const char *id, const char *reason) +{ + if (show_only || verbose) + printf_ln(_("Removing %s/%s: %s"), "worktrees", id, reason); + if (!show_only) + delete_git_dir(id); +} + +static int prune_cmp(const void *a, const void *b) +{ + const struct string_list_item *x = a; + const struct string_list_item *y = b; + int c; + + if ((c = fspathcmp(x->string, y->string))) + return c; + /* + * paths same; prune_dupes() removes all but the first worktree entry + * having the same path, so sort main worktree ('util' is NULL) above + * linked worktrees ('util' not NULL) since main worktree can't be + * removed + */ + if (!x->util) + return -1; + if (!y->util) + return 1; + /* paths same; sort by .git/worktrees/<id> */ + return strcmp(x->util, y->util); +} + +static void prune_dups(struct string_list *l) +{ + int i; + + QSORT(l->items, l->nr, prune_cmp); + for (i = 1; i < l->nr; i++) { + if (!fspathcmp(l->items[i].string, l->items[i - 1].string)) + prune_worktree(l->items[i].util, "duplicate entry"); + } +} + static void prune_worktrees(void) { struct strbuf reason = STRBUF_INIT; + struct strbuf main_path = STRBUF_INIT; + struct string_list kept = STRING_LIST_INIT_NODUP; DIR *dir = opendir(git_path("worktrees")); struct dirent *d; if (!dir) return; while ((d = readdir(dir)) != NULL) { + char *path; if (is_dot_or_dotdot(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; - delete_git_dir(d->d_name); + if (should_prune_worktree(d->d_name, &reason, &path)) + prune_worktree(d->d_name, reason.buf); + else if (path) + string_list_append(&kept, path)->util = xstrdup(d->d_name); } closedir(dir); + + strbuf_add_absolute_path(&main_path, get_git_common_dir()); + /* massage main worktree absolute path to match 'gitdir' content */ + strbuf_strip_suffix(&main_path, "/."); + string_list_append(&kept, strbuf_detach(&main_path, NULL)); + prune_dups(&kept); + string_list_clear(&kept, 1); + if (!show_only) delete_worktrees_dir_if_empty(); strbuf_release(&reason); @@ -224,34 +280,33 @@ static const char *worktree_basename(const char *path, int *olen) return name; } -static void validate_worktree_add(const char *path, const struct add_opts *opts) +/* check that path is viable location for worktree */ +static void check_candidate_path(const char *path, + int force, + struct worktree **worktrees, + const char *cmd) { - struct worktree **worktrees; struct worktree *wt; int locked; if (file_exists(path) && !is_empty_dir(path)) die(_("'%s' already exists"), path); - worktrees = get_worktrees(0); wt = find_worktree_by_path(worktrees, path); if (!wt) - goto done; + return; locked = !!worktree_lock_reason(wt); - if ((!locked && opts->force) || (locked && opts->force > 1)) { + if ((!locked && force) || (locked && force > 1)) { if (delete_git_dir(wt->id)) - die(_("unable to re-add worktree '%s'"), path); - goto done; + die(_("unusable worktree destination '%s'"), path); + return; } if (locked) - die(_("'%s' is a missing but locked worktree;\nuse 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path); + die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), cmd, path); else - die(_("'%s' is a missing but already registered worktree;\nuse 'add -f' to override, or 'prune' or 'remove' to clear"), path); - -done: - free_worktrees(worktrees); + die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), cmd, path); } static int add_worktree(const char *path, const char *refname, @@ -268,8 +323,12 @@ static int add_worktree(const char *path, const char *refname, struct commit *commit = NULL; int is_branch = 0; struct strbuf sb_name = STRBUF_INIT; + struct worktree **worktrees; - validate_worktree_add(path, opts); + worktrees = get_worktrees(); + check_candidate_path(path, opts->force, worktrees, "add"); + free_worktrees(worktrees); + worktrees = NULL; /* is 'refname' a branch or commit? */ if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && @@ -638,6 +697,23 @@ static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen) } } +static int pathcmp(const void *a_, const void *b_) +{ + const struct worktree *const *a = a_; + const struct worktree *const *b = b_; + return fspathcmp((*a)->path, (*b)->path); +} + +static void pathsort(struct worktree **wt) +{ + int n = 0; + struct worktree **p = wt; + + while (*p++) + n++; + QSORT(wt, n, pathcmp); +} + static int list(int ac, const char **av, const char *prefix) { int porcelain = 0; @@ -651,9 +727,12 @@ static int list(int ac, const char **av, const char *prefix) if (ac) usage_with_options(worktree_usage, options); else { - struct worktree **worktrees = get_worktrees(GWT_SORT_LINKED); + struct worktree **worktrees = get_worktrees(); int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i; + /* sort worktrees by path but keep main worktree at top */ + pathsort(worktrees + 1); + if (!porcelain) measure_widths(worktrees, &abbrev, &path_maxlen); @@ -682,7 +761,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix) if (ac != 1) usage_with_options(worktree_usage, options); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -715,7 +794,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) if (ac != 1) usage_with_options(worktree_usage, options); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -789,7 +868,7 @@ static int move_worktree(int ac, const char **av, const char *prefix) strbuf_addstr(&dst, path); free(path); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -804,8 +883,7 @@ static int move_worktree(int ac, const char **av, const char *prefix) strbuf_trim_trailing_dir_sep(&dst); strbuf_addstr(&dst, sep); } - if (file_exists(dst.buf)) - die(_("target '%s' already exists"), dst.buf); + check_candidate_path(dst.buf, force, worktrees, "move"); validate_no_submodules(wt); @@ -916,7 +994,7 @@ static int remove_worktree(int ac, const char **av, const char *prefix) if (ac != 1) usage_with_options(worktree_usage, options); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); |
