[PATCH v2 7/6] t7814: do not generate same commits in different repos

2019-06-28 Thread Nguyễn Thái Ngọc Duy
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()

2019-06-27 Thread Nguyễn Thái Ngọc Duy
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*()

2019-06-27 Thread Nguyễn Thái Ngọc Duy
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

2019-06-27 Thread Nguyễn Thái Ngọc Duy
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()

2019-06-27 Thread Nguyễn Thái Ngọc Duy
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()

2019-06-27 Thread Nguyễn Thái Ngọc Duy
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

2019-06-27 Thread Nguyễn Thái Ngọc Duy
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()

2019-06-27 Thread Nguyễn Thái Ngọc Duy
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

2019-06-26 Thread Nguyễn Thái Ngọc Duy
[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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
;: ,
+"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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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*()

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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()

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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()

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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()

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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()

2019-06-24 Thread Nguyễn Thái Ngọc Duy
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

2019-06-22 Thread Nguyễn Thái Ngọc Duy
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

2019-06-20 Thread Nguyễn Thái Ngọc Duy
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

2019-06-20 Thread Nguyễn Thái Ngọc Duy
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

2019-06-20 Thread Nguyễn Thái Ngọc Duy
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

2019-06-20 Thread Nguyễn Thái Ngọc Duy
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)

2019-06-20 Thread Nguyễn Thái Ngọc Duy
"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

2019-06-20 Thread Nguyễn Thái Ngọc Duy
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

2019-06-20 Thread Nguyễn Thái Ngọc Duy
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"

2019-06-20 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-19 Thread Nguyễn Thái Ngọc Duy
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

2019-06-12 Thread Nguyễn Thái Ngọc Duy
"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

2019-06-07 Thread Nguyễn Thái Ngọc Duy
"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

2019-05-29 Thread Nguyễn Thái Ngọc Duy
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

2019-05-29 Thread Nguyễn Thái Ngọc Duy
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

2019-05-29 Thread Nguyễn Thái Ngọc Duy
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

2019-05-29 Thread Nguyễn Thái Ngọc Duy
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

2019-05-24 Thread Nguyễn Thái Ngọc Duy
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

2019-05-24 Thread Nguyễn Thái Ngọc Duy
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

2019-05-24 Thread Nguyễn Thái Ngọc Duy
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

2019-05-24 Thread Nguyễn Thái Ngọc Duy
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

2019-05-24 Thread Nguyễn Thái Ngọc Duy
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

2019-05-19 Thread Nguyễn Thái Ngọc Duy
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

2019-05-18 Thread Nguyễn Thái Ngọc Duy
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

2019-05-18 Thread Nguyễn Thái Ngọc Duy
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()

2019-05-18 Thread Nguyễn Thái Ngọc Duy
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

2019-05-14 Thread Nguyễn Thái Ngọc Duy
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()

2019-05-14 Thread Nguyễn Thái Ngọc Duy
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

2019-05-14 Thread Nguyễn Thái Ngọc Duy
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

2019-05-13 Thread Nguyễn Thái Ngọc Duy
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

2019-05-10 Thread Nguyễn Thái Ngọc Duy
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

2019-05-09 Thread Nguyễn Thái Ngọc Duy
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

2019-05-09 Thread Nguyễn Thái Ngọc Duy
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()

2019-05-09 Thread Nguyễn Thái Ngọc Duy
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()

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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()

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-08 Thread Nguyễn Thái Ngọc Duy
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

2019-05-07 Thread Nguyễn Thái Ngọc Duy
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

2019-05-02 Thread Nguyễn Thái Ngọc Duy
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()

2019-05-01 Thread Nguyễn Thái Ngọc Duy
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

2019-05-01 Thread Nguyễn Thái Ngọc Duy
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"

2019-05-01 Thread Nguyễn Thái Ngọc Duy
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

2019-04-29 Thread Nguyễn Thái Ngọc Duy
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

2019-04-25 Thread Nguyễn Thái Ngọc Duy
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"

2019-04-25 Thread Nguyễn Thái Ngọc Duy
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

2019-04-25 Thread Nguyễn Thái Ngọc Duy
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

2019-04-25 Thread Nguyễn Thái Ngọc Duy
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

2019-04-25 Thread Nguyễn Thái Ngọc Duy
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->

  1   2   3   4   5   6   7   8   9   10   >