[PATCH v2 7/6] t7814: do not generate same commits in different repos
t7814 has repo tree like this initial-repo submodule sub In each repo 'submodule' and 'sub', a commit is made to add the same initial file 'a' with the same message 'add a'. If tests run fast enough, the two commits are made in the same second, resulting identical commits. There is nothing wrong with that per-se. But it could make the test flaky. Currently all submodule odbs are merged back in the main one (because we can't, or couldn't, access separate submodule repos otherwise). But eventually we need to access objects from the right repo. Because the same commit could sometimes be present in both 'submodule' and 'sub', if there is a bug looking up objects in the wrong repo, sometimes it will go unnoticed because it finds the needed object in the wrong repo anyway. Fix this by changing commit time after every commit. This makes all commits unique. Of course there are still identical blobs in different repos, but because we often lookup commit first, then tree and blob, unique commits are already quite safe. Signed-off-by: Nguyễn Thái Ngọc Duy --- > And I can't quite understand how t7814 sometimes passed. I do now. This patch makes it fail consistently for me. This patch technically has nothing to do with this series, but I'll try to sneak it in because it was started from there. t/t7814-grep-recurse-submodules.sh | 18 +- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index 134a694516..a11366b4ce 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -14,12 +14,14 @@ test_expect_success 'setup directory structure and submodule' ' echo "(3|4)" >b/b && git add a b && git commit -m "add a and b" && + test_tick && git init submodule && echo "(1|2)d(3|4)" >submodule/a && git -C submodule add a && git -C submodule commit -m "add a" && git submodule add ./submodule && - git commit -m "added submodule" + git commit -m "added submodule" && + test_tick ' test_expect_success 'grep correctly finds patterns in a submodule' ' @@ -65,11 +67,14 @@ test_expect_success 'grep and nested submodules' ' echo "(1|2)d(3|4)" >submodule/sub/a && git -C submodule/sub add a && git -C submodule/sub commit -m "add a" && + test_tick && git -C submodule submodule add ./sub && git -C submodule add sub && git -C submodule commit -m "added sub" && + test_tick && git add submodule && git commit -m "updated submodule" && + test_tick && cat >expect <<-\EOF && a:(1|2)d(3|4) @@ -179,15 +184,18 @@ test_expect_success !MINGW 'grep recurse submodule colon in name' ' echo "(1|2)d(3|4)" >"parent/fi:le" && git -C parent add "fi:le" && git -C parent commit -m "add fi:le" && + test_tick && git init "su:b" && test_when_finished "rm -rf su:b" && echo "(1|2)d(3|4)" >"su:b/fi:le" && git -C "su:b" add "fi:le" && git -C "su:b" commit -m "add fi:le" && + test_tick && git -C parent submodule add "../su:b" "su:b" && git -C parent commit -m "add submodule" && + test_tick && cat >expect <<-\EOF && fi:le:(1|2)d(3|4) @@ -210,15 +218,18 @@ test_expect_success 'grep history with moved submoules' ' echo "(1|2)d(3|4)" >parent/file && git -C parent add file && git -C parent commit -m "add file" && + test_tick && git init sub && test_when_finished "rm -rf sub" && echo "(1|2)d(3|4)" >sub/file && git -C sub add file && git -C sub commit -m "add file" && + test_tick && git -C parent submodule add ../sub dir/sub && git -C parent commit -m "add submodule" && + test_tick && cat >expect <<-\EOF && dir/sub/file:(1|2)d(3|4) @@ -229,6 +240,7 @@ test_expect_success 'grep history
[PATCH v2 3/6] tree-walk.c: remove the_repo from get_tree_entry()
Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- archive.c | 4 +++- blame.c| 4 ++-- builtin/rm.c | 2 +- builtin/update-index.c | 2 +- line-log.c | 7 --- match-trees.c | 6 +++--- merge-recursive.c | 8 +--- notes.c| 2 +- sha1-name.c| 9 + tree-walk.c| 18 -- tree-walk.h| 2 +- 11 files changed, 38 insertions(+), 26 deletions(-) diff --git a/archive.c b/archive.c index 53141c1f0e..a8da0fcc4f 100644 --- a/archive.c +++ b/archive.c @@ -418,7 +418,9 @@ static void parse_treeish_arg(const char **argv, unsigned short mode; int err; - err = get_tree_entry(&tree->object.oid, prefix, &tree_oid, + err = get_tree_entry(ar_args->repo, +&tree->object.oid, +prefix, &tree_oid, &mode); if (err || !S_ISDIR(mode)) die(_("current working directory is untracked")); diff --git a/blame.c b/blame.c index 145eaf2faf..ef022809e9 100644 --- a/blame.c +++ b/blame.c @@ -101,7 +101,7 @@ static void verify_working_tree_path(struct repository *r, struct object_id blob_oid; unsigned short mode; - if (!get_tree_entry(commit_oid, path, &blob_oid, &mode) && + if (!get_tree_entry(r, commit_oid, path, &blob_oid, &mode) && oid_object_info(r, &blob_oid, NULL) == OBJ_BLOB) return; } @@ -532,7 +532,7 @@ static int fill_blob_sha1_and_mode(struct repository *r, { if (!is_null_oid(&origin->blob_oid)) return 0; - if (get_tree_entry(&origin->commit->object.oid, origin->path, &origin->blob_oid, &origin->mode)) + if (get_tree_entry(r, &origin->commit->object.oid, origin->path, &origin->blob_oid, &origin->mode)) goto error_out; if (oid_object_info(r, &origin->blob_oid, NULL) != OBJ_BLOB) goto error_out; diff --git a/builtin/rm.c b/builtin/rm.c index be8edc6d1e..2eacda42b4 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -179,7 +179,7 @@ static int check_local_mod(struct object_id *head, int index_only) * way as changed from the HEAD. */ if (no_head -|| get_tree_entry(head, name, &oid, &mode) +|| get_tree_entry(the_repository, head, name, &oid, &mode) || ce->ce_mode != create_ce_mode(mode) || !oideq(&ce->oid, &oid)) staged_changes = 1; diff --git a/builtin/update-index.c b/builtin/update-index.c index 3f8cc6ccb4..dff2f4b837 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -601,7 +601,7 @@ static struct cache_entry *read_one_ent(const char *which, struct object_id oid; struct cache_entry *ce; - if (get_tree_entry(ent, path, &oid, &mode)) { + if (get_tree_entry(the_repository, ent, path, &oid, &mode)) { if (which) error("%s: not in %s branch.", path, which); return NULL; diff --git a/line-log.c b/line-log.c index 0a17b21187..3aff1849e7 100644 --- a/line-log.c +++ b/line-log.c @@ -496,12 +496,13 @@ static struct commit *check_single_commit(struct rev_info *revs) return (struct commit *) commit; } -static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec) +static void fill_blob_sha1(struct repository *r, struct commit *commit, + struct diff_filespec *spec) { unsigned short mode; struct object_id oid; - if (get_tree_entry(&commit->object.oid, spec->path, &oid, &mode)) + if (get_tree_entry(r, &commit->object.oid, spec->path, &oid, &mode)) die("There is no path %s in the commit", spec->path); fill_filespec(spec, &oid, 1, mode); @@ -585,7 +586,7 @@ parse_lines(struct repository *r, struct commit *commit, name_part); spec = alloc_filespec(full_name); - fill_blob_sha1(commit, spec); + fill_blob_sha1(r, commit, spec); fill_line_ends(r, spec, &lines, &ends); cb_data.spec = spec; cb_data.lines = lines; diff --git a/match-trees.c b/match-trees.c index 9d1ec8d6b0..de7e8a6783 100644 --- a/match-trees.c +++ b/match-trees.c @@ -290,7 +290,7 @@ void shift_tree(const struct object_id *hash1, if (!*del_
[PATCH v2 5/6] match-trees.c: remove the_repo from shift_tree*()
Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- cache.h | 4 ++-- match-trees.c | 12 +++- merge-recursive.c | 4 ++-- t/helper/test-match-trees.c | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cache.h b/cache.h index cd84cc9bbe..ddefda2bb6 100644 --- a/cache.h +++ b/cache.h @@ -1786,8 +1786,8 @@ int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int extern int diff_auto_refresh_index; /* match-trees.c */ -void shift_tree(const struct object_id *, const struct object_id *, struct object_id *, int); -void shift_tree_by(const struct object_id *, const struct object_id *, struct object_id *, const char *); +void shift_tree(struct repository *, const struct object_id *, const struct object_id *, struct object_id *, int); +void shift_tree_by(struct repository *, const struct object_id *, const struct object_id *, struct object_id *, const char *); /* * whitespace rules. diff --git a/match-trees.c b/match-trees.c index de7e8a6783..f6c194c1cc 100644 --- a/match-trees.c +++ b/match-trees.c @@ -248,7 +248,8 @@ static int splice_tree(const struct object_id *oid1, const char *prefix, * other hand, it could cover tree one and we might need to pick a * subtree of it. */ -void shift_tree(const struct object_id *hash1, +void shift_tree(struct repository *r, + const struct object_id *hash1, const struct object_id *hash2, struct object_id *shifted, int depth_limit) @@ -290,7 +291,7 @@ void shift_tree(const struct object_id *hash1, if (!*del_prefix) return; - if (get_tree_entry(the_repository, hash2, del_prefix, shifted, &mode)) + if (get_tree_entry(r, hash2, del_prefix, shifted, &mode)) die("cannot find path %s in tree %s", del_prefix, oid_to_hex(hash2)); return; @@ -307,7 +308,8 @@ void shift_tree(const struct object_id *hash1, * Unfortunately we cannot fundamentally tell which one to * be prefixed, as recursive merge can work in either direction. */ -void shift_tree_by(const struct object_id *hash1, +void shift_tree_by(struct repository *r, + const struct object_id *hash1, const struct object_id *hash2, struct object_id *shifted, const char *shift_prefix) @@ -317,12 +319,12 @@ void shift_tree_by(const struct object_id *hash1, unsigned candidate = 0; /* Can hash2 be a tree at shift_prefix in tree hash1? */ - if (!get_tree_entry(the_repository, hash1, shift_prefix, &sub1, &mode1) && + if (!get_tree_entry(r, hash1, shift_prefix, &sub1, &mode1) && S_ISDIR(mode1)) candidate |= 1; /* Can hash1 be a tree at shift_prefix in tree hash2? */ - if (!get_tree_entry(the_repository, hash2, shift_prefix, &sub2, &mode2) && + if (!get_tree_entry(r, hash2, shift_prefix, &sub2, &mode2) && S_ISDIR(mode2)) candidate |= 2; diff --git a/merge-recursive.c b/merge-recursive.c index b051066795..6d772eb0eb 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -153,9 +153,9 @@ static struct tree *shift_tree_object(struct repository *repo, struct object_id shifted; if (!*subtree_shift) { - shift_tree(&one->object.oid, &two->object.oid, &shifted, 0); + shift_tree(repo, &one->object.oid, &two->object.oid, &shifted, 0); } else { - shift_tree_by(&one->object.oid, &two->object.oid, &shifted, + shift_tree_by(repo, &one->object.oid, &two->object.oid, &shifted, subtree_shift); } if (oideq(&two->object.oid, &shifted)) diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c index 96857f26ac..b9fd427571 100644 --- a/t/helper/test-match-trees.c +++ b/t/helper/test-match-trees.c @@ -20,7 +20,7 @@ int cmd__match_trees(int ac, const char **av) if (!two) die("not a tree-ish %s", av[2]); - shift_tree(&one->object.oid, &two->object.oid, &shifted, -1); + shift_tree(the_repository, &one->object.oid, &two->object.oid, &shifted, -1); printf("shifted: %s\n", oid_to_hex(&shifted)); exit(0); -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 6/6] Use the right 'struct repository' instead of the_repository
There are a couple of places where 'struct repository' is already passed around, but the_repository is still used. Use the right repo. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- merge-recursive.c | 35 --- sequencer.c | 4 ++-- sha1-name.c | 6 ++ shallow.c | 3 ++- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 6d772eb0eb..12300131fc 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -465,17 +465,18 @@ static void get_files_dirs(struct merge_options *opt, struct tree *tree) { struct pathspec match_all; memset(&match_all, 0, sizeof(match_all)); - read_tree_recursive(the_repository, tree, "", 0, 0, + read_tree_recursive(opt->repo, tree, "", 0, 0, &match_all, save_files_dirs, opt); } -static int get_tree_entry_if_blob(const struct object_id *tree, +static int get_tree_entry_if_blob(struct repository *r, + const struct object_id *tree, const char *path, struct diff_filespec *dfs) { int ret; - ret = get_tree_entry(the_repository, tree, path, &dfs->oid, &dfs->mode); + ret = get_tree_entry(r, tree, path, &dfs->oid, &dfs->mode); if (S_ISDIR(dfs->mode)) { oidcpy(&dfs->oid, &null_oid); dfs->mode = 0; @@ -487,15 +488,16 @@ static int get_tree_entry_if_blob(const struct object_id *tree, * Returns an index_entry instance which doesn't have to correspond to * a real cache entry in Git's index. */ -static struct stage_data *insert_stage_data(const char *path, +static struct stage_data *insert_stage_data(struct repository *r, + const char *path, struct tree *o, struct tree *a, struct tree *b, struct string_list *entries) { struct string_list_item *item; struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); - get_tree_entry_if_blob(&o->object.oid, path, &e->stages[1]); - get_tree_entry_if_blob(&a->object.oid, path, &e->stages[2]); - get_tree_entry_if_blob(&b->object.oid, path, &e->stages[3]); + get_tree_entry_if_blob(r, &o->object.oid, path, &e->stages[1]); + get_tree_entry_if_blob(r, &a->object.oid, path, &e->stages[2]); + get_tree_entry_if_blob(r, &b->object.oid, path, &e->stages[3]); item = string_list_insert(entries, path); item->util = e; return e; @@ -1900,12 +1902,13 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt, return ret; } -static int tree_has_path(struct tree *tree, const char *path) +static int tree_has_path(struct repository *r, struct tree *tree, +const char *path) { struct object_id hashy; unsigned short mode_o; - return !get_tree_entry(the_repository, + return !get_tree_entry(r, &tree->object.oid, path, &hashy, &mode_o); } @@ -2057,7 +2060,7 @@ static char *handle_path_level_conflicts(struct merge_options *opt, */ if (collision_ent->reported_already) { clean = 0; - } else if (tree_has_path(tree, new_path)) { + } else if (tree_has_path(opt->repo, tree, new_path)) { collision_ent->reported_already = 1; strbuf_add_separated_string_list(&collision_paths, ", ", &collision_ent->source_files); @@ -2135,7 +2138,7 @@ static void handle_directory_level_conflicts(struct merge_options *opt, string_list_append(&remove_from_merge, merge_ent->dir)->util = merge_ent; strbuf_release(&merge_ent->new_dir); - } else if (tree_has_path(head, head_ent->dir)) { + } else if (tree_has_path(opt->repo, head, head_ent->dir)) { /* 2. This wasn't a directory rename after all */ string_list_append(&remove_from_head, head_ent->dir)->util = head_ent; @@ -2149,7 +2152,7 @@ static void handle_directory_level_conflicts(struct merge_options *opt, hashmap_iter_init(dir_re_merge, &iter); while ((merge_ent = hashmap_iter_next(&iter))) { head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir); - if (tree_has_path(merge, merge_ent->dir)) { + if (tree_has_path(opt->repo, merge, merge_ent->dir)
[PATCH v2 4/6] tree-walk.c: remove the_repo from get_tree_entry_follow_symlinks()
Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- sha1-name.c | 10 +- tree-walk.c | 12 tree-walk.h | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/sha1-name.c b/sha1-name.c index e8fb215e5c..3c9fa10af8 100644 --- a/sha1-name.c +++ b/sha1-name.c @@ -1890,16 +1890,8 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo, new_filename = resolve_relative_path(repo, filename); if (new_filename) filename = new_filename; - /* -* NEEDSWORK: Eventually get_tree_entry*() should -* learn to take struct repository directly and we -* would not need to inject submodule odb to the -* in-core odb. -*/ - if (repo != the_repository) - add_to_alternates_memory(repo->objects->odb->path); if (flags & GET_OID_FOLLOW_SYMLINKS) { - ret = get_tree_entry_follow_symlinks(&tree_oid, + ret = get_tree_entry_follow_symlinks(repo, &tree_oid, filename, oid, &oc->symlink_path, &oc->mode); } else { diff --git a/tree-walk.c b/tree-walk.c index 506e12a031..c20b62f49e 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -593,7 +593,10 @@ int get_tree_entry(struct repository *r, * See the code for enum get_oid_result for a description of * the return values. */ -enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned short *mode) +enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, + struct object_id *tree_oid, const char *name, + struct object_id *result, struct strbuf *result_path, + unsigned short *mode) { int retval = MISSING_OBJECT; struct dir_state *parents = NULL; @@ -617,7 +620,7 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, c void *tree; struct object_id root; unsigned long size; - tree = read_object_with_reference(the_repository, + tree = read_object_with_reference(r, ¤t_tree_oid, tree_type, &size, &root); @@ -687,7 +690,7 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, c } /* Look up the first (or only) path component in the tree. */ - find_result = find_tree_entry(the_repository, &t, namebuf.buf, + find_result = find_tree_entry(r, &t, namebuf.buf, ¤t_tree_oid, mode); if (find_result) { goto done; @@ -731,7 +734,8 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, c */ retval = DANGLING_SYMLINK; - contents = read_object_file(¤t_tree_oid, &type, + contents = repo_read_object_file(r, + ¤t_tree_oid, &type, &link_len); if (!contents) diff --git a/tree-walk.h b/tree-walk.h index 639f79187f..2a5db29e8f 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -53,7 +53,7 @@ struct traverse_info; typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *); int traverse_trees(struct index_state *istate, int n, struct tree_desc *t, struct traverse_info *info); -enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned short *mode); +enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned short *mode); struct traverse_info { const char *traverse_path; -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 1/6] sha1-file.c: remove the_repo from read_object_with_reference()
Signed-off-by: Nguyễn Thái Ngọc Duy --- builtin/cat-file.c | 3 ++- builtin/grep.c | 6 -- builtin/pack-objects.c | 3 ++- cache.h| 3 ++- fast-import.c | 9 ++--- sha1-file.c| 5 +++-- tree-walk.c| 7 --- 7 files changed, 23 insertions(+), 13 deletions(-) diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 0f092382e1..995d47c85a 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -172,7 +172,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, * fall-back to the usual case. */ } - buf = read_object_with_reference(&oid, exp_type, &size, NULL); + buf = read_object_with_reference(the_repository, +&oid, exp_type, &size, NULL); break; default: diff --git a/builtin/grep.c b/builtin/grep.c index 580fd38f41..560051784e 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -458,7 +458,8 @@ static int grep_submodule(struct grep_opt *opt, object = parse_object_or_die(oid, oid_to_hex(oid)); grep_read_lock(); - data = read_object_with_reference(&object->oid, tree_type, + data = read_object_with_reference(&subrepo, + &object->oid, tree_type, &size, NULL); grep_read_unlock(); @@ -623,7 +624,8 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, int hit, len; grep_read_lock(); - data = read_object_with_reference(&obj->oid, tree_type, + data = read_object_with_reference(opt->repo, + &obj->oid, tree_type, &size, NULL); grep_read_unlock(); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index b2be8869c2..a030c24a4a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1428,7 +1428,8 @@ static void add_preferred_base(struct object_id *oid) if (window <= num_preferred_base++) return; - data = read_object_with_reference(oid, tree_type, &size, &tree_oid); + data = read_object_with_reference(the_repository, oid, + tree_type, &size, &tree_oid); if (!data) return; diff --git a/cache.h b/cache.h index bf20337ef4..cd84cc9bbe 100644 --- a/cache.h +++ b/cache.h @@ -1500,7 +1500,8 @@ int df_name_compare(const char *name1, int len1, int mode1, const char *name2, i int name_compare(const char *name1, size_t len1, const char *name2, size_t len2); int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2); -void *read_object_with_reference(const struct object_id *oid, +void *read_object_with_reference(struct repository *r, +const struct object_id *oid, const char *required_type, unsigned long *size, struct object_id *oid_ret); diff --git a/fast-import.c b/fast-import.c index 76a7bd3699..3970b50acc 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2410,7 +2410,8 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa oidcpy(&commit_oid, &commit_oe->idx.oid); } else if (!get_oid(p, &commit_oid)) { unsigned long size; - char *buf = read_object_with_reference(&commit_oid, + char *buf = read_object_with_reference(the_repository, + &commit_oid, commit_type, &size, &commit_oid); if (!buf || size < the_hash_algo->hexsz + 6) @@ -2482,7 +2483,8 @@ static void parse_from_existing(struct branch *b) unsigned long size; char *buf; - buf = read_object_with_reference(&b->oid, commit_type, &size, + buf = read_object_with_reference(the_repository, +&b->oid, commit_type, &size, &b->oid); parse_from_commit(b, buf, size); free(buf); @@ -2560,7 +2562,8 @@ static struct hash_list *parse_merge(unsigned int *count) oidcpy(&n->oid, &oe->idx.oid); } else if (!get_oid(from, &n->oid)) { unsi
[PATCH v2 0/6] Kill the_repository in tree-walk.c
v2 fixes t7814 flakiness. The problem is git-grep can operate on multiple repos and I read objects from the wrong repo (supermodule) instead of the submodule one. There are still the_repository hidden in git-grep code paths, and the hack of asborbing submodule's object db to the_repo's in order to make it work. And I can't quite understand how t7814 sometimes passed. I'll revisit this after this series is done and will try to get rid of add_to_alternates_memory() in git-grep. Nguyễn Thái Ngọc Duy (6): sha1-file.c: remove the_repo from read_object_with_reference() tree-walk.c: remove the_repo from fill_tree_descriptor() tree-walk.c: remove the_repo from get_tree_entry() tree-walk.c: remove the_repo from get_tree_entry_follow_symlinks() match-trees.c: remove the_repo from shift_tree*() Use the right 'struct repository' instead of the_repository archive.c | 4 +++- blame.c | 4 ++-- builtin/cat-file.c | 3 ++- builtin/grep.c | 6 -- builtin/merge-tree.c| 22 +++ builtin/pack-objects.c | 3 ++- builtin/rebase.c| 4 ++-- builtin/reset.c | 4 ++-- builtin/rm.c| 2 +- builtin/update-index.c | 2 +- cache.h | 7 +++--- fast-import.c | 9 +--- line-log.c | 7 +++--- match-trees.c | 12 ++- merge-recursive.c | 43 + notes.c | 4 ++-- sequencer.c | 6 +++--- sha1-file.c | 5 +++-- sha1-name.c | 25 +++-- shallow.c | 3 ++- t/helper/test-match-trees.c | 2 +- tree-diff.c | 4 ++-- tree-walk.c | 35 -- tree-walk.h | 8 --- unpack-trees.c | 2 +- 25 files changed, 129 insertions(+), 97 deletions(-) Range-diff dựa trên v1: 1: 35d7cdbe6a ! 1: 9e73c39f9a sha1-file.c: remove the_repo from read_object_with_reference() @@ -3,7 +3,6 @@ sha1-file.c: remove the_repo from read_object_with_reference() Signed-off-by: Nguyễn Thái Ngọc Duy -Signed-off-by: Junio C Hamano diff --git a/builtin/cat-file.c b/builtin/cat-file.c --- a/builtin/cat-file.c @@ -27,7 +26,7 @@ grep_read_lock(); - data = read_object_with_reference(&object->oid, tree_type, -+ data = read_object_with_reference(opt->repo, ++ data = read_object_with_reference(&subrepo, +&object->oid, tree_type, &size, NULL); grep_read_unlock(); 2: 4ff146fb64 = 2: b9107f7503 tree-walk.c: remove the_repo from fill_tree_descriptor() 3: 47f956bd0f = 3: 87ed67bde5 tree-walk.c: remove the_repo from get_tree_entry() 4: e19c4b9ce6 = 4: 557b61f2ba tree-walk.c: remove the_repo from get_tree_entry_follow_symlinks() 5: 3fe87a7fde = 5: 53f09e0437 match-trees.c: remove the_repo from shift_tree*() 6: 6d0449f1a7 = 6: d5d4d2ba65 Use the right 'struct repository' instead of the_repository -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 2/6] tree-walk.c: remove the_repo from fill_tree_descriptor()
While at there, clean up the_repo usage in builtin/merge-tree.c a tiny bit. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/merge-tree.c | 22 +- builtin/rebase.c | 4 ++-- builtin/reset.c | 4 ++-- notes.c | 2 +- sequencer.c | 2 +- tree-diff.c | 4 ++-- tree-walk.c | 6 -- tree-walk.h | 4 +++- unpack-trees.c | 2 +- 9 files changed, 29 insertions(+), 21 deletions(-) diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 34ca0258b1..97b54caeb9 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -205,6 +205,7 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s static void unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) { + struct repository *r = the_repository; char *newbase; struct name_entry *p; struct tree_desc t[3]; @@ -220,9 +221,9 @@ static void unresolved_directory(const struct traverse_info *info, newbase = traverse_path(info, p); #define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? &(e)->oid : NULL) - buf0 = fill_tree_descriptor(t + 0, ENTRY_OID(n + 0)); - buf1 = fill_tree_descriptor(t + 1, ENTRY_OID(n + 1)); - buf2 = fill_tree_descriptor(t + 2, ENTRY_OID(n + 2)); + buf0 = fill_tree_descriptor(r, t + 0, ENTRY_OID(n + 0)); + buf1 = fill_tree_descriptor(r, t + 1, ENTRY_OID(n + 1)); + buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2)); #undef ENTRY_OID merge_trees(t, newbase); @@ -351,14 +352,16 @@ static void merge_trees(struct tree_desc t[3], const char *base) traverse_trees(&the_index, 3, t, &info); } -static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) +static void *get_tree_descriptor(struct repository *r, +struct tree_desc *desc, +const char *rev) { struct object_id oid; void *buf; - if (get_oid(rev, &oid)) + if (repo_get_oid(r, rev, &oid)) die("unknown rev %s", rev); - buf = fill_tree_descriptor(desc, &oid); + buf = fill_tree_descriptor(r, desc, &oid); if (!buf) die("%s is not a tree", rev); return buf; @@ -366,15 +369,16 @@ static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) int cmd_merge_tree(int argc, const char **argv, const char *prefix) { + struct repository *r = the_repository; struct tree_desc t[3]; void *buf1, *buf2, *buf3; if (argc != 4) usage(merge_tree_usage); - buf1 = get_tree_descriptor(t+0, argv[1]); - buf2 = get_tree_descriptor(t+1, argv[2]); - buf3 = get_tree_descriptor(t+2, argv[3]); + buf1 = get_tree_descriptor(r, t+0, argv[1]); + buf2 = get_tree_descriptor(r, t+1, argv[2]); + buf3 = get_tree_descriptor(r, t+2, argv[3]); merge_trees(t, ""); free(buf1); free(buf2); diff --git a/builtin/rebase.c b/builtin/rebase.c index b8116db487..28490f5f88 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -840,13 +840,13 @@ static int reset_head(struct object_id *oid, const char *action, goto leave_reset_head; } - if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) { + if (!reset_hard && !fill_tree_descriptor(the_repository, &desc[nr++], &head_oid)) { ret = error(_("failed to find tree of %s"), oid_to_hex(&head_oid)); goto leave_reset_head; } - if (!fill_tree_descriptor(&desc[nr++], oid)) { + if (!fill_tree_descriptor(the_repository, &desc[nr++], oid)) { ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); goto leave_reset_head; } diff --git a/builtin/reset.c b/builtin/reset.c index 26ef9a7bd0..77c38f28c2 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -79,13 +79,13 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet) struct object_id head_oid; if (get_oid("HEAD", &head_oid)) return error(_("You do not have a valid HEAD.")); - if (!fill_tree_descriptor(desc + nr, &head_oid)) + if (!fill_tree_descriptor(the_repository, desc + nr, &head_oid)) return error(_("Failed to find tree of HEAD.")); nr++; opts.fn = twoway_merge; } - if (!fill_tree_descriptor(desc + nr, oid)) { + if (!fill_tree_descriptor(the_repository, desc + nr, oid)) { error(_("Failed to
[PATCH/RFC] get_oid: new extended SHA-1 syntax to control resolution process
[I feel like this is something we should do, but I'm not sure. Hence RFC. The patch is mostly to play with if you're curious. The die() thing there is definitely wrong. And yeah all the syntax names are placeholders.] get_oid() is flexible and accepts multiple SHA-1 sources. Sometimes this flexibility is actually unwanted, especially for scripts. Let's talk problems: - Ambiguity aside, a script may want to check if branch A exists. Of course "git rev-parse A" won't cut it. But even "git rev-parse refs/heads/A" may fail: if you have refs/heads/refs/heads/A for whatever reason and the real branch "A" does not exist, the rev-parse rules allow to expand "refs/heads/A" to the long ref. - And then there's problem with using the wrong rule. 9309ba may look like a short hash. But if such short hash does not match any object, and there is refs/heads/9309ba, you'll have a little surprise. - The same for blahblah-g9309ba which could either be expanded to refs/heads/blahblah-g9309ba, or interpreted as git-describe output. - Ambiguation will also cause problems, but I don't think we need to get into that. Ambiguation may be addressed separately actually. There could be existing mitigation (e.g. maybe you can resolve blahblah-g9309ba and see if it's a ref or not). But it feels like we work around the problem than addressing it. The problem is we try every possible way to resolve a rev. Let's have some annotation to express that we only want to resolve a rev in a certain way: - @{hash} only accepts a full hash or a short hash. If it's a short hash, it cannot be ambiguous. - @{literal} only accepts full ref. No turning "master" into "refs/heads/master". - @{describe} interprets as git-describe output only, not an object name or a reference. This gives scripts much better control over get_oid(), which translates to rev-parse and a bunch other commands. PS. The new syntax can stack with existing ones. E.g. you could write refs/heads/master@{literal}@{yesterday} or @{hash}^{tree}. Perhaps I should allow these tags at the end too, so you can enforce a variable like "$REV"@{literal} where $REV could be even HEAD~123 Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/revisions.txt | 10 refs.c | 41 +-- refs.h | 2 ++ sha1-name.c | 48 +++-- 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index 82c1e5754e..93eb278743 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -86,6 +86,16 @@ some output processing may assume ref names in UTF-8. immediately following a ref name and the ref must have an existing log ('$GIT_DIR/logs/'). +'@{literal}':: + The ref is not expanded by Git. In other words, if '$GIT_DIR/' + does not exist, the ref is not valid. + +'@{hash}':: + '' must be an unambiguous (short or full) object name. + +'@{describe}':: + ... + '@{}', e.g. '@\{1\}':: You can use the '@' construct with an empty ref part to get at a reflog entry of the current branch. For example, if you are on diff --git a/refs.c b/refs.c index b8a8430c96..2ee33257fd 100644 --- a/refs.c +++ b/refs.c @@ -634,6 +634,27 @@ int repo_dwim_ref(struct repository *r, const char *str, int len, return refs_found; } +int repo_dwim_ref_strict(struct repository *r, +const char *str, int len, +struct object_id *oid, +char **ref) +{ + char *last_branch = substitute_branch_name(r, &str, &len); + struct strbuf sb = STRBUF_INIT; + int flag; + + FREE_AND_NULL(last_branch); + strbuf_add(&sb, str, len); + *ref = xstrdup_or_null( + refs_resolve_ref_unsafe(get_main_ref_store(r), + sb.buf, + RESOLVE_REF_READING, + oid, + &flag)); + strbuf_release(&sb); + return *ref != NULL; +} + int dwim_ref(const char *str, int len, struct object_id *oid, char **ref) { return repo_dwim_ref(the_repository, str, len, oid, ref); @@ -673,8 +694,9 @@ int expand_ref(struct repository *repo, const char *str, int len, return refs_found; } -int repo_dwim_log(struct repository *r, const char *str, int len, - struct object_id *oid, char **log) +static int do_dwim_log(struct repository *r, const char *str, int len, + struct object_id *oid, char **log, + int ignore_rev_parse_rules) { struct re
[PATCH v2 10/10] t3008: use the new SINGLE_CPU prereq
Signed-off-by: Nguyễn Thái Ngọc Duy --- t/t3008-ls-files-lazy-init-name-hash.sh | 8 +--- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/t/t3008-ls-files-lazy-init-name-hash.sh b/t/t3008-ls-files-lazy-init-name-hash.sh index 64f047332b..7f918c05f6 100755 --- a/t/t3008-ls-files-lazy-init-name-hash.sh +++ b/t/t3008-ls-files-lazy-init-name-hash.sh @@ -4,15 +4,9 @@ test_description='Test the lazy init name hash with various folder structures' . ./test-lib.sh -if test 1 -eq $($GIT_BUILD_DIR/t/helper/test-tool online-cpus) -then - skip_all='skipping lazy-init tests, single cpu' - test_done -fi - LAZY_THREAD_COST=2000 -test_expect_success 'no buffer overflow in lazy_init_name_hash' ' +test_expect_success !SINGLE_CPU 'no buffer overflow in lazy_init_name_hash' ' ( test_seq $LAZY_THREAD_COST | sed "s/^/a_/" && echo b/b/b && -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 08/10] read-cache.c: dump "EOIE" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- read-cache.c | 8 t/t3011-ls-files-json.sh | 13 ++ t/t3011/eoie (new) | 96 t/test-lib.sh| 4 ++ 4 files changed, 121 insertions(+) diff --git a/read-cache.c b/read-cache.c index e5183636fc..37491dd03d 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1731,6 +1731,14 @@ static int read_index_extension(struct index_state *istate, read_fsmonitor_extension(istate, data, sz); break; case CACHE_EXT_ENDOFINDEXENTRIES: + if (istate->jw) { + /* must be synchronized with read_eoie_extension() */ + jw_object_intmax(istate->jw, "offset", get_be32(data)); + jw_object_string(istate->jw, "oid", +hash_to_hex((const unsigned char*)data + sizeof(uint32_t))); + } + /* already handled in do_read_index() */ + break; case CACHE_EXT_INDEXENTRYOFFSETTABLE: /* already handled in do_read_index() */ break; diff --git a/t/t3011-ls-files-json.sh b/t/t3011-ls-files-json.sh index dc57138f5b..9f4ad4c9cf 100755 --- a/t/t3011-ls-files-json.sh +++ b/t/t3011-ls-files-json.sh @@ -90,4 +90,17 @@ test_expect_success 'ls-files --json, rerere extension' ' ) ' +test_expect_success !SINGLE_CPU 'ls-files --json and multicore extensions' ' + git init eoie && + ( + cd eoie && + git config index.threads 2 && + touch one two three four && + git add . && + cp ../filter.sed . && + strip_number offset && + compare_json eoie + ) +' + test_done diff --git a/t/t3011/eoie b/t/t3011/eoie new file mode 100644 index 00..85ec61517b --- /dev/null +++ b/t/t3011/eoie @@ -0,0 +1,96 @@ +{ + "version": 2, + "oid": , + "mtime_sec": , + "mtime_nsec": , + "entries": [ +{ + "id": 0, + "name": "four", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 0 + }, + "file_offset": +}, +{ + "id": 1, + "name": "one", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 0 + }, + "file_offset": +}, +{ + "id": 2, + "name": "three", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 0 + }, + "file_offset": +}, +{ + "id": 3, + "name": "two", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 0 + }, + "file_offset": +} + ], + "extensions": { +"IEOT": { + "file_offset": , + "ext_size": +}, +"EOIE": { + "file_offset": , + "ext_size": , + "offset": , + "oid": +} + } +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 4b346467df..9d5b273b40 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1611,3 +1611,7 @@ test_lazy_prereq REBASE_P ' test_lazy_prereq FAIL_PREREQS ' test -n "$GIT_TEST_FAIL_PREREQS" ' + +test_lazy_prereq SINGLE_CPU ' + test "$(test-tool online-cpus)" -eq 1 +' -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 04/10] dir.c: dump "UNTR" extension as json
The big part of UNTR extension is dumped at the end instead of dumping as soon as we read it, because we actually "patch" some fields in untracked_cache_dir with EWAH bitmaps at the end. Signed-off-by: Nguyễn Thái Ngọc Duy --- dir.c| 57 +++- dir.h| 4 ++- json-writer.h| 6 + read-cache.c | 2 +- t/t3011-ls-files-json.sh | 3 ++- t/t3011/basic| 39 +++ 6 files changed, 107 insertions(+), 4 deletions(-) diff --git a/dir.c b/dir.c index ba4a51c296..8808577ea3 100644 --- a/dir.c +++ b/dir.c @@ -19,6 +19,7 @@ #include "varint.h" #include "ewah/ewok.h" #include "fsmonitor.h" +#include "json-writer.h" #include "submodule-config.h" /* @@ -2826,7 +2827,42 @@ static void load_oid_stat(struct oid_stat *oid_stat, const unsigned char *data, oid_stat->valid = 1; } -struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz) +static void jw_object_oid_stat(struct json_writer *jw, const char *key, + const struct oid_stat *oid_stat) +{ + jw_object_inline_begin_object(jw, key); + jw_object_bool(jw, "valid", oid_stat->valid); + jw_object_string(jw, "oid", oid_to_hex(&oid_stat->oid)); + jw_object_stat_data(jw, "stat", &oid_stat->stat); + jw_end(jw); +} + +static void jw_object_untracked_cache_dir(struct json_writer *jw, + const struct untracked_cache_dir *ucd) +{ + int i; + + jw_object_bool(jw, "valid", ucd->valid); + jw_object_bool(jw, "check-only", ucd->check_only); + jw_object_stat_data(jw, "stat", &ucd->stat_data); + jw_object_string(jw, "exclude-oid", oid_to_hex(&ucd->exclude_oid)); + jw_object_inline_begin_array(jw, "untracked"); + for (i = 0; i < ucd->untracked_nr; i++) + jw_array_string(jw, ucd->untracked[i]); + jw_end(jw); + + jw_object_inline_begin_object(jw, "dirs"); + for (i = 0; i < ucd->dirs_nr; i++) { + jw_object_inline_begin_object(jw, ucd->dirs[i]->name); + jw_object_untracked_cache_dir(jw, ucd->dirs[i]); + jw_end(jw); + } + jw_end(jw); +} + +struct untracked_cache *read_untracked_extension(const void *data, +unsigned long sz, +struct json_writer *jw) { struct untracked_cache *uc; struct read_data rd; @@ -2864,6 +2900,19 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long uc->dir_flags = get_be32(next + ouc_offset(dir_flags)); exclude_per_dir = (const char *)next + exclude_per_dir_offset; uc->exclude_per_dir = xstrdup(exclude_per_dir); + + if (jw) { + jw_object_string(jw, "ident", ident); + jw_object_oid_stat(jw, "info_exclude", &uc->ss_info_exclude); + jw_object_oid_stat(jw, "excludes_file", &uc->ss_excludes_file); + jw_object_intmax(jw, "flags", uc->dir_flags); + if (uc->dir_flags & DIR_SHOW_OTHER_DIRECTORIES) + jw_object_bool(jw, "show_other_directories", 1); + if (uc->dir_flags & DIR_HIDE_EMPTY_DIRECTORIES) + jw_object_bool(jw, "hide_empty_directories", 1); + jw_object_string(jw, "excludes_per_dir", uc->exclude_per_dir); + } + /* NUL after exclude_per_dir is covered by sizeof(*ouc) */ next += exclude_per_dir_offset + strlen(exclude_per_dir) + 1; if (next >= end) @@ -2905,6 +2954,12 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long ewah_each_bit(rd.sha1_valid, read_oid, &rd); next = rd.data; + if (jw) { + jw_object_inline_begin_object(jw, "root"); + jw_object_untracked_cache_dir(jw, uc->root); + jw_end(jw); + } + done: free(rd.ucd); ewah_free(rd.valid); diff --git a/dir.h b/dir.h index 680079bbe3..80efdd05c4 100644 --- a/dir.h +++ b/dir.h @@ -6,6 +6,8 @@ #include "cache.h" #include "strbuf.h" +struct json_writer; + struct dir_entry { unsigned int len; char name[FLEX_ARRAY]; /* more */ @@ -362,7 +364,7 @@ void untracked_cache_remove_from_index(struct index_state *, const char *); void untracked_cache_add_to_index(struct index_state *, const char *); void free_untracked_cache(struct untracked_cache *); -struct untracked_cache *read_untracked_ex
[PATCH v2 06/10] fsmonitor.c: dump "FSMN" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- fsmonitor.c | 6 ++ t/t3011-ls-files-json.sh | 14 +- t/t3011/fsmonitor (new) | 38 ++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/fsmonitor.c b/fsmonitor.c index 1dee0aded1..5ed55ad176 100644 --- a/fsmonitor.c +++ b/fsmonitor.c @@ -3,6 +3,7 @@ #include "dir.h" #include "ewah/ewok.h" #include "fsmonitor.h" +#include "json-writer.h" #include "run-command.h" #include "strbuf.h" @@ -50,6 +51,11 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data, } istate->fsmonitor_dirty = fsmonitor_dirty; + if (istate->jw) { + jw_object_intmax(istate->jw, "version", hdr_version); + jw_object_intmax(istate->jw, "last_update", istate->fsmonitor_last_update); + jw_object_ewah(istate->jw, "dirty", fsmonitor_dirty); + } trace_printf_key(&trace_fsmonitor, "read fsmonitor extension successful"); return 0; } diff --git a/t/t3011-ls-files-json.sh b/t/t3011-ls-files-json.sh index dbb572ce9d..25215f83ae 100755 --- a/t/t3011-ls-files-json.sh +++ b/t/t3011-ls-files-json.sh @@ -36,7 +36,7 @@ test_expect_success 'setup' ' git add -N ita && strip_number ctime_sec ctime_nsec mtime_sec mtime_nsec && - strip_number device inode uid gid file_offset ext_size && + strip_number device inode uid gid file_offset ext_size last_update && strip_string oid ident ' @@ -58,4 +58,16 @@ test_expect_success 'ls-files --json, split index' ' ) ' +test_expect_success 'ls-files --json, fsmonitor extension ' ' + git init fsmonitor && + ( + cd fsmonitor && + echo one >one && + git add one && + git update-index --fsmonitor && + cp ../filter.sed . && + compare_json fsmonitor + ) +' + test_done diff --git a/t/t3011/fsmonitor b/t/t3011/fsmonitor new file mode 100644 index 00..17f2d4a664 --- /dev/null +++ b/t/t3011/fsmonitor @@ -0,0 +1,38 @@ +{ + "version": 2, + "oid": , + "mtime_sec": , + "mtime_nsec": , + "entries": [ +{ + "id": 0, + "name": "one", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 4 + }, + "file_offset": +} + ], + "extensions": { +"FSMN": { + "file_offset": , + "ext_size": , + "version": 1, + "last_update": , + "dirty": [ +0 + ] +} + } +} -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 07/10] resolve-undo.c: dump "REUC" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- read-cache.c | 2 +- resolve-undo.c | 30 +- resolve-undo.h | 4 ++- t/t3011-ls-files-json.sh | 20 t/t3011/rerere (new) | 66 5 files changed, 119 insertions(+), 3 deletions(-) diff --git a/read-cache.c b/read-cache.c index a70df4b0a5..e5183636fc 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1719,7 +1719,7 @@ static int read_index_extension(struct index_state *istate, istate->cache_tree = cache_tree_read(data, sz, istate->jw); break; case CACHE_EXT_RESOLVE_UNDO: - istate->resolve_undo = resolve_undo_read(data, sz); + istate->resolve_undo = resolve_undo_read(data, sz, istate->jw); break; case CACHE_EXT_LINK: ret = read_link_extension(istate, data, sz); diff --git a/resolve-undo.c b/resolve-undo.c index 236320f179..68921e3dfe 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -1,5 +1,6 @@ #include "cache.h" #include "dir.h" +#include "json-writer.h" #include "resolve-undo.h" #include "string-list.h" @@ -49,7 +50,30 @@ void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo) } } -struct string_list *resolve_undo_read(const char *data, unsigned long size) +static void dump_resolve_undo(struct json_writer *jw, + const char *path, + const struct resolve_undo_info *ui) +{ + int i; + + if (!jw) + return; + + jw_array_inline_begin_object(jw); + jw_object_string(jw, "path", path); + + jw_object_inline_begin_array(jw, "stages"); + for (i = 0; i < 3; i++) { + jw_array_inline_begin_object(jw); + jw_object_filemode(jw, "mode", ui->mode[i]); + jw_object_string(jw, "oid", oid_to_hex(&ui->oid[i])); + jw_end(jw); + } + jw_end(jw); +} + +struct string_list *resolve_undo_read(const char *data, unsigned long size, + struct json_writer *jw) { struct string_list *resolve_undo; size_t len; @@ -59,6 +83,7 @@ struct string_list *resolve_undo_read(const char *data, unsigned long size) resolve_undo = xcalloc(1, sizeof(*resolve_undo)); resolve_undo->strdup_strings = 1; + jw_object_inline_begin_array_gently(jw, "entries"); while (size) { struct string_list_item *lost; @@ -94,7 +119,10 @@ struct string_list *resolve_undo_read(const char *data, unsigned long size) size -= rawsz; data += rawsz; } + + dump_resolve_undo(jw, lost->string, ui); } + jw_end_gently(jw); return resolve_undo; error: diff --git a/resolve-undo.h b/resolve-undo.h index 2b3f0f901e..46b4e93a7e 100644 --- a/resolve-undo.h +++ b/resolve-undo.h @@ -3,6 +3,8 @@ #include "cache.h" +struct json_writer; + struct resolve_undo_info { unsigned int mode[3]; struct object_id oid[3]; @@ -10,7 +12,7 @@ struct resolve_undo_info { void record_resolve_undo(struct index_state *, struct cache_entry *); void resolve_undo_write(struct strbuf *, struct string_list *); -struct string_list *resolve_undo_read(const char *, unsigned long); +struct string_list *resolve_undo_read(const char *, unsigned long, struct json_writer *); void resolve_undo_clear_index(struct index_state *); int unmerge_index_entry_at(struct index_state *, int); void unmerge_index(struct index_state *, const struct pathspec *); diff --git a/t/t3011-ls-files-json.sh b/t/t3011-ls-files-json.sh index 25215f83ae..dc57138f5b 100755 --- a/t/t3011-ls-files-json.sh +++ b/t/t3011-ls-files-json.sh @@ -70,4 +70,24 @@ test_expect_success 'ls-files --json, fsmonitor extension ' ' ) ' +test_expect_success 'ls-files --json, rerere extension' ' + git init rerere && + ( + cd rerere && + mkdir fi && + test_commit initial fi/le first && + git branch side && + test_commit second fi/le second && + git checkout side && + test_commit third fi/le third && + git checkout master && + git config rerere.enabled true && + test_must_fail git merge side && + echo resolved >fi/le && + git add fi/le && + cp ../filter.sed . && + compare_json rerere + ) +' + test_done diff --git a/t/t3011/rerere b/t/t3011/rerere new fil
[PATCH v2 01/10] ls-files: add --json to dump the index
So far we don't have a command to basically dump the index file out, with all its glory details. Checking some info, for example, stat time, usually involves either writing new code or firing up "xxd" and decoding values by yourself. This --json is supposed to help that. It dumps the index in a human readable format but also easy to be processed with tools. And it will print almost enough info to reconstruct the index later. In this patch we only dump the main part, not extensions. But at the end of the series, the entire index is dumped. The end result could be very verbose even on a small repository such as git.git. Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/git-ls-files.txt| 5 +++ builtin/ls-files.c| 38 +--- cache.h | 2 + json-writer.c | 22 ++ json-writer.h | 23 ++ read-cache.c | 72 ++- t/t3011-ls-files-json.sh (new +x) | 44 +++ t/t3011/basic (new) | 67 8 files changed, 265 insertions(+), 8 deletions(-) diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 8461c0e83e..fec5cb7170 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -162,6 +162,11 @@ a space) at the start of each line: possible for manual inspection; the exact format may change at any time. +--debug-json:: + Dump the entire index content in JSON format. This is for + debugging purposes. The JSON structure is subject to change. + Note that the strings are not always valid UTF-8. + --eol:: Show and of files. is the file content identification used by Git when diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 7f83c9a6f2..b60cd9ab28 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -8,6 +8,7 @@ #include "cache.h" #include "repository.h" #include "config.h" +#include "json-writer.h" #include "quote.h" #include "dir.h" #include "builtin.h" @@ -31,6 +32,7 @@ static int show_modified; static int show_killed; static int show_valid_bit; static int show_fsmonitor_bit; +static int show_json; static int line_terminator = '\n'; static int debug_mode; static int show_eol; @@ -577,6 +579,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("pretend that paths removed since are still present")), OPT__ABBREV(&abbrev), OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")), + OPT_BOOL(0, "debug-json", &show_json, + N_("dump index content in JSON format")), OPT_END() }; @@ -632,7 +636,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) "--error-unmatch"); parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD, + show_json ? PATHSPEC_PREFER_FULL : PATHSPEC_PREFER_CWD, prefix, argv); /* @@ -660,8 +664,18 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) /* With no flags, we default to showing the cached files */ if (!(show_stage || show_deleted || show_others || show_unmerged || - show_killed || show_modified || show_resolve_undo)) + show_killed || show_modified || show_resolve_undo || show_json)) show_cached = 1; + if (show_json && (show_stage || show_deleted || show_others || + show_unmerged || show_killed || show_modified || + show_cached || pathspec.nr)) + die(_("--debug-json cannot be used with other file selection options")); + if (show_json && show_resolve_undo) + die(_("--debug-json cannot be used with %s"), "--resolve-undo"); + if (show_json && with_tree) + die(_("--debug-json cannot be used with %s"), "--with-tree"); + if (show_json && debug_mode) + die(_("--debug-json cannot be used with %s"), "--debug"); if (with_tree) { /* @@ -673,10 +687,22 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) overlay_tree_on_index(the_repository->index, with_tree, max_prefix); } - show_files(the_repository, &dir); - - if (show_resolve_undo) - show_ru_info(the_repository->index); + if (!show_json) { + show_files(the_repository, &dir); + + if (show_resolve_undo) +
[PATCH v2 03/10] cache-tree.c: dump "TREE" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- cache-tree.c | 36 +++- cache-tree.h | 5 - read-cache.c | 2 +- t/t3011-ls-files-json.sh | 4 +++- t/t3011/basic| 20 +++- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/cache-tree.c b/cache-tree.c index b13bfaf71e..b6a233307e 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -3,6 +3,7 @@ #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" +#include "json-writer.h" #include "object-store.h" #include "replace-object.h" @@ -492,7 +493,8 @@ void cache_tree_write(struct strbuf *sb, struct cache_tree *root) write_one(sb, root, "", 0); } -static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) +static struct cache_tree *read_one(const char **buffer, unsigned long *size_p, + struct json_writer *jw) { const char *buf = *buffer; unsigned long size = *size_p; @@ -546,6 +548,15 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) *buffer, subtree_nr); #endif + if (jw) { + if (it->entry_count >= 0) { + jw_object_string(jw, "oid", oid_to_hex(&it->oid)); + jw_object_intmax(jw, "entry_count", it->entry_count); + } else { + jw_object_null(jw, "oid"); + } + jw_object_inline_begin_array(jw, "subdirs"); + } /* * Just a heuristic -- we do not add directories that often but * we do not want to have to extend it immediately when we do, @@ -559,12 +570,18 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) struct cache_tree_sub *subtree; const char *name = buf; - sub = read_one(&buf, &size); + if (jw) { + jw_array_inline_begin_object(jw); + jw_object_string(jw, "name", name); + } + sub = read_one(&buf, &size, jw); + jw_end_gently(jw); if (!sub) goto free_return; subtree = cache_tree_sub(it, name); subtree->cache_tree = sub; } + jw_end_gently(jw); if (subtree_nr != it->subtree_nr) die("cache-tree: internal error"); *buffer = buf; @@ -576,11 +593,20 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) return NULL; } -struct cache_tree *cache_tree_read(const char *buffer, unsigned long size) +struct cache_tree *cache_tree_read(const char *buffer, unsigned long size, + struct json_writer *jw) { + struct cache_tree *ret; + + if (jw) { + jw_object_inline_begin_object(jw, "root"); + } if (buffer[0]) - return NULL; /* not the whole tree */ - return read_one(&buffer, &size); + ret = NULL; /* not the whole tree */ + else + ret = read_one(&buffer, &size, jw); + jw_end_gently(jw); + return ret; } static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path) diff --git a/cache-tree.h b/cache-tree.h index 757bbc48bc..fc3c73284b 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -6,6 +6,8 @@ #include "tree-walk.h" struct cache_tree; +struct json_writer; + struct cache_tree_sub { struct cache_tree *cache_tree; int count; /* internally used by update_one() */ @@ -28,7 +30,8 @@ void cache_tree_invalidate_path(struct index_state *, const char *); struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *); void cache_tree_write(struct strbuf *, struct cache_tree *root); -struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); +struct cache_tree *cache_tree_read(const char *buffer, unsigned long size, + struct json_writer *jw); int cache_tree_fully_valid(struct cache_tree *); int cache_tree_update(struct index_state *, int); diff --git a/read-cache.c b/read-cache.c index 4accd8bb08..d09ce42b9a 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1716,7 +1716,7 @@ static int read_index_extension(struct index_state *istate, switch (CACHE_EXT(ext)) { case CACHE_EXT_TREE: - istate->cache_tree = cache_tree_read(data, sz); + istate->cache_tree = cache_tree_read(data, sz, istate->jw); break; case CACHE_EXT_RESOLVE_UNDO: istate->resolve_undo = resolve_undo_read(data, sz); diff --git a/t/t30
[PATCH v2 09/10] read-cache.c: dump "IEOT" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- read-cache.c | 43 --- t/t3011/eoie | 13 - 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/read-cache.c b/read-cache.c index 37491dd03d..c26edcc9d9 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1693,6 +1693,7 @@ static int verify_hdr(const struct cache_header *hdr, unsigned long size) return 0; } +static struct index_entry_offset_table *do_read_ieot_extension(struct index_state *, const char *, uint32_t); static int read_index_extension(struct index_state *istate, const char *map, unsigned long *offset) @@ -1740,7 +1741,11 @@ static int read_index_extension(struct index_state *istate, /* already handled in do_read_index() */ break; case CACHE_EXT_INDEXENTRYOFFSETTABLE: - /* already handled in do_read_index() */ + if (istate->jw) { + free(do_read_ieot_extension(istate, data, sz)); + } else { + /* already handled in do_read_index() */ + } break; default: if (*ext < 'A' || 'Z' < *ext) @@ -1938,7 +1943,7 @@ struct index_entry_offset_table struct index_entry_offset entries[FLEX_ARRAY]; }; -static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset); +static struct index_entry_offset_table *read_ieot_extension(struct index_state *istate, const char *mmap, size_t mmap_size, size_t offset); static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot); static size_t read_eoie_extension(const char *mmap, size_t mmap_size); @@ -2292,7 +2297,7 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) * to multi-thread the reading of the cache entries. */ if (extension_offset && nr_threads > 1) - ieot = read_ieot_extension(mmap, mmap_size, extension_offset); + ieot = read_ieot_extension(istate, mmap, mmap_size, extension_offset); if (ieot) { src_offset += load_cache_entries_threaded(istate, mmap, mmap_size, nr_threads, ieot); @@ -3634,12 +3639,13 @@ static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, #define IEOT_VERSION (1) -static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset) +static struct index_entry_offset_table *read_ieot_extension( + struct index_state *istate, + const char *mmap, size_t mmap_size, + size_t offset) { const char *index = NULL; - uint32_t extsize, ext_version; - struct index_entry_offset_table *ieot; - int i, nr; + uint32_t extsize; /* find the IEOT extension */ if (!offset) @@ -3655,6 +3661,17 @@ static struct index_entry_offset_table *read_ieot_extension(const char *mmap, si } if (!index) return NULL; + return do_read_ieot_extension(istate, index, extsize); +} + +static struct index_entry_offset_table *do_read_ieot_extension( + struct index_state *istate, + const char *index, + uint32_t extsize) +{ + uint32_t ext_version; + struct index_entry_offset_table *ieot; + int i, nr; /* validate the version is IEOT_VERSION */ ext_version = get_be32(index); @@ -3670,6 +3687,10 @@ static struct index_entry_offset_table *read_ieot_extension(const char *mmap, si error("invalid number of IEOT entries %d", nr); return NULL; } + if (istate->jw) { + jw_object_intmax(istate->jw, "version", ext_version); + jw_object_inline_begin_array(istate->jw, "entries"); + } ieot = xmalloc(sizeof(struct index_entry_offset_table) + (nr * sizeof(struct index_entry_offset))); ieot->nr = nr; @@ -3678,7 +3699,15 @@ static struct index_entry_offset_table *read_ieot_extension(const char *mmap, si index += sizeof(uint32_t); ieot->entries[i].nr = get_be32(index); index += sizeof(uint32_t); + + if (istate->jw) { + jw_array_inline_begin_object(istate->jw); + jw_object_intmax(istate->jw, "offset", ieot->entries[i].offset); + jw_object_intmax(istate->jw, "count", ieot->entries[i].nr); + jw_end(istate->jw); + } } + jw_end_gently(istate->jw); return ieot; } diff --git a/t/t3011/eoie b/t/t3011/eoie index 85ec61517b..66a0feb3b6 100644 --
[PATCH v2 02/10] read-cache.c: dump common extension info in json
Signed-off-by: Nguyễn Thái Ngọc Duy --- read-cache.c | 49 +++-- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/read-cache.c b/read-cache.c index db5147d088..4accd8bb08 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1694,8 +1694,26 @@ static int verify_hdr(const struct cache_header *hdr, unsigned long size) } static int read_index_extension(struct index_state *istate, - const char *ext, const char *data, unsigned long sz) + const char *map, + unsigned long *offset) { + int ret = 0; + const char *ext = map + *offset; + uint32_t sz = get_be32(ext + 4); + const char *data = ext + 8; + + if (istate->jw) { + char buf[5]; + + memcpy(buf, ext, 4); + buf[4] = '\0'; + jw_object_inline_begin_object(istate->jw, buf); + + jw_object_intmax(istate->jw, "file_offset", *offset); + jw_object_intmax(istate->jw, "ext_size", sz); + } + *offset += sz + 8; + switch (CACHE_EXT(ext)) { case CACHE_EXT_TREE: istate->cache_tree = cache_tree_read(data, sz); @@ -1704,8 +1722,7 @@ static int read_index_extension(struct index_state *istate, istate->resolve_undo = resolve_undo_read(data, sz); break; case CACHE_EXT_LINK: - if (read_link_extension(istate, data, sz)) - return -1; + ret = read_link_extension(istate, data, sz); break; case CACHE_EXT_UNTRACKED: istate->untracked = read_untracked_extension(data, sz); @@ -1719,12 +1736,14 @@ static int read_index_extension(struct index_state *istate, break; default: if (*ext < 'A' || 'Z' < *ext) - return error(_("index uses %.4s extension, which we do not understand"), + ret = error(_("index uses %.4s extension, which we do not understand"), ext); - fprintf_ln(stderr, _("ignoring %.4s extension"), ext); + else + fprintf_ln(stderr, _("ignoring %.4s extension"), ext); break; } - return 0; + jw_end_gently(istate->jw); + return ret; } static struct cache_entry *create_from_disk(struct mem_pool *ce_mem_pool, @@ -1930,25 +1949,27 @@ static void *load_index_extensions(void *_data) { struct load_index_extensions *p = _data; unsigned long src_offset = p->src_offset; + int dump_json = 0; while (src_offset <= p->mmap_size - the_hash_algo->rawsz - 8) { - /* After an array of active_nr index entries, + if (p->istate->jw && !dump_json) { + jw_object_inline_begin_object(p->istate->jw, "extensions"); + dump_json = 1; + } + /* +* After an array of active_nr index entries, * there can be arbitrary number of extended * sections, each of which is prefixed with * extension name (4-byte) and section length * in 4-byte network byte order. */ - uint32_t extsize = get_be32(p->mmap + src_offset + 4); - if (read_index_extension(p->istate, -p->mmap + src_offset, -p->mmap + src_offset + 8, -extsize) < 0) { + if (read_index_extension(p->istate, p->mmap, &src_offset) < 0) { munmap((void *)p->mmap, p->mmap_size); die(_("index file corrupt")); } - src_offset += 8; - src_offset += extsize; } + if (dump_json) + jw_end(p->istate->jw); return NULL; } -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 05/10] split-index.c: dump "link" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- json-writer.c | 14 ++ json-writer.h | 3 +++ split-index.c | 9 - t/t3011-ls-files-json.sh | 14 ++ t/t3011/split-index (new) | 39 +++ 5 files changed, 78 insertions(+), 1 deletion(-) diff --git a/json-writer.c b/json-writer.c index 0608726512..c0bd302e4e 100644 --- a/json-writer.c +++ b/json-writer.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "ewah/ewok.h" #include "json-writer.h" void jw_init(struct json_writer *jw) @@ -224,6 +225,19 @@ void jw_object_stat_data(struct json_writer *jw, const char *name, jw_end(jw); } +static void dump_ewah_one(size_t pos, void *jw) +{ + jw_array_intmax(jw, pos); +} + +void jw_object_ewah(struct json_writer *jw, const char *key, + struct ewah_bitmap *ewah) +{ + jw_object_inline_begin_array(jw, key); + ewah_each_bit(ewah, dump_ewah_one, jw); + jw_end(jw); +} + static void increase_indent(struct strbuf *sb, const struct json_writer *jw, int indent) diff --git a/json-writer.h b/json-writer.h index c3d0fbd1ef..07d841d52a 100644 --- a/json-writer.h +++ b/json-writer.h @@ -45,6 +45,7 @@ #include "git-compat-util.h" #include "strbuf.h" +struct ewah_bitmap; struct stat_data; struct json_writer @@ -87,6 +88,8 @@ void jw_object_null(struct json_writer *jw, const char *key); void jw_object_filemode(struct json_writer *jw, const char *key, mode_t value); void jw_object_stat_data(struct json_writer *jw, const char *key, const struct stat_data *sd); +void jw_object_ewah(struct json_writer *jw, const char *key, + struct ewah_bitmap *ewah); void jw_object_sub_jw(struct json_writer *jw, const char *key, const struct json_writer *value); diff --git a/split-index.c b/split-index.c index e6154e4ea9..41552bf771 100644 --- a/split-index.c +++ b/split-index.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "json-writer.h" #include "split-index.h" #include "ewah/ewok.h" @@ -25,7 +26,7 @@ int read_link_extension(struct index_state *istate, data += the_hash_algo->rawsz; sz -= the_hash_algo->rawsz; if (!sz) - return 0; + goto done; si->delete_bitmap = ewah_new(); ret = ewah_read_mmap(si->delete_bitmap, data, sz); if (ret < 0) @@ -38,6 +39,12 @@ int read_link_extension(struct index_state *istate, return error("corrupt replace bitmap in link extension"); if (ret != sz) return error("garbage at the end of link extension"); +done: + if (istate->jw) { + jw_object_string(istate->jw, "oid", oid_to_hex(&si->base_oid)); + jw_object_ewah(istate->jw, "delete_bitmap", si->delete_bitmap); + jw_object_ewah(istate->jw, "replace_bitmap", si->replace_bitmap); + } return 0; } diff --git a/t/t3011-ls-files-json.sh b/t/t3011-ls-files-json.sh index 082fe8e966..dbb572ce9d 100755 --- a/t/t3011-ls-files-json.sh +++ b/t/t3011-ls-files-json.sh @@ -44,4 +44,18 @@ test_expect_success 'ls-files --json, main entries, UNTR and TREE' ' compare_json basic ' +test_expect_success 'ls-files --json, split index' ' + git init split && + ( + cd split && + echo one >one && + git add one && + git update-index --split-index && + echo updated >>one && + test_must_fail git -c splitIndex.maxPercentChange=100 update-index --refresh && + cp ../filter.sed . && + compare_json split-index + ) +' + test_done diff --git a/t/t3011/split-index b/t/t3011/split-index new file mode 100644 index 00..cdcc4ddded --- /dev/null +++ b/t/t3011/split-index @@ -0,0 +1,39 @@ +{ + "version": 2, + "oid": , + "mtime_sec": , + "mtime_nsec": , + "entries": [ +{ + "id": 0, + "name": "", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 4 + }, + "file_offset": +} + ], + "extensions": { +"link": { + "file_offset": , + "ext_size": , + "oid": , + "delete_bitmap": [ + ], + "replace_bitmap": [ +0 + ] +} + } +} -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 00/10] Add 'ls-files --debug-json' to dump the index in json
;: , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 0 + }, + "file_offset": +}, +{ + "id": 2, + "name": "three", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 0 + }, + "file_offset": +}, +{ + "id": 3, + "name": "two", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 0 + }, + "file_offset": +} + ], + "extensions": { +"IEOT": { + "file_offset": , + "ext_size": , + "version": 1, + "entries": [ +{ + "offset": , + "count": 2 +}, +{ + "offset": , + "count": 2 +} + ] +}, +"EOIE": { + "file_offset": , + "ext_size": , + "offset": , + "oid": +} + } +} diff --git a/t/t3011/fsmonitor b/t/t3011/fsmonitor new file mode 100644 index 00..17f2d4a664 --- /dev/null +++ b/t/t3011/fsmonitor @@ -0,0 +1,38 @@ +{ + "version": 2, + "oid": , + "mtime_sec": , + "mtime_nsec": , + "entries": [ +{ + "id": 0, + "name": "one", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 4 + }, + "file_offset": +} + ], + "extensions": { +"FSMN": { + "file_offset": , + "ext_size": , + "version": 1, + "last_update": , + "dirty": [ +0 + ] +} + } +} diff --git a/t/t3011/rerere b/t/t3011/rerere new file mode 100644 index 00..a8ec4b16ee --- /dev/null +++ b/t/t3011/rerere @@ -0,0 +1,66 @@ +{ + "version": 2, + "oid": , + "mtime_sec": , + "mtime_nsec": , + "entries": [ +{ + "id": 0, + "name": "fi/le", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 9 + }, + "file_offset": +} + ], + "extensions": { +"TREE": { + "file_offset": , + "ext_size": , + "root": { +"oid": null, +"subdirs": [ + { +"name": "fi", +"oid": null, +"subdirs": [ +] + } +] + } +}, +"REUC": { + "file_offset": , + "ext_size": , + "entries": [ +{ + "path": "fi/le", + "stages": [ +{ + "mode": "100644", + "oid": +}, +{ + "mode": "100644", + "oid": +}, +{ + "mode": "100644", + "oid": +} + ] +} + ] +} + } diff --git a/t/t3011/split-index b/t/t3011/split-index new file mode 100644 index 00..cdcc4ddded --- /dev/null +++ b/t/t3011/split-index @@ -0,0 +1,39 @@ +{ + "version": 2, + "oid": , + "mtime_sec": , + "mtime_nsec": , + "entries": [ +{ + "id": 0, + "name": "", + "mode": "100644", + "flags": 0, + "oid": , + "stat": { +"ctime_sec": , +"ctime_nsec": , +"mtime_sec": , +"mtime_nsec": , +"device": , +"inode": , +"uid": , +"gid": , +"size": 4 + }, + "file_offset": +} + ], + "extensions": { +"link": { + "file_offset": , + "ext_size": , + "oid": , + "delete_bitmap": [ + ], + "replace_bitmap": [ +0 + ] +} + } +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 4b346467df..9d5b273b40 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1611,3 +1611,7 @@ test_lazy_prereq REBASE_P ' test_lazy_prereq FAIL_PREREQS ' test -n "$GIT_TEST_FAIL_PREREQS" ' + +test_lazy_prereq SINGLE_CPU ' + test "$(test-tool online-cpus)" -eq 1 +' Nguyễn Thái Ngọc Duy (10): ls-files: add --json to dump the index read-cache.c: dump common extension info in json cache-tree.c: dump "TREE" extension as json dir.c: dump "UNTR" extension as json split-index.c: dump "link" extension as json fsmonitor.c: dump "FSMN" extension as json resolve-undo.c: dump "REUC" extension as json read-cache.c: dump "EOIE" extension as json read-cache.c: dump "IEOT" extension as json t3008: use the new SINGLE_CPU prereq Documentation/git-ls-files.txt | 5 + builtin/ls-files.c | 38 - cache-tree.c| 36 - cache-tree.h| 5 +- cache.h | 2 + dir.c | 57 +++- dir.h | 4 +- fsmonitor.c | 6 + json-writer.c | 36 + json-writer.h | 32 + read-cache.c| 178 resolve-undo.c | 30 +++- resolve-undo.h | 4 +- split-index.c | 9 +- t/t3008-ls-files-lazy-init-name-hash.sh | 8 +- t/t3011-ls-files-json.sh (new +x) | 106 ++ t/t3011/basic (new) | 124 + t/t3011/eoie (new) | 107 ++ t/t3011/fsmonitor (new) | 38 + t/t3011/rerere (new)| 66 + t/t3011/split-index (new) | 39 ++ t/test-lib.sh | 4 + 22 files changed, 884 insertions(+), 50 deletions(-) create mode 100755 t/t3011-ls-files-json.sh create mode 100644 t/t3011/basic create mode 100644 t/t3011/eoie create mode 100644 t/t3011/fsmonitor create mode 100644 t/t3011/rerere create mode 100644 t/t3011/split-index -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 6/6] Use the right 'struct repository' instead of the_repository
There are a couple of places where 'struct repository' is already passed around, but the_repository is still used. Use the right repo. Signed-off-by: Nguyễn Thái Ngọc Duy --- merge-recursive.c | 35 --- sequencer.c | 4 ++-- sha1-name.c | 6 ++ shallow.c | 3 ++- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 6d772eb0eb..12300131fc 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -465,17 +465,18 @@ static void get_files_dirs(struct merge_options *opt, struct tree *tree) { struct pathspec match_all; memset(&match_all, 0, sizeof(match_all)); - read_tree_recursive(the_repository, tree, "", 0, 0, + read_tree_recursive(opt->repo, tree, "", 0, 0, &match_all, save_files_dirs, opt); } -static int get_tree_entry_if_blob(const struct object_id *tree, +static int get_tree_entry_if_blob(struct repository *r, + const struct object_id *tree, const char *path, struct diff_filespec *dfs) { int ret; - ret = get_tree_entry(the_repository, tree, path, &dfs->oid, &dfs->mode); + ret = get_tree_entry(r, tree, path, &dfs->oid, &dfs->mode); if (S_ISDIR(dfs->mode)) { oidcpy(&dfs->oid, &null_oid); dfs->mode = 0; @@ -487,15 +488,16 @@ static int get_tree_entry_if_blob(const struct object_id *tree, * Returns an index_entry instance which doesn't have to correspond to * a real cache entry in Git's index. */ -static struct stage_data *insert_stage_data(const char *path, +static struct stage_data *insert_stage_data(struct repository *r, + const char *path, struct tree *o, struct tree *a, struct tree *b, struct string_list *entries) { struct string_list_item *item; struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); - get_tree_entry_if_blob(&o->object.oid, path, &e->stages[1]); - get_tree_entry_if_blob(&a->object.oid, path, &e->stages[2]); - get_tree_entry_if_blob(&b->object.oid, path, &e->stages[3]); + get_tree_entry_if_blob(r, &o->object.oid, path, &e->stages[1]); + get_tree_entry_if_blob(r, &a->object.oid, path, &e->stages[2]); + get_tree_entry_if_blob(r, &b->object.oid, path, &e->stages[3]); item = string_list_insert(entries, path); item->util = e; return e; @@ -1900,12 +1902,13 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt, return ret; } -static int tree_has_path(struct tree *tree, const char *path) +static int tree_has_path(struct repository *r, struct tree *tree, +const char *path) { struct object_id hashy; unsigned short mode_o; - return !get_tree_entry(the_repository, + return !get_tree_entry(r, &tree->object.oid, path, &hashy, &mode_o); } @@ -2057,7 +2060,7 @@ static char *handle_path_level_conflicts(struct merge_options *opt, */ if (collision_ent->reported_already) { clean = 0; - } else if (tree_has_path(tree, new_path)) { + } else if (tree_has_path(opt->repo, tree, new_path)) { collision_ent->reported_already = 1; strbuf_add_separated_string_list(&collision_paths, ", ", &collision_ent->source_files); @@ -2135,7 +2138,7 @@ static void handle_directory_level_conflicts(struct merge_options *opt, string_list_append(&remove_from_merge, merge_ent->dir)->util = merge_ent; strbuf_release(&merge_ent->new_dir); - } else if (tree_has_path(head, head_ent->dir)) { + } else if (tree_has_path(opt->repo, head, head_ent->dir)) { /* 2. This wasn't a directory rename after all */ string_list_append(&remove_from_head, head_ent->dir)->util = head_ent; @@ -2149,7 +2152,7 @@ static void handle_directory_level_conflicts(struct merge_options *opt, hashmap_iter_init(dir_re_merge, &iter); while ((merge_ent = hashmap_iter_next(&iter))) { head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir); - if (tree_has_path(merge, merge_ent->dir)) { + if (tree_has_path(opt->repo, merge, merge_ent->dir)) {
[PATCH 5/6] match-trees.c: remove the_repo from shift_tree*()
Signed-off-by: Nguyễn Thái Ngọc Duy --- cache.h | 4 ++-- match-trees.c | 12 +++- merge-recursive.c | 4 ++-- t/helper/test-match-trees.c | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cache.h b/cache.h index cd84cc9bbe..ddefda2bb6 100644 --- a/cache.h +++ b/cache.h @@ -1786,8 +1786,8 @@ int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int extern int diff_auto_refresh_index; /* match-trees.c */ -void shift_tree(const struct object_id *, const struct object_id *, struct object_id *, int); -void shift_tree_by(const struct object_id *, const struct object_id *, struct object_id *, const char *); +void shift_tree(struct repository *, const struct object_id *, const struct object_id *, struct object_id *, int); +void shift_tree_by(struct repository *, const struct object_id *, const struct object_id *, struct object_id *, const char *); /* * whitespace rules. diff --git a/match-trees.c b/match-trees.c index de7e8a6783..f6c194c1cc 100644 --- a/match-trees.c +++ b/match-trees.c @@ -248,7 +248,8 @@ static int splice_tree(const struct object_id *oid1, const char *prefix, * other hand, it could cover tree one and we might need to pick a * subtree of it. */ -void shift_tree(const struct object_id *hash1, +void shift_tree(struct repository *r, + const struct object_id *hash1, const struct object_id *hash2, struct object_id *shifted, int depth_limit) @@ -290,7 +291,7 @@ void shift_tree(const struct object_id *hash1, if (!*del_prefix) return; - if (get_tree_entry(the_repository, hash2, del_prefix, shifted, &mode)) + if (get_tree_entry(r, hash2, del_prefix, shifted, &mode)) die("cannot find path %s in tree %s", del_prefix, oid_to_hex(hash2)); return; @@ -307,7 +308,8 @@ void shift_tree(const struct object_id *hash1, * Unfortunately we cannot fundamentally tell which one to * be prefixed, as recursive merge can work in either direction. */ -void shift_tree_by(const struct object_id *hash1, +void shift_tree_by(struct repository *r, + const struct object_id *hash1, const struct object_id *hash2, struct object_id *shifted, const char *shift_prefix) @@ -317,12 +319,12 @@ void shift_tree_by(const struct object_id *hash1, unsigned candidate = 0; /* Can hash2 be a tree at shift_prefix in tree hash1? */ - if (!get_tree_entry(the_repository, hash1, shift_prefix, &sub1, &mode1) && + if (!get_tree_entry(r, hash1, shift_prefix, &sub1, &mode1) && S_ISDIR(mode1)) candidate |= 1; /* Can hash1 be a tree at shift_prefix in tree hash2? */ - if (!get_tree_entry(the_repository, hash2, shift_prefix, &sub2, &mode2) && + if (!get_tree_entry(r, hash2, shift_prefix, &sub2, &mode2) && S_ISDIR(mode2)) candidate |= 2; diff --git a/merge-recursive.c b/merge-recursive.c index b051066795..6d772eb0eb 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -153,9 +153,9 @@ static struct tree *shift_tree_object(struct repository *repo, struct object_id shifted; if (!*subtree_shift) { - shift_tree(&one->object.oid, &two->object.oid, &shifted, 0); + shift_tree(repo, &one->object.oid, &two->object.oid, &shifted, 0); } else { - shift_tree_by(&one->object.oid, &two->object.oid, &shifted, + shift_tree_by(repo, &one->object.oid, &two->object.oid, &shifted, subtree_shift); } if (oideq(&two->object.oid, &shifted)) diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c index 96857f26ac..b9fd427571 100644 --- a/t/helper/test-match-trees.c +++ b/t/helper/test-match-trees.c @@ -20,7 +20,7 @@ int cmd__match_trees(int ac, const char **av) if (!two) die("not a tree-ish %s", av[2]); - shift_tree(&one->object.oid, &two->object.oid, &shifted, -1); + shift_tree(the_repository, &one->object.oid, &two->object.oid, &shifted, -1); printf("shifted: %s\n", oid_to_hex(&shifted)); exit(0); -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 0/6] Kill the_repository in tree-walk.c
This is the continuation of nd/sha1-name-c-wo-the-repository. In that series I sealed off one place in sha1-name.c that cannot walk trees from arbitrary repositories. With tree-walk.c taking 'struct repository *' directly, that check in there can now be removed. Nguyễn Thái Ngọc Duy (6): sha1-file.c: remove the_repo from read_object_with_reference() tree-walk.c: remove the_repo from fill_tree_descriptor() tree-walk.c: remove the_repo from get_tree_entry() tree-walk.c: remove the_repo from get_tree_entry_follow_symlinks() match-trees.c: remove the_repo from shift_tree*() Use the right 'struct repository' instead of the_repository archive.c | 4 +++- blame.c | 4 ++-- builtin/cat-file.c | 3 ++- builtin/grep.c | 6 -- builtin/merge-tree.c| 22 +++ builtin/pack-objects.c | 3 ++- builtin/rebase.c| 4 ++-- builtin/reset.c | 4 ++-- builtin/rm.c| 2 +- builtin/update-index.c | 2 +- cache.h | 7 +++--- fast-import.c | 9 +--- line-log.c | 7 +++--- match-trees.c | 12 ++- merge-recursive.c | 43 + notes.c | 4 ++-- sequencer.c | 6 +++--- sha1-file.c | 5 +++-- sha1-name.c | 25 +++-- shallow.c | 3 ++- t/helper/test-match-trees.c | 2 +- tree-diff.c | 4 ++-- tree-walk.c | 35 -- tree-walk.h | 8 --- unpack-trees.c | 2 +- 25 files changed, 129 insertions(+), 97 deletions(-) -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 1/6] sha1-file.c: remove the_repo from read_object_with_reference()
Signed-off-by: Nguyễn Thái Ngọc Duy --- builtin/cat-file.c | 3 ++- builtin/grep.c | 6 -- builtin/pack-objects.c | 3 ++- cache.h| 3 ++- fast-import.c | 9 ++--- sha1-file.c| 5 +++-- tree-walk.c| 7 --- 7 files changed, 23 insertions(+), 13 deletions(-) diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 0f092382e1..995d47c85a 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -172,7 +172,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, * fall-back to the usual case. */ } - buf = read_object_with_reference(&oid, exp_type, &size, NULL); + buf = read_object_with_reference(the_repository, +&oid, exp_type, &size, NULL); break; default: diff --git a/builtin/grep.c b/builtin/grep.c index 580fd38f41..85da7ee542 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -458,7 +458,8 @@ static int grep_submodule(struct grep_opt *opt, object = parse_object_or_die(oid, oid_to_hex(oid)); grep_read_lock(); - data = read_object_with_reference(&object->oid, tree_type, + data = read_object_with_reference(opt->repo, + &object->oid, tree_type, &size, NULL); grep_read_unlock(); @@ -623,7 +624,8 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, int hit, len; grep_read_lock(); - data = read_object_with_reference(&obj->oid, tree_type, + data = read_object_with_reference(opt->repo, + &obj->oid, tree_type, &size, NULL); grep_read_unlock(); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index b2be8869c2..a030c24a4a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1428,7 +1428,8 @@ static void add_preferred_base(struct object_id *oid) if (window <= num_preferred_base++) return; - data = read_object_with_reference(oid, tree_type, &size, &tree_oid); + data = read_object_with_reference(the_repository, oid, + tree_type, &size, &tree_oid); if (!data) return; diff --git a/cache.h b/cache.h index bf20337ef4..cd84cc9bbe 100644 --- a/cache.h +++ b/cache.h @@ -1500,7 +1500,8 @@ int df_name_compare(const char *name1, int len1, int mode1, const char *name2, i int name_compare(const char *name1, size_t len1, const char *name2, size_t len2); int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2); -void *read_object_with_reference(const struct object_id *oid, +void *read_object_with_reference(struct repository *r, +const struct object_id *oid, const char *required_type, unsigned long *size, struct object_id *oid_ret); diff --git a/fast-import.c b/fast-import.c index 76a7bd3699..3970b50acc 100644 --- a/fast-import.c +++ b/fast-import.c @@ -2410,7 +2410,8 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa oidcpy(&commit_oid, &commit_oe->idx.oid); } else if (!get_oid(p, &commit_oid)) { unsigned long size; - char *buf = read_object_with_reference(&commit_oid, + char *buf = read_object_with_reference(the_repository, + &commit_oid, commit_type, &size, &commit_oid); if (!buf || size < the_hash_algo->hexsz + 6) @@ -2482,7 +2483,8 @@ static void parse_from_existing(struct branch *b) unsigned long size; char *buf; - buf = read_object_with_reference(&b->oid, commit_type, &size, + buf = read_object_with_reference(the_repository, +&b->oid, commit_type, &size, &b->oid); parse_from_commit(b, buf, size); free(buf); @@ -2560,7 +2562,8 @@ static struct hash_list *parse_merge(unsigned int *count) oidcpy(&n->oid, &oe->idx.oid); } else if (!get_oid(from, &n->oid)) { unsig
[PATCH 4/6] tree-walk.c: remove the_repo from get_tree_entry_follow_symlinks()
Signed-off-by: Nguyễn Thái Ngọc Duy --- sha1-name.c | 10 +- tree-walk.c | 12 tree-walk.h | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/sha1-name.c b/sha1-name.c index e8fb215e5c..3c9fa10af8 100644 --- a/sha1-name.c +++ b/sha1-name.c @@ -1890,16 +1890,8 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo, new_filename = resolve_relative_path(repo, filename); if (new_filename) filename = new_filename; - /* -* NEEDSWORK: Eventually get_tree_entry*() should -* learn to take struct repository directly and we -* would not need to inject submodule odb to the -* in-core odb. -*/ - if (repo != the_repository) - add_to_alternates_memory(repo->objects->odb->path); if (flags & GET_OID_FOLLOW_SYMLINKS) { - ret = get_tree_entry_follow_symlinks(&tree_oid, + ret = get_tree_entry_follow_symlinks(repo, &tree_oid, filename, oid, &oc->symlink_path, &oc->mode); } else { diff --git a/tree-walk.c b/tree-walk.c index 506e12a031..c20b62f49e 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -593,7 +593,10 @@ int get_tree_entry(struct repository *r, * See the code for enum get_oid_result for a description of * the return values. */ -enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned short *mode) +enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, + struct object_id *tree_oid, const char *name, + struct object_id *result, struct strbuf *result_path, + unsigned short *mode) { int retval = MISSING_OBJECT; struct dir_state *parents = NULL; @@ -617,7 +620,7 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, c void *tree; struct object_id root; unsigned long size; - tree = read_object_with_reference(the_repository, + tree = read_object_with_reference(r, ¤t_tree_oid, tree_type, &size, &root); @@ -687,7 +690,7 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, c } /* Look up the first (or only) path component in the tree. */ - find_result = find_tree_entry(the_repository, &t, namebuf.buf, + find_result = find_tree_entry(r, &t, namebuf.buf, ¤t_tree_oid, mode); if (find_result) { goto done; @@ -731,7 +734,8 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, c */ retval = DANGLING_SYMLINK; - contents = read_object_file(¤t_tree_oid, &type, + contents = repo_read_object_file(r, + ¤t_tree_oid, &type, &link_len); if (!contents) diff --git a/tree-walk.h b/tree-walk.h index 639f79187f..2a5db29e8f 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -53,7 +53,7 @@ struct traverse_info; typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *); int traverse_trees(struct index_state *istate, int n, struct tree_desc *t, struct traverse_info *info); -enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned short *mode); +enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned short *mode); struct traverse_info { const char *traverse_path; -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 2/6] tree-walk.c: remove the_repo from fill_tree_descriptor()
While at there, clean up the_repo usage in builtin/merge-tree.c a tiny bit. Signed-off-by: Nguyễn Thái Ngọc Duy --- builtin/merge-tree.c | 22 +- builtin/rebase.c | 4 ++-- builtin/reset.c | 4 ++-- notes.c | 2 +- sequencer.c | 2 +- tree-diff.c | 4 ++-- tree-walk.c | 6 -- tree-walk.h | 4 +++- unpack-trees.c | 2 +- 9 files changed, 29 insertions(+), 21 deletions(-) diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 34ca0258b1..97b54caeb9 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -205,6 +205,7 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s static void unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) { + struct repository *r = the_repository; char *newbase; struct name_entry *p; struct tree_desc t[3]; @@ -220,9 +221,9 @@ static void unresolved_directory(const struct traverse_info *info, newbase = traverse_path(info, p); #define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? &(e)->oid : NULL) - buf0 = fill_tree_descriptor(t + 0, ENTRY_OID(n + 0)); - buf1 = fill_tree_descriptor(t + 1, ENTRY_OID(n + 1)); - buf2 = fill_tree_descriptor(t + 2, ENTRY_OID(n + 2)); + buf0 = fill_tree_descriptor(r, t + 0, ENTRY_OID(n + 0)); + buf1 = fill_tree_descriptor(r, t + 1, ENTRY_OID(n + 1)); + buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2)); #undef ENTRY_OID merge_trees(t, newbase); @@ -351,14 +352,16 @@ static void merge_trees(struct tree_desc t[3], const char *base) traverse_trees(&the_index, 3, t, &info); } -static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) +static void *get_tree_descriptor(struct repository *r, +struct tree_desc *desc, +const char *rev) { struct object_id oid; void *buf; - if (get_oid(rev, &oid)) + if (repo_get_oid(r, rev, &oid)) die("unknown rev %s", rev); - buf = fill_tree_descriptor(desc, &oid); + buf = fill_tree_descriptor(r, desc, &oid); if (!buf) die("%s is not a tree", rev); return buf; @@ -366,15 +369,16 @@ static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) int cmd_merge_tree(int argc, const char **argv, const char *prefix) { + struct repository *r = the_repository; struct tree_desc t[3]; void *buf1, *buf2, *buf3; if (argc != 4) usage(merge_tree_usage); - buf1 = get_tree_descriptor(t+0, argv[1]); - buf2 = get_tree_descriptor(t+1, argv[2]); - buf3 = get_tree_descriptor(t+2, argv[3]); + buf1 = get_tree_descriptor(r, t+0, argv[1]); + buf2 = get_tree_descriptor(r, t+1, argv[2]); + buf3 = get_tree_descriptor(r, t+2, argv[3]); merge_trees(t, ""); free(buf1); free(buf2); diff --git a/builtin/rebase.c b/builtin/rebase.c index b8116db487..28490f5f88 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -840,13 +840,13 @@ static int reset_head(struct object_id *oid, const char *action, goto leave_reset_head; } - if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) { + if (!reset_hard && !fill_tree_descriptor(the_repository, &desc[nr++], &head_oid)) { ret = error(_("failed to find tree of %s"), oid_to_hex(&head_oid)); goto leave_reset_head; } - if (!fill_tree_descriptor(&desc[nr++], oid)) { + if (!fill_tree_descriptor(the_repository, &desc[nr++], oid)) { ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); goto leave_reset_head; } diff --git a/builtin/reset.c b/builtin/reset.c index 26ef9a7bd0..77c38f28c2 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -79,13 +79,13 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet) struct object_id head_oid; if (get_oid("HEAD", &head_oid)) return error(_("You do not have a valid HEAD.")); - if (!fill_tree_descriptor(desc + nr, &head_oid)) + if (!fill_tree_descriptor(the_repository, desc + nr, &head_oid)) return error(_("Failed to find tree of HEAD.")); nr++; opts.fn = twoway_merge; } - if (!fill_tree_descriptor(desc + nr, oid)) { + if (!fill_tree_descriptor(the_repository, desc + nr, oid)) { error(_("Failed to find tree of %s."), oi
[PATCH 3/6] tree-walk.c: remove the_repo from get_tree_entry()
Signed-off-by: Nguyễn Thái Ngọc Duy --- archive.c | 4 +++- blame.c| 4 ++-- builtin/rm.c | 2 +- builtin/update-index.c | 2 +- line-log.c | 7 --- match-trees.c | 6 +++--- merge-recursive.c | 8 +--- notes.c| 2 +- sha1-name.c| 9 + tree-walk.c| 18 -- tree-walk.h| 2 +- 11 files changed, 38 insertions(+), 26 deletions(-) diff --git a/archive.c b/archive.c index 53141c1f0e..a8da0fcc4f 100644 --- a/archive.c +++ b/archive.c @@ -418,7 +418,9 @@ static void parse_treeish_arg(const char **argv, unsigned short mode; int err; - err = get_tree_entry(&tree->object.oid, prefix, &tree_oid, + err = get_tree_entry(ar_args->repo, +&tree->object.oid, +prefix, &tree_oid, &mode); if (err || !S_ISDIR(mode)) die(_("current working directory is untracked")); diff --git a/blame.c b/blame.c index 145eaf2faf..ef022809e9 100644 --- a/blame.c +++ b/blame.c @@ -101,7 +101,7 @@ static void verify_working_tree_path(struct repository *r, struct object_id blob_oid; unsigned short mode; - if (!get_tree_entry(commit_oid, path, &blob_oid, &mode) && + if (!get_tree_entry(r, commit_oid, path, &blob_oid, &mode) && oid_object_info(r, &blob_oid, NULL) == OBJ_BLOB) return; } @@ -532,7 +532,7 @@ static int fill_blob_sha1_and_mode(struct repository *r, { if (!is_null_oid(&origin->blob_oid)) return 0; - if (get_tree_entry(&origin->commit->object.oid, origin->path, &origin->blob_oid, &origin->mode)) + if (get_tree_entry(r, &origin->commit->object.oid, origin->path, &origin->blob_oid, &origin->mode)) goto error_out; if (oid_object_info(r, &origin->blob_oid, NULL) != OBJ_BLOB) goto error_out; diff --git a/builtin/rm.c b/builtin/rm.c index be8edc6d1e..2eacda42b4 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -179,7 +179,7 @@ static int check_local_mod(struct object_id *head, int index_only) * way as changed from the HEAD. */ if (no_head -|| get_tree_entry(head, name, &oid, &mode) +|| get_tree_entry(the_repository, head, name, &oid, &mode) || ce->ce_mode != create_ce_mode(mode) || !oideq(&ce->oid, &oid)) staged_changes = 1; diff --git a/builtin/update-index.c b/builtin/update-index.c index 3f8cc6ccb4..dff2f4b837 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -601,7 +601,7 @@ static struct cache_entry *read_one_ent(const char *which, struct object_id oid; struct cache_entry *ce; - if (get_tree_entry(ent, path, &oid, &mode)) { + if (get_tree_entry(the_repository, ent, path, &oid, &mode)) { if (which) error("%s: not in %s branch.", path, which); return NULL; diff --git a/line-log.c b/line-log.c index 0a17b21187..3aff1849e7 100644 --- a/line-log.c +++ b/line-log.c @@ -496,12 +496,13 @@ static struct commit *check_single_commit(struct rev_info *revs) return (struct commit *) commit; } -static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec) +static void fill_blob_sha1(struct repository *r, struct commit *commit, + struct diff_filespec *spec) { unsigned short mode; struct object_id oid; - if (get_tree_entry(&commit->object.oid, spec->path, &oid, &mode)) + if (get_tree_entry(r, &commit->object.oid, spec->path, &oid, &mode)) die("There is no path %s in the commit", spec->path); fill_filespec(spec, &oid, 1, mode); @@ -585,7 +586,7 @@ parse_lines(struct repository *r, struct commit *commit, name_part); spec = alloc_filespec(full_name); - fill_blob_sha1(commit, spec); + fill_blob_sha1(r, commit, spec); fill_line_ends(r, spec, &lines, &ends); cb_data.spec = spec; cb_data.lines = lines; diff --git a/match-trees.c b/match-trees.c index 9d1ec8d6b0..de7e8a6783 100644 --- a/match-trees.c +++ b/match-trees.c @@ -290,7 +290,7 @@ void shift_tree(const struct object_id *hash1, if (!*del_prefix) return;
[PATCH] rm: add --intent-to-add, to be used with --cached
An index entry serves two purposes: to keep the content to be committed, and to mark that the same path on worktree is tracked. When git rm --cached foo is called and there is "foo" in worktree, its status is changed from tracked to untracked. Which I think is not intended, at least from the user perspective because we almost always tell people "Git is about the content" (*). Add --intent-to-add, which will replace the current index entry with an intent-to-add one. "git commit -m gone" will record "foo" gone, while "git commit -am not-gone" will ignore the index as usual and keep "foo". Before this, "commit -am" will also remove "foo". While I think --intent-to-add (and also the one in git-reset) should be the default behavior, changing it flip the test suite out because it relies on the current behavior. Let's leave that for later. At least having the ability to just remove the staged content is the right thing to do. (*) From the developer perspective, keeping the tool dumb actually sounds good. When you tell git to remove something from the index, it should go do just that, no trying to be clever. But that's more suitable for plumbing commands like update-index than rm, in my opinion. Signed-off-by: Nguyễn Thái Ngọc Duy --- This occurred to me while adding intent-to-add support to git-restore. It's not related to nd/switch-and-restore though and I originally wanted to make it default, so I post it separately here. Documentation/git-rm.txt | 6 ++ builtin/rm.c | 10 -- t/t3600-rm.sh| 9 + 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index b5c46223c4..aa0aa6063f 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -60,6 +60,12 @@ OPTIONS Working tree files, whether modified or not, will be left alone. +--intent-to-add:: +--no-intent-to-add:: + When `--cached` is used, if the given path also exist as a + working tree file, the index is updated to record the fact + that the path will be added later, similar to `git add -N`. + --ignore-unmatch:: Exit with a zero status even if no files matched. diff --git a/builtin/rm.c b/builtin/rm.c index be8edc6d1e..135bc4b76e 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -235,12 +235,14 @@ static int check_local_mod(struct object_id *head, int index_only) } static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; -static int ignore_unmatch = 0; +static int ignore_unmatch = 0, intent_to_add = 0; static struct option builtin_rm_options[] = { OPT__DRY_RUN(&show_only, N_("dry run")), OPT__QUIET(&quiet, N_("do not list removed files")), OPT_BOOL( 0 , "cached", &index_only, N_("only remove from the index")), + OPT_BOOL( 0 , "intent-to-add", &intent_to_add, + N_("record that the path will be added later if needed")), OPT__FORCE(&force, N_("override the up-to-date check"), PARSE_OPT_NOCOMPLETE), OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")), OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch, @@ -262,7 +264,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!argc) usage_with_options(builtin_rm_usage, builtin_rm_options); - if (!index_only) + if (!index_only || intent_to_add) setup_work_tree(); hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); @@ -344,6 +346,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (remove_file_from_cache(path)) die(_("git rm: unable to remove %s"), path); + + if (index_only && intent_to_add && file_exists(path) && + add_file_to_index(the_repository->index, path, ADD_CACHE_INTENT)) + error(_("failed to mark '%s' as intent-to-add"), path); } if (show_only) diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 85ae7dc1e4..04233b7dc4 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -34,6 +34,15 @@ test_expect_success 'Test that git rm foo succeeds' ' git rm --cached foo ' +test_expect_success 'Test that git rm --cached --intent-to-add foo succeeds' ' + echo content >foo && + git add foo && + git rm --cached --intent-to-add foo && + git diff --summary -- foo >actual && + echo " create mode 100644 foo" >expected && + test_cmp expected actual +' + test_expect_success 'Test that git rm --cached foo succeeds if the index matches the file' ' echo content >foo && git add foo && -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 3/3] fetch-pack: print server version at the top in -v -v
Before the previous patch, the server version is printed after all the "Server supports" lines. The previous one puts the version in the middle of "Server supports" group. Instead of moving it to the bottom, I move it to the top. Version may stand out more at the top as we will have even more debug out after capabilities. Signed-off-by: Nguyễn Thái Ngọc Duy --- fetch-pack.c | 13 +++-- 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/fetch-pack.c b/fetch-pack.c index de935f8776..445a261f14 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -902,6 +902,13 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, sort_ref_list(&ref, ref_compare_name); QSORT(sought, nr_sought, cmp_ref_by_name); + if ((agent_feature = server_feature_value("agent", &agent_len))) { + agent_supported = 1; + if (agent_len) + print_verbose(args, _("Server version is %.*s"), + agent_len, agent_feature); + } + if (server_supports("shallow")) print_verbose(args, _("Server supports %s"), "shallow"); else if (args->depth > 0 || is_repository_shallow(the_repository)) @@ -961,12 +968,6 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, warning("filtering not recognized by server, ignoring"); } - if ((agent_feature = server_feature_value("agent", &agent_len))) { - agent_supported = 1; - if (agent_len) - print_verbose(args, _("Server version is %.*s"), - agent_len, agent_feature); - } if (server_supports("deepen-since")) { print_verbose(args, _("Server supports %s"), "deepen-since"); deepen_since_ok = 1; -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 2/3] fetch-pack: print all relevant supported capabilities with -v -v
When we check if some capability is supported, we do print something in verbose mode. Some capabilities are not printed though (and it made me think it's not supported; I was more used to GIT_TRACE_PACKET) so let's print them all. It's a bit more code. And one could argue for printing all supported capabilities the server sends us. But I think it's still valuable this way because we see the capabilities that the client cares about. Signed-off-by: Nguyễn Thái Ngọc Duy --- fetch-pack.c | 30 +- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/fetch-pack.c b/fetch-pack.c index 0532029f2c..de935f8776 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -902,7 +902,9 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, sort_ref_list(&ref, ref_compare_name); QSORT(sought, nr_sought, cmp_ref_by_name); - if ((args->depth > 0 || is_repository_shallow(the_repository)) && !server_supports("shallow")) + if (server_supports("shallow")) + print_verbose(args, _("Server supports %s"), "shallow"); + else if (args->depth > 0 || is_repository_shallow(the_repository)) die(_("Server does not support shallow clients")); if (args->depth > 0 || args->deepen_since || args->deepen_not) args->deepen = 1; @@ -935,11 +937,17 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, print_verbose(args, _("Server supports %s"), "allow-reachable-sha1-in-want"); allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1; } - if (!server_supports("thin-pack")) + if (server_supports("thin-pack")) + print_verbose(args, _("Server supports %s"), "thin-pack"); + else args->use_thin_pack = 0; - if (!server_supports("no-progress")) + if (server_supports("no-progress")) + print_verbose(args, _("Server supports %s"), "no-progress"); + else args->no_progress = 0; - if (!server_supports("include-tag")) + if (server_supports("include-tag")) + print_verbose(args, _("Server supports %s"), "include-tag"); + else args->include_tag = 0; if (server_supports("ofs-delta")) print_verbose(args, _("Server supports %s"), "ofs-delta"); @@ -959,15 +967,19 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, print_verbose(args, _("Server version is %.*s"), agent_len, agent_feature); } - if (server_supports("deepen-since")) + if (server_supports("deepen-since")) { + print_verbose(args, _("Server supports %s"), "deepen-since"); deepen_since_ok = 1; - else if (args->deepen_since) + } else if (args->deepen_since) die(_("Server does not support --shallow-since")); - if (server_supports("deepen-not")) + if (server_supports("deepen-not")) { + print_verbose(args, _("Server supports %s"), "deepen-not"); deepen_not_ok = 1; - else if (args->deepen_not) + } else if (args->deepen_not) die(_("Server does not support --shallow-exclude")); - if (!server_supports("deepen-relative") && args->deepen_relative) + if (server_supports("deepen-relative")) + print_verbose(args, _("Server supports %s"), "deepen-relative"); + else if (args->deepen_relative) die(_("Server does not support --deepen")); if (!args->no_dependents) { -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 1/3] fetch-pack: move capability names out of i18n strings
This reduces the work on translators since they only have one string to translate (and I think it's still enough context to translate). It also makes sure no capability name is translated by accident. Signed-off-by: Nguyễn Thái Ngọc Duy --- fetch-pack.c | 18 +- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fetch-pack.c b/fetch-pack.c index 1c10f54e78..0532029f2c 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -907,32 +907,32 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, if (args->depth > 0 || args->deepen_since || args->deepen_not) args->deepen = 1; if (server_supports("multi_ack_detailed")) { - print_verbose(args, _("Server supports multi_ack_detailed")); + print_verbose(args, _("Server supports %s"), "multi_ack_detailed"); multi_ack = 2; if (server_supports("no-done")) { - print_verbose(args, _("Server supports no-done")); + print_verbose(args, _("Server supports %s"), "no-done"); if (args->stateless_rpc) no_done = 1; } } else if (server_supports("multi_ack")) { - print_verbose(args, _("Server supports multi_ack")); + print_verbose(args, _("Server supports %s"), "multi_ack"); multi_ack = 1; } if (server_supports("side-band-64k")) { - print_verbose(args, _("Server supports side-band-64k")); + print_verbose(args, _("Server supports %s"), "side-band-64k"); use_sideband = 2; } else if (server_supports("side-band")) { - print_verbose(args, _("Server supports side-band")); + print_verbose(args, _("Server supports %s"), "side-band"); use_sideband = 1; } if (server_supports("allow-tip-sha1-in-want")) { - print_verbose(args, _("Server supports allow-tip-sha1-in-want")); + print_verbose(args, _("Server supports %s"), "allow-tip-sha1-in-want"); allow_unadvertised_object_request |= ALLOW_TIP_SHA1; } if (server_supports("allow-reachable-sha1-in-want")) { - print_verbose(args, _("Server supports allow-reachable-sha1-in-want")); + print_verbose(args, _("Server supports %s"), "allow-reachable-sha1-in-want"); allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1; } if (!server_supports("thin-pack")) @@ -942,13 +942,13 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, if (!server_supports("include-tag")) args->include_tag = 0; if (server_supports("ofs-delta")) - print_verbose(args, _("Server supports ofs-delta")); + print_verbose(args, _("Server supports %s"), "ofs-delta"); else prefer_ofs_delta = 0; if (server_supports("filter")) { server_supports_filtering = 1; - print_verbose(args, _("Server supports filter")); + print_verbose(args, _("Server supports %s"), "filter"); } else if (args->filter_options.choice) { warning("filtering not recognized by server, ignoring"); } -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 1/4] t2027: use test_must_be_empty
Signed-off-by: Nguyễn Thái Ngọc Duy --- t/t2070-restore.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh index 73ea13ede9..2650df1966 100755 --- a/t/t2070-restore.sh +++ b/t/t2070-restore.sh @@ -90,9 +90,8 @@ test_expect_success 'restore --ignore-unmerged ignores unmerged entries' ' git restore --ignore-unmerged --quiet . >output 2>&1 && git diff common >diff-output && - : >empty && - test_cmp empty output && - test_cmp empty diff-output + test_must_be_empty output && + test_must_be_empty diff-output ) ' -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 4/4] restore: add --intent-to-add (restoring worktree only)
"git restore --source" (without --staged) could create new files (i.e. not present in index) on worktree to match the given source. But the new files are not tracked, so both "git diff" and "git diff " ignore new files. "git commit -a" will not recreate a commit exactly as the given source either. Add --intent-to-add to help track new files in this case, which is the default on the least surprise principle. Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/git-restore.txt | 7 builtin/checkout.c| 78 +++ t/t2070-restore.sh| 17 3 files changed, 102 insertions(+) diff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt index d90093f195..43a7f43b2b 100644 --- a/Documentation/git-restore.txt +++ b/Documentation/git-restore.txt @@ -93,6 +93,13 @@ in linkgit:git-checkout[1] for details. are "merge" (default) and "diff3" (in addition to what is shown by "merge" style, shows the original contents). +--intent-to-add:: +--no-intent-to-add:: + When restoring files only on working tree with `--source`, + new files are marked as "intent to add" (see + linkgit:git-add[1]). This is the default behavior. Use + `--no-intent-to-add` to disable it. + --ignore-unmerged:: When restoring files on the working tree from the index, do not abort the operation if there are unmerged entries and diff --git a/builtin/checkout.c b/builtin/checkout.c index f884d27f1f..c519067d3d 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -70,6 +70,7 @@ struct checkout_opts { int checkout_worktree; const char *ignore_unmerged_opt; int ignore_unmerged; + int intent_to_add; const char *new_branch; const char *new_branch_force; @@ -392,6 +393,69 @@ static int checkout_worktree(const struct checkout_opts *opts) return errs; } +/* + * Input condition: r->index contains the file list matching worktree. + * + * r->index is reloaded with $GIT_DIR/index. Files that exist in the + * current worktree but not in $GIT_DIR/index are added back as + * intent-to-add. + */ +static int add_intent_to_add_files(struct repository *r) +{ + char **file_list; + int pos, worktree_nr, ita_nr = 0; + int ret = 0; + + worktree_nr = r->index->cache_nr; + ALLOC_ARRAY(file_list, worktree_nr); + for (pos = 0; pos < worktree_nr; pos++) + file_list[pos] = xstrdup(r->index->cache[pos]->name); + + discard_index(r->index); + if (repo_read_index(r) < 0) { + ret = error(_("index file corrupt")); + goto done; + } + + for (pos = 0; pos < worktree_nr; ) { + const char *worktree = file_list[pos]; + int index_pos = index_name_pos(r->index, worktree, strlen(worktree)); + + if (index_pos < 0) { + if (add_file_to_index(r->index, worktree, ADD_CACHE_INTENT)) + ret = error(_("failed to add %s"), worktree); + else + ita_nr++; + pos++; + continue; + } + + /* +* Try to speed up the scanning process a bit. +* +* The assumption here is file_list[] and r->index->cache[] +* are 90% the same. We can just skip a big chunk of the same +* entries and reduce the number of binary lookups. +*/ + pos++; + index_pos++; + while (pos < worktree_nr && index_pos < r->index->cache_nr && + !fspathcmp(file_list[pos], r->index->cache[index_pos]->name)) { + pos++; + index_pos++; + } + } + + if (!ret) + ret = ita_nr; + +done: + for (pos = 0; pos < worktree_nr; pos++) + free(file_list[pos]); + free(file_list); + return ret; +} + static int checkout_paths(const struct checkout_opts *opts, const char *revision) { @@ -531,6 +595,16 @@ static int checkout_paths(const struct checkout_opts *opts, else checkout_index = opts->checkout_index; + if (opts->intent_to_add && opts->from_treeish && + !opts->checkout_index && opts->checkout_worktree) { + int ita_nr = add_intent_to_add_files(the_repository); + + if (ita_nr > 0) + checkout_index = 1; + if (ita_nr < 0) + errs = -1; + } + if (checkout_index) { if (writ
[PATCH 2/4] switch: allow to switch in the middle of bisect
In c45f0f525d (switch: reject if some operation is in progress, 2019-03-29), a check is added to prevent switching when some operation is in progress. The reason is it's often not safe to do so. This is true for merge, am, rebase, cherry-pick and revert, but not so much for bisect because bisecting is basically jumping/switching between a bunch of commits to pin point the first bad one. git-bisect suggests the next commit to test, but it's not wrong for the user to test a different commit because git-bisect cannot have the knowledge to know better. For this reason, allow to switch when bisecting (*). I considered if we should still prevent switching by default and allow it with --ignore-in-progress. But I don't think the prevention really adds anything much. If the user switches away by mistake, since we print the previous HEAD value, even if they don't know about the "-" shortcut, switching back is still possible. The warning will be printed on every switch while bisect is still ongoing, not the first time you switch away from bisect's suggested commit, so it could become a bit annoying. (*) of course when it's safe to do so, i.e. no loss of local changes and stuff. Signed-off-by: Nguyễn Thái Ngọc Duy --- builtin/checkout.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index bed79ae595..f884d27f1f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1326,9 +1326,7 @@ static void die_if_some_operation_in_progress(void) "Consider \"git revert --quit\" " "or \"git worktree add\".")); if (state.bisect_in_progress) - die(_("cannot switch branch while bisecting\n" - "Consider \"git bisect reset HEAD\" " - "or \"git worktree add\".")); + warning(_("you are switching branch while bisecting")); } static int checkout_branch(struct checkout_opts *opts, -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 0/4] Some more on top of nd/switch-and-restore
This is small refinements (except 4/4). 2/4 relaxes the 'in-progress' check for bisect because switching while bisecting is normal _and_ safe. 3/4 makes 'switch -d' completion much more useful. 4/4 adds the last missing piece in 'git restore', records new files in worktree as i-t-a. Still on the agenda (but may take some or much more time to do): - submodule support in 'git restore' - handling "git restore *.c" where *.c is expanded by shell One item I have a patch for but decided not to send, is to imply --detach in 'git switch' if you are already in detached HEAD mode and want to switch to a non-branch. In other words, it behaves just like git-checkout. No more protection is needed in that case because you're in trouble already if you don't know about detached HEAD. And if you do know, then adding '-d' is just annoyance. But I don't find myself using it and I'm a pretty heavy detached user. So while it kinda makes sense to do, I don't think it's worth the complication. Nguyễn Thái Ngọc Duy (4): t2027: use test_must_be_empty switch: allow to switch in the middle of bisect completion: disable dwim on "git switch -d" restore: add --intent-to-add (restoring worktree only) Documentation/git-restore.txt | 7 +++ builtin/checkout.c | 82 +- contrib/completion/git-completion.bash | 4 ++ t/t2070-restore.sh | 22 ++- 4 files changed, 109 insertions(+), 6 deletions(-) -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 3/4] completion: disable dwim on "git switch -d"
Even though dwim is enabled by default, it will never be done when --detached is specified. If you force "-d --guess" you will get an error because --guess then implies -c which cannot be used with -d. So we can disable dwim in "switch -d". It makes the completion list in this case a bit shorter. Signed-off-by: Nguyễn Thái Ngọc Duy --- contrib/completion/git-completion.bash | 4 1 file changed, 4 insertions(+) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 58d18d41a2..656e49710e 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2183,6 +2183,10 @@ _git_switch () fi if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then only_local_ref=y + else + # --guess --detach is invalid combination, no + # dwim will be done when --detach is specified + track_opt= fi if [ $only_local_ref = y -a -z "$track_opt" ]; then __gitcomp_direct "$(__git_heads "" "$cur" " ")" -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 7/8] cache-tree.c: dump "TREE" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- cache-tree.c | 41 - cache-tree.h | 5 - read-cache.c | 2 +- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/cache-tree.c b/cache-tree.c index b13bfaf71e..fc44016fe8 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -3,6 +3,7 @@ #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" +#include "json-writer.h" #include "object-store.h" #include "replace-object.h" @@ -492,7 +493,8 @@ void cache_tree_write(struct strbuf *sb, struct cache_tree *root) write_one(sb, root, "", 0); } -static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) +static struct cache_tree *read_one(const char **buffer, unsigned long *size_p, + struct json_writer *jw) { const char *buf = *buffer; unsigned long size = *size_p; @@ -546,6 +548,15 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) *buffer, subtree_nr); #endif + if (jw) { + if (it->entry_count >= 0) { + jw_object_string(jw, "oid", oid_to_hex(&it->oid)); + jw_object_intmax(jw, "entry_count", it->entry_count); + } else { + jw_object_null(jw, "oid"); + } + jw_object_inline_begin_array(jw, "subdirs"); + } /* * Just a heuristic -- we do not add directories that often but * we do not want to have to extend it immediately when we do, @@ -559,12 +570,18 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) struct cache_tree_sub *subtree; const char *name = buf; - sub = read_one(&buf, &size); + if (jw) { + jw_array_inline_begin_object(jw); + jw_object_string(jw, "name", name); + } + sub = read_one(&buf, &size, jw); + jw_end_gently(jw); if (!sub) goto free_return; subtree = cache_tree_sub(it, name); subtree->cache_tree = sub; } + jw_end_gently(jw); if (subtree_nr != it->subtree_nr) die("cache-tree: internal error"); *buffer = buf; @@ -576,11 +593,25 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) return NULL; } -struct cache_tree *cache_tree_read(const char *buffer, unsigned long size) +struct cache_tree *cache_tree_read(const char *buffer, unsigned long size, + struct json_writer *jw) { + struct cache_tree *ret; + + if (jw) { + jw_object_inline_begin_object(jw, "cache-tree"); + jw_object_intmax(jw, "ext-size", size); + jw_object_inline_begin_object(jw, "root"); + } if (buffer[0]) - return NULL; /* not the whole tree */ - return read_one(&buffer, &size); + ret = NULL; /* not the whole tree */ + else + ret = read_one(&buffer, &size, jw); + if (jw) { + jw_end(jw); /* root */ + jw_end(jw); /* cache-tree */ + } + return ret; } static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path) diff --git a/cache-tree.h b/cache-tree.h index 757bbc48bc..fc3c73284b 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -6,6 +6,8 @@ #include "tree-walk.h" struct cache_tree; +struct json_writer; + struct cache_tree_sub { struct cache_tree *cache_tree; int count; /* internally used by update_one() */ @@ -28,7 +30,8 @@ void cache_tree_invalidate_path(struct index_state *, const char *); struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *); void cache_tree_write(struct strbuf *, struct cache_tree *root); -struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); +struct cache_tree *cache_tree_read(const char *buffer, unsigned long size, + struct json_writer *jw); int cache_tree_fully_valid(struct cache_tree *); int cache_tree_update(struct index_state *, int); diff --git a/read-cache.c b/read-cache.c index 200834e77e..289705b816 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1698,7 +1698,7 @@ static int read_index_extension(struct index_state *istate, { switch (CACHE_EXT(ext)) { case CACHE_EXT_TREE: - istate->cache_tree = cache_tree_read(data, sz); + istate->cache_tree = cache_tree_read(data, sz, istate->jw); break; case CACHE_EXT_RESOLVE_UNDO: istate->resolve_undo = resolve_undo_read(data, sz, istate->jw); -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 8/8] dir.c: dump "UNTR" extension as json
The big part of UNTR extension is dumped at the end instead of dumping as soon as we read it, because we actually "patch" some fields in untracked_cache_dir with EWAH bitmaps at the end. Signed-off-by: Nguyễn Thái Ngọc Duy --- dir.c | 56 ++- dir.h | 4 +++- json-writer.h | 6 ++ read-cache.c | 2 +- 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/dir.c b/dir.c index ba4a51c296..f389eee24a 100644 --- a/dir.c +++ b/dir.c @@ -19,6 +19,7 @@ #include "varint.h" #include "ewah/ewok.h" #include "fsmonitor.h" +#include "json-writer.h" #include "submodule-config.h" /* @@ -2826,7 +2827,42 @@ static void load_oid_stat(struct oid_stat *oid_stat, const unsigned char *data, oid_stat->valid = 1; } -struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz) +static void jw_object_oid_stat(struct json_writer *jw, const char *key, + const struct oid_stat *oid_stat) +{ + jw_object_inline_begin_object(jw, key); + jw_object_bool(jw, "valid", oid_stat->valid); + jw_object_string(jw, "oid", oid_to_hex(&oid_stat->oid)); + jw_object_stat_data(jw, "stat", &oid_stat->stat); + jw_end(jw); +} + +static void jw_object_untracked_cache_dir(struct json_writer *jw, + const struct untracked_cache_dir *ucd) +{ + int i; + + jw_object_bool(jw, "valid", ucd->valid); + jw_object_bool(jw, "check-only", ucd->check_only); + jw_object_stat_data(jw, "stat", &ucd->stat_data); + jw_object_string(jw, "exclude-oid", oid_to_hex(&ucd->exclude_oid)); + jw_object_inline_begin_array(jw, "untracked"); + for (i = 0; i < ucd->untracked_nr; i++) + jw_array_string(jw, ucd->untracked[i]); + jw_end(jw); + + jw_object_inline_begin_object(jw, "dirs"); + for (i = 0; i < ucd->dirs_nr; i++) { + jw_object_inline_begin_object(jw, ucd->dirs[i]->name); + jw_object_untracked_cache_dir(jw, ucd->dirs[i]); + jw_end(jw); + } + jw_end(jw); +} + +struct untracked_cache *read_untracked_extension(const void *data, +unsigned long sz, +struct json_writer *jw) { struct untracked_cache *uc; struct read_data rd; @@ -2864,6 +2900,17 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long uc->dir_flags = get_be32(next + ouc_offset(dir_flags)); exclude_per_dir = (const char *)next + exclude_per_dir_offset; uc->exclude_per_dir = xstrdup(exclude_per_dir); + + if (jw) { + jw_object_inline_begin_object(jw, "untracked-cache"); + jw_object_intmax(jw, "ext-size", sz); + jw_object_string(jw, "ident", ident); + jw_object_oid_stat(jw, "info/exclude", &uc->ss_info_exclude); + jw_object_oid_stat(jw, "excludes-file", &uc->ss_excludes_file); + jw_object_intmax(jw, "flags", uc->dir_flags); + jw_object_string(jw, "excludes-per-dir", uc->exclude_per_dir); + } + /* NUL after exclude_per_dir is covered by sizeof(*ouc) */ next += exclude_per_dir_offset + strlen(exclude_per_dir) + 1; if (next >= end) @@ -2905,6 +2952,12 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long ewah_each_bit(rd.sha1_valid, read_oid, &rd); next = rd.data; + if (jw) { + jw_object_inline_begin_object(jw, "root"); + jw_object_untracked_cache_dir(jw, uc->root); + jw_end(jw); + } + done: free(rd.ucd); ewah_free(rd.valid); @@ -2915,6 +2968,7 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long free_untracked_cache(uc); uc = NULL; } + jw_end_gently(jw); return uc; } diff --git a/dir.h b/dir.h index 680079bbe3..80efdd05c4 100644 --- a/dir.h +++ b/dir.h @@ -6,6 +6,8 @@ #include "cache.h" #include "strbuf.h" +struct json_writer; + struct dir_entry { unsigned int len; char name[FLEX_ARRAY]; /* more */ @@ -362,7 +364,7 @@ void untracked_cache_remove_from_index(struct index_state *, const char *); void untracked_cache_add_to_index(struct index_state *, const char *); void free_untracked_cache(struct untracked_cache *); -struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz); +struct untr
[PATCH 1/8] ls-files: add --json to dump the index
So far we don't have a command to basically dump the index file out, with all its glory details. Checking some info, for example, stat time, usually involves either writing new code or firing up "xxd" and decoding values by yourself. This --json is supposed to help that. It dumps the index in a human readable format but also easy to be processed with tools. And it will print almost enough info to reconstruct the index later. In this patch we only dump the main part, not extensions. But at the end of the series, the entire index is dumped. The end result could be very verbose even on a small repository such as git.git. Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/git-ls-files.txt | 5 +++ builtin/ls-files.c | 30 +++--- cache.h| 2 + json-writer.c | 16 json-writer.h | 21 ++ read-cache.c | 73 +- 6 files changed, 140 insertions(+), 7 deletions(-) diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 8461c0e83e..54011c8f65 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -60,6 +60,11 @@ OPTIONS --stage:: Show staged contents' mode bits, object name and stage number in the output. +--json:: + Dump the entire index content in JSON format. This is for + debugging purposes and the JSON structure may change from time + to time. + --directory:: If a whole directory is classified as "other", show just its name (with a trailing slash) and not its whole contents. diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 7f83c9a6f2..d00f6d3074 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -8,6 +8,7 @@ #include "cache.h" #include "repository.h" #include "config.h" +#include "json-writer.h" #include "quote.h" #include "dir.h" #include "builtin.h" @@ -31,6 +32,7 @@ static int show_modified; static int show_killed; static int show_valid_bit; static int show_fsmonitor_bit; +static int show_json; static int line_terminator = '\n'; static int debug_mode; static int show_eol; @@ -543,6 +545,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("show staged contents' object name in the output")), OPT_BOOL('k', "killed", &show_killed, N_("show files on the filesystem that need to be removed")), + OPT_BOOL(0, "json", &show_json, + N_("dump index content in json format")), OPT_BIT(0, "directory", &dir.flags, N_("show 'other' directories' names only"), DIR_SHOW_OTHER_DIRECTORIES), @@ -660,8 +664,12 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) /* With no flags, we default to showing the cached files */ if (!(show_stage || show_deleted || show_others || show_unmerged || - show_killed || show_modified || show_resolve_undo)) + show_killed || show_modified || show_resolve_undo || show_json)) show_cached = 1; + if (show_json && (show_stage || show_deleted || show_others || + show_unmerged || show_killed || show_modified || + show_cached || show_resolve_undo || with_tree)) + die(_("--show-json cannot be used with other --show- options, or --with-tree")); if (with_tree) { /* @@ -673,10 +681,22 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) overlay_tree_on_index(the_repository->index, with_tree, max_prefix); } - show_files(the_repository, &dir); - - if (show_resolve_undo) - show_ru_info(the_repository->index); + if (!show_json) { + show_files(the_repository, &dir); + + if (show_resolve_undo) + show_ru_info(the_repository->index); + } else { + struct json_writer jw = JSON_WRITER_INIT; + + discard_index(the_repository->index); + the_repository->index->jw = &jw; + if (repo_read_index(the_repository) < 0) + die("index file corrupt"); + puts(jw.json.buf); + the_repository->index->jw = NULL; + jw_release(&jw); + } if (ps_matched) { int bad; diff --git a/cache.h b/cache.h index bf20337ef4..84d0aeed20 100644 --- a/cache.h +++ b/cache.h @@ -326,6 +326,7 @@ static inline unsigned int canon_mode(uns
[PATCH 6/8] read-cache.c: dump "IEOT" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- read-cache.c | 34 +- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/read-cache.c b/read-cache.c index 04863c3853..200834e77e 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1911,7 +1911,7 @@ struct index_entry_offset_table struct index_entry_offset entries[FLEX_ARRAY]; }; -static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset); +static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset, struct json_writer *jw); static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot); static size_t read_eoie_extension(const char *mmap, size_t mmap_size, struct json_writer *jw); @@ -2232,6 +2232,8 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) nr_threads = 1; if (istate->jw) { + size_t off; + jw_object_begin(istate->jw, jw_pretty); jw_object_intmax(istate->jw, "version", istate->version); jw_object_string(istate->jw, "oid", oid_to_hex(&istate->oid)); @@ -2243,8 +2245,11 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) * debugging only, so performance is not a concern. */ nr_threads = 1; - /* and dump EOIE extension even with threading off */ - read_eoie_extension(mmap, mmap_size, istate->jw); + /* and dump EOIE/IOET extensions even with threading off */ + off = read_eoie_extension(mmap, mmap_size, istate->jw); + if (off) + free(read_ieot_extension(mmap, mmap_size, +off, istate->jw)); } if (nr_threads > 1) { @@ -2266,7 +2271,7 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) * to multi-thread the reading of the cache entries. */ if (extension_offset && nr_threads > 1) - ieot = read_ieot_extension(mmap, mmap_size, extension_offset); + ieot = read_ieot_extension(mmap, mmap_size, extension_offset, NULL); if (ieot) { src_offset += load_cache_entries_threaded(istate, mmap, mmap_size, nr_threads, ieot); @@ -3630,7 +3635,9 @@ static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, #define IEOT_VERSION (1) -static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset) +static struct index_entry_offset_table *read_ieot_extension( + const char *mmap, size_t mmap_size, + size_t offset, struct json_writer *jw) { const char *index = NULL; uint32_t extsize, ext_version; @@ -3666,6 +3673,12 @@ static struct index_entry_offset_table *read_ieot_extension(const char *mmap, si error("invalid number of IEOT entries %d", nr); return NULL; } + if (jw) { + jw_object_inline_begin_object(jw, "index-entry-offsets"); + jw_object_intmax(jw, "version", ext_version); + jw_object_intmax(jw, "ext-size", extsize); + jw_object_inline_begin_array(jw, "entries"); + } ieot = xmalloc(sizeof(struct index_entry_offset_table) + (nr * sizeof(struct index_entry_offset))); ieot->nr = nr; @@ -3674,6 +3687,17 @@ static struct index_entry_offset_table *read_ieot_extension(const char *mmap, si index += sizeof(uint32_t); ieot->entries[i].nr = get_be32(index); index += sizeof(uint32_t); + + if (jw) { + jw_array_inline_begin_object(jw); + jw_object_intmax(jw, "offset", ieot->entries[i].offset); + jw_object_intmax(jw, "count", ieot->entries[i].nr); + jw_end(jw); + } + } + if (jw) { + jw_end(jw); /* entries */ + jw_end(jw); /* index-entry-offsets */ } return ieot; -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 4/8] resolve-undo.c: dump "REUC" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- read-cache.c | 2 +- resolve-undo.c | 36 +++- resolve-undo.h | 4 +++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/read-cache.c b/read-cache.c index eec030b3bb..3b5c63f53a 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1701,7 +1701,7 @@ static int read_index_extension(struct index_state *istate, istate->cache_tree = cache_tree_read(data, sz); break; case CACHE_EXT_RESOLVE_UNDO: - istate->resolve_undo = resolve_undo_read(data, sz); + istate->resolve_undo = resolve_undo_read(data, sz, istate->jw); break; case CACHE_EXT_LINK: if (read_link_extension(istate, data, sz)) diff --git a/resolve-undo.c b/resolve-undo.c index 236320f179..999020bc40 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -1,5 +1,6 @@ #include "cache.h" #include "dir.h" +#include "json-writer.h" #include "resolve-undo.h" #include "string-list.h" @@ -49,7 +50,8 @@ void resolve_undo_write(struct strbuf *sb, struct string_list *resolve_undo) } } -struct string_list *resolve_undo_read(const char *data, unsigned long size) +struct string_list *resolve_undo_read(const char *data, unsigned long size, + struct json_writer *jw) { struct string_list *resolve_undo; size_t len; @@ -59,6 +61,11 @@ struct string_list *resolve_undo_read(const char *data, unsigned long size) resolve_undo = xcalloc(1, sizeof(*resolve_undo)); resolve_undo->strdup_strings = 1; + if (jw) { + jw_object_inline_begin_object(jw, "resolve-undo"); + jw_object_intmax(jw, "ext-size", size); + jw_object_inline_begin_array(jw, "entries"); + } while (size) { struct string_list_item *lost; @@ -94,6 +101,33 @@ struct string_list *resolve_undo_read(const char *data, unsigned long size) size -= rawsz; data += rawsz; } + + if (jw) { + struct strbuf sb = STRBUF_INIT; + + jw_array_inline_begin_object(jw); + jw_object_string(jw, "path", lost->string); + + jw_object_inline_begin_array(jw, "mode"); + for (i = 0; i < 3; i++) { + strbuf_addf(&sb, "%06o", ui->mode[i]); + jw_array_string(jw, sb.buf); + strbuf_reset(&sb); + } + jw_end(jw); + + jw_object_inline_begin_array(jw, "oid"); + for (i = 0; i < 3; i++) + jw_array_string(jw, oid_to_hex(&ui->oid[i])); + jw_end(jw); + + jw_end(jw); + strbuf_release(&sb); + } + } + if (jw) { + jw_end(jw); /* entries */ + jw_end(jw); /* resolve-undo */ } return resolve_undo; diff --git a/resolve-undo.h b/resolve-undo.h index 2b3f0f901e..46b4e93a7e 100644 --- a/resolve-undo.h +++ b/resolve-undo.h @@ -3,6 +3,8 @@ #include "cache.h" +struct json_writer; + struct resolve_undo_info { unsigned int mode[3]; struct object_id oid[3]; @@ -10,7 +12,7 @@ struct resolve_undo_info { void record_resolve_undo(struct index_state *, struct cache_entry *); void resolve_undo_write(struct strbuf *, struct string_list *); -struct string_list *resolve_undo_read(const char *, unsigned long); +struct string_list *resolve_undo_read(const char *, unsigned long, struct json_writer *); void resolve_undo_clear_index(struct index_state *); int unmerge_index_entry_at(struct index_state *, int); void unmerge_index(struct index_state *, const struct pathspec *); -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 5/8] read-cache.c: dump "EOIE" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- read-cache.c | 30 +++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/read-cache.c b/read-cache.c index 3b5c63f53a..04863c3853 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1914,7 +1914,7 @@ struct index_entry_offset_table static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset); static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot); -static size_t read_eoie_extension(const char *mmap, size_t mmap_size); +static size_t read_eoie_extension(const char *mmap, size_t mmap_size, struct json_writer *jw); static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, size_t offset); struct load_index_extensions @@ -2243,10 +2243,12 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) * debugging only, so performance is not a concern. */ nr_threads = 1; + /* and dump EOIE extension even with threading off */ + read_eoie_extension(mmap, mmap_size, istate->jw); } if (nr_threads > 1) { - extension_offset = read_eoie_extension(mmap, mmap_size); + extension_offset = read_eoie_extension(mmap, mmap_size, NULL); if (extension_offset) { int err; @@ -3504,7 +3506,8 @@ int should_validate_cache_entries(void) #define EOIE_SIZE (4 + GIT_SHA1_RAWSZ) /* <4-byte offset> + <20-byte hash> */ #define EOIE_SIZE_WITH_HEADER (4 + 4 + EOIE_SIZE) /* <4-byte signature> + <4-byte length> + EOIE_SIZE */ -static size_t read_eoie_extension(const char *mmap, size_t mmap_size) +static size_t read_eoie_extension(const char *mmap, size_t mmap_size, + struct json_writer *jw) { /* * The end of index entries (EOIE) extension is guaranteed to be last @@ -3548,6 +3551,12 @@ static size_t read_eoie_extension(const char *mmap, size_t mmap_size) return 0; index += sizeof(uint32_t); + if (jw) { + jw_object_inline_begin_object(jw, "end-of-index"); + jw_object_intmax(jw, "offset", offset); + jw_object_intmax(jw, "ext-size", extsize); + jw_object_inline_begin_array(jw, "extensions"); + } /* * The hash is computed over extension types and their sizes (but not * their contents). E.g. if we have "TREE" extension that is N-bytes @@ -3576,9 +3585,24 @@ static size_t read_eoie_extension(const char *mmap, size_t mmap_size) the_hash_algo->update_fn(&c, mmap + src_offset, 8); + if (jw) { + char name[5]; + + jw_array_inline_begin_object(jw); + memcpy(name, mmap + src_offset, 4); + name[4] = '\0'; + jw_object_string(jw, "name", name); + jw_object_intmax(jw, "size", extsize); + jw_end(jw); + } + src_offset += 8; src_offset += extsize; } + if (jw) { + jw_end(jw); /* extensions */ + jw_end(jw); /* end-of-index */ + } the_hash_algo->final_fn(hash, &c); if (!hasheq(hash, (const unsigned char *)index)) return 0; -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 2/8] split-index.c: dump "link" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- json-writer.c | 14 ++ json-writer.h | 2 ++ split-index.c | 13 - 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/json-writer.c b/json-writer.c index 281bc50b39..70403580ca 100644 --- a/json-writer.c +++ b/json-writer.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "ewah/ewok.h" #include "json-writer.h" void jw_init(struct json_writer *jw) @@ -218,6 +219,19 @@ void jw_object_stat_data(struct json_writer *jw, const char *name, jw_end(jw); } +static void dump_ewah_one(size_t pos, void *jw) +{ + jw_array_intmax(jw, pos); +} + +void jw_object_ewah(struct json_writer *jw, const char *key, + struct ewah_bitmap *ewah) +{ + jw_object_inline_begin_array(jw, key); + ewah_each_bit(ewah, dump_ewah_one, jw); + jw_end(jw); +} + static void increase_indent(struct strbuf *sb, const struct json_writer *jw, int indent) diff --git a/json-writer.h b/json-writer.h index 38f9c9bf68..3c173647d3 100644 --- a/json-writer.h +++ b/json-writer.h @@ -85,6 +85,8 @@ void jw_object_bool(struct json_writer *jw, const char *key, int value); void jw_object_null(struct json_writer *jw, const char *key); void jw_object_stat_data(struct json_writer *jw, const char *key, const struct stat_data *sd); +void jw_object_ewah(struct json_writer *jw, const char *key, + struct ewah_bitmap *ewah); void jw_object_sub_jw(struct json_writer *jw, const char *key, const struct json_writer *value); diff --git a/split-index.c b/split-index.c index e6154e4ea9..d7b4420c92 100644 --- a/split-index.c +++ b/split-index.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "json-writer.h" #include "split-index.h" #include "ewah/ewok.h" @@ -16,6 +17,7 @@ int read_link_extension(struct index_state *istate, { const unsigned char *data = data_; struct split_index *si; + unsigned long original_sz = sz; int ret; if (sz < the_hash_algo->rawsz) @@ -25,7 +27,7 @@ int read_link_extension(struct index_state *istate, data += the_hash_algo->rawsz; sz -= the_hash_algo->rawsz; if (!sz) - return 0; + goto done; si->delete_bitmap = ewah_new(); ret = ewah_read_mmap(si->delete_bitmap, data, sz); if (ret < 0) @@ -38,6 +40,15 @@ int read_link_extension(struct index_state *istate, return error("corrupt replace bitmap in link extension"); if (ret != sz) return error("garbage at the end of link extension"); +done: + if (istate->jw) { + jw_object_inline_begin_object(istate->jw, "split-index"); + jw_object_string(istate->jw, "oid", oid_to_hex(&si->base_oid)); + jw_object_ewah(istate->jw, "delete-bitmap", si->delete_bitmap); + jw_object_ewah(istate->jw, "replace-bitmap", si->replace_bitmap); + jw_object_intmax(istate->jw, "ext-size", original_sz); + jw_end(istate->jw); + } return 0; } -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 3/8] fsmonitor.c: dump "FSMN" extension as json
Signed-off-by: Nguyễn Thái Ngọc Duy --- fsmonitor.c | 9 + 1 file changed, 9 insertions(+) diff --git a/fsmonitor.c b/fsmonitor.c index 1dee0aded1..f6ba437255 100644 --- a/fsmonitor.c +++ b/fsmonitor.c @@ -3,6 +3,7 @@ #include "dir.h" #include "ewah/ewok.h" #include "fsmonitor.h" +#include "json-writer.h" #include "run-command.h" #include "strbuf.h" @@ -50,6 +51,14 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data, } istate->fsmonitor_dirty = fsmonitor_dirty; + if (istate->jw) { + jw_object_inline_begin_object(istate->jw, "fsmonitor"); + jw_object_intmax(istate->jw, "version", hdr_version); + jw_object_intmax(istate->jw, "last-update", istate->fsmonitor_last_update); + jw_object_ewah(istate->jw, "dirty", fsmonitor_dirty); + jw_object_intmax(istate->jw, "ext-size", sz); + jw_end(istate->jw); + } trace_printf_key(&trace_fsmonitor, "read fsmonitor extension successful"); return 0; } -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 0/8] Add 'ls-files --json' to dump the index in json
This is probably just my itch. Every time I have to do something with the index, I need to add a little bit code here, a little bit there to get a better "view" of the index. This solves it for me. It allows me to see pretty much everything in the index (except really low detail stuff like pathname compression). It's readable by human, but also easy to parse if you need to do statistics and stuff. You could even do a "diff" between two indexes. I'm not really sure if anybody else finds this useful. Because if not, I guess there's not much point trying to merge it to git.git just for a single user. Maintaining off tree is still a pain for me, but I think I can manage it. Nguyễn Thái Ngọc Duy (8): ls-files: add --json to dump the index split-index.c: dump "link" extension as json fsmonitor.c: dump "FSMN" extension as json resolve-undo.c: dump "REUC" extension as json read-cache.c: dump "EOIE" extension as json read-cache.c: dump "IEOT" extension as json cache-tree.c: dump "TREE" extension as json dir.c: dump "UNTR" extension as json Documentation/git-ls-files.txt | 5 ++ builtin/ls-files.c | 30 +-- cache-tree.c | 41 -- cache-tree.h | 5 +- cache.h| 2 + dir.c | 56 - dir.h | 4 +- fsmonitor.c| 9 +++ json-writer.c | 30 +++ json-writer.h | 29 +++ read-cache.c | 139 ++--- resolve-undo.c | 36 - resolve-undo.h | 4 +- split-index.c | 13 ++- 14 files changed, 376 insertions(+), 27 deletions(-) -- 2.22.0.rc0.322.g2b0371e29a
[PATCH] fetch: only run 'gc' once when fetching multiple remotes
In multiple remotes mode, git-fetch is launched for n-1 remotes and the last remote is handled by the current process. Each of these processes will in turn run 'gc' at the end. This is not really a problem because even if multiple 'gc --auto' is run at the same time we still handle it correctly. It does show multiple "auto packing in the background" messages though. And we may waste some resources when gc actually runs because we still do some stuff before checking the lock and moving it to background. So let's try to avoid that. We should only need one 'gc' run after all objects and references are added anyway. Add a new option --no-auto-gc that will be used by those n-1 processes. 'gc --auto' will always run on the main fetch process (*). (*) even if we fetch remotes in parallel at some point in future, this should still be fine because we should "join" all those processes before this step. Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/fetch-options.txt | 4 builtin/fetch.c | 17 +++-- t/t5514-fetch-multiple.sh | 7 +-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index 91c47752ec..592f391298 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -88,6 +88,10 @@ ifndef::git-pull[] Allow several and arguments to be specified. No s may be specified. +--[no-]auto-gc:: + Run `git gc --auto` at the end to perform garbage collection + if needed. This is enabled by default. + -p:: --prune:: Before fetching, remove any remote-tracking references that no diff --git a/builtin/fetch.c b/builtin/fetch.c index 4ba63d5ac6..6a3c507897 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -48,6 +48,7 @@ static int prune_tags = -1; /* unspecified */ static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative; static int progress = -1; +static int enable_auto_gc = 1; static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen; static int max_children = 1; static enum transport_family family; @@ -169,6 +170,8 @@ static struct option builtin_fetch_options[] = { OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"), N_("report that we have only objects reachable from this object")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "auto-gc", &enable_auto_gc, +N_("run 'gc --auto' after fetching")), OPT_END() }; @@ -1424,7 +1427,7 @@ static int fetch_multiple(struct string_list *list) return errcode; } - argv_array_pushl(&argv, "fetch", "--append", NULL); + argv_array_pushl(&argv, "fetch", "--append", "--no-auto-gc", NULL); add_options_to_argv(&argv); for (i = 0; i < list->nr; i++) { @@ -1674,11 +1677,13 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) close_all_packs(the_repository->objects); - argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); - if (verbosity < 0) - argv_array_push(&argv_gc_auto, "--quiet"); - run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD); - argv_array_clear(&argv_gc_auto); + if (enable_auto_gc) { + argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); + if (verbosity < 0) + argv_array_push(&argv_gc_auto, "--quiet"); + run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD); + argv_array_clear(&argv_gc_auto); + } return result; } diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index 0030c92e1a..5426d4b5ab 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -105,9 +105,12 @@ test_expect_success 'git fetch --multiple (two remotes)' ' git remote rm origin && git remote add one ../one && git remote add two ../two && -git fetch --multiple one two && +GIT_TRACE=1 git fetch --multiple one two 2>trace && git branch -r > output && -test_cmp ../expect output) +test_cmp ../expect output && +grep "built-in: git gc" trace >gc && +test_line_count = 1 gc + ) ' test_expect_success 'git fetch --multiple (bad remote names)' ' -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2] completion: do not cache if --git-completion-helper fails
"git --git-completion-helper" could fail if the command checks for a repo before parse_options(). If the result is cached, later on when the user moves to a worktree with repo, tab completion will still fail. Avoid this by detecting errors and not cache the completion output. We can try again and hopefully succeed next time (e.g. when a repo is found). Of course if --git-completion-helper fails permanently because of other reasons (*), this will slow down completion. But I don't see any better option to handle that case. (*) one of those cases is if __gitcomp_builtin is called on a command that does not support --git-completion-helper. And we do have a generic call __git_complete_common "$command" but this case is protected with __git_support_parseopt_helper so we're good. Reported-by: Felipe Contreras Signed-off-by: Nguyễn Thái Ngọc Duy --- v2 simplifies the code. $nocache in v1 was overkill. contrib/completion/git-completion.bash | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9f71bcde96..8c6b610a24 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -400,7 +400,8 @@ __gitcomp_builtin () if [ -z "$options" ]; then # leading and trailing spaces are significant to make # option removal work correctly. - options=" $incl $(__git ${cmd/_/ } --git-completion-helper) " + options=" $incl $(__git ${cmd/_/ } --git-completion-helper) " || return + for i in $excl; do options="${options/ $i / }" done -- 2.22.0.rc0.322.g2b0371e29a
[PATCH] completion: do not cache if --git-completion-helper fails
"git --git-completion-helper" could fail if the command checks for a repo before parse_options(). If the result is cached, later on when the user moves to a worktree with repo, tab completion will still fail. Avoid this by detecting errors and not cache the completion output. We can try again and hopefully succeed next time (e.g. when a repo is found). Of course if --git-completion-helper fails permanently because of other reasons (*), this will slow down completion. But I don't see any better option to handle that case. (*) one of those cases is if __gitcomp_builtin is called on a command that does not support --git-completion-helper. And we do have a generic call __git_complete_common "$command" but this case is protected with __git_support_parseopt_helper so we're good. Reported-by: Felipe Contreras Signed-off-by: Nguyễn Thái Ngọc Duy --- contrib/completion/git-completion.bash | 6 -- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9f71bcde96..a515de8501 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -398,13 +398,15 @@ __gitcomp_builtin () eval "options=\$$var" if [ -z "$options" ]; then + local nocache= # leading and trailing spaces are significant to make # option removal work correctly. - options=" $incl $(__git ${cmd/_/ } --git-completion-helper) " + options=" $incl $(__git ${cmd/_/ } --git-completion-helper) " || nocache=t + for i in $excl; do options="${options/ $i / }" done - eval "$var=\"$options\"" + test -n "$nocache" || eval "$var=\"$options\"" fi __gitcomp "$options" -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 3/3] parse-options: check empty value in OPT_INTEGER and OPT_ABBREV
When parsing the argument for OPT_INTEGER and OPT_ABBREV, we check if we can parse the entire argument to a number with "if (*s)". There is one missing check: if "arg" is empty to begin with, we fail to notice. This could happen with long option by writing like git diff --inter-hunk-context= blah blah Before 16ed6c97cc (diff-parseopt: convert --inter-hunk-context, 2019-03-24), --inter-hunk-context is handled by a custom parser opt_arg() and does detect this correctly. This restores the bahvior for --inter-hunk-context and make sure all other integer options are handled the same (sane) way. For OPT_ABBREV this is new behavior. But it makes it consistent with the rest. PS. OPT_MAGNITUDE has similar code but git_parse_ulong() does detect empty "arg". So it's good to go. Signed-off-by: Nguyễn Thái Ngọc Duy --- parse-options-cb.c | 3 +++ parse-options.c| 3 +++ 2 files changed, 6 insertions(+) diff --git a/parse-options-cb.c b/parse-options-cb.c index 4b95d04a37..a3de795c58 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -16,6 +16,9 @@ int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset) if (!arg) { v = unset ? 0 : DEFAULT_ABBREV; } else { + if (!*arg) + return error(_("option `%s' expects a numerical value"), +opt->long_name); v = strtol(arg, (char **)&arg, 10); if (*arg) return error(_("option `%s' expects a numerical value"), diff --git a/parse-options.c b/parse-options.c index 987e27cb91..87b26a1d92 100644 --- a/parse-options.c +++ b/parse-options.c @@ -195,6 +195,9 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, } if (get_arg(p, opt, flags, &arg)) return -1; + if (!*arg) + return error(_("%s expects a numerical value"), +optname(opt, flags)); *(int *)opt->value = strtol(arg, (char **)&s, 10); if (*s) return error(_("%s expects a numerical value"), -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 2/3] diff-parseopt: restore -U (no argument) behavior
Before d473e2e0e8 (diff.c: convert -U|--unified, 2019-01-27), -U and --unified are implemented with a custom parser opt_arg() in diff.c. I didn't check this code carefully and not realize that it's the equivalent of PARSE_OPT_NONEG | PARSE_OPT_OPTARG. In other words, if -U is specified without any argument, the option should be accepted, and the default value should be used. Without PARSE_OPT_OPTARG, parse_options() will reject this case and cause a regression. Reported-by: Bryan Turner Signed-off-by: Nguyễn Thái Ngọc Duy --- diff.c| 10 --- t/t4013-diff-various.sh | 2 ++ t/t4013/diff.diff_-U1_initial..side (new) | 29 t/t4013/diff.diff_-U2_initial..side (new) | 31 ++ t/t4013/diff.diff_-U_initial..side (new) | 32 +++ 5 files changed, 100 insertions(+), 4 deletions(-) diff --git a/diff.c b/diff.c index 4d3cf83a27..80ddc11671 100644 --- a/diff.c +++ b/diff.c @@ -5211,9 +5211,11 @@ static int diff_opt_unified(const struct option *opt, BUG_ON_OPT_NEG(unset); - options->context = strtol(arg, &s, 10); - if (*s) - return error(_("%s expects a numerical value"), "--unified"); + if (arg) { + options->context = strtol(arg, &s, 10); + if (*s) + return error(_("%s expects a numerical value"), "--unified"); + } enable_patch_output(&options->output_format); return 0; @@ -5272,7 +5274,7 @@ static void prep_parse_options(struct diff_options *options) DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT), OPT_CALLBACK_F('U', "unified", options, N_(""), N_("generate diffs with lines context"), - PARSE_OPT_NONEG, diff_opt_unified), + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_unified), OPT_BOOL('W', "function-context", &options->flags.funccontext, N_("generate diffs with lines context")), OPT_BIT_F(0, "raw", &options->output_format, diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 9f8f0e84ad..a9054d2db1 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -338,6 +338,8 @@ format-patch --inline --stdout initial..master^^ format-patch --stdout --cover-letter -n initial..master^ diff --abbrev initial..side +diff -U initial..side +diff -U1 initial..side diff -r initial..side diff --stat initial..side diff -r --stat initial..side diff --git a/t/t4013/diff.diff_-U1_initial..side b/t/t4013/diff.diff_-U1_initial..side new file mode 100644 index 00..b69f8f048a --- /dev/null +++ b/t/t4013/diff.diff_-U1_initial..side @@ -0,0 +1,29 @@ +$ git diff -U1 initial..side +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub b/dir/sub +@@ -2 +2,3 @@ A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 b/file0 +@@ -3 +3,4 @@ + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 000..7289e35 +--- /dev/null b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.diff_-U2_initial..side b/t/t4013/diff.diff_-U2_initial..side new file mode 100644 index 00..8ffe04f203 --- /dev/null +++ b/t/t4013/diff.diff_-U2_initial..side @@ -0,0 +1,31 @@ +$ git diff -U2 initial..side +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 b/file0 +@@ -2,2 +2,5 @@ + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 000..7289e35 +--- /dev/null b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.diff_-U_initial..side b/t/t4013/diff.diff_-U_initial..side new file mode 100644 index 00..c66c0dd5c6 --- /dev/null +++ b/t/t4013/diff.diff_-U_initial..side @@ -0,0 +1,32 @@ +$ git diff -U initial..side +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 000..7289e35 +--- /dev/null b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 1/3] diff-parseopt: correct variable types that are used by parseopt
Most number-related OPT_ macros store the value in an 'int' variable. Many of the variables in 'struct diff_options' have a different type, but during the conversion to using parse_options() I failed to notice and correct. The problem was reported on s360x which is a big-endian architechture. The variable to store '-w' option in this case is xdl_opts, 'long' type, 8 bytes. But since parse_options() assumes 'int' (4 bytes), it will store bits in the wrong part of xdl_opts. The problem was found on little-endian platforms because parse_options() will accidentally store at the right part of xdl_opts. There aren't much to say about the type change (except that 'int' for xdl_opts should still be big enough, since Windows' long is the same size as 'int' and nobody has complained so far). Some safety checks may be implemented in the future to prevent class of bugs. Reported-by: Todd Zullinger Signed-off-by: Nguyễn Thái Ngọc Duy --- diff.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff.h b/diff.h index b20cbcc091..d5e44baa96 100644 --- a/diff.h +++ b/diff.h @@ -169,7 +169,7 @@ struct diff_options { const char *prefix; int prefix_length; const char *stat_sep; - long xdl_opts; + int xdl_opts; /* see Documentation/diff-options.txt */ char **anchors; -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v2 0/3] fix diff-parseopt regressions
v2 reduces diff noise. My C is rusty (and probably holey too). For some reason I remember "unsigned" is equivalent to "unsigned short", not "unsigned int". Nguyễn Thái Ngọc Duy (3): diff-parseopt: correct variable types that are used by parseopt diff-parseopt: restore -U (no argument) behavior parse-options: check empty value in OPT_INTEGER and OPT_ABBREV diff.c| 10 --- diff.h| 2 +- parse-options-cb.c| 3 +++ parse-options.c | 3 +++ t/t4013-diff-various.sh | 2 ++ t/t4013/diff.diff_-U1_initial..side (new) | 29 t/t4013/diff.diff_-U2_initial..side (new) | 31 ++ t/t4013/diff.diff_-U_initial..side (new) | 32 +++ 8 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 t/t4013/diff.diff_-U1_initial..side create mode 100644 t/t4013/diff.diff_-U2_initial..side create mode 100644 t/t4013/diff.diff_-U_initial..side Interdiff dựa trên v1: diff --git a/diff.h b/diff.h index 4527daf6b7..d5e44baa96 100644 --- a/diff.h +++ b/diff.h @@ -65,39 +65,39 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_FLAGS_INIT { 0 } struct diff_flags { - unsigned int recursive; - unsigned int tree_in_recursive; - unsigned int binary; - unsigned int text; - unsigned int full_index; - unsigned int silent_on_remove; - unsigned int find_copies_harder; - unsigned int follow_renames; - unsigned int rename_empty; - unsigned int has_changes; - unsigned int quick; - unsigned int no_index; - unsigned int allow_external; - unsigned int exit_with_status; - unsigned int reverse_diff; - unsigned int check_failed; - unsigned int relative_name; - unsigned int ignore_submodules; - unsigned int dirstat_cumulative; - unsigned int dirstat_by_file; - unsigned int allow_textconv; - unsigned int textconv_set_via_cmdline; - unsigned int diff_from_contents; - unsigned int dirty_submodules; - unsigned int ignore_untracked_in_submodules; - unsigned int ignore_dirty_submodules; - unsigned int override_submodule_config; - unsigned int dirstat_by_line; - unsigned int funccontext; - unsigned int default_follow_renames; - unsigned int stat_with_summary; - unsigned int suppress_diff_headers; - unsigned int dual_color_diffed_diffs; + unsigned recursive; + unsigned tree_in_recursive; + unsigned binary; + unsigned text; + unsigned full_index; + unsigned silent_on_remove; + unsigned find_copies_harder; + unsigned follow_renames; + unsigned rename_empty; + unsigned has_changes; + unsigned quick; + unsigned no_index; + unsigned allow_external; + unsigned exit_with_status; + unsigned reverse_diff; + unsigned check_failed; + unsigned relative_name; + unsigned ignore_submodules; + unsigned dirstat_cumulative; + unsigned dirstat_by_file; + unsigned allow_textconv; + unsigned textconv_set_via_cmdline; + unsigned diff_from_contents; + unsigned dirty_submodules; + unsigned ignore_untracked_in_submodules; + unsigned ignore_dirty_submodules; + unsigned override_submodule_config; + unsigned dirstat_by_line; + unsigned funccontext; + unsigned default_follow_renames; + unsigned stat_with_summary; + unsigned suppress_diff_headers; + unsigned dual_color_diffed_diffs; }; static inline void diff_flags_or(struct diff_flags *a, @@ -151,7 +151,7 @@ struct diff_options { int skip_stat_unmatch; int line_termination; int output_format; - unsigned int pickaxe_opts; + unsigned pickaxe_opts; int rename_score; int rename_limit; int needed_rename_limit; -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 4/3] parse-options: make compiler check value type mismatch
There is a disconnect between the actual value type from parse-options user and the parser itself. This is because we have to store 'value' pointer as 'void *' and the compiler cannot help point out that the user is passing a 'long' while the parser expects an 'int'. This could lead to memory corruption. In order to spot these type mismatch problems, a dummy inline function is used to process the 'value' input with the right type, before the input is stored in 'struct option'. This gives the compiler some context to start complaining. The catch though, is that we can only call a function in variable declaration if it's in automatic scope. Global and static 'struct option' variables will fail to build after this. But I think this is an reasonable price to pay, compared to memory corruption. Signed-off-by: Nguyễn Thái Ngọc Duy --- For the record, this is what I used to make patch 1/3 (I don't think I could just rely on manual code inspection to catch these problems) If we are doing something like this, then we have some clean up to do first. I think it's worth doing though. But maybe there's a better way? parse-options.h | 50 ++--- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/parse-options.h b/parse-options.h index ac6ba8abf9..b6ea0ac66d 100644 --- a/parse-options.h +++ b/parse-options.h @@ -128,55 +128,67 @@ struct option { intptr_t extra; }; -#define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), (v), NULL, (h), \ +#define DEFINE_OPT_TYPE_CHECK(name, type) \ + static inline void *_opt_ ## name(type *p) \ + { \ + return p; \ + } + +DEFINE_OPT_TYPE_CHECK(int, int) +DEFINE_OPT_TYPE_CHECK(ulong, unsigned long) +DEFINE_OPT_TYPE_CHECK(string, const char *) +DEFINE_OPT_TYPE_CHECK(string_list, struct string_list) +DEFINE_OPT_TYPE_CHECK(timestamp, timestamp_t) + +#define OPT_BIT_F(s, l, v, h, b, f) { OPTION_BIT, (s), (l), _opt_int(v), NULL, (h), \ PARSE_OPT_NOARG|(f), NULL, (b) } -#define OPT_COUNTUP_F(s, l, v, h, f) { OPTION_COUNTUP, (s), (l), (v), NULL, \ +#define OPT_COUNTUP_F(s, l, v, h, f) { OPTION_COUNTUP, (s), (l), _opt_int(v), NULL, \ (h), PARSE_OPT_NOARG|(f) } -#define OPT_SET_INT_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), (v), NULL, \ +#define OPT_SET_INT_F(s, l, v, h, i, f) { OPTION_SET_INT, (s), (l), _opt_int(v), NULL, \ (h), PARSE_OPT_NOARG | (f), NULL, (i) } #define OPT_BOOL_F(s, l, v, h, f) OPT_SET_INT_F(s, l, v, h, 1, f) #define OPT_CALLBACK_F(s, l, v, a, h, f, cb) \ { OPTION_CALLBACK, (s), (l), (v), (a), (h), (f), (cb) } -#define OPT_STRING_F(s, l, v, a, h, f) { OPTION_STRING, (s), (l), (v), (a), (h), (f) } -#define OPT_INTEGER_F(s, l, v, h, f) { OPTION_INTEGER, (s), (l), (v), N_("n"), (h), (f) } +#define OPT_STRING_F(s, l, v, a, h, f) { OPTION_STRING, (s), (l), _opt_string(v), (a), (h), (f) } +#define OPT_INTEGER_F(s, l, v, h, f) { OPTION_INTEGER, (s), (l), _opt_int(v), N_("n"), (h), (f) } #define OPT_END() { OPTION_END } -#define OPT_ARGUMENT(l, v, h) { OPTION_ARGUMENT, 0, (l), (v), NULL, \ +#define OPT_ARGUMENT(l, v, h) { OPTION_ARGUMENT, 0, (l), _opt_int(v), NULL, \ (h), PARSE_OPT_NOARG, NULL, 1 } #define OPT_GROUP(h){ OPTION_GROUP, 0, NULL, NULL, NULL, (h) } #define OPT_BIT(s, l, v, h, b) OPT_BIT_F(s, l, v, h, b, 0) -#define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), (v), NULL, (h), \ +#define OPT_BITOP(s, l, v, h, set, clear) { OPTION_BITOP, (s), (l), _opt_int(v), NULL, (h), \ PARSE_OPT_NOARG|PARSE_OPT_NONEG, NULL, \ (set), NULL, (clear) } -#define OPT_NEGBIT(s, l, v, h, b) { OPTION_NEGBIT, (s), (l), (v), NULL, \ +#define OPT_NEGBIT(s, l, v, h, b) { OPTION_NEGBIT, (s), (l), _opt_int(v), NULL, \ (h), PARSE_OPT_NOARG, NULL, (b) } #define OPT_COUNTUP(s, l, v, h) OPT_COUNTUP_F(s, l, v, h, 0) #define OPT_SET_INT(s, l, v, h, i) OPT_SET_INT_F(s, l, v, h, i, 0) #define OPT_BOOL(s, l, v, h)OPT_BOOL_F(s, l, v, h, 0) -#define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), (v), NULL, \ +#define OPT_HIDDEN_BOOL(s, l, v, h) { OPTION_SET_INT, (s), (l), _opt_int(v), NULL, \ (h), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1} -#define OPT_CMDMODE(s, l, v, h, i) { OPTION_CMDMODE, (s), (l), (v), NULL, \ +#define OPT_CMDMODE(s, l, v, h, i) { OPTION_CMDMODE, (s), (l), _opt_int(v), NULL, \
[PATCH 0/3] fix diff-parseopt regressions
This should fix the diff tests failure on s360x. It's a serious problem and I plan to do something to prevent it from happening again. The second patch should bring '-U' (no argument) back. Whether it makes sense to accept this behavior is not part of this conversion. We can deal with that later. The third patch also brings back a corner case behavior of --inter-hunk-context and as a result strengthens OPT_INTEGER() error handling a bit. Nguyễn Thái Ngọc Duy (3): diff-parseopt: correct variable types that are used by parseopt diff-parseopt: restore -U (no argument) behavior parse-options: check empty value in OPT_INTEGER and OPT_ABBREV diff.c| 10 ++-- diff.h| 70 +++ parse-options-cb.c| 3 + parse-options.c | 3 + t/t4013-diff-various.sh | 2 + t/t4013/diff.diff_-U1_initial..side (new) | 29 ++ t/t4013/diff.diff_-U2_initial..side (new) | 31 ++ t/t4013/diff.diff_-U_initial..side (new) | 32 +++ 8 files changed, 141 insertions(+), 39 deletions(-) create mode 100644 t/t4013/diff.diff_-U1_initial..side create mode 100644 t/t4013/diff.diff_-U2_initial..side create mode 100644 t/t4013/diff.diff_-U_initial..side -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 2/3] diff-parseopt: restore -U (no argument) behavior
Before d473e2e0e8 (diff.c: convert -U|--unified, 2019-01-27), -U and --unified are implemented with a custom parser opt_arg() in diff.c. I didn't check this code carefully and not realize that it's the equivalent of PARSE_OPT_NONEG | PARSE_OPT_OPTARG. In other words, if -U is specified without any argument, the option should be accepted, and the default value should be used. Without PARSE_OPT_OPTARG, parse_options() will reject this case and cause a regression. Reported-by: Bryan Turner Signed-off-by: Nguyễn Thái Ngọc Duy --- diff.c| 10 --- t/t4013-diff-various.sh | 2 ++ t/t4013/diff.diff_-U1_initial..side (new) | 29 t/t4013/diff.diff_-U2_initial..side (new) | 31 ++ t/t4013/diff.diff_-U_initial..side (new) | 32 +++ 5 files changed, 100 insertions(+), 4 deletions(-) diff --git a/diff.c b/diff.c index 4d3cf83a27..80ddc11671 100644 --- a/diff.c +++ b/diff.c @@ -5211,9 +5211,11 @@ static int diff_opt_unified(const struct option *opt, BUG_ON_OPT_NEG(unset); - options->context = strtol(arg, &s, 10); - if (*s) - return error(_("%s expects a numerical value"), "--unified"); + if (arg) { + options->context = strtol(arg, &s, 10); + if (*s) + return error(_("%s expects a numerical value"), "--unified"); + } enable_patch_output(&options->output_format); return 0; @@ -5272,7 +5274,7 @@ static void prep_parse_options(struct diff_options *options) DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT), OPT_CALLBACK_F('U', "unified", options, N_(""), N_("generate diffs with lines context"), - PARSE_OPT_NONEG, diff_opt_unified), + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_unified), OPT_BOOL('W', "function-context", &options->flags.funccontext, N_("generate diffs with lines context")), OPT_BIT_F(0, "raw", &options->output_format, diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 9f8f0e84ad..a9054d2db1 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -338,6 +338,8 @@ format-patch --inline --stdout initial..master^^ format-patch --stdout --cover-letter -n initial..master^ diff --abbrev initial..side +diff -U initial..side +diff -U1 initial..side diff -r initial..side diff --stat initial..side diff -r --stat initial..side diff --git a/t/t4013/diff.diff_-U1_initial..side b/t/t4013/diff.diff_-U1_initial..side new file mode 100644 index 00..b69f8f048a --- /dev/null +++ b/t/t4013/diff.diff_-U1_initial..side @@ -0,0 +1,29 @@ +$ git diff -U1 initial..side +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub b/dir/sub +@@ -2 +2,3 @@ A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 b/file0 +@@ -3 +3,4 @@ + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 000..7289e35 +--- /dev/null b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.diff_-U2_initial..side b/t/t4013/diff.diff_-U2_initial..side new file mode 100644 index 00..8ffe04f203 --- /dev/null +++ b/t/t4013/diff.diff_-U2_initial..side @@ -0,0 +1,31 @@ +$ git diff -U2 initial..side +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 b/file0 +@@ -2,2 +2,5 @@ + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 000..7289e35 +--- /dev/null b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.diff_-U_initial..side b/t/t4013/diff.diff_-U_initial..side new file mode 100644 index 00..c66c0dd5c6 --- /dev/null +++ b/t/t4013/diff.diff_-U_initial..side @@ -0,0 +1,32 @@ +$ git diff -U initial..side +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 000..7289e35 +--- /dev/null b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 3/3] parse-options: check empty value in OPT_INTEGER and OPT_ABBREV
When parsing the argument for OPT_INTEGER and OPT_ABBREV, we check if we can parse the entire argument to a number with "if (*s)". There is one missing check: if "arg" is empty to begin with, we fail to notice. This could happen with long option by writing like git diff --inter-hunk-context= blah blah Before 16ed6c97cc (diff-parseopt: convert --inter-hunk-context, 2019-03-24), --inter-hunk-context is handled by a custom parser opt_arg() and does detect this correctly. This restores the bahvior for --inter-hunk-context and make sure all other integer options are handled the same (sane) way. For OPT_ABBREV this is new behavior. But it makes it consistent with the rest. PS. OPT_MAGNITUDE has similar code but git_parse_ulong() does detect empty "arg". So it's good to go. Signed-off-by: Nguyễn Thái Ngọc Duy --- parse-options-cb.c | 3 +++ parse-options.c| 3 +++ 2 files changed, 6 insertions(+) diff --git a/parse-options-cb.c b/parse-options-cb.c index 4b95d04a37..a3de795c58 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -16,6 +16,9 @@ int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset) if (!arg) { v = unset ? 0 : DEFAULT_ABBREV; } else { + if (!*arg) + return error(_("option `%s' expects a numerical value"), +opt->long_name); v = strtol(arg, (char **)&arg, 10); if (*arg) return error(_("option `%s' expects a numerical value"), diff --git a/parse-options.c b/parse-options.c index 987e27cb91..87b26a1d92 100644 --- a/parse-options.c +++ b/parse-options.c @@ -195,6 +195,9 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, } if (get_arg(p, opt, flags, &arg)) return -1; + if (!*arg) + return error(_("%s expects a numerical value"), +optname(opt, flags)); *(int *)opt->value = strtol(arg, (char **)&s, 10); if (*s) return error(_("%s expects a numerical value"), -- 2.22.0.rc0.322.g2b0371e29a
[PATCH 1/3] diff-parseopt: correct variable types that are used by parseopt
Most number-related OPT_ macros store the value in an 'int' variable. Many of the variables in 'struct diff_options' have a different type, but during the conversion to using parse_options() I failed to notice and correct. The problem was reported on s360x which is a big-endian architechture. The variable to store '-w' option in this case is xdl_opts, 'long' type, 8 bytes. But since parse_options() assumes 'int' (4 bytes), it will store bits in the wrong part of xdl_opts. The problem was found on little-endian platforms because parse_options() will accidentally store at the right part of xdl_opts. There aren't much to say about the type change (except that 'int' for xdl_opts should still be big enough, since Windows' long is the same size as 'int' and nobody has complained so far). Some safety checks may be implemented in the future to prevent class of bugs. Reported-by: Todd Zullinger Signed-off-by: Nguyễn Thái Ngọc Duy --- diff.h | 70 +- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/diff.h b/diff.h index b20cbcc091..4527daf6b7 100644 --- a/diff.h +++ b/diff.h @@ -65,39 +65,39 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data) #define DIFF_FLAGS_INIT { 0 } struct diff_flags { - unsigned recursive; - unsigned tree_in_recursive; - unsigned binary; - unsigned text; - unsigned full_index; - unsigned silent_on_remove; - unsigned find_copies_harder; - unsigned follow_renames; - unsigned rename_empty; - unsigned has_changes; - unsigned quick; - unsigned no_index; - unsigned allow_external; - unsigned exit_with_status; - unsigned reverse_diff; - unsigned check_failed; - unsigned relative_name; - unsigned ignore_submodules; - unsigned dirstat_cumulative; - unsigned dirstat_by_file; - unsigned allow_textconv; - unsigned textconv_set_via_cmdline; - unsigned diff_from_contents; - unsigned dirty_submodules; - unsigned ignore_untracked_in_submodules; - unsigned ignore_dirty_submodules; - unsigned override_submodule_config; - unsigned dirstat_by_line; - unsigned funccontext; - unsigned default_follow_renames; - unsigned stat_with_summary; - unsigned suppress_diff_headers; - unsigned dual_color_diffed_diffs; + unsigned int recursive; + unsigned int tree_in_recursive; + unsigned int binary; + unsigned int text; + unsigned int full_index; + unsigned int silent_on_remove; + unsigned int find_copies_harder; + unsigned int follow_renames; + unsigned int rename_empty; + unsigned int has_changes; + unsigned int quick; + unsigned int no_index; + unsigned int allow_external; + unsigned int exit_with_status; + unsigned int reverse_diff; + unsigned int check_failed; + unsigned int relative_name; + unsigned int ignore_submodules; + unsigned int dirstat_cumulative; + unsigned int dirstat_by_file; + unsigned int allow_textconv; + unsigned int textconv_set_via_cmdline; + unsigned int diff_from_contents; + unsigned int dirty_submodules; + unsigned int ignore_untracked_in_submodules; + unsigned int ignore_dirty_submodules; + unsigned int override_submodule_config; + unsigned int dirstat_by_line; + unsigned int funccontext; + unsigned int default_follow_renames; + unsigned int stat_with_summary; + unsigned int suppress_diff_headers; + unsigned int dual_color_diffed_diffs; }; static inline void diff_flags_or(struct diff_flags *a, @@ -151,7 +151,7 @@ struct diff_options { int skip_stat_unmatch; int line_termination; int output_format; - unsigned pickaxe_opts; + unsigned int pickaxe_opts; int rename_score; int rename_limit; int needed_rename_limit; @@ -169,7 +169,7 @@ struct diff_options { const char *prefix; int prefix_length; const char *stat_sep; - long xdl_opts; + int xdl_opts; /* see Documentation/diff-options.txt */ char **anchors; -- 2.22.0.rc0.322.g2b0371e29a
[PATCH] repository.c: always allocate 'index' at repo init time
There are two ways a 'struct repository' could be initialized before using: via initialize_the_repository() and repo_init(). The first way always initializes 'index' field because that's how it is before the introduction of 'struct repository'. Back then 'the_index' is always available (even if not loaded). The second way however leaves 'index' NULL and relies on repo_read_index() to allocate it on demand. The problem with the second way is that, the majority of our code base was written with 'the_index' (i.e. the first way) in mind, where dereferencing 'the_index' (or the 'index' field now) is always safe. The second way breaks this assumption. The 'index' field can be NULL until loading from disk, which could lead to segfaults like 581d2fd9f2 (get_oid: handle NULL repo->index, 2019-05-14). We have two options to handle this: either we audit the entire code base, adding 'is index NULL' when needed, or we make sure 'index' is never NULL to begin with. This patch goes with the second option, making sure that 'index' is always allocated after initialization. It's less effort than the first one, and also safer because you could still miss things during the code audit. The extra allocation cost is not a real concern. The 'index' field is still freed and reset to NULL in repo_clear(). But after that call, a lot more is missing in 'repo' and it can never be used again without going through reinitialization phase. So it should be fine. Signed-off-by: Nguyễn Thái Ngọc Duy --- repository.c | 3 ++- repository.h | 4 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/repository.c b/repository.c index 682c239fe3..ca58692504 100644 --- a/repository.c +++ b/repository.c @@ -160,6 +160,7 @@ int repo_init(struct repository *repo, struct repository_format format = REPOSITORY_FORMAT_INIT; memset(repo, 0, sizeof(*repo)); + repo->index = xcalloc(1, sizeof(*repo->index)); repo->objects = raw_object_store_new(); repo->parsed_objects = parsed_object_pool_new(); @@ -262,7 +263,7 @@ void repo_clear(struct repository *repo) int repo_read_index(struct repository *repo) { if (!repo->index) - repo->index = xcalloc(1, sizeof(*repo->index)); + BUG("the repo hasn't been setup"); return read_index_from(repo->index, repo->index_file, repo->gitdir); } diff --git a/repository.h b/repository.h index 4fb6a5885f..75c4f68b22 100644 --- a/repository.h +++ b/repository.h @@ -85,6 +85,7 @@ struct repository { /* * Repository's in-memory index. +* Cannot be NULL after initialization. * 'repo_read_index()' can be used to populate 'index'. */ struct index_state *index; @@ -132,6 +133,9 @@ struct submodule; int repo_submodule_init(struct repository *subrepo, struct repository *superproject, const struct submodule *sub); +/* + * Release all resources in 'repo'. 'repo' cannot be used again. + */ void repo_clear(struct repository *repo); /* -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v4 0/2] nd/merge-quit updates
Another round because apparently the test case is not perfect. Nguyễn Thái Ngọc Duy (2): merge: remove drop_save() in favor of remove_merge_branch_state() merge: add --quit Documentation/git-merge.txt | 4 branch.c| 11 --- branch.h| 6 ++ builtin/merge.c | 30 ++ t/t7600-merge.sh| 26 ++ 5 files changed, 62 insertions(+), 15 deletions(-) Interdiff dựa trên v3: diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index d453710ef6..625a24a980 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -823,17 +823,29 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue' ' test_expect_success 'merge --quit' ' - git reset --hard c2 && - test_must_fail git -c rerere.enabled=true merge master && - test_path_is_file .git/MERGE_HEAD && - test_path_is_file .git/MERGE_MODE && - test_path_is_file .git/MERGE_MSG && - test_path_is_file .git/MERGE_RR && - git merge --quit && - test_path_is_missing .git/MERGE_HEAD && - test_path_is_missing .git/MERGE_MODE && - test_path_is_missing .git/MERGE_MSG && - test_path_is_missing .git/MERGE_RR + git init merge-quit && + ( + cd merge-quit && + test_commit base && + echo one >>base.t && + git commit -am one && + git branch one && + git checkout base && + echo two >>base.t && + git commit -am two && + test_must_fail git -c rerere.enabled=true merge one && + test_path_is_file .git/MERGE_HEAD && + test_path_is_file .git/MERGE_MODE && + test_path_is_file .git/MERGE_MSG && + git rerere status >rerere.before && + git merge --quit && + test_path_is_missing .git/MERGE_HEAD && + test_path_is_missing .git/MERGE_MODE && + test_path_is_missing .git/MERGE_MSG && + git rerere status >rerere.after && + test_must_be_empty rerere.after && + ! test_cmp rerere.after rerere.before + ) ' test_done -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v4 2/2] merge: add --quit
This allows to cancel the current merge without resetting worktree/index, which is what --abort is for. Like other --quit(s), this is often used when you forgot that you're in the middle of a merge and already switched away, doing different things. By the time you've realized, you can't even continue the merge anymore. This also makes all in-progress commands, am, merge, rebase, revert and cherry-pick, take all three --abort, --continue and --quit (bisect has a different UI). Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/git-merge.txt | 4 builtin/merge.c | 13 + t/t7600-merge.sh| 26 ++ 3 files changed, 43 insertions(+) diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 4cc86469f3..b7d581fc76 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -99,6 +99,10 @@ commit or stash your changes before running 'git merge'. 'git merge --abort' is equivalent to 'git reset --merge' when `MERGE_HEAD` is present. +--quit:: + Forget about the current merge in progress. Leave the index + and the working tree as-is. + --continue:: After a 'git merge' stops due to conflicts you can conclude the merge by running 'git merge --continue' (see "HOW TO RESOLVE diff --git a/builtin/merge.c b/builtin/merge.c index e9663f027a..598d56edfe 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -73,6 +73,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int quit_current_merge; static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; @@ -267,6 +268,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "quit", &quit_current_merge, + N_("--abort but leave index and working tree alone")), OPT_BOOL(0, "continue", &continue_current_merge, N_("continue the current in-progress merge")), OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, @@ -1252,6 +1255,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (quit_current_merge) { + if (orig_argc != 2) + usage_msg_opt(_("--quit expects no arguments"), + builtin_merge_usage, + builtin_merge_options); + + remove_merge_branch_state(the_repository); + goto done; + } + if (continue_current_merge) { int nargc = 1; const char *nargv[] = {"commit", NULL}; diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 106148254d..625a24a980 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -822,4 +822,30 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue' verify_parents $c0 $c1 ' +test_expect_success 'merge --quit' ' + git init merge-quit && + ( + cd merge-quit && + test_commit base && + echo one >>base.t && + git commit -am one && + git branch one && + git checkout base && + echo two >>base.t && + git commit -am two && + test_must_fail git -c rerere.enabled=true merge one && + test_path_is_file .git/MERGE_HEAD && + test_path_is_file .git/MERGE_MODE && + test_path_is_file .git/MERGE_MSG && + git rerere status >rerere.before && + git merge --quit && + test_path_is_missing .git/MERGE_HEAD && + test_path_is_missing .git/MERGE_MODE && + test_path_is_missing .git/MERGE_MSG && + git rerere status >rerere.after && + test_must_be_empty rerere.after && + ! test_cmp rerere.after rerere.before + ) +' + test_done -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v4 1/2] merge: remove drop_save() in favor of remove_merge_branch_state()
Both remove_branch_state() and drop_save() delete almost the same set of files about the current merge state. The only difference is MERGE_RR but it should also be cleaned up after a successful merge, which is what drop_save() is for. Make a new function that deletes all merge-related state files and use it instead of drop_save(). This function will also be used in the next patch that introduces --quit. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- branch.c| 11 --- branch.h| 6 ++ builtin/merge.c | 17 + 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/branch.c b/branch.c index 28b81a7e02..1db0601a11 100644 --- a/branch.c +++ b/branch.c @@ -337,15 +337,20 @@ void create_branch(struct repository *r, free(real_ref); } -void remove_branch_state(struct repository *r) +void remove_merge_branch_state(struct repository *r) { - unlink(git_path_cherry_pick_head(r)); - unlink(git_path_revert_head(r)); unlink(git_path_merge_head(r)); unlink(git_path_merge_rr(r)); unlink(git_path_merge_msg(r)); unlink(git_path_merge_mode(r)); +} + +void remove_branch_state(struct repository *r) +{ + unlink(git_path_cherry_pick_head(r)); + unlink(git_path_revert_head(r)); unlink(git_path_squash_msg(r)); + remove_merge_branch_state(r); } void die_if_checked_out(const char *branch, int ignore_current_worktree) diff --git a/branch.h b/branch.h index 29c1afa4d0..c90ba9d7bf 100644 --- a/branch.h +++ b/branch.h @@ -60,6 +60,12 @@ extern int validate_branchname(const char *name, struct strbuf *ref); */ extern int validate_new_branchname(const char *name, struct strbuf *ref, int force); +/* + * Remove information about the merge state on the current + * branch. (E.g., MERGE_HEAD) + */ +void remove_merge_branch_state(struct repository *r); + /* * Remove information about the state of working on the current * branch. (E.g., MERGE_HEAD) diff --git a/builtin/merge.c b/builtin/merge.c index e47d77baee..e9663f027a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -37,6 +37,7 @@ #include "packfile.h" #include "tag.h" #include "alias.h" +#include "branch.h" #include "commit-reach.h" #define DEFAULT_TWOHEAD (1<<0) @@ -279,14 +280,6 @@ static struct option builtin_merge_options[] = { OPT_END() }; -/* Cleans up metadata that is uninteresting after a succeeded merge. */ -static void drop_save(void) -{ - unlink(git_path_merge_head(the_repository)); - unlink(git_path_merge_msg(the_repository)); - unlink(git_path_merge_mode(the_repository)); -} - static int save_state(struct object_id *stash) { int len; @@ -380,7 +373,7 @@ static void finish_up_to_date(const char *msg) { if (verbosity >= 0) printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); - drop_save(); + remove_merge_branch_state(the_repository); } static void squash_message(struct commit *commit, struct commit_list *remoteheads) @@ -858,7 +851,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) &result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, &result_commit, "In-index merge"); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -885,7 +878,7 @@ static int finish_automerge(struct commit *head, strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, remoteheads, &result_commit, buf.buf); strbuf_release(&buf); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -1463,7 +1456,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } finish(head_commit, remoteheads, &commit->object.oid, msg.buf); - drop_save(); + remove_merge_branch_state(the_repository); goto done; } else if (!remoteheads->next && common->next) ; -- 2.22.0.rc0.322.g2b0371e29a
[PATCH v3 2/2] merge: add --quit
This allows to cancel the current merge without resetting worktree/index, which is what --abort is for. Like other --quit(s), this is often used when you forgot that you're in the middle of a merge and already switched away, doing different things. By the time you've realized, you can't even continue the merge anymore. This also makes all in-progress commands, am, merge, rebase, revert and cherry-pick, take all three --abort, --continue and --quit (bisect has a different UI). Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/git-merge.txt | 4 builtin/merge.c | 13 + t/t7600-merge.sh| 14 ++ 3 files changed, 31 insertions(+) diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 4cc86469f3..b7d581fc76 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -99,6 +99,10 @@ commit or stash your changes before running 'git merge'. 'git merge --abort' is equivalent to 'git reset --merge' when `MERGE_HEAD` is present. +--quit:: + Forget about the current merge in progress. Leave the index + and the working tree as-is. + --continue:: After a 'git merge' stops due to conflicts you can conclude the merge by running 'git merge --continue' (see "HOW TO RESOLVE diff --git a/builtin/merge.c b/builtin/merge.c index e9663f027a..598d56edfe 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -73,6 +73,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int quit_current_merge; static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; @@ -267,6 +268,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "quit", &quit_current_merge, + N_("--abort but leave index and working tree alone")), OPT_BOOL(0, "continue", &continue_current_merge, N_("continue the current in-progress merge")), OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, @@ -1252,6 +1255,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (quit_current_merge) { + if (orig_argc != 2) + usage_msg_opt(_("--quit expects no arguments"), + builtin_merge_usage, + builtin_merge_options); + + remove_merge_branch_state(the_repository); + goto done; + } + if (continue_current_merge) { int nargc = 1; const char *nargv[] = {"commit", NULL}; diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 106148254d..d453710ef6 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -822,4 +822,18 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue' verify_parents $c0 $c1 ' +test_expect_success 'merge --quit' ' + git reset --hard c2 && + test_must_fail git -c rerere.enabled=true merge master && + test_path_is_file .git/MERGE_HEAD && + test_path_is_file .git/MERGE_MODE && + test_path_is_file .git/MERGE_MSG && + test_path_is_file .git/MERGE_RR && + git merge --quit && + test_path_is_missing .git/MERGE_HEAD && + test_path_is_missing .git/MERGE_MODE && + test_path_is_missing .git/MERGE_MSG && + test_path_is_missing .git/MERGE_RR +' + test_done -- 2.21.0.1141.gd54ac2cb17
[PATCH v3 1/2] merge: remove drop_save() in favor of remove_merge_branch_state()
Both remove_branch_state() and drop_save() delete almost the same set of files about the current merge state. The only difference is MERGE_RR but it should also be cleaned up after a successful merge, which is what drop_save() is for. Make a new function that deletes all merge-related state files and use it instead of drop_save(). This function will also be used in the next patch that introduces --quit. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- branch.c| 11 --- branch.h| 6 ++ builtin/merge.c | 17 + 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/branch.c b/branch.c index 28b81a7e02..1db0601a11 100644 --- a/branch.c +++ b/branch.c @@ -337,15 +337,20 @@ void create_branch(struct repository *r, free(real_ref); } -void remove_branch_state(struct repository *r) +void remove_merge_branch_state(struct repository *r) { - unlink(git_path_cherry_pick_head(r)); - unlink(git_path_revert_head(r)); unlink(git_path_merge_head(r)); unlink(git_path_merge_rr(r)); unlink(git_path_merge_msg(r)); unlink(git_path_merge_mode(r)); +} + +void remove_branch_state(struct repository *r) +{ + unlink(git_path_cherry_pick_head(r)); + unlink(git_path_revert_head(r)); unlink(git_path_squash_msg(r)); + remove_merge_branch_state(r); } void die_if_checked_out(const char *branch, int ignore_current_worktree) diff --git a/branch.h b/branch.h index 29c1afa4d0..c90ba9d7bf 100644 --- a/branch.h +++ b/branch.h @@ -60,6 +60,12 @@ extern int validate_branchname(const char *name, struct strbuf *ref); */ extern int validate_new_branchname(const char *name, struct strbuf *ref, int force); +/* + * Remove information about the merge state on the current + * branch. (E.g., MERGE_HEAD) + */ +void remove_merge_branch_state(struct repository *r); + /* * Remove information about the state of working on the current * branch. (E.g., MERGE_HEAD) diff --git a/builtin/merge.c b/builtin/merge.c index e47d77baee..e9663f027a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -37,6 +37,7 @@ #include "packfile.h" #include "tag.h" #include "alias.h" +#include "branch.h" #include "commit-reach.h" #define DEFAULT_TWOHEAD (1<<0) @@ -279,14 +280,6 @@ static struct option builtin_merge_options[] = { OPT_END() }; -/* Cleans up metadata that is uninteresting after a succeeded merge. */ -static void drop_save(void) -{ - unlink(git_path_merge_head(the_repository)); - unlink(git_path_merge_msg(the_repository)); - unlink(git_path_merge_mode(the_repository)); -} - static int save_state(struct object_id *stash) { int len; @@ -380,7 +373,7 @@ static void finish_up_to_date(const char *msg) { if (verbosity >= 0) printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); - drop_save(); + remove_merge_branch_state(the_repository); } static void squash_message(struct commit *commit, struct commit_list *remoteheads) @@ -858,7 +851,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) &result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, &result_commit, "In-index merge"); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -885,7 +878,7 @@ static int finish_automerge(struct commit *head, strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, remoteheads, &result_commit, buf.buf); strbuf_release(&buf); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -1463,7 +1456,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } finish(head_commit, remoteheads, &commit->object.oid, msg.buf); - drop_save(); + remove_merge_branch_state(the_repository); goto done; } else if (!remoteheads->next && common->next) ; -- 2.21.0.1141.gd54ac2cb17
[PATCH v3 0/2] nd/merge-quit updates
v3 fixes the test breakage when GPG tests are skipped ('side' branch is affected by these skipped tests) Nguyễn Thái Ngọc Duy (2): merge: remove drop_save() in favor of remove_merge_branch_state() merge: add --quit Documentation/git-merge.txt | 4 branch.c| 11 --- branch.h| 6 ++ builtin/merge.c | 30 ++ t/t7600-merge.sh| 14 ++ 5 files changed, 50 insertions(+), 15 deletions(-) Range-diff dựa trên v2: 1: 324d237f0c ! 1: 86dd0fd99c merge: add --quit @@ -13,7 +13,6 @@ different UI). Signed-off-by: Nguyễn Thái Ngọc Duy -Signed-off-by: Junio C Hamano diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt --- a/Documentation/git-merge.txt @@ -76,7 +75,7 @@ ' +test_expect_success 'merge --quit' ' -+ git reset --hard side && ++ git reset --hard c2 && + test_must_fail git -c rerere.enabled=true merge master && + test_path_is_file .git/MERGE_HEAD && + test_path_is_file .git/MERGE_MODE && -- 2.21.0.1141.gd54ac2cb17
[PATCH] worktree add: be tolerant of corrupt worktrees
find_worktree() can die() unexpectedly because it uses real_path() instead of the gentler version. When it's used in 'git worktree add' [1] and there's a bad worktree, this die() could prevent people from adding new worktrees. The "bad" condition to trigger this is when a parent of the worktree's location is deleted. Then real_path() will complain. Use the other version so that bad worktrees won't affect 'worktree add'. The bad ones will eventually be pruned, we just have to tolerate them for a bit. [1] added in cb56f55c16 (worktree: disallow adding same path multiple times, 2018-08-28), or since v2.20.0. Though the real bug in find_worktree() is much older. Reported-by: Shaheed Haque Signed-off-by: Nguyễn Thái Ngọc Duy --- t/t2025-worktree-add.sh | 12 worktree.c | 7 +-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index 286bba35d8..d83a9f0fdc 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -570,4 +570,16 @@ test_expect_success '"add" an existing locked but missing worktree' ' git worktree add --force --force --detach gnoo ' +test_expect_success '"add" should not fail because of another bad worktree' ' + git init add-fail && + ( + cd add-fail && + test_commit first && + mkdir sub && + git worktree add sub/to-be-deleted && + rm -rf sub && + git worktree add second + ) +' + test_done diff --git a/worktree.c b/worktree.c index d6a0ee7f73..c79b3e42bb 100644 --- a/worktree.c +++ b/worktree.c @@ -222,9 +222,12 @@ struct worktree *find_worktree(struct worktree **list, free(to_free); return NULL; } - for (; *list; list++) - if (!fspathcmp(path, real_path((*list)->path))) + for (; *list; list++) { + const char *wt_path = real_path_if_valid((*list)->path); + + if (wt_path && !fspathcmp(path, wt_path)) break; + } free(path); free(to_free); return *list; -- 2.21.0.1141.gd54ac2cb17
[PATCH] init: make --template path relative to $CWD
During git-init we chdir() to the target directory, but --template is not adjusted. So it's relative to the target directory instead of current directory. It would be ok if it's documented, but --template in git-init.txt mentions nothing about this behavior. Change it to be relative to $CWD, which is much more intuitive. The changes in the test suite show that this relative-to-target behavior is actually used. I just hope that it's only used in the test suite and it's safe to change. Otherwise, the other option is just document it (i.e. relative to target dir) and move on. Signed-off-by: Nguyễn Thái Ngọc Duy --- builtin/init-db.c | 3 +++ t/t0001-init.sh| 2 +- t/t1301-shared-repo.sh | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/builtin/init-db.c b/builtin/init-db.c index 93eff7618c..6b72a9bb09 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -494,6 +494,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) if (real_git_dir && !is_absolute_path(real_git_dir)) real_git_dir = real_pathdup(real_git_dir, 1); + if (template_dir && *template_dir && !is_absolute_path(template_dir)) + template_dir = absolute_pathdup(template_dir); + if (argc == 1) { int mkdir_tried = 0; retry: diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 42a263cada..802edb6c8f 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -174,7 +174,7 @@ test_expect_success 'reinit' ' test_expect_success 'init with --template' ' mkdir template-source && echo content >template-source/file && - git init --template=../template-source template-custom && + git init --template=template-source template-custom && test_cmp template-source/file template-custom/.git/file ' diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index dfece751b5..2dc853d1be 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -136,7 +136,7 @@ test_expect_success POSIXPERM 'forced modes' ' ( cd new && umask 002 && - git init --shared=0660 --template=../templates && + git init --shared=0660 --template=templates && >frotz && git add frotz && git commit -a -m initial && @@ -192,7 +192,7 @@ test_expect_success POSIXPERM 're-init respects core.sharedrepository (remote)' umask 0022 && git init --bare --shared=0666 child.git && test_path_is_missing child.git/foo && - git init --bare --template=../templates child.git && + git init --bare --template=templates child.git && echo "-rw-rw-rw-" >expect && test_modebits child.git/foo >actual && test_cmp expect actual @@ -203,7 +203,7 @@ test_expect_success POSIXPERM 'template can set core.sharedrepository' ' umask 0022 && git config core.sharedrepository 0666 && cp .git/config templates/config && - git init --bare --template=../templates child.git && + git init --bare --template=templates child.git && echo "-rw-rw-rw-" >expect && test_modebits child.git/HEAD >actual && test_cmp expect actual -- 2.21.0.1141.gd54ac2cb17
[PATCH v2 0/2] nd/merge-quit update
A couple typos in the commit message. No code change. Nguyễn Thái Ngọc Duy (2): merge: remove drop_save() in favor of remove_merge_branch_state() merge: add --quit Documentation/git-merge.txt | 4 branch.c| 11 --- branch.h| 6 ++ builtin/merge.c | 30 ++ t/t7600-merge.sh| 14 ++ 5 files changed, 50 insertions(+), 15 deletions(-) Range-diff dựa trên v1: 1: a87e56a43a ! 1: 51710c4c6c merge: add --quit @@ -2,10 +2,10 @@ merge: add --quit -This allows to cancel the current merge without reseting worktree/index, +This allows to cancel the current merge without resetting worktree/index, which is what --abort is for. Like other --quit(s), this is often used when you forgot that you're in the middle of a merge and already -switched away, doing different things. By the time you're realize, you +switched away, doing different things. By the time you've realized, you can't even continue the merge anymore. This also makes all in-progress commands, am, merge, rebase, revert and -- 2.21.0.1141.gd54ac2cb17
[PATCH v2 2/2] merge: add --quit
This allows to cancel the current merge without resetting worktree/index, which is what --abort is for. Like other --quit(s), this is often used when you forgot that you're in the middle of a merge and already switched away, doing different things. By the time you've realized, you can't even continue the merge anymore. This also makes all in-progress commands, am, merge, rebase, revert and cherry-pick, take all three --abort, --continue and --quit (bisect has a different UI). Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-merge.txt | 4 builtin/merge.c | 13 + t/t7600-merge.sh| 14 ++ 3 files changed, 31 insertions(+) diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 4cc86469f3..b7d581fc76 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -99,6 +99,10 @@ commit or stash your changes before running 'git merge'. 'git merge --abort' is equivalent to 'git reset --merge' when `MERGE_HEAD` is present. +--quit:: + Forget about the current merge in progress. Leave the index + and the working tree as-is. + --continue:: After a 'git merge' stops due to conflicts you can conclude the merge by running 'git merge --continue' (see "HOW TO RESOLVE diff --git a/builtin/merge.c b/builtin/merge.c index e9663f027a..598d56edfe 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -73,6 +73,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int quit_current_merge; static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; @@ -267,6 +268,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "quit", &quit_current_merge, + N_("--abort but leave index and working tree alone")), OPT_BOOL(0, "continue", &continue_current_merge, N_("continue the current in-progress merge")), OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, @@ -1252,6 +1255,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (quit_current_merge) { + if (orig_argc != 2) + usage_msg_opt(_("--quit expects no arguments"), + builtin_merge_usage, + builtin_merge_options); + + remove_merge_branch_state(the_repository); + goto done; + } + if (continue_current_merge) { int nargc = 1; const char *nargv[] = {"commit", NULL}; diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 106148254d..ea82cb744b 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -822,4 +822,18 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue' verify_parents $c0 $c1 ' +test_expect_success 'merge --quit' ' + git reset --hard side && + test_must_fail git -c rerere.enabled=true merge master && + test_path_is_file .git/MERGE_HEAD && + test_path_is_file .git/MERGE_MODE && + test_path_is_file .git/MERGE_MSG && + test_path_is_file .git/MERGE_RR && + git merge --quit && + test_path_is_missing .git/MERGE_HEAD && + test_path_is_missing .git/MERGE_MODE && + test_path_is_missing .git/MERGE_MSG && + test_path_is_missing .git/MERGE_RR +' + test_done -- 2.21.0.1141.gd54ac2cb17
[PATCH v2 1/2] merge: remove drop_save() in favor of remove_merge_branch_state()
Both remove_branch_state() and drop_save() delete almost the same set of files about the current merge state. The only difference is MERGE_RR but it should also be cleaned up after a successful merge, which is what drop_save() is for. Make a new function that deletes all merge-related state files and use it instead of drop_save(). This function will also be used in the next patch that introduces --quit. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- branch.c| 11 --- branch.h| 6 ++ builtin/merge.c | 17 + 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/branch.c b/branch.c index 28b81a7e02..1db0601a11 100644 --- a/branch.c +++ b/branch.c @@ -337,15 +337,20 @@ void create_branch(struct repository *r, free(real_ref); } -void remove_branch_state(struct repository *r) +void remove_merge_branch_state(struct repository *r) { - unlink(git_path_cherry_pick_head(r)); - unlink(git_path_revert_head(r)); unlink(git_path_merge_head(r)); unlink(git_path_merge_rr(r)); unlink(git_path_merge_msg(r)); unlink(git_path_merge_mode(r)); +} + +void remove_branch_state(struct repository *r) +{ + unlink(git_path_cherry_pick_head(r)); + unlink(git_path_revert_head(r)); unlink(git_path_squash_msg(r)); + remove_merge_branch_state(r); } void die_if_checked_out(const char *branch, int ignore_current_worktree) diff --git a/branch.h b/branch.h index 29c1afa4d0..c90ba9d7bf 100644 --- a/branch.h +++ b/branch.h @@ -60,6 +60,12 @@ extern int validate_branchname(const char *name, struct strbuf *ref); */ extern int validate_new_branchname(const char *name, struct strbuf *ref, int force); +/* + * Remove information about the merge state on the current + * branch. (E.g., MERGE_HEAD) + */ +void remove_merge_branch_state(struct repository *r); + /* * Remove information about the state of working on the current * branch. (E.g., MERGE_HEAD) diff --git a/builtin/merge.c b/builtin/merge.c index e47d77baee..e9663f027a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -37,6 +37,7 @@ #include "packfile.h" #include "tag.h" #include "alias.h" +#include "branch.h" #include "commit-reach.h" #define DEFAULT_TWOHEAD (1<<0) @@ -279,14 +280,6 @@ static struct option builtin_merge_options[] = { OPT_END() }; -/* Cleans up metadata that is uninteresting after a succeeded merge. */ -static void drop_save(void) -{ - unlink(git_path_merge_head(the_repository)); - unlink(git_path_merge_msg(the_repository)); - unlink(git_path_merge_mode(the_repository)); -} - static int save_state(struct object_id *stash) { int len; @@ -380,7 +373,7 @@ static void finish_up_to_date(const char *msg) { if (verbosity >= 0) printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); - drop_save(); + remove_merge_branch_state(the_repository); } static void squash_message(struct commit *commit, struct commit_list *remoteheads) @@ -858,7 +851,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) &result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, &result_commit, "In-index merge"); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -885,7 +878,7 @@ static int finish_automerge(struct commit *head, strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, remoteheads, &result_commit, buf.buf); strbuf_release(&buf); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -1463,7 +1456,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } finish(head_commit, remoteheads, &commit->object.oid, msg.buf); - drop_save(); + remove_merge_branch_state(the_repository); goto done; } else if (!remoteheads->next && common->next) ; -- 2.21.0.1141.gd54ac2cb17
[PATCH 03/19] revision.c: prepare to convert handle_revision_pseudo_opt()
This patch is essentially no-op. It allows to parse_options() to handle some options. But the new option list remains empty. The option will be moved one by one from the old manual parsing code to this list. Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 44 ++-- revision.h | 2 ++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/revision.c b/revision.c index d4aaf0ef25..65d40c9255 100644 --- a/revision.c +++ b/revision.c @@ -26,6 +26,7 @@ #include "argv-array.h" #include "commit-reach.h" #include "commit-graph.h" +#include "parse-options.h" #include "prio-queue.h" #include "hashmap.h" @@ -1598,6 +1599,8 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags, return 1; } +static void make_pseudo_options(struct rev_info *revs); + void repo_init_revisions(struct repository *r, struct rev_info *revs, const char *prefix) @@ -1638,6 +1641,7 @@ void repo_init_revisions(struct repository *r, } revs->notes_opt.use_default_notes = -1; + make_pseudo_options(revs); } static void add_pending_commit_list(struct rev_info *revs, @@ -2355,6 +2359,25 @@ static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void return for_each_bisect_ref(refs, fn, cb_data, term_good); } +static void make_pseudo_options(struct rev_info *revs) +{ + /* +* NOTE! +* +* Commands like "git shortlog" will not accept the options below +* unless parse_revision_opt queues them (as opposed to erroring +* out). +* +* When implementing your new pseudo-option, remember to +* register it in the list at the top of handle_revision_opt. +*/ + struct option options[] = { + OPT_END() + }; + ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); + memcpy(revs->pseudo_options, options, sizeof(options)); +} + static int handle_revision_pseudo_opt(const char *submodule, struct rev_info *revs, int argc, const char **argv, int *flags) @@ -2377,16 +2400,16 @@ static int handle_revision_pseudo_opt(const char *submodule, } else refs = get_main_ref_store(revs->repo); - /* -* NOTE! -* -* Commands like "git shortlog" will not accept the options below -* unless parse_revision_opt queues them (as opposed to erroring -* out). -* -* When implementing your new pseudo-option, remember to -* register it in the list at the top of handle_revision_opt. -*/ + argc = parse_options(argc, argv, revs->prefix, +revs->pseudo_options, NULL, +PARSE_OPT_KEEP_DASHDASH | +PARSE_OPT_KEEP_UNKNOWN | +PARSE_OPT_NO_INTERNAL_HELP | +PARSE_OPT_ONE_SHOT | +PARSE_OPT_STOP_AT_NON_OPTION); + if (argc) + return argc; + if (!strcmp(arg, "--all")) { handle_refs(refs, revs, *flags, refs_for_each_ref); handle_refs(refs, revs, *flags, refs_head_ref); @@ -2685,6 +2708,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->expand_tabs_in_log < 0) revs->expand_tabs_in_log = revs->expand_tabs_in_log_default; + FREE_AND_NULL(revs->pseudo_options); return left; } diff --git a/revision.h b/revision.h index 71e724c59c..0769c97dee 100644 --- a/revision.h +++ b/revision.h @@ -39,6 +39,7 @@ #define DECORATE_FULL_REFS 2 struct log_info; +struct option; struct repository; struct rev_info; struct string_list; @@ -279,6 +280,7 @@ struct rev_info { struct topo_walk_info *topo_walk_info; struct repository *repo; + struct option *pseudo_options; }; int ref_excluded(struct string_list *, const char *path); -- 2.21.0.1141.gd54ac2cb17
[PATCH 11/19] rev-parseopt: convert --reflog
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 19 --- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/revision.c b/revision.c index d34e17984d..6efa9bee1e 100644 --- a/revision.c +++ b/revision.c @@ -2452,6 +2452,18 @@ static int rev_opt_glob(const struct option *opt, return 0; } +static int rev_opt_reflog(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + int flags = *revs->pseudo_flags; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + add_reflogs_to_pending(revs, flags); + return 0; +} + static int rev_opt_remotes(const struct option *opt, const char *arg, int unset) { @@ -2528,6 +2540,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV(0, "exclude", N_(""), N_("exclude refs matching glob pattern"), rev_opt_exclude), + OPT_REV_NOARG(0, "reflog", + N_("include all refs from reflog"), + rev_opt_reflog), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2567,9 +2582,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if (!strcmp(arg, "--reflog")) { - add_reflogs_to_pending(revs, *flags); - } else if (!strcmp(arg, "--indexed-objects")) { + if (!strcmp(arg, "--indexed-objects")) { add_index_objects_to_pending(revs, *flags); } else if (!strcmp(arg, "--not")) { *flags ^= UNINTERESTING | BOTTOM; -- 2.21.0.1141.gd54ac2cb17
[PATCH 15/19] rev-parseopt: convert --single-worktree
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 15 --- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/revision.c b/revision.c index f04eb7f140..dd22ac5c39 100644 --- a/revision.c +++ b/revision.c @@ -2601,6 +2601,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_SET_INT_F(0, "do-walk", &revs->no_walk, N_("override a previous --no-walk"), 0, PARSE_OPT_NONEG), + OPT_BOOL_F(0, "single-worktree", &revs->single_worktree, + N_("only consider refs from the current worktree"), + PARSE_OPT_NONEG), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2611,7 +2614,6 @@ static int handle_revision_pseudo_opt(const char *submodule, struct rev_info *revs, int argc, const char **argv, int *flags) { - const char *arg = argv[0]; struct ref_store *refs; if (submodule) { @@ -2636,16 +2638,7 @@ static int handle_revision_pseudo_opt(const char *submodule, PARSE_OPT_NO_INTERNAL_HELP | PARSE_OPT_ONE_SHOT | PARSE_OPT_STOP_AT_NON_OPTION); - if (argc) - return argc; - - if (!strcmp(arg, "--single-worktree")) { - revs->single_worktree = 1; - } else { - return 0; - } - - return 1; + return argc; } static void NORETURN diagnose_missing_default(const char *def) -- 2.21.0.1141.gd54ac2cb17
[PATCH 02/19] revision.h: move repo field down
This block at the top of rev_info is "Starting list" and repo is obviously not one. Move it to the bottom since it's not that important to stay on top. Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/revision.h b/revision.h index 01e4c42274..71e724c59c 100644 --- a/revision.h +++ b/revision.h @@ -74,7 +74,6 @@ struct rev_info { /* Starting list */ struct commit_list *commits; struct object_array pending; - struct repository *repo; /* Parents of shown commits */ struct object_array boundary_commits; @@ -278,6 +277,8 @@ struct rev_info { struct revision_sources *sources; struct topo_walk_info *topo_walk_info; + + struct repository *repo; }; int ref_excluded(struct string_list *, const char *path); -- 2.21.0.1141.gd54ac2cb17
[PATCH 08/19] rev-parseopt: convert --remotes
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 34 +- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/revision.c b/revision.c index 5183cdf66d..bcfca8856f 100644 --- a/revision.c +++ b/revision.c @@ -2425,6 +2425,27 @@ static int rev_opt_branches(const struct option *opt, return 0; } +static int rev_opt_remotes(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + int flags = *revs->pseudo_flags; + struct ref_store*refs = revs->pseudo_refs; + + BUG_ON_OPT_NEG(unset); + if (arg) { + struct all_refs_cb cb; + + init_all_refs_cb(&cb, revs, flags); + for_each_glob_ref_in(handle_one_ref, arg, "refs/remotes/", &cb); + clear_ref_exclusion(&revs->ref_excludes); + } else { + handle_refs(refs, revs, flags, refs_for_each_remote_ref); + clear_ref_exclusion(&revs->ref_excludes); + } + return 0; +} + static int rev_opt_tags(const struct option *opt, const char *arg, int unset) { @@ -2471,6 +2492,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV_OPTARG(0, "tags", N_(""), N_("include all refs in refs/tags (optionally matches pattern)"), rev_opt_tags), + OPT_REV_OPTARG(0, "remotes", N_(""), + N_("include all refs in refs/remotes (optionally matches pattern)"), + rev_opt_remotes), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2511,10 +2535,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if (!strcmp(arg, "--remotes")) { - handle_refs(refs, revs, *flags, refs_for_each_remote_ref); - clear_ref_exclusion(&revs->ref_excludes); - } else if ((argcount = parse_long_opt("glob", argv, &optarg))) { + if ((argcount = parse_long_opt("glob", argv, &optarg))) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); for_each_glob_ref(handle_one_ref, optarg, &cb); @@ -2523,11 +2544,6 @@ static int handle_revision_pseudo_opt(const char *submodule, } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) { add_ref_exclusion(&revs->ref_excludes, optarg); return argcount; - } else if (skip_prefix(arg, "--remotes=", &optarg)) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, *flags); - for_each_glob_ref_in(handle_one_ref, optarg, "refs/remotes/", &cb); - clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--reflog")) { add_reflogs_to_pending(revs, *flags); } else if (!strcmp(arg, "--indexed-objects")) { -- 2.21.0.1141.gd54ac2cb17
[PATCH 16/19] rev-parseopt: prepare to convert handle_revision_opt()
Besides preparing an empty option table to be added in. The table is also concatenated with diff option table so we don't need diff_opt_parse() anymore. Merging with pseudo-opt table though will not happen until we kill parse_revision_opt(). --abbrev has to be converted right away to override the same one from the diff option parser (which runs first now, if not overriden) Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/technical/api-diff.txt | 6 +-- diff.c | 16 --- diff.h | 1 - revision.c | 64 +--- revision.h | 1 + 5 files changed, 53 insertions(+), 35 deletions(-) diff --git a/Documentation/technical/api-diff.txt b/Documentation/technical/api-diff.txt index 30fc0e9c93..1e4e6968f4 100644 --- a/Documentation/technical/api-diff.txt +++ b/Documentation/technical/api-diff.txt @@ -22,9 +22,9 @@ Calling sequence sets up the vanilla default. * Fill in the options structure to specify desired output format, rename - detection, etc. `diff_opt_parse()` can be used to parse options given - from the command line in a way consistent with existing git-diff - family of programs. + detection, etc. `parseopts[]` can be used with parse_options() to + parse options from the command line in a way consistent with + existing git-diff family of programs. * Call `diff_setup_done()`; this inspects the options set up so far for internal consistency and make necessary tweaking to it (e.g. if diff --git a/diff.c b/diff.c index 4d3cf83a27..ef0eb2a160 100644 --- a/diff.c +++ b/diff.c @@ -5522,22 +5522,6 @@ static void prep_parse_options(struct diff_options *options) memcpy(options->parseopts, parseopts, sizeof(parseopts)); } -int diff_opt_parse(struct diff_options *options, - const char **av, int ac, const char *prefix) -{ - if (!prefix) - prefix = ""; - - ac = parse_options(ac, av, prefix, options->parseopts, NULL, - PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_UNKNOWN | - PARSE_OPT_NO_INTERNAL_HELP | - PARSE_OPT_ONE_SHOT | - PARSE_OPT_STOP_AT_NON_OPTION); - - return ac; -} - int parse_rename_score(const char **cp_p) { unsigned long num, scale; diff --git a/diff.h b/diff.h index b20cbcc091..c75480a998 100644 --- a/diff.h +++ b/diff.h @@ -351,7 +351,6 @@ int git_diff_ui_config(const char *var, const char *value, void *cb); #define diff_setup(diffopts) repo_diff_setup(the_repository, diffopts) #endif void repo_diff_setup(struct repository *, struct diff_options *); -int diff_opt_parse(struct diff_options *, const char **, int, const char *); void diff_setup_done(struct diff_options *); int git_config_rename(const char *var, const char *value); diff --git a/revision.c b/revision.c index dd22ac5c39..f15aa3e62d 100644 --- a/revision.c +++ b/revision.c @@ -1611,6 +1611,7 @@ static int add_parents_only(struct rev_info *revs, const char *arg_, int flags, } static void make_pseudo_options(struct rev_info *revs); +static void make_rev_options(struct rev_info *revs); void repo_init_revisions(struct repository *r, struct rev_info *revs, @@ -1653,6 +1654,7 @@ void repo_init_revisions(struct repository *r, revs->notes_opt.use_default_notes = -1; make_pseudo_options(revs); + make_rev_options(revs); } static void add_pending_commit_list(struct rev_info *revs, @@ -1953,6 +1955,38 @@ static void add_message_grep(struct rev_info *revs, const char *pattern) add_grep(revs, pattern, GREP_PATTERN_BODY); } +static int rev_opt_abbrev(const struct option *opt, + const char *optarg, int unset) +{ + struct rev_info *revs = opt->value; + + if (unset) { + revs->abbrev = 0; + } else if (!optarg) { + revs->abbrev = DEFAULT_ABBREV; + } else { + const unsigned hexsz = the_hash_algo->hexsz; + + revs->abbrev = strtoul(optarg, NULL, 10); + if (revs->abbrev < MINIMUM_ABBREV) + revs->abbrev = MINIMUM_ABBREV; + else if (revs->abbrev > hexsz) + revs->abbrev = hexsz; + } + return 0; +} + +static void make_rev_options(struct rev_info *revs) +{ + struct option options[] = { + OPT_CALLBACK_F(0, "abbrev", revs, N_("n"), + N_("show the given source prefix instead of \"a/\""), + PARSE_OPT_OPTARG, rev_opt_abbrev), + OPT_END(), + }; + revs->options = parse_options_concat(options, revs->diffopt.parseopts); +} + static int handle_revision_opt(st
[PATCH 07/19] rev-parseopt: convert --tags
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 34 +- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/revision.c b/revision.c index 0d34f81716..5183cdf66d 100644 --- a/revision.c +++ b/revision.c @@ -2425,6 +2425,27 @@ static int rev_opt_branches(const struct option *opt, return 0; } +static int rev_opt_tags(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + int flags = *revs->pseudo_flags; + struct ref_store*refs = revs->pseudo_refs; + + BUG_ON_OPT_NEG(unset); + if (arg) { + struct all_refs_cb cb; + + init_all_refs_cb(&cb, revs, flags); + for_each_glob_ref_in(handle_one_ref, arg, "refs/tags/", &cb); + clear_ref_exclusion(&revs->ref_excludes); + } else { + handle_refs(refs, revs, flags, refs_for_each_tag_ref); + clear_ref_exclusion(&revs->ref_excludes); + } + return 0; +} + static void make_pseudo_options(struct rev_info *revs) { /* @@ -2447,6 +2468,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV_NOARG(0, "bisect", N_("synonym to refs/bisect/good-* --not refs/bisect/bad"), rev_opt_bisect), + OPT_REV_OPTARG(0, "tags", N_(""), + N_("include all refs in refs/tags (optionally matches pattern)"), + rev_opt_tags), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2487,10 +2511,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if (!strcmp(arg, "--tags")) { - handle_refs(refs, revs, *flags, refs_for_each_tag_ref); - clear_ref_exclusion(&revs->ref_excludes); - } else if (!strcmp(arg, "--remotes")) { + if (!strcmp(arg, "--remotes")) { handle_refs(refs, revs, *flags, refs_for_each_remote_ref); clear_ref_exclusion(&revs->ref_excludes); } else if ((argcount = parse_long_opt("glob", argv, &optarg))) { @@ -2502,11 +2523,6 @@ static int handle_revision_pseudo_opt(const char *submodule, } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) { add_ref_exclusion(&revs->ref_excludes, optarg); return argcount; - } else if (skip_prefix(arg, "--tags=", &optarg)) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, *flags); - for_each_glob_ref_in(handle_one_ref, optarg, "refs/tags/", &cb); - clear_ref_exclusion(&revs->ref_excludes); } else if (skip_prefix(arg, "--remotes=", &optarg)) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); -- 2.21.0.1141.gd54ac2cb17
[PATCH 04/19] rev-parseopt: convert --all
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 41 ++--- revision.h | 2 ++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/revision.c b/revision.c index 65d40c9255..9a346577f6 100644 --- a/revision.c +++ b/revision.c @@ -30,6 +30,10 @@ #include "prio-queue.h" #include "hashmap.h" +#define OPT_REV_NOARG(s, l, h, cb) \ + OPT_CALLBACK_F(s, l, revs, NULL, h, \ + PARSE_OPT_NONEG | PARSE_OPT_NOARG, cb) + volatile show_early_output_fn_t show_early_output; static const char *term_bad; @@ -2359,6 +2363,26 @@ static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void return for_each_bisect_ref(refs, fn, cb_data, term_good); } +static int rev_opt_all(const struct option *opt, const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + int flags = *revs->pseudo_flags; + struct ref_store*refs = revs->pseudo_refs; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + handle_refs(refs, revs, flags, refs_for_each_ref); + handle_refs(refs, revs, flags, refs_head_ref); + if (!revs->single_worktree) { + struct all_refs_cb cb; + + init_all_refs_cb(&cb, revs, flags); + other_head_refs(handle_one_ref, &cb); + } + clear_ref_exclusion(&revs->ref_excludes); + return 0; +} + static void make_pseudo_options(struct rev_info *revs) { /* @@ -2372,6 +2396,9 @@ static void make_pseudo_options(struct rev_info *revs) * register it in the list at the top of handle_revision_opt. */ struct option options[] = { + OPT_REV_NOARG(0, "all", + N_("include all refs in refs/ and HEAD"), + rev_opt_all), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2400,6 +2427,8 @@ static int handle_revision_pseudo_opt(const char *submodule, } else refs = get_main_ref_store(revs->repo); + revs->pseudo_flags = flags; + revs->pseudo_refs = refs; argc = parse_options(argc, argv, revs->prefix, revs->pseudo_options, NULL, PARSE_OPT_KEEP_DASHDASH | @@ -2410,17 +2439,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if (!strcmp(arg, "--all")) { - handle_refs(refs, revs, *flags, refs_for_each_ref); - handle_refs(refs, revs, *flags, refs_head_ref); - if (!revs->single_worktree) { - struct all_refs_cb cb; - - init_all_refs_cb(&cb, revs, *flags); - other_head_refs(handle_one_ref, &cb); - } - clear_ref_exclusion(&revs->ref_excludes); - } else if (!strcmp(arg, "--branches")) { + if (!strcmp(arg, "--branches")) { handle_refs(refs, revs, *flags, refs_for_each_branch_ref); clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--bisect")) { diff --git a/revision.h b/revision.h index 0769c97dee..cec5215c04 100644 --- a/revision.h +++ b/revision.h @@ -281,6 +281,8 @@ struct rev_info { struct repository *repo; struct option *pseudo_options; + int *pseudo_flags; + struct ref_store *pseudo_refs; }; int ref_excluded(struct string_list *, const char *path); -- 2.21.0.1141.gd54ac2cb17
[PATCH 12/19] rev-parseopt: convert --indexed-objects
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 19 --- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/revision.c b/revision.c index 6efa9bee1e..9cfa4dc151 100644 --- a/revision.c +++ b/revision.c @@ -2438,6 +2438,18 @@ static int rev_opt_exclude(const struct option *opt, return 0; } +static int rev_opt_indexed_objects(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + int flags = *revs->pseudo_flags; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + add_index_objects_to_pending(revs, flags); + return 0; +} + static int rev_opt_glob(const struct option *opt, const char *arg, int unset) { @@ -2543,6 +2555,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV_NOARG(0, "reflog", N_("include all refs from reflog"), rev_opt_reflog), + OPT_REV_NOARG(0, "indexed-objects", + N_("include all trees and blobs used by the index"), + rev_opt_indexed_objects), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2582,9 +2597,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if (!strcmp(arg, "--indexed-objects")) { - add_index_objects_to_pending(revs, *flags); - } else if (!strcmp(arg, "--not")) { + if (!strcmp(arg, "--not")) { *flags ^= UNINTERESTING | BOTTOM; } else if (!strcmp(arg, "--no-walk")) { revs->no_walk = REVISION_WALK_NO_WALK_SORTED; -- 2.21.0.1141.gd54ac2cb17
[PATCH 14/19] rev-parseopt: convert --no-walk and --do-walk
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 47 ++- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/revision.c b/revision.c index b02cb4660b..f04eb7f140 100644 --- a/revision.c +++ b/revision.c @@ -2464,6 +2464,29 @@ static int rev_opt_glob(const struct option *opt, return 0; } +static int rev_opt_no_walk(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + + BUG_ON_OPT_NEG(unset); + if (!arg) { + revs->no_walk = REVISION_WALK_NO_WALK_SORTED; + } else { + /* +* Detached form ("--no-walk X" as opposed to "--no-walk=X") +* not allowed, since the argument is optional. +*/ + if (!strcmp(arg, "sorted")) + revs->no_walk = REVISION_WALK_NO_WALK_SORTED; + else if (!strcmp(arg, "unsorted")) + revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED; + else + return error(_("invalid argument to --no-walk")); + } + return 0; +} + static int rev_opt_not(const struct option *opt, const char *arg, int unset) { @@ -2572,6 +2595,12 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV_NOARG(0, "not", N_("reverse the meaning of '^' for all following revisions"), rev_opt_not), + OPT_REV_OPTARG(0, "no-walk", N_("(sorted|unsorted)"), + N_("only show given commits but do not traverse their ancestors"), + rev_opt_no_walk), + OPT_SET_INT_F(0, "do-walk", &revs->no_walk, + N_("override a previous --no-walk"), + 0, PARSE_OPT_NONEG), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2583,7 +2612,6 @@ static int handle_revision_pseudo_opt(const char *submodule, int argc, const char **argv, int *flags) { const char *arg = argv[0]; - const char *optarg; struct ref_store *refs; if (submodule) { @@ -2611,22 +2639,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if (!strcmp(arg, "--no-walk")) { - revs->no_walk = REVISION_WALK_NO_WALK_SORTED; - } else if (skip_prefix(arg, "--no-walk=", &optarg)) { - /* -* Detached form ("--no-walk X" as opposed to "--no-walk=X") -* not allowed, since the argument is optional. -*/ - if (!strcmp(optarg, "sorted")) - revs->no_walk = REVISION_WALK_NO_WALK_SORTED; - else if (!strcmp(optarg, "unsorted")) - revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED; - else - return error("invalid argument to --no-walk"); - } else if (!strcmp(arg, "--do-walk")) { - revs->no_walk = 0; - } else if (!strcmp(arg, "--single-worktree")) { + if (!strcmp(arg, "--single-worktree")) { revs->single_worktree = 1; } else { return 0; -- 2.21.0.1141.gd54ac2cb17
[PATCH 00/19] Convert revision.c to parseopt part 1/4
Following the conversion in diff.c to use parse_options, this time it's revision.c. There are about 76 patches to convert all options, split in 4 parts. After that there are about 10 more to convert rev users to use parse_options() directly. Full 76 patches are available here [1]. I'm not quite done with those last "10 more" patches yet [2], still stuck at that ancient blame UI. But I should be done by the 76 patches are merged. [1] https://gitlab.com/pclouds/git/commits/revision-opt-parse-options [2] https://gitlab.com/pclouds/git/commits/parse-options-step-no-more Nguyễn Thái Ngọc Duy (19): revision.h: avoid bit fields in struct rev_info revision.h: move repo field down revision.c: prepare to convert handle_revision_pseudo_opt() rev-parseopt: convert --all rev-parseopt: convert --branches rev-parseopt: convert --bisect rev-parseopt: convert --tags rev-parseopt: convert --remotes rev-parseopt: convert --glob rev-parseopt: convert --exclude rev-parseopt: convert --reflog rev-parseopt: convert --indexed-objects rev-parseopt: convert --not rev-parseopt: convert --no-walk and --do-walk rev-parseopt: convert --single-worktree rev-parseopt: prepare to convert handle_revision_opt() rev-parseopt: convert --max-count rev-parseopt: convert --skip rev-parseopt: convert --min-age and --max-age Documentation/technical/api-diff.txt | 6 +- diff.c | 16 - diff.h | 1 - parse-options-cb.c | 8 + parse-options.h | 4 + revision.c | 465 +++ revision.h | 172 +- 7 files changed, 441 insertions(+), 231 deletions(-) -- 2.21.0.1141.gd54ac2cb17
[PATCH 09/19] rev-parseopt: convert --glob
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 28 +--- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/revision.c b/revision.c index bcfca8856f..013b8ec19f 100644 --- a/revision.c +++ b/revision.c @@ -30,6 +30,9 @@ #include "prio-queue.h" #include "hashmap.h" +#define OPT_REV(s, l, a, h, cb) \ + OPT_CALLBACK_F(s, l, revs, a, h, PARSE_OPT_NONEG, cb) + #define OPT_REV_NOARG(s, l, h, cb) \ OPT_CALLBACK_F(s, l, revs, NULL, h, \ PARSE_OPT_NONEG | PARSE_OPT_NOARG, cb) @@ -2425,6 +2428,20 @@ static int rev_opt_branches(const struct option *opt, return 0; } +static int rev_opt_glob(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + int flags = *revs->pseudo_flags; + struct all_refs_cb cb; + + BUG_ON_OPT_NEG(unset); + init_all_refs_cb(&cb, revs, flags); + for_each_glob_ref(handle_one_ref, arg, &cb); + clear_ref_exclusion(&revs->ref_excludes); + return 0; +} + static int rev_opt_remotes(const struct option *opt, const char *arg, int unset) { @@ -2495,6 +2512,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV_OPTARG(0, "remotes", N_(""), N_("include all refs in refs/remotes (optionally matches pattern)"), rev_opt_remotes), + OPT_REV(0, "glob", N_(""), + N_("include all refs matching shell glob"), + rev_opt_glob), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2535,13 +2555,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if ((argcount = parse_long_opt("glob", argv, &optarg))) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, *flags); - for_each_glob_ref(handle_one_ref, optarg, &cb); - clear_ref_exclusion(&revs->ref_excludes); - return argcount; - } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) { + if ((argcount = parse_long_opt("exclude", argv, &optarg))) { add_ref_exclusion(&revs->ref_excludes, optarg); return argcount; } else if (!strcmp(arg, "--reflog")) { -- 2.21.0.1141.gd54ac2cb17
[PATCH 10/19] rev-parseopt: convert --exclude
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 19 ++- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/revision.c b/revision.c index 013b8ec19f..d34e17984d 100644 --- a/revision.c +++ b/revision.c @@ -2428,6 +2428,16 @@ static int rev_opt_branches(const struct option *opt, return 0; } +static int rev_opt_exclude(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + + BUG_ON_OPT_NEG(unset); + add_ref_exclusion(&revs->ref_excludes, arg); + return 0; +} + static int rev_opt_glob(const struct option *opt, const char *arg, int unset) { @@ -2515,6 +2525,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV(0, "glob", N_(""), N_("include all refs matching shell glob"), rev_opt_glob), + OPT_REV(0, "exclude", N_(""), + N_("exclude refs matching glob pattern"), + rev_opt_exclude), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2528,7 +2541,6 @@ static int handle_revision_pseudo_opt(const char *submodule, const char *arg = argv[0]; const char *optarg; struct ref_store *refs; - int argcount; if (submodule) { /* @@ -2555,10 +2567,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if ((argcount = parse_long_opt("exclude", argv, &optarg))) { - add_ref_exclusion(&revs->ref_excludes, optarg); - return argcount; - } else if (!strcmp(arg, "--reflog")) { + if (!strcmp(arg, "--reflog")) { add_reflogs_to_pending(revs, *flags); } else if (!strcmp(arg, "--indexed-objects")) { add_index_objects_to_pending(revs, *flags); -- 2.21.0.1141.gd54ac2cb17
[PATCH 06/19] rev-parseopt: convert --bisect
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 28 +--- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/revision.c b/revision.c index 7db1109b57..0d34f81716 100644 --- a/revision.c +++ b/revision.c @@ -2387,6 +2387,23 @@ static int rev_opt_all(const struct option *opt, const char *arg, int unset) return 0; } +static int rev_opt_bisect(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + int flags = *revs->pseudo_flags; + struct ref_store*refs = revs->pseudo_refs; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + read_bisect_terms(&term_bad, &term_good); + handle_refs(refs, revs, flags, for_each_bad_bisect_ref); + handle_refs(refs, revs, flags ^ (UNINTERESTING | BOTTOM), + for_each_good_bisect_ref); + revs->bisect = 1; + return 0; +} + static int rev_opt_branches(const struct option *opt, const char *arg, int unset) { @@ -2427,6 +2444,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV_OPTARG(0, "branches", N_(""), N_("include all refs in refs/heads (optionally matches pattern)"), rev_opt_branches), + OPT_REV_NOARG(0, "bisect", + N_("synonym to refs/bisect/good-* --not refs/bisect/bad"), + rev_opt_bisect), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2467,13 +2487,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if (!strcmp(arg, "--bisect")) { - read_bisect_terms(&term_bad, &term_good); - handle_refs(refs, revs, *flags, for_each_bad_bisect_ref); - handle_refs(refs, revs, *flags ^ (UNINTERESTING | BOTTOM), - for_each_good_bisect_ref); - revs->bisect = 1; - } else if (!strcmp(arg, "--tags")) { + if (!strcmp(arg, "--tags")) { handle_refs(refs, revs, *flags, refs_for_each_tag_ref); clear_ref_exclusion(&revs->ref_excludes); } else if (!strcmp(arg, "--remotes")) { -- 2.21.0.1141.gd54ac2cb17
[PATCH 05/19] rev-parseopt: convert --branches
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 38 +- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/revision.c b/revision.c index 9a346577f6..7db1109b57 100644 --- a/revision.c +++ b/revision.c @@ -34,6 +34,10 @@ OPT_CALLBACK_F(s, l, revs, NULL, h, \ PARSE_OPT_NONEG | PARSE_OPT_NOARG, cb) +#define OPT_REV_OPTARG(s, l, a, h, cb) \ + OPT_CALLBACK_F(s, l, revs, a, h, \ + PARSE_OPT_NONEG | PARSE_OPT_OPTARG, cb) + volatile show_early_output_fn_t show_early_output; static const char *term_bad; @@ -2383,6 +2387,27 @@ static int rev_opt_all(const struct option *opt, const char *arg, int unset) return 0; } +static int rev_opt_branches(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + int flags = *revs->pseudo_flags; + struct ref_store*refs = revs->pseudo_refs; + + BUG_ON_OPT_NEG(unset); + if (arg) { + struct all_refs_cb cb; + + init_all_refs_cb(&cb, revs, flags); + for_each_glob_ref_in(handle_one_ref, arg, "refs/heads/", &cb); + clear_ref_exclusion(&revs->ref_excludes); + } else { + handle_refs(refs, revs, flags, refs_for_each_branch_ref); + clear_ref_exclusion(&revs->ref_excludes); + } + return 0; +} + static void make_pseudo_options(struct rev_info *revs) { /* @@ -2399,6 +2424,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV_NOARG(0, "all", N_("include all refs in refs/ and HEAD"), rev_opt_all), + OPT_REV_OPTARG(0, "branches", N_(""), + N_("include all refs in refs/heads (optionally matches pattern)"), + rev_opt_branches), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2439,10 +2467,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if (!strcmp(arg, "--branches")) { - handle_refs(refs, revs, *flags, refs_for_each_branch_ref); - clear_ref_exclusion(&revs->ref_excludes); - } else if (!strcmp(arg, "--bisect")) { + if (!strcmp(arg, "--bisect")) { read_bisect_terms(&term_bad, &term_good); handle_refs(refs, revs, *flags, for_each_bad_bisect_ref); handle_refs(refs, revs, *flags ^ (UNINTERESTING | BOTTOM), @@ -2463,11 +2488,6 @@ static int handle_revision_pseudo_opt(const char *submodule, } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) { add_ref_exclusion(&revs->ref_excludes, optarg); return argcount; - } else if (skip_prefix(arg, "--branches=", &optarg)) { - struct all_refs_cb cb; - init_all_refs_cb(&cb, revs, *flags); - for_each_glob_ref_in(handle_one_ref, optarg, "refs/heads/", &cb); - clear_ref_exclusion(&revs->ref_excludes); } else if (skip_prefix(arg, "--tags=", &optarg)) { struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); -- 2.21.0.1141.gd54ac2cb17
[PATCH 01/19] revision.h: avoid bit fields in struct rev_info
Bitfield addresses cannot be passed around in a pointer. This makes it hard to use parse-options to set/unset them. Turn this struct to normal integers. This of course increases the size of this struct multiple times, but since we only have a handful of rev_info variables around, memory consumption is not at all a concern. Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.h | 164 ++--- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/revision.h b/revision.h index 4134dc6029..01e4c42274 100644 --- a/revision.h +++ b/revision.h @@ -107,95 +107,95 @@ struct rev_info { unsigned int early_output; - unsigned intignore_missing:1, - ignore_missing_links:1; + unsigned int ignore_missing; + unsigned int ignore_missing_links; /* Traversal flags */ - unsigned intdense:1, - prune:1, - no_walk:2, - remove_empty_trees:1, - simplify_history:1, - topo_order:1, - simplify_merges:1, - simplify_by_decoration:1, - single_worktree:1, - tag_objects:1, - tree_objects:1, - blob_objects:1, - verify_objects:1, - edge_hint:1, - edge_hint_aggressive:1, - limited:1, - unpacked:1, - boundary:2, - count:1, - left_right:1, - left_only:1, - right_only:1, - rewrite_parents:1, - print_parents:1, - show_decorations:1, - reverse:1, - reverse_output_stage:1, - cherry_pick:1, - cherry_mark:1, - bisect:1, - ancestry_path:1, - first_parent_only:1, - line_level_traverse:1, - tree_blobs_in_commit_order:1, - - /* -* Blobs are shown without regard for their existence. -* But not so for trees: unless exclude_promisor_objects -* is set and the tree in question is a promisor object; -* OR ignore_missing_links is set, the revision walker -* dies with a "bad tree object HASH" message when -* encountering a missing tree. For callers that can -* handle missing trees and want them to be filterable -* and showable, set this to true. The revision walker -* will filter and show such a missing tree as usual, -* but will not attempt to recurse into this tree -* object. -*/ - do_not_die_on_missing_tree:1, - - /* for internal use only */ - exclude_promisor_objects:1; + unsigned int dense; + unsigned int prune; + unsigned int no_walk; + unsigned int remove_empty_trees; + unsigned int simplify_history; + unsigned int topo_order; + unsigned int simplify_merges; + unsigned int simplify_by_decoration; + unsigned int single_worktree; + unsigned int tag_objects; + unsigned int tree_objects; + unsigned int blob_objects; + unsigned int verify_objects; + unsigned int edge_hint; + unsigned int edge_hint_aggressive; + unsigned int limited; + unsigned int unpacked; + unsigned int boundary; + unsigned int count; + unsigned int left_right; + unsigned int left_only; + unsigned int right_only; + unsigned int rewrite_parents; + unsigned int print_parents; + unsigned int show_decorations; + unsigned int reverse; + unsigned int reverse_output_stage; + unsigned int cherry_pick; + unsigned int cherry_mark; + unsigned int bisect; + unsigned int ancestry_path; + unsigned int first_parent_only; + unsigned int line_level_traverse; + unsigned int tree_blobs_in_commit_order; + + /* +* Blobs are shown without regard for their existence. +* But not so for trees: unless exclude_promisor_objects +* is set and the tree in question is a promisor object; +* OR ignore_missing_links is set, the revision walker +* dies with a "bad tree object HASH" message when +* encountering a missing tree. For callers that can +* handle missing trees and want them to be filterable +* and showable, set this t
[PATCH 13/19] rev-parseopt: convert --not
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 18 +++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/revision.c b/revision.c index 9cfa4dc151..b02cb4660b 100644 --- a/revision.c +++ b/revision.c @@ -2464,6 +2464,17 @@ static int rev_opt_glob(const struct option *opt, return 0; } +static int rev_opt_not(const struct option *opt, + const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + *revs->pseudo_flags ^= UNINTERESTING | BOTTOM; + return 0; +} + static int rev_opt_reflog(const struct option *opt, const char *arg, int unset) { @@ -2558,6 +2569,9 @@ static void make_pseudo_options(struct rev_info *revs) OPT_REV_NOARG(0, "indexed-objects", N_("include all trees and blobs used by the index"), rev_opt_indexed_objects), + OPT_REV_NOARG(0, "not", + N_("reverse the meaning of '^' for all following revisions"), + rev_opt_not), OPT_END() }; ALLOC_ARRAY(revs->pseudo_options, ARRAY_SIZE(options)); @@ -2597,9 +2611,7 @@ static int handle_revision_pseudo_opt(const char *submodule, if (argc) return argc; - if (!strcmp(arg, "--not")) { - *flags ^= UNINTERESTING | BOTTOM; - } else if (!strcmp(arg, "--no-walk")) { + if (!strcmp(arg, "--no-walk")) { revs->no_walk = REVISION_WALK_NO_WALK_SORTED; } else if (skip_prefix(arg, "--no-walk=", &optarg)) { /* -- 2.21.0.1141.gd54ac2cb17
[PATCH 19/19] rev-parseopt: convert --min-age and --max-age
Signed-off-by: Nguyễn Thái Ngọc Duy --- parse-options-cb.c | 8 parse-options.h| 4 revision.c | 10 -- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/parse-options-cb.c b/parse-options-cb.c index 6e2e8d6273..7cdbbf5f6d 100644 --- a/parse-options-cb.c +++ b/parse-options-cb.c @@ -39,6 +39,14 @@ int parse_opt_expiry_date_cb(const struct option *opt, const char *arg, return 0; } +int parse_opt_timestamp_cb(const struct option *opt, + const char *arg, int unset) +{ + BUG_ON_OPT_NEG(unset); + *(timestamp_t *)opt->value = atoi(arg); + return 0; +} + int parse_opt_color_flag_cb(const struct option *opt, const char *arg, int unset) { diff --git a/parse-options.h b/parse-options.h index cc9230adac..7637864c41 100644 --- a/parse-options.h +++ b/parse-options.h @@ -168,6 +168,9 @@ struct option { #define OPT_EXPIRY_DATE(s, l, v, h) \ { OPTION_CALLBACK, (s), (l), (v), N_("expiry-date"),(h), 0, \ parse_opt_expiry_date_cb } +#define OPT_TIMESTAMP(s, l, v, h) \ + { OPTION_CALLBACK, (s), (l), (v), N_("timestamp"),(h), \ + PARSE_OPT_NONEG, parse_opt_timestamp_cb } #define OPT_CALLBACK(s, l, v, a, h, f) OPT_CALLBACK_F(s, l, v, a, h, 0, f) #define OPT_NUMBER_CALLBACK(v, h, f) \ { OPTION_NUMBER, 0, NULL, (v), NULL, (h), \ @@ -275,6 +278,7 @@ struct option *parse_options_concat(struct option *a, struct option *b); /*- some often used options -*/ int parse_opt_abbrev_cb(const struct option *, const char *, int); int parse_opt_expiry_date_cb(const struct option *, const char *, int); +int parse_opt_timestamp_cb(const struct option *, const char *, int); int parse_opt_color_flag_cb(const struct option *, const char *, int); int parse_opt_verbosity_cb(const struct option *, const char *, int); int parse_opt_object_name(const struct option *, const char *, int); diff --git a/revision.c b/revision.c index 42d466cd08..0c28b67978 100644 --- a/revision.c +++ b/revision.c @@ -1999,6 +1999,10 @@ static void make_rev_options(struct rev_info *revs) OPT_INTEGER_F(0, "skip", &revs->skip_count, N_("skip a number of commits before starting to show"), PARSE_OPT_NONEG), + OPT_TIMESTAMP(0, "min-age", &revs->min_age, + N_("limit the commits output to a specified time range")), + OPT_TIMESTAMP(0, "max-age", &revs->max_age, + N_("limit the commits output to a specified time range")), OPT_END(), }; revs->options = parse_options_concat(options, revs->diffopt.parseopts); @@ -2045,18 +2049,12 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->max_count < 0) die("'%s': not a non-negative integer", arg + 1); revs->no_walk = 0; - } else if ((argcount = parse_long_opt("max-age", argv, &optarg))) { - revs->max_age = atoi(optarg); - return argcount; } else if ((argcount = parse_long_opt("since", argv, &optarg))) { revs->max_age = approxidate(optarg); return argcount; } else if ((argcount = parse_long_opt("after", argv, &optarg))) { revs->max_age = approxidate(optarg); return argcount; - } else if ((argcount = parse_long_opt("min-age", argv, &optarg))) { - revs->min_age = atoi(optarg); - return argcount; } else if ((argcount = parse_long_opt("before", argv, &optarg))) { revs->min_age = approxidate(optarg); return argcount; -- 2.21.0.1141.gd54ac2cb17
[PATCH 18/19] rev-parseopt: convert --skip
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 8 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/revision.c b/revision.c index c53347d362..42d466cd08 100644 --- a/revision.c +++ b/revision.c @@ -1996,6 +1996,9 @@ static void make_rev_options(struct rev_info *revs) OPT_REV('n', "max-count", N_(""), N_("limit the number of commis to stdout"), rev_opt_max_count), + OPT_INTEGER_F(0, "skip", &revs->skip_count, + N_("skip a number of commits before starting to show"), + PARSE_OPT_NONEG), OPT_END(), }; revs->options = parse_options_concat(options, revs->diffopt.parseopts); @@ -2036,10 +2039,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg if (argc) return argc; - if ((argcount = parse_long_opt("skip", argv, &optarg))) { - revs->skip_count = atoi(optarg); - return argcount; - } else if ((*arg == '-') && isdigit(arg[1])) { + if ((*arg == '-') && isdigit(arg[1])) { /* accept -, like traditional "head" */ if (strtol_i(arg + 1, 10, &revs->max_count) < 0 || revs->max_count < 0) -- 2.21.0.1141.gd54ac2cb17
[PATCH 17/19] rev-parseopt: convert --max-count
Signed-off-by: Nguyễn Thái Ngọc Duy --- revision.c | 29 +++-- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/revision.c b/revision.c index f15aa3e62d..c53347d362 100644 --- a/revision.c +++ b/revision.c @@ -1976,12 +1976,26 @@ static int rev_opt_abbrev(const struct option *opt, return 0; } +static int rev_opt_max_count(const struct option *opt, +const char *arg, int unset) +{ + struct rev_info *revs = opt->value; + + BUG_ON_OPT_NEG(unset); + revs->max_count = atoi(arg); + revs->no_walk = 0; + return 0; +} + static void make_rev_options(struct rev_info *revs) { struct option options[] = { OPT_CALLBACK_F(0, "abbrev", revs, N_("n"), N_("show the given source prefix instead of \"a/\""), PARSE_OPT_OPTARG, rev_opt_abbrev), + OPT_REV('n', "max-count", N_(""), + N_("limit the number of commis to stdout"), + rev_opt_max_count), OPT_END(), }; revs->options = parse_options_concat(options, revs->diffopt.parseopts); @@ -2022,11 +2036,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg if (argc) return argc; - if ((argcount = parse_long_opt("max-count", argv, &optarg))) { - revs->max_count = atoi(optarg); - revs->no_walk = 0; - return argcount; - } else if ((argcount = parse_long_opt("skip", argv, &optarg))) { + if ((argcount = parse_long_opt("skip", argv, &optarg))) { revs->skip_count = atoi(optarg); return argcount; } else if ((*arg == '-') && isdigit(arg[1])) { @@ -2035,15 +2045,6 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->max_count < 0) die("'%s': not a non-negative integer", arg + 1); revs->no_walk = 0; - } else if (!strcmp(arg, "-n")) { - if (argc <= 1) - return error("-n requires an argument"); - revs->max_count = atoi(argv[1]); - revs->no_walk = 0; - return 2; - } else if (skip_prefix(arg, "-n", &optarg)) { - revs->max_count = atoi(optarg); - revs->no_walk = 0; } else if ((argcount = parse_long_opt("max-age", argv, &optarg))) { revs->max_age = atoi(optarg); return argcount; -- 2.21.0.1141.gd54ac2cb17
[PATCH] submodule--helper: add a missing \n
This is a complete line. We're not expecting the next function to add anything to the same line. Signed-off-by: Nguyễn Thái Ngọc Duy --- builtin/submodule--helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 8c72ea864c..0bf4aa088e 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1301,7 +1301,7 @@ static int add_possible_reference_from_superproject( die(_("submodule '%s' cannot add alternate: %s"), sas->submodule_name, err.buf); case SUBMODULE_ALTERNATE_ERROR_INFO: - fprintf(stderr, _("submodule '%s' cannot add alternate: %s"), + fprintf_ln(stderr, _("submodule '%s' cannot add alternate: %s"), sas->submodule_name, err.buf); case SUBMODULE_ALTERNATE_ERROR_IGNORE: ; /* nothing */ -- 2.21.0.1120.gde2b49a866
[PATCH] git-worktree.txt: update the per-worktree refs exceptions
This section describes the two exceptions where refs inside refs/ are not shared. Except that it's three exceptions, the third one being refs/rewritten [1]. I was not aware of it when I wrote this part. Update it to include refs/rewritten. [1] a9be29c981 (sequencer: make refs generated by the `label` command worktree-local, 2018-04-25) Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/git-worktree.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 85d92c9761..8ed5250d5f 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -214,8 +214,8 @@ refs of one working tree from another. In general, all pseudo refs are per working tree and all refs starting with "refs/" are shared. Pseudo refs are ones like HEAD which are directly under GIT_DIR instead of inside GIT_DIR/refs. There is one -exception to this: refs inside refs/bisect and refs/worktree is not -shared. +exception to this: refs inside refs/bisect, refs/rewritten and +refs/worktree are not shared. Refs that are per working tree can still be accessed from another working tree via two special paths, main-worktree and worktrees. The -- 2.21.0.1120.gde2b49a866
[PATCH 1/2] merge: remove drop_save() in favor of remove_merge_branch_state()
Both remove_branch_state() and drop_save() delete almost the same set of files about the current merge state. The only difference is MERGE_RR but it should also be cleaned up after a successful merge, which is what drop_save() is for. Make a new function that deletes all merge-related state files and use it instead of drop_save(). This function will also be used in the next patch that introduces --quit. Signed-off-by: Nguyễn Thái Ngọc Duy --- branch.c| 11 --- branch.h| 6 ++ builtin/merge.c | 17 + 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/branch.c b/branch.c index 28b81a7e02..1db0601a11 100644 --- a/branch.c +++ b/branch.c @@ -337,15 +337,20 @@ void create_branch(struct repository *r, free(real_ref); } -void remove_branch_state(struct repository *r) +void remove_merge_branch_state(struct repository *r) { - unlink(git_path_cherry_pick_head(r)); - unlink(git_path_revert_head(r)); unlink(git_path_merge_head(r)); unlink(git_path_merge_rr(r)); unlink(git_path_merge_msg(r)); unlink(git_path_merge_mode(r)); +} + +void remove_branch_state(struct repository *r) +{ + unlink(git_path_cherry_pick_head(r)); + unlink(git_path_revert_head(r)); unlink(git_path_squash_msg(r)); + remove_merge_branch_state(r); } void die_if_checked_out(const char *branch, int ignore_current_worktree) diff --git a/branch.h b/branch.h index 29c1afa4d0..c90ba9d7bf 100644 --- a/branch.h +++ b/branch.h @@ -60,6 +60,12 @@ extern int validate_branchname(const char *name, struct strbuf *ref); */ extern int validate_new_branchname(const char *name, struct strbuf *ref, int force); +/* + * Remove information about the merge state on the current + * branch. (E.g., MERGE_HEAD) + */ +void remove_merge_branch_state(struct repository *r); + /* * Remove information about the state of working on the current * branch. (E.g., MERGE_HEAD) diff --git a/builtin/merge.c b/builtin/merge.c index 5ce8946d39..0fd448b403 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -37,6 +37,7 @@ #include "packfile.h" #include "tag.h" #include "alias.h" +#include "branch.h" #include "commit-reach.h" #define DEFAULT_TWOHEAD (1<<0) @@ -282,14 +283,6 @@ static struct option builtin_merge_options[] = { OPT_END() }; -/* Cleans up metadata that is uninteresting after a succeeded merge. */ -static void drop_save(void) -{ - unlink(git_path_merge_head(the_repository)); - unlink(git_path_merge_msg(the_repository)); - unlink(git_path_merge_mode(the_repository)); -} - static int save_state(struct object_id *stash) { int len; @@ -383,7 +376,7 @@ static void finish_up_to_date(const char *msg) { if (verbosity >= 0) printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); - drop_save(); + remove_merge_branch_state(the_repository); } static void squash_message(struct commit *commit, struct commit_list *remoteheads) @@ -861,7 +854,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) &result_commit, NULL, sign_commit)) die(_("failed to write commit object")); finish(head, remoteheads, &result_commit, "In-index merge"); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -888,7 +881,7 @@ static int finish_automerge(struct commit *head, strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, remoteheads, &result_commit, buf.buf); strbuf_release(&buf); - drop_save(); + remove_merge_branch_state(the_repository); return 0; } @@ -1466,7 +1459,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } finish(head_commit, remoteheads, &commit->object.oid, msg.buf); - drop_save(); + remove_merge_branch_state(the_repository); goto done; } else if (!remoteheads->next && common->next) ; -- 2.21.0.1110.g9614c01b33
[PATCH 2/2] merge: add --quit
This allows to cancel the current merge without reseting worktree/index, which is what --abort is for. Like other --quit(s), this is often used when you forgot that you're in the middle of a merge and already switched away, doing different things. By the time you're realize, you can't even continue the merge anymore. This also makes all in-progress commands, am, merge, rebase, revert and cherry-pick, take all three --abort, --continue and --quit (bisect has a different UI). Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/git-merge.txt | 4 builtin/merge.c | 13 + t/t7600-merge.sh| 14 ++ 3 files changed, 31 insertions(+) diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 6294dbc09d..c01cfa6595 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -100,6 +100,10 @@ commit or stash your changes before running 'git merge'. 'git merge --abort' is equivalent to 'git reset --merge' when `MERGE_HEAD` is present. +--quit:: + Forget about the current merge in progress. Leave the index + and the working tree as-is. + --continue:: After a 'git merge' stops due to conflicts you can conclude the merge by running 'git merge --continue' (see "HOW TO RESOLVE diff --git a/builtin/merge.c b/builtin/merge.c index 0fd448b403..13392ba1cf 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -73,6 +73,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int quit_current_merge; static int continue_current_merge; static int allow_unrelated_histories; static int show_progress = -1; @@ -270,6 +271,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "quit", &quit_current_merge, + N_("--abort but leave index and working tree alone")), OPT_BOOL(0, "continue", &continue_current_merge, N_("continue the current in-progress merge")), OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, @@ -1255,6 +1258,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix) goto done; } + if (quit_current_merge) { + if (orig_argc != 2) + usage_msg_opt(_("--quit expects no arguments"), + builtin_merge_usage, + builtin_merge_options); + + remove_merge_branch_state(the_repository); + goto done; + } + if (continue_current_merge) { int nargc = 1; const char *nargv[] = {"commit", NULL}; diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 106148254d..ea82cb744b 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -822,4 +822,18 @@ test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue' verify_parents $c0 $c1 ' +test_expect_success 'merge --quit' ' + git reset --hard side && + test_must_fail git -c rerere.enabled=true merge master && + test_path_is_file .git/MERGE_HEAD && + test_path_is_file .git/MERGE_MODE && + test_path_is_file .git/MERGE_MSG && + test_path_is_file .git/MERGE_RR && + git merge --quit && + test_path_is_missing .git/MERGE_HEAD && + test_path_is_missing .git/MERGE_MODE && + test_path_is_missing .git/MERGE_MSG && + test_path_is_missing .git/MERGE_RR +' + test_done -- 2.21.0.1110.g9614c01b33
[PATCH 0/2] Add "git merge --quit"
nd/switch-and-restore suggests 'git merge --quit' to get out of a merge even though this option is not implemented [1]. It's a soft dependency, no actual functionality is broken by the lack of --quit, so I'm sending it separately. [1] https://public-inbox.org/git/78c7c281-82ec-2ba9-a607-dd2ecba54...@gmail.com/ Nguyễn Thái Ngọc Duy (2): merge: remove drop_save() in favor of remove_merge_branch_state() merge: add --quit Documentation/git-merge.txt | 4 branch.c| 11 --- branch.h| 6 ++ builtin/merge.c | 30 ++ t/t7600-merge.sh| 14 ++ 5 files changed, 50 insertions(+), 15 deletions(-) -- 2.21.0.1110.g9614c01b33
[PATCH v2] parse-options: don't emit "ambiguous option" for aliases
Change the option parsing machinery so that e.g. "clone --recurs ..." doesn't error out because "clone" understands both "--recursive" and "--recurse-submodules" to mean the same thing. Initially "clone" just understood --recursive until the --recurses-submodules alias was added in ccdd3da652 ("clone: Add the --recurse-submodules option as alias for --recursive", 2010-11-04). Since bb62e0a99f ("clone: teach --recurse-submodules to optionally take a pathspec", 2017-03-17) the longer form has been promoted to the default. But due to the way the options parsing machinery works this resulted in the rather absurd situation of: $ git clone --recurs [...] error: ambiguous option: recurs (could be --recursive or --recurse-submodules) Add OPT_ALIAS() to express this link between two or more options and use it in git-clone. Multiple aliases of an option could be written as OPT_ALIAS(0, "alias1", "original-name"), OPT_ALIAS(0, "alias2", "original-name"), ... The current implementation is not exactly optimal in this case. But we can optimize it when it becomes a problem. So far we don't even have two aliases of any option. A big chunk of code is actually from Junio C Hamano. Signed-off-by: Nguyễn Thái Ngọc Duy --- OK it's working for real this time. test-parse-options.c is also updated to for testing OPT_ALIAS. builtin/clone.c | 5 +- parse-options.c | 143 -- parse-options.h | 6 ++ t/helper/test-parse-options.c | 3 + t/t0040-parse-options.sh | 17 5 files changed, 164 insertions(+), 10 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 50bde99618..703b7247ad 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -98,10 +98,7 @@ static struct option builtin_clone_options[] = { N_("don't use local hardlinks, always copy")), OPT_BOOL('s', "shared", &option_shared, N_("setup as shared repository")), - { OPTION_CALLBACK, 0, "recursive", &option_recurse_submodules, - N_("pathspec"), N_("initialize submodules in the clone"), - PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, recurse_submodules_cb, - (intptr_t)"." }, + OPT_ALIAS(0, "recursive", "recurse-submodules"), { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules, N_("pathspec"), N_("initialize submodules in the clone"), PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." }, diff --git a/parse-options.c b/parse-options.c index acc3a93660..1b1cc2add7 100644 --- a/parse-options.c +++ b/parse-options.c @@ -261,6 +261,35 @@ static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p, return PARSE_OPT_UNKNOWN; } +static int has_string(const char *it, const char **array) +{ + while (*array) + if (!strcmp(it, *(array++))) + return 1; + return 0; +} + +static int is_alias(struct parse_opt_ctx_t *ctx, + const struct option *one_opt, + const struct option *another_opt) +{ + const char **group; + + if (!ctx->alias_groups) + return 0; + + if (!one_opt->long_name || !another_opt->long_name) + return 0; + + for (group = ctx->alias_groups; *group; group += 3) { + /* it and other are from the same family? */ + if (has_string(one_opt->long_name, group) && + has_string(another_opt->long_name, group)) + return 1; + } + return 0; +} + static enum parse_opt_result parse_long_opt( struct parse_opt_ctx_t *p, const char *arg, const struct option *options) @@ -296,7 +325,8 @@ static enum parse_opt_result parse_long_opt( if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) && !strncmp(long_name, arg, arg_end - arg)) { is_abbreviated: - if (abbrev_option) { + if (abbrev_option && + !is_alias(p, abbrev_option, options)) { /* * If this is abbreviated, it is * ambiguous. So when there is no @@ -445,6 +475,10 @@ static void parse_options_check(const struct option *opts) if (opts->callback) BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback"); break; + case OPTION_ALIAS: +
[PATCH v3 15/16] help: move git-diff and git-reset to different groups
The third column in command-list.txt determines what group a common command is printed in 'git help'. "git reset" is currently in the "work on the current change (see also: git help everyday)" group. While it's true that "git reset" can manipulate the index and can be in this group, its unique functionality is resetting HEAD, which should be the "grow, mark, tweak history" group. Moving it there will also avoid the confusion because both 'restore' and 'reset' are in the same group, next to each other. While looking at the 'group, mark, tweak history', I realize "git diff" should not be there. All the commands in this group is about _changing_ the commit history while "git diff" is a read-only operation. It fits better in the "examine the history and state" group (especially when "git status", its close friend, is already there). This is what we have after the reorganization: work on the current change (see also: git help everyday) add Add file contents to the index mvMove or rename a file, a directory, or a symlink restore Restore working tree files rmRemove files from the working tree and from the index examine the history and state (see also: git help revisions) bisectUse binary search to find the commit that introduced a bug diff Show changes between commits, commit and working tree, etc grep Print lines matching a pattern log Show commit logs show Show various types of objects statusShow the working tree status grow, mark and tweak your common history branchList, create, or delete branches commitRecord changes to the repository merge Join two or more development histories together rebaseReapply commits on top of another base tip reset Reset current HEAD to the specified state switchSwitch branches tag Create, list, delete or verify a tag object signed with GPG Signed-off-by: Nguyễn Thái Ngọc Duy --- command-list.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/command-list.txt b/command-list.txt index cf8dccb439..a9ac72bef4 100644 --- a/command-list.txt +++ b/command-list.txt @@ -81,7 +81,7 @@ git-cvsimport foreignscminterface git-cvsserver foreignscminterface git-daemon synchingrepositories git-describemainporcelain -git-diffmainporcelain history +git-diffmainporcelain info git-diff-files plumbinginterrogators git-diff-index plumbinginterrogators git-diff-tree plumbinginterrogators @@ -150,7 +150,7 @@ git-repack ancillarymanipulators complete git-replace ancillarymanipulators complete git-request-pullforeignscminterface complete git-rerere ancillaryinterrogators -git-reset mainporcelain worktree +git-reset mainporcelain history git-restore mainporcelain worktree git-revert mainporcelain git-rev-listplumbinginterrogators -- 2.21.0.854.ge34a79f761
[PATCH v3 14/16] doc: promote "git restore"
The new command "git restore" (together with "git switch") are added to avoid the confusion of one-command-do-all "git checkout" for new users. They are also helpful to avoid ambiguous context. For these reasons, promote it everywhere possible. This includes documentation, suggestions/advice from other commands. One nice thing about git-restore is the ability to restore "everything", so it can be used in "git status" advice instead of both "git checkout" and "git reset". The three commands suggested by "git status" are add, rm and restore. "git checkout" is also removed from "git help" (i.e. it's no longer considered a commonly used command) Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/git-clean.txt| 2 +- Documentation/git-commit.txt | 2 +- Documentation/git-format-patch.txt | 2 +- Documentation/git-reset.txt| 6 +-- Documentation/git-revert.txt | 4 +- Documentation/gitcli.txt | 4 +- Documentation/giteveryday.txt | 5 +- Documentation/gittutorial-2.txt| 4 +- Documentation/gittutorial.txt | 2 +- Documentation/user-manual.txt | 12 ++--- builtin/clone.c| 2 +- builtin/commit.c | 2 +- command-list.txt | 2 +- t/t7508-status.sh | 82 +++--- t/t7512-status-help.sh | 20 wt-status.c| 26 +++--- 16 files changed, 93 insertions(+), 84 deletions(-) diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index 03056dad0d..8a31ccffea 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -64,7 +64,7 @@ OPTIONS directory) and $GIT_DIR/info/exclude, but do still use the ignore rules given with `-e` options. This allows removing all untracked files, including build products. This can be used (possibly in - conjunction with 'git reset') to create a pristine + conjunction with 'git restore' or 'git reset') to create a pristine working directory to test a clean build. -X:: diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index a85c2c2a4c..7628193284 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -359,7 +359,7 @@ When recording your own work, the contents of modified files in your working tree are temporarily stored to a staging area called the "index" with 'git add'. A file can be reverted back, only in the index but not in the working tree, -to that of the last commit with `git reset HEAD -- `, +to that of the last commit with `git restore --staged `, which effectively reverts 'git add' and prevents the changes to this file from participating in the next commit. After building the state to be committed incrementally with these commands, diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 0a24a5679e..01bfcecf69 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -422,7 +422,7 @@ One way to test if your MUA is set up correctly is: $ git fetch master:test-apply $ git switch test-apply -$ git reset --hard +$ git restore --source=HEAD --staged --worktree :/ $ git am a.patch If it does not apply correctly, there can be various reasons. diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index c25f8a95b9..633d71d36a 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -29,9 +29,9 @@ This means that `git reset ` is the opposite of `git add `git restore [--source=] --staged ...`. + After running `git reset ` to update the index entry, you can -use linkgit:git-checkout[1] to check the contents out of the index to -the working tree. -Alternatively, using linkgit:git-checkout[1] and specifying a commit, you +use linkgit:git-restore[1] to check the contents out of the index to +the working tree. Alternatively, using linkgit:git-restore[1] +and specifying a commit with `--source`, you can copy the contents of a path out of a commit to the index and to the working tree in one go. diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 018ecf49d3..9aadc36881 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -26,8 +26,8 @@ effect of some earlier commits (often only a faulty one). If you want to throw away all uncommitted changes in your working directory, you should see linkgit:git-reset[1], particularly the `--hard` option. If you want to extract specific files as they were in another commit, you -should see linkgit:git-checkout[1], specifically the `git checkout - -- ` syntax. Take care with these alternatives as +should see linkgit:git-restore[1], specifically
[PATCH v3 16/16] Declare both git-switch and git-restore experimental
These two commands are basically redesigned git-checkout. We will not have that many opportunities to redo (because we run out of verbs, and that would also increase maintenance cost). To play it safe, let's declare the two commands experimental in one or two releases. If there is a serious flaw in the UI, we could still fix it. If everything goes well and nobody complains loudly, we can remove the experimental status by reverting this patch. Signed-off-by: Nguyễn Thái Ngọc Duy --- Documentation/git-restore.txt | 2 ++ Documentation/git-switch.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Documentation/git-restore.txt b/Documentation/git-restore.txt index b608f3f360..d90093f195 100644 --- a/Documentation/git-restore.txt +++ b/Documentation/git-restore.txt @@ -28,6 +28,8 @@ commit as the restore source. See "Reset, restore and revert" in linkgit:git[1] for the differences between the three commands. +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. + OPTIONS --- -s :: diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt index f15e5bdcf4..197900363b 100644 --- a/Documentation/git-switch.txt +++ b/Documentation/git-switch.txt @@ -29,6 +29,8 @@ Switching branches does not require a clean index and working tree however if the operation leads to loss of local changes, unless told otherwise with `--discard-changes` or `--merge`. +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. + OPTIONS --- :: -- 2.21.0.854.ge34a79f761
[PATCH v3 11/16] t: add tests for restore
Signed-off-by: Nguyễn Thái Ngọc Duy --- t/lib-patch-mode.sh | 12 t/t2070-restore.sh (new +x) | 99 +++ t/t2071-restore-patch.sh (new +x) | 110 ++ 3 files changed, 221 insertions(+) diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh index 06c3c91762..cfd76bf987 100644 --- a/t/lib-patch-mode.sh +++ b/t/lib-patch-mode.sh @@ -2,28 +2,40 @@ . ./test-lib.sh +# set_state +# +# Prepare the content for path in worktree and the index as specified. set_state () { echo "$3" > "$1" && git add "$1" && echo "$2" > "$1" } +# save_state +# +# Save index/worktree content of in the files _worktree_ +# and _index_ save_state () { noslash="$(echo "$1" | tr / _)" && cat "$1" > _worktree_"$noslash" && git show :"$1" > _index_"$noslash" } +# set_and_save_state set_and_save_state () { set_state "$@" && save_state "$1" } +# verify_state verify_state () { test "$(cat "$1")" = "$2" && test "$(git show :"$1")" = "$3" } +# verify_saved_state +# +# Call verify_state with expected contents from the last save_state verify_saved_state () { noslash="$(echo "$1" | tr / _)" && verify_state "$1" "$(cat _worktree_"$noslash")" "$(cat _index_"$noslash")" diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh new file mode 100755 index 00..73ea13ede9 --- /dev/null +++ b/t/t2070-restore.sh @@ -0,0 +1,99 @@ +#!/bin/sh + +test_description='restore basic functionality' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit first && + echo first-and-a-half >>first.t && + git add first.t && + test_commit second && + echo one >one && + echo two >two && + echo untracked >untracked && + echo ignored >ignored && + echo /ignored >.gitignore && + git add one two .gitignore && + git update-ref refs/heads/one master +' + +test_expect_success 'restore without pathspec is not ok' ' + test_must_fail git restore && + test_must_fail git restore --source=first +' + +test_expect_success 'restore a file, ignoring branch of same name' ' + cat one >expected && + echo dirty >>one && + git restore one && + test_cmp expected one +' + +test_expect_success 'restore a file on worktree from another ref' ' + test_when_finished git reset --hard && + git cat-file blob first:./first.t >expected && + git restore --source=first first.t && + test_cmp expected first.t && + git cat-file blob HEAD:./first.t >expected && + git show :first.t >actual && + test_cmp expected actual +' + +test_expect_success 'restore a file in the index from another ref' ' + test_when_finished git reset --hard && + git cat-file blob first:./first.t >expected && + git restore --source=first --staged first.t && + git show :first.t >actual && + test_cmp expected actual && + git cat-file blob HEAD:./first.t >expected && + test_cmp expected first.t +' + +test_expect_success 'restore a file in both the index and worktree from another ref' ' + test_when_finished git reset --hard && + git cat-file blob first:./first.t >expected && + git restore --source=first --staged --worktree first.t && + git show :first.t >actual && + test_cmp expected actual && + test_cmp expected first.t +' + +test_expect_success 'restore --staged uses HEAD as source' ' + test_when_finished git reset --hard && + git cat-file blob :./first.t >expected && + echo index-dirty >>first.t && + git add first.t && + git restore --staged first.t && + git cat-file blob :./first.t >actual && + test_cmp expected actual +' + +test_expect_success 'restore --ignore-unmerged ignores unmerged entries' ' + git init unmerged && + ( + cd unmerged && + echo one >unmerged && + echo one >common && + git add unmerged common &&
[PATCH v3 09/16] restore: replace --force with --ignore-unmerged
Use a more specific option name to express its purpose. --force may come back as an alias of --ignore-unmerged and possibly more. But since this is a destructive operation, I don't see why we need to "force" anything more. We already don't hold back. When 'checkout --force' or 'restore --ignore-unmerged' is used, we may also print warnings about unmerged entries being ignore. Since this is not exactly warning (people tell us to do so), more informational, let it be suppressed if --quiet is given. This is a behavior change for git-checkout. PS. The diff looks a bit iffy since --force is moved to add_common_switch_branch_options() (i.e. for switching). But git-checkout is also doing switching and inherits this --force. Signed-off-by: Nguyễn Thái Ngọc Duy --- builtin/checkout.c | 29 - 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/builtin/checkout.c b/builtin/checkout.c index 09a03f1ff8..824ab65886 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -68,6 +68,8 @@ struct checkout_opts { int empty_pathspec_ok; int checkout_index; int checkout_worktree; + const char *ignore_unmerged_opt; + int ignore_unmerged; const char *new_branch; const char *new_branch_force; @@ -409,8 +411,9 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->new_branch_log) die(_("'%s' cannot be used with updating paths"), "-l"); - if (opts->force && opts->patch_mode) - die(_("'%s' cannot be used with updating paths"), "-f"); + if (opts->ignore_unmerged && opts->patch_mode) + die(_("'%s' cannot be used with updating paths"), + opts->ignore_unmerged_opt); if (opts->force_detach) die(_("'%s' cannot be used with updating paths"), "--detach"); @@ -418,8 +421,9 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->merge && opts->patch_mode) die(_("'%s' cannot be used with %s"), "--merge", "--patch"); - if (opts->force && opts->merge) - die(_("'%s' cannot be used with %s"), "-f", "-m"); + if (opts->ignore_unmerged && opts->merge) + die(_("'%s' cannot be used with %s"), + opts->ignore_unmerged_opt, "-m"); if (opts->new_branch) die(_("Cannot update paths and switch to branch '%s' at the same time."), @@ -495,8 +499,9 @@ static int checkout_paths(const struct checkout_opts *opts, if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) continue; - if (opts->force) { - warning(_("path '%s' is unmerged"), ce->name); + if (opts->ignore_unmerged) { + if (!opts->quiet) + warning(_("path '%s' is unmerged"), ce->name); } else if (opts->writeout_stage) { errs |= check_stage(opts->writeout_stage, ce, pos, opts->overlay_mode); } else if (opts->merge) { @@ -1414,8 +1419,6 @@ static struct option *add_common_options(struct checkout_opts *opts, "checkout", "control recursive updating of submodules", PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")), - OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"), - PARSE_OPT_NOCOMPLETE), OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")), OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"), N_("conflict style (merge or diff3)")), @@ -1433,6 +1436,8 @@ static struct option *add_common_switch_branch_options( OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")), OPT_SET_INT('t', "track", &opts->track, N_("set upstream info for new branch"), BRANCH_TRACK_EXPLICIT), + OPT__FORCE(&opts->