Skip to contentSkip to conversation

Clarify commit-graph and grafts/replace/shallow incompatibilities

PATCH In review Derrick Stolee via GitGitGadget 9 participants 47 messages Jul 18, 2018 – Aug 21, 2018 Documentation

lore
Share & tools
lore
#0 lore raw

cover Clarify commit-graph and grafts/replace/shallow incompatibilities

One unresolved issue with the commit-graph feature is that it can cause issues when combined with replace objects, commit grafts, or shallow clones. These are not 100% incompatible, as one could be reasonably successful writing a commit-graph after replacing some objects and not have issues. The problems happen when commits that are already in the commit-graph file are replaced, or when git is run with the --no-replace-objects option; this can cause incorrect parents or incorrect generation numbers. Similar things occur with commit grafts and shallow clones, especially when running git fetch --unshallow in a shallow repo.

Instead of trying (and probably failing) to make these features work together, default to making the commit-graph feature unavailable in these situations. Create a new method 'commit_graph_compatible(r)' that checks if the repository 'r' has any of these features enabled.

I will send a follow-up patch that shows how I tested these interactions by computing the commit-graph on every 'git commit'.

This approach works for most cases, but I found one nagging test case that was causing problems. This led to the commit "commit-graph: close_commit_graph before shallow walk" and is the patch I am least confident about. Please take a close look at that one and suggest alternatives.

This approach is very different from the previous RFC on the subject [1].

While building this series, I got some test failures in the non-the_repository tests. These issues are related to missing references to an arbitrary repository (instead of the_repository) and some uninitialized values in the tests. Stefan already sent a patch to address this [2], and I've included those commits (along with a small tweak [3]). These are only included for convenience.

Thanks,
-Stolee

[1] https://public-inbox.org/git/[email protected]/
[RFC PATCH 0/6] Fix commit-graph/graft/replace/shallow combo

[2] https://public-inbox.org/git/[email protected]/T/#t
[PATCH 0/2] RFC ref store to repository migration

[3] https://public-inbox.org/git/[email protected]/T/#m966eac85fd58c66523654ddaf0bec72877d3295a
[PATCH] TO-SQUASH: replace the_repository with arbitrary r

Based-On: jt/commit-graph-per-object-store
Cc: [email protected]
Cc: [email protected]

Derrick Stolee (6):
commit-graph: update design document
test-repository: properly init repo
commit-graph: not compatible with replace objects
commit-graph: not compatible with grafts
commit-graph: not compatible with uninitialized repo
commit-graph: close_commit_graph before shallow walk

Stefan Beller (2):
refs.c: migrate internal ref iteration to pass thru repository
argument
refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback

 Documentation/technical/commit-graph.txt | 18 ++++++--
 builtin/replace.c                        |  8 ++--
 commit-graph.c                           | 34 ++++++++++++--
 commit-graph.h                           |  1 +
 commit.c                                 |  2 +-
 commit.h                                 |  1 +
 refs.c                                   | 48 +++++++++++++++++---
 refs.h                                   | 12 ++++-
 refs/iterator.c                          |  6 +--
 refs/refs-internal.h                     |  5 +-
 replace-object.c                         |  7 +--
 replace-object.h                         |  2 +
 t/helper/test-repository.c               | 10 +++-
 t/t5318-commit-graph.sh                  | 58 ++++++++++++++++++++++++
 upload-pack.c                            |  2 +
 15 files changed, 184 insertions(+), 30 deletions(-)

base-commit: dade47c06cf849b0ca180a8e6383b55ea6f75812
Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-11%2Fderrickstolee%2Fshallow%2Fupstream-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-11/derrickstolee/shallow/upstream-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/11

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/8 refs.c: migrate internal ref iteration to pass thru repository argument

From: Stefan Beller <[email protected]>

In 60ce76d3581 (refs: add repository argument to for_each_replace_ref, 2018-04-11) and 0d296c57aec (refs: allow for_each_replace_ref to handle arbitrary repositories, 2018-04-11), for_each_replace_ref learned how to iterate over refs by a given arbitrary repository.
New attempts in the object store conversion have shown that it is useful to have the repository handle available that the refs iteration is currently iterating over.

To achieve this goal we will need to add a repository argument to each_ref_fn in refs.h. However as many callers rely on the signature such a patch would be too large.

So convert the internals of the ref subsystem first to pass through a repository argument without exposing the change to the user. Assume the_repository for the passed through repository, although it is not used anywhere yet.

---
 refs.c               | 39 +++++++++++++++++++++++++++++++++++++--
 refs.h               | 10 ++++++++++
 refs/iterator.c      |  6 +++---
 refs/refs-internal.h |  5 +++--
 4 files changed, 53 insertions(+), 7 deletions(-)
refs.c +37 -2
index fcfd3171e..2513f77ac 100644
@@ -1390,17 +1390,50 @@ struct ref_iterator *refs_ref_iterator_begin(
  * non-zero value, stop the iteration and return that value;
  * otherwise, return 0.
  */
+static int do_for_each_repo_ref(struct repository *r, const char *prefix,
+				each_repo_ref_fn fn, int trim, int flags,
+				void *cb_data)
+{
+	struct ref_iterator *iter;
+	struct ref_store *refs = get_main_ref_store(r);
+
+	if (!refs)
+		return 0;
+
+	iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
+
+	return do_for_each_repo_ref_iterator(r, iter, fn, cb_data);
+}
+
+struct do_for_each_ref_help {
+	each_ref_fn *fn;
+	void *cb_data;
+};
+
+static int do_for_each_ref_helper(struct repository *r,
+				  const char *refname,
+				  const struct object_id *oid,
+				  int flags,
+				  void *cb_data)
+{
+	struct do_for_each_ref_help *hp = cb_data;
+
+	return hp->fn(refname, oid, flags, hp->cb_data);
+}
+
 static int do_for_each_ref(struct ref_store *refs, const char *prefix,
 			   each_ref_fn fn, int trim, int flags, void *cb_data)
 {
 	struct ref_iterator *iter;
+	struct do_for_each_ref_help hp = { fn, cb_data };
 
 	if (!refs)
 		return 0;
 
 	iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
 
-	return do_for_each_ref_iterator(iter, fn, cb_data);
+	return do_for_each_repo_ref_iterator(the_repository, iter,
+					do_for_each_ref_helper, &hp);
 }
 
 int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
@@ -2029,10 +2062,12 @@ cleanup:
 int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
 	struct ref_iterator *iter;
+	struct do_for_each_ref_help hp = { fn, cb_data };
 
 	iter = refs->be->reflog_iterator_begin(refs);
 
-	return do_for_each_ref_iterator(iter, fn, cb_data);
+	return do_for_each_repo_ref_iterator(the_repository, iter,
+					     do_for_each_ref_helper, &hp);
 }
 
 int for_each_reflog(each_ref_fn fn, void *cb_data)
refs.h +10 -0
index cc2fb4c68..80eec8bbc 100644
@@ -274,6 +274,16 @@ struct ref_transaction;
 typedef int each_ref_fn(const char *refname,
 			const struct object_id *oid, int flags, void *cb_data);
 
+/*
+ * The same as each_ref_fn, but also with a repository argument that
+ * contains the repository associated with the callback.
+ */
+typedef int each_repo_ref_fn(struct repository *r,
+			     const char *refname,
+			     const struct object_id *oid,
+			     int flags,
+			     void *cb_data);
+
 /*
  * The following functions invoke the specified callback function for
  * each reference indicated.  If the function ever returns a nonzero
refs/iterator.c +3 -3
index 2ac91ac34..629e00a12 100644
@@ -407,15 +407,15 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
 
 struct ref_iterator *current_ref_iter = NULL;
 
-int do_for_each_ref_iterator(struct ref_iterator *iter,
-			     each_ref_fn fn, void *cb_data)
+int do_for_each_repo_ref_iterator(struct repository *r, struct ref_iterator *iter,
+				  each_repo_ref_fn fn, void *cb_data)
 {
 	int retval = 0, ok;
 	struct ref_iterator *old_ref_iter = current_ref_iter;
 
 	current_ref_iter = iter;
 	while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
-		retval = fn(iter->refname, iter->oid, iter->flags, cb_data);
+		retval = fn(r, iter->refname, iter->oid, iter->flags, cb_data);
 		if (retval) {
 			/*
 			 * If ref_iterator_abort() returns ITER_ERROR,
refs/refs-internal.h +3 -2
index dd834314b..5c7414bf0 100644
@@ -473,8 +473,9 @@ extern struct ref_iterator *current_ref_iter;
  * adapter between the callback style of reference iteration and the
  * iterator style.
  */
-int do_for_each_ref_iterator(struct ref_iterator *iter,
-			     each_ref_fn fn, void *cb_data);
+int do_for_each_repo_ref_iterator(struct repository *r,
+				  struct ref_iterator *iter,
+				  each_repo_ref_fn fn, void *cb_data);
 
 /*
  * Only include per-worktree refs in a do_for_each_ref*() iteration.
Signature · 1 line
gitgitgadget
#2 lore raw

PATCH 2/8 refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback

In reply to Derrick Stolee via GitGitGadget

From: Stefan Beller <[email protected]>

---
 builtin/replace.c | 8 ++++----
 refs.c            | 9 ++++-----
 refs.h            | 2 +-
 replace-object.c  | 5 +++--
 4 files changed, 12 insertions(+), 12 deletions(-)
builtin/replace.c +4 -4
index ef22d724b..d0b1cdb06 100644
@@ -39,7 +39,8 @@ struct show_data {
 	enum replace_format format;
 };
 
-static int show_reference(const char *refname, const struct object_id *oid,
+static int show_reference(struct repository *r, const char *refname,
+			  const struct object_id *oid,
 			  int flag, void *cb_data)
 {
 	struct show_data *data = cb_data;
@@ -56,9 +57,8 @@ static int show_reference(const char *refname, const struct object_id *oid,
 			if (get_oid(refname, &object))
 				return error("Failed to resolve '%s' as a valid ref.", refname);
 
-			obj_type = oid_object_info(the_repository, &object,
-						   NULL);
-			repl_type = oid_object_info(the_repository, oid, NULL);
+			obj_type = oid_object_info(r, &object, NULL);
+			repl_type = oid_object_info(r, oid, NULL);
 
 			printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
 			       oid_to_hex(oid), type_name(repl_type));
refs.c +4 -5
index 2513f77ac..5700cd468 100644
@@ -1478,12 +1478,11 @@ int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
 	return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
 }
 
-int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data)
+int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
 {
-	return do_for_each_ref(get_main_ref_store(r),
-			       git_replace_ref_base, fn,
-			       strlen(git_replace_ref_base),
-			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+	return do_for_each_repo_ref(r, git_replace_ref_base, fn,
+				    strlen(git_replace_ref_base),
+				    DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
 int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
refs.h +1 -1
index 80eec8bbc..a0a18223a 100644
@@ -317,7 +317,7 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
 int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 int for_each_remote_ref(each_ref_fn fn, void *cb_data);
-int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data);
+int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);
 int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
 int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 			 const char *prefix, void *cb_data);
replace-object.c +3 -2
index 801b5c167..017f02f8e 100644
@@ -6,7 +6,8 @@
 #include "repository.h"
 #include "commit.h"
 
-static int register_replace_ref(const char *refname,
+static int register_replace_ref(struct repository *r,
+				const char *refname,
 				const struct object_id *oid,
 				int flag, void *cb_data)
 {
@@ -25,7 +26,7 @@ static int register_replace_ref(const char *refname,
 	oidcpy(&repl_obj->replacement, oid);
 
 	/* Register new object */
-	if (oidmap_put(the_repository->objects->replace_map, repl_obj))
+	if (oidmap_put(r->objects->replace_map, repl_obj))
 		die("duplicate replace ref: %s", refname);
 
 	return 0;
Signature · 1 line
gitgitgadget
#3 lore raw

Re: [PATCH 2/8] refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback

Quoted 63 lines · Stefan Beller via GitGitGadget

From: Stefan Beller <[email protected]>

---
 builtin/replace.c | 8 ++++----
 refs.c            | 9 ++++-----
 refs.h            | 2 +-
 replace-object.c  | 5 +++--
 4 files changed, 12 insertions(+), 12 deletions(-)
builtin/replace.c +4 -4
index ef22d724b..d0b1cdb06 100644
@@ -39,7 +39,8 @@ struct show_data {
 	enum replace_format format;
 };
 
-static int show_reference(const char *refname, const struct object_id *oid,
+static int show_reference(struct repository *r, const char *refname,
+			  const struct object_id *oid,
 			  int flag, void *cb_data)
 {
 	struct show_data *data = cb_data;
@@ -56,9 +57,8 @@ static int show_reference(const char *refname, const struct object_id *oid,
 			if (get_oid(refname, &object))
 				return error("Failed to resolve '%s' as a valid ref.", refname);
 
-			obj_type = oid_object_info(the_repository, &object,
-						   NULL);
-			repl_type = oid_object_info(the_repository, oid, NULL);
+			obj_type = oid_object_info(r, &object, NULL);
+			repl_type = oid_object_info(r, oid, NULL);
 
 			printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
 			       oid_to_hex(oid), type_name(repl_type));
refs.c +4 -5
index 2513f77ac..5700cd468 100644
@@ -1478,12 +1478,11 @@ int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
 	return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
 }
 
-int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data)
+int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
 {
-	return do_for_each_ref(get_main_ref_store(r),
-			       git_replace_ref_base, fn,
-			       strlen(git_replace_ref_base),
-			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+	return do_for_each_repo_ref(r, git_replace_ref_base, fn,
+				    strlen(git_replace_ref_base),
+				    DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
 int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
refs.h +0 -0
index 80eec8bbc..a0a18223a 100644

refs.h int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, · line 320

Quoted 5 lines
refs.h +1 -1
@@ -317,7 +317,7 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
 int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 int for_each_remote_ref(each_ref_fn fn, void *cb_data);
-int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data);
+int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);

With a signature change like this, any change that introduces new call to for_each_replace_ref() using eac_ref_fn() would get compilation error, so this is minimally correct.

Two things that bothersome are that

- for_each_tag/branch/remote_ref() and for_each_replace_ref() now work and look quite differently.

- existing users of for_each_replace_ref() who were all happy working in the_repository have to pass it explicitly, even thought they do not have any need to.

In this case, even if you introduced for_each_replace_ref_in_repo(), making for_each_replace_ref() a thin wrapper that always uses the_repository is a bit more cumbersome than just a simple macro.

But it *is* doable (you'd need to use a wrapping structure around cb_data), and a developer who case about maintainability during API transition would have taken pains to do so. A bit dissapointing.

Quoted 26 lines
refs.h +0 -0
 int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
 int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 			 const char *prefix, void *cb_data);
replace-object.c +3 -2
index 801b5c167..017f02f8e 100644
@@ -6,7 +6,8 @@
 #include "repository.h"
 #include "commit.h"
 
-static int register_replace_ref(const char *refname,
+static int register_replace_ref(struct repository *r,
+				const char *refname,
 				const struct object_id *oid,
 				int flag, void *cb_data)
 {
@@ -25,7 +26,7 @@ static int register_replace_ref(const char *refname,
 	oidcpy(&repl_obj->replacement, oid);
 
 	/* Register new object */
-	if (oidmap_put(the_repository->objects->replace_map, repl_obj))
+	if (oidmap_put(r->objects->replace_map, repl_obj))
 		die("duplicate replace ref: %s", refname);
 
 	return 0;
#4 lore raw

Re: [PATCH 2/8] refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback

Quoted 11 lines

refs.h int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, · line 320

Quoted 6 lines
refs.h +1 -1
@@ -317,7 +317,7 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
 int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 int for_each_remote_ref(each_ref_fn fn, void *cb_data);
-int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data);
+int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);

With a signature change like this, any change that introduces new call to for_each_replace_ref() using eac_ref_fn() would get compilation error, so this is minimally correct.

Quoted 4 lines

Two things that bothersome are that

- for_each_tag/branch/remote_ref() and for_each_replace_ref() now work and look quite differently.

Yes, this series is not finished; we need to convert/upgrade for_each_tag et al, too.

Quoted 3 lines

- existing users of for_each_replace_ref() who were all happy working in the_repository have to pass it explicitly, even thought they do not have any need to.

All callbacks that are passed to for_each_replace_ref now operate on 'r' instead of the_repository, and that actually fixes a bug (commit message is lacking), but the cover letter hints at:

While building this series, I got some test failures in the non-the_repository tests. These issues are related to missing references to an arbitrary repository (instead of the_repository) and some uninitialized values in the tests. Stefan already sent a patch to address this [2], and I've included those commits (along with a small tweak [3]). These are only included for convenience.

Quoted 3 lines

In this case, even if you introduced for_each_replace_ref_in_repo(), making for_each_replace_ref() a thin wrapper that always uses the_repository is a bit more cumbersome than just a simple macro.

Yes, but such a callback would do the *wrong* subtle thing in some cases as you really want to work in the correct repository for e.g. looking up commits.

Quoted 3 lines

But it *is* doable (you'd need to use a wrapping structure around cb_data), and a developer who case about maintainability during API transition would have taken pains to do so. A bit dissapointing.

My original patches were RFC-ish and a trade off as for the reflog only there is nothing in flight to care about.

Given that we would want to upgrade all the ref callbacks, we have to take this route, I think.

Thanks,
Stefan

#5 lore raw

Re: [PATCH 2/8] refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback

Quoted 20 lines · Stefan Beller
Quoted 3 lines

- existing users of for_each_replace_ref() who were all happy working in the_repository have to pass it explicitly, even thought they do not have any need to.

All callbacks that are passed to for_each_replace_ref now operate on 'r' instead of the_repository, and that actually fixes a bug (commit message is lacking), but the cover letter hints at:
...

Quoted 3 lines

In this case, even if you introduced for_each_replace_ref_in_repo(), making for_each_replace_ref() a thin wrapper that always uses the_repository is a bit more cumbersome than just a simple macro.

Yes, but such a callback would do the *wrong* subtle thing in some cases as you really want to work in the correct repository for e.g. looking up commits.

Quoted 3 lines

But it *is* doable (you'd need to use a wrapping structure around cb_data), and a developer who case about maintainability during API transition would have taken pains to do so. A bit dissapointing.

Quoted 5 lines

My original patches were RFC-ish and a trade off as for the reflog only there is nothing in flight to care about.

Given that we would want to upgrade all the ref callbacks, we have to take this route, I think.

Try to rebuild 'pu' on top of 'master' yoruself to realize how many in-flight topics you are hurting and causing unnecessary load on the maintainer with the "let's add the 'r' parameter without changing the function names; compiler would catch and cause breakages" approach.

Until that happens, I won't waste any more time trying to educate you on this further.

#6 lore raw

PATCH 3/8 commit-graph: update design document

In reply to Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

As it exists right now, the commit-graph feature may provide inconsistent results when combined with commit grafts, replace objects, and shallow clones. Update the design document to discuss why these interactions are difficult to reconcile and how we will avoid errors by preventing updates to and reads from the commit-graph file when these other features exist.

---
 Documentation/technical/commit-graph.txt | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)
Documentation/technical/commit-graph.txt +15 -3
index c664acbd7..18948f11a 100644
@@ -112,12 +112,24 @@ Design Details
 - The file format includes parameters for the object ID hash function,
   so a future change of hash algorithm does not require a change in format.
 
+- Commit grafts and replace objects can change the shape of the commit
+  history. These can also be enabled/disabled on the fly using
+  `--no-replace-objects`. This leads to difficultly storing both possible
+  interpretations of a commit id, especially when computing generation
+  numbers. The commit-graph will not be read or written when
+  replace-objects or grafts are present.
+
+- Shallow clones create grafts of commits by dropping their parents. This
+  leads the commit-graph to think those commits have generation number 1.
+  If and when those commits are made unshallow, those generation numbers
+  become invalid. Since shallow clones are intended to restrict the commit
+  history to a very small set of commits, the commit-graph feature is less
+  helpful for these clones, anyway. The commit-graph will not be read or
+  written when shallow commits are present.
+
 Future Work
 -----------
 
-- The commit graph feature currently does not honor commit grafts. This can
-  be remedied by duplicating or refactoring the current graft logic.
-
 - After computing and storing generation numbers, we must make graph
   walks aware of generation numbers to gain the performance benefits they
   enable. This will mostly be accomplished by swapping a commit-date-ordered
Signature · 1 line
gitgitgadget
#7 lore raw

Re: [PATCH 3/8] commit-graph: update design document

Quoted 9 lines · Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

As it exists right now, the commit-graph feature may provide inconsistent results when combined with commit grafts, replace objects, and shallow clones. Update the design document to discuss why these interactions are difficult to reconcile and how we will avoid errors by preventing updates to and reads from the commit-graph file when these other features exist.

Quoted 4 lines
---
 Documentation/technical/commit-graph.txt | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

It would be nice to have this information not only in the technical documentation, but also in user-facing documentation, for example git-commit-graph manpage.

Documentation/technical/commit-graph.txt Design Details · line 116

Quoted 11 lines
Documentation/technical/commit-graph.txt +2 -0
index c664acbd7..18948f11a 100644
@@ -112,12 +112,24 @@ Design Details
 - The file format includes parameters for the object ID hash function,
   so a future change of hash algorithm does not require a change in format.
 
+- Commit grafts and replace objects can change the shape of the commit
+  history. These can also be enabled/disabled on the fly using

I think you meant "The latter can..." instead of "These can...", as grafts cannot be disabled with --no-replace-objects option.

Quoted 22 lines
Documentation/technical/commit-graph.txt +13 -3
+  `--no-replace-objects`. This leads to difficultly storing both possible
+  interpretations of a commit id, especially when computing generation
+  numbers. The commit-graph will not be read or written when
+  replace-objects or grafts are present.
+
+- Shallow clones create grafts of commits by dropping their parents. This
+  leads the commit-graph to think those commits have generation number 1.
+  If and when those commits are made unshallow, those generation numbers
+  become invalid. Since shallow clones are intended to restrict the commit
+  history to a very small set of commits, the commit-graph feature is less
+  helpful for these clones, anyway. The commit-graph will not be read or
+  written when shallow commits are present.
+
 Future Work
 -----------
 
-- The commit graph feature currently does not honor commit grafts. This can
-  be remedied by duplicating or refactoring the current graft logic.
-
 - After computing and storing generation numbers, we must make graph
   walks aware of generation numbers to gain the performance benefits they
   enable. This will mostly be accomplished by swapping a commit-date-ordered
#8 lore raw

PATCH 4/8 test-repository: properly init repo

In reply to Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

---
 t/helper/test-repository.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
t/helper/test-repository.c +8 -2
index 2762ca656..6a84a53ef 100644
@@ -15,7 +15,10 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree,
 	struct commit *c;
 	struct commit_list *parent;
 
-	repo_init(&r, gitdir, worktree);
+	setup_git_env(gitdir);
+
+	if (repo_init(&r, gitdir, worktree))
+		die("Couldn't init repo");
 
 	c = lookup_commit(&r, commit_oid);
 
@@ -38,7 +41,10 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
 	struct commit *c;
 	struct tree *tree;
 
-	repo_init(&r, gitdir, worktree);
+	setup_git_env(gitdir);
+
+	if (repo_init(&r, gitdir, worktree))
+		die("Couldn't init repo");
 
 	c = lookup_commit(&r, commit_oid);
 
Signature · 1 line
gitgitgadget
#9 lore raw

Re: [PATCH 4/8] test-repository: properly init repo

Quoted 12 lines · Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

---
 t/helper/test-repository.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
t/helper/test-repository.c +0 -0
index 2762ca656..6a84a53ef 100644

t/helper/test-repository.c static void test_parse_commit_in_graph(const char *gitdir, const char *worktree, · line 21

Quoted 8 lines
t/helper/test-repository.c +4 -1
@@ -15,7 +15,10 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree,
 	struct commit *c;
 	struct commit_list *parent;
 
-	repo_init(&r, gitdir, worktree);
+	setup_git_env(gitdir);
+
+	if (repo_init(&r, gitdir, worktree))
+		die("Couldn't init repo");

Shouldn't this message be marked for translation?

  +		die(_("Couldn't init repo"));

Perhaps we could provide also more detail about error, if possible.

t/helper/test-repository.c static void test_get_commit_tree_in_graph(const char *gitdir, · line 47

Quoted 12 lines
t/helper/test-repository.c +4 -1
 	c = lookup_commit(&r, commit_oid);
 
@@ -38,7 +41,10 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
 	struct commit *c;
 	struct tree *tree;
 
-	repo_init(&r, gitdir, worktree);
+	setup_git_env(gitdir);
+
+	if (repo_init(&r, gitdir, worktree))
+		die("Couldn't init repo");

I wonder if it would be worth it to extract this idiom into separate function, called for exaple repo_init_or_die(), to avoid code duplication (and maybe use it in other places?).

Quoted 2 lines
t/helper/test-repository.c +0 -0
 	c = lookup_commit(&r, commit_oid);
#10 lore raw

PATCH 5/8 commit-graph: not compatible with replace objects

In reply to Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Create new method commit_graph_compatible(r) to check if a given repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist. Test this interaction succeeds, including ignoring an existing commit-graph and failing to write a new commit-graph.

---
 commit-graph.c          | 17 +++++++++++++++++
 replace-object.c        |  2 +-
 replace-object.h        |  2 ++
 t/t5318-commit-graph.sh | 22 ++++++++++++++++++++++
 4 files changed, 42 insertions(+), 1 deletion(-)
commit-graph.c +17 -0
index b0a55ad12..711099858 100644
@@ -13,6 +13,8 @@
 #include "commit-graph.h"
 #include "object-store.h"
 #include "alloc.h"
+#include "hashmap.h"
+#include "replace-object.h"
 
 #define GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
 #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
@@ -56,6 +58,15 @@ static struct commit_graph *alloc_commit_graph(void)
 	return g;
 }
 
+static int commit_graph_compatible(struct repository *r)
+{
+	prepare_replace_object(r);
+	if (hashmap_get_size(&r->objects->replace_map->map))
+		return 0;
+
+	return 1;
+}
+
 struct commit_graph *load_commit_graph_one(const char *graph_file)
 {
 	void *graph_map;
@@ -223,6 +234,9 @@ static int prepare_commit_graph(struct repository *r)
 		 */
 		return 0;
 
+	if (!commit_graph_compatible(r))
+		return 0;
+
 	obj_dir = r->objects->objectdir;
 	prepare_commit_graph_one(r, obj_dir);
 	prepare_alt_odb(r);
@@ -693,6 +707,9 @@ void write_commit_graph(const char *obj_dir,
 	int num_extra_edges;
 	struct commit_list *parent;
 
+	if (!commit_graph_compatible(the_repository))
+		return;
+
 	oids.nr = 0;
 	oids.alloc = approximate_object_count() / 4;
 
replace-object.c +1 -1
index 017f02f8e..4d2d84cf4 100644
@@ -32,7 +32,7 @@ static int register_replace_ref(struct repository *r,
 	return 0;
 }
 
-static void prepare_replace_object(struct repository *r)
+void prepare_replace_object(struct repository *r)
 {
 	if (r->objects->replace_map)
 		return;
replace-object.h +2 -0
index f996de3d6..c7a99fc35 100644
@@ -10,6 +10,8 @@ struct replace_object {
 	struct object_id replacement;
 };
 
+void prepare_replace_object(struct repository *r);
+
 /*
  * This internal function is only declared here for the benefit of
  * lookup_replace_object().  Please do not call it directly.
t/t5318-commit-graph.sh +22 -0
index 4f17d7701..c90626f5d 100755
@@ -259,6 +259,28 @@ test_expect_success 'check that gc computes commit-graph' '
 	test_cmp commit-graph-after-gc $objdir/info/commit-graph
 '
 
+test_expect_success 'replace-objects invalidates commit-graph' '
+	cd "$TRASH_DIRECTORY" &&
+	test_when_finished rm -rf replace &&
+	git clone full replace &&
+	(
+		cd replace &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph &&
+		git replace HEAD~1 HEAD~2 &&
+		git -c core.commitGraph=false log >expect &&
+		git -c core.commitGraph=true log >actual &&
+		test_cmp expect actual &&
+		git commit-graph write --reachable &&
+		git -c core.commitGraph=false --no-replace-objects log >expect &&
+		git -c core.commitGraph=true --no-replace-objects log >actual &&
+		test_cmp expect actual &&
+		rm -rf .git/objects/info/commit-graph &&
+		git commit-graph write --reachable &&
+		test_path_is_missing .git/objects/info/commit-graph
+	)
+'
+
 # the verify tests below expect the commit-graph to contain
 # exactly the commits reachable from the commits/8 branch.
 # If the file changes the set of commits in the list, then the
Signature · 1 line
gitgitgadget
#11 lore raw

Re: [PATCH 5/8] commit-graph: not compatible with replace objects

Quoted 7 lines · Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Create new method commit_graph_compatible(r) to check if a given repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist. Test this interaction succeeds, including ignoring an existing commit-graph and failing to write a new commit-graph.

I think this approach is sensible. These are optimizations, and it's not a big deal to just punt no cases we can't handle.

I do have one complaint about the implementation, though:

commit-graph.c static struct commit_graph *alloc_commit_graph(void) · line 68

Quoted 8 lines
commit-graph.c +8 -0
+static int commit_graph_compatible(struct repository *r)
+{
+	prepare_replace_object(r);
+	if (hashmap_get_size(&r->objects->replace_map->map))
+		return 0;
+
+	return 1;
+}

If I'm reading the code correctly, this will predicate the decision entirely on the presence of refs in refs/replace/. But you may have a situation where those refs exist, but you are not respecting them in this run.

For instance, imagine[1] a server that hosts arbitrary repositories, but wants to use commit graphs to speed up server-side operations (e.g., serving fetches, but also perhaps a web interface doing --contains, etc). If it runs all of the server-side commands with GIT_NO_REPLACE_OBJECTS=1, then there should be no problem. But if a user pushes anything to refs/replace (because they _do_ use replace refs locally, and want to share them with other clients), that would disable graphs on the server.

So I think this should at least be loosened to:

  if (check_replace_refs) {
	prepare_replace_object(r);
	if (...)

}

which would make this case work. I'd even go so far as to say that for writing, we should just always ignore replace refs and generate the full graph (just like pack-objects does so when writing a pack). Then the resulting graph can be used selectively by disabling replace refs for particular commands. But for the scenario I described above, the distinction is mostly academic, as replacements would be disabled anyway during the write command anyway.

-Peff

[1] You can probably guess that this is how GitHub handles replace refs. We ran into this long ago because replacements and grafts mess up any other caches external to Git that rely on the immutability of the hash.

We do it with a config option, though, which requires a trivial patch. I'll post that momentarily.

#12 lore raw

Re: [PATCH 5/8] commit-graph: not compatible with replace objects

Quoted 4 lines · Jeff King
Quoted 2 lines · Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Quoted 8 lines
Quoted 5 lines

Create new method commit_graph_compatible(r) to check if a given repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist. Test this interaction succeeds, including ignoring an existing commit-graph and failing to write a new commit-graph.

I think this approach is sensible. These are optimizations, and it's not a big deal to just punt no cases we can't handle.

Er, this should be "punt _on_ cases we can't handle". Sorry for my belated proof-reading.

-Peff

#13 lore raw

Re: [PATCH 5/8] commit-graph: not compatible with replace objects

In reply to Jeff King
Quoted 53 lines · Jeff King
Quoted 7 lines · Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Create new method commit_graph_compatible(r) to check if a given repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist. Test this interaction succeeds, including ignoring an existing commit-graph and failing to write a new commit-graph.

I think this approach is sensible. These are optimizations, and it's not a big deal to just punt no cases we can't handle.

I do have one complaint about the implementation, though:

commit-graph.c static struct commit_graph *alloc_commit_graph(void) · line 68

Quoted 8 lines
commit-graph.c +8 -0
+static int commit_graph_compatible(struct repository *r)
+{
+	prepare_replace_object(r);
+	if (hashmap_get_size(&r->objects->replace_map->map))
+		return 0;
+
+	return 1;
+}

If I'm reading the code correctly, this will predicate the decision entirely on the presence of refs in refs/replace/. But you may have a situation where those refs exist, but you are not respecting them in this run.

For instance, imagine[1] a server that hosts arbitrary repositories, but wants to use commit graphs to speed up server-side operations (e.g., serving fetches, but also perhaps a web interface doing --contains, etc). If it runs all of the server-side commands with GIT_NO_REPLACE_OBJECTS=1, then there should be no problem. But if a user pushes anything to refs/replace (because they _do_ use replace refs locally, and want to share them with other clients), that would disable graphs on the server.

So I think this should at least be loosened to:

   if (check_replace_refs) {
	prepare_replace_object(r);
	if (...)
   }

which would make this case work. I'd even go so far as to say that for writing, we should just always ignore replace refs and generate the full graph (just like pack-objects does so when writing a pack). Then the resulting graph can be used selectively by disabling replace refs for particular commands. But for the scenario I described above, the distinction is mostly academic, as replacements would be disabled anyway during the write command anyway.

-Peff

Quoted 7 lines

[1] You can probably guess that this is how GitHub handles replace refs. We ran into this long ago because replacements and grafts mess up any other caches external to Git that rely on the immutability of the hash.

We do it with a config option, though, which requires a trivial patch. I'll post that momentarily.

Thanks for the details! I never considered that someone would have these refs around, but would ignore them most of the time.

The biggest reason I wanted to punt here was that it is easy to toggle between using replace refs and not using them. Writing and reading as long as we are ignoring those refs is a good idea, and I'll use that approach in v2.

Thanks,

-Stolee

#14 lore raw

Re: [PATCH 5/8] commit-graph: not compatible with replace objects

Quoted 59 lines · Derrick Stolee
Quoted 58 lines · Jeff King
Quoted 7 lines · Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Create new method commit_graph_compatible(r) to check if a given repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist. Test this interaction succeeds, including ignoring an existing commit-graph and failing to write a new commit-graph.

I think this approach is sensible. These are optimizations, and it's not a big deal to just punt no cases we can't handle.

I do have one complaint about the implementation, though:

commit-graph.c static struct commit_graph *alloc_commit_graph(void) · line 68

Quoted 8 lines
commit-graph.c +8 -0
+static int commit_graph_compatible(struct repository *r)
+{
+    prepare_replace_object(r);
+    if (hashmap_get_size(&r->objects->replace_map->map))
+        return 0;
+
+    return 1;
+}

If I'm reading the code correctly, this will predicate the decision entirely on the presence of refs in refs/replace/. But you may have a situation where those refs exist, but you are not respecting them in this run.

For instance, imagine[1] a server that hosts arbitrary repositories, but wants to use commit graphs to speed up server-side operations (e.g., serving fetches, but also perhaps a web interface doing --contains, etc). If it runs all of the server-side commands with GIT_NO_REPLACE_OBJECTS=1, then there should be no problem. But if a user pushes anything to refs/replace (because they _do_ use replace refs locally, and want to share them with other clients), that would disable graphs on the server.

So I think this should at least be loosened to:

   if (check_replace_refs) {
    prepare_replace_object(r);
    if (...)
   }

which would make this case work. I'd even go so far as to say that for writing, we should just always ignore replace refs and generate the full graph (just like pack-objects does so when writing a pack). Then the resulting graph can be used selectively by disabling replace refs for particular commands. But for the scenario I described above, the distinction is mostly academic, as replacements would be disabled anyway during the write command anyway.

-Peff

[1] You can probably guess that this is how GitHub handles replace refs. We ran into this long ago because replacements and grafts mess up any other caches external to Git that rely on the immutability of the hash.

Quoted 6 lines
Quoted 2 lines

     We do it with a config option, though, which requires a trivial patch. I'll post that momentarily.

Thanks for the details! I never considered that someone would have these refs around, but would ignore them most of the time.

The biggest reason I wanted to punt here was that it is easy to toggle between using replace refs and not using them. Writing and reading as long as we are ignoring those refs is a good idea, and I'll use that approach in v2.

Thanks, Peff, for improving the check_replace_refs interaction [1].

Since this series now has two dependencies (including Stefan's ref-store fix [2] that I had included in my v1), I'll let those topics settle before I send a new v2.

If there are more comments about how I'm handling these cases, then please jump in and tell me. I'll revisit this topic in a few weeks.

Thanks,

-Stolee

[1] https://public-inbox.org/git/[email protected]/T/#u

     [PATCH 1/3] check_replace_refs: fix outdated comment

[2] https://public-inbox.org/git/[email protected]/T/#u

    [PATCH 0/2] RFC ref store to repository migration

#15 lore raw

Re: [PATCH 5/8] commit-graph: not compatible with replace objects

Quoted 5 lines

Thanks, Peff, for improving the check_replace_refs interaction [1].

Since this series now has two dependencies (including Stefan's ref-store fix [2] that I had included in my v1), I'll let those topics settle before I send a new v2.

FYI: I have not been working on, nor plan to work on (in the short term) on the ref store fix series, which needs another abstraction layer IIUC to make it easier to integrate such a thing as well as extend the series to all functions in the refstore.

Feel free to take over that series partially (and defer the extension for all functions in the ref store until later) ?

Stefan

#16 lore raw

Re: [PATCH 5/8] commit-graph: not compatible with replace objects

Quoted 5 lines · Stefan Beller
Quoted 5 lines

Thanks, Peff, for improving the check_replace_refs interaction [1].

Since this series now has two dependencies (including Stefan's ref-store fix [2] that I had included in my v1), I'll let those topics settle before I send a new v2.

Quoted 7 lines

FYI: I have not been working on, nor plan to work on (in the short term) on the ref store fix series, which needs another abstraction layer IIUC to make it easier to integrate such a thing as well as extend the series to all functions in the refstore.

Feel free to take over that series partially (and defer the extension for all functions in the ref store until later) ?

OK. Thanks for letting me know.

Perhaps I'll give it a try to pass the repository struct in the cbdata for the function pointers, as a minimum amount of change option.

Thanks,

-Stolee

#17 lore raw

Re: [PATCH 5/8] commit-graph: not compatible with replace objects

In reply to Derrick Stolee via GitGitGadget
Quoted 5 lines · Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Create new method commit_graph_compatible(r) to check if a given repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist.

All right, looks sensible. Did you start with the history-"altering" feature with simplest detection mechanism?

Quoted 3 lines

Test this
interaction succeeds, including ignoring an existing commit-graph and failing to write a new commit-graph.

I wonder if we should test the implementation, or just that you get correct answer when history view is altered after writing commit-graph file... oh, wait, you do that (well, except for testing that git commit-graph write does not create commit-graph file instead of testing that the commit-graph file stores correct information even after the feature is removed (with git replace --delete) or turned off (with --no-replace-objects or equivalent). Good.

Sidenote: the inconsistency between command options and subcommands... ehh... compare e.g. git-replace and git-remote.

Quoted 23 lines
---
 commit-graph.c          | 17 +++++++++++++++++
 replace-object.c        |  2 +-
 replace-object.h        |  2 ++
 t/t5318-commit-graph.sh | 22 ++++++++++++++++++++++
 4 files changed, 42 insertions(+), 1 deletion(-)
commit-graph.c +2 -0
index b0a55ad12..711099858 100644
@@ -13,6 +13,8 @@
 #include "commit-graph.h"
 #include "object-store.h"
 #include "alloc.h"
+#include "hashmap.h"
+#include "replace-object.h"
 
 #define GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
 #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */

commit-graph.c static struct commit_graph *alloc_commit_graph(void) · line 64

Quoted 7 lines
commit-graph.c +4 -0
@@ -56,6 +58,15 @@ static struct commit_graph *alloc_commit_graph(void)
 	return g;
 }
 
+static int commit_graph_compatible(struct repository *r)
+{
+	prepare_replace_object(r);
+	if (hashmap_get_size(&r->objects->replace_map->map))

A very minor nitpick, feel free to ignore. I think that the condition would be more readable written as

+ if (hashmap_get_size(&r->objects->replace_map->map) > 0)

Another issue is that the condition may be too strict (but it is also the easiest to test for). You might have replacement objects for non-commit objects (e.g. replacing contents of configuration files with one with API keys and/or other secrets - not that I know any tool that does that), or replacing commit-objects without changing their history (e.g. to fix some annoying error in demo repository, where you want to show reflog). But I think that would be rare, so easier if maybe over-eager test is fine for me.

Quoted 19 lines
commit-graph.c +8 -0
+		return 0;
+
+	return 1;
+}
+
 struct commit_graph *load_commit_graph_one(const char *graph_file)
 {
 	void *graph_map;
@@ -223,6 +234,9 @@ static int prepare_commit_graph(struct repository *r)
 		 */
 		return 0;
 
+	if (!commit_graph_compatible(r))
+		return 0;
+
 	obj_dir = r->objects->objectdir;
 	prepare_commit_graph_one(r, obj_dir);
 	prepare_alt_odb(r);

commit-graph.c void write_commit_graph(const char *obj_dir, · line 712

Quoted 6 lines
commit-graph.c +3 -0
@@ -693,6 +707,9 @@ void write_commit_graph(const char *obj_dir,
 	int num_extra_edges;
 	struct commit_list *parent;
 
+	if (!commit_graph_compatible(the_repository))
+		return;
+

Hmmm... we might want to write commit-graph for immutable history; but not all history-"altering" feature can be easily turned off. So if it is to stay universal, without aadditional complications, it is good enough.

Quoted 21 lines
commit-graph.c +0 -0
 	oids.nr = 0;
 	oids.alloc = approximate_object_count() / 4;
 
replace-object.c +1 -1
index 017f02f8e..4d2d84cf4 100644
@@ -32,7 +32,7 @@ static int register_replace_ref(struct repository *r,
 	return 0;
 }
 
-static void prepare_replace_object(struct repository *r)
+void prepare_replace_object(struct repository *r)
 {
 	if (r->objects->replace_map)
 		return;
replace-object.h +0 -0
index f996de3d6..c7a99fc35 100644

replace-object.h struct replace_object { · line 14

Quoted 5 lines
replace-object.h +2 -0
@@ -10,6 +10,8 @@ struct replace_object {
 	struct object_id replacement;
 };
 
+void prepare_replace_object(struct repository *r);
+

Hmmm... how it is that this function didn't need to be exported before, I do wonder?

Quoted 9 lines
replace-object.h +0 -0
 /*
  * This internal function is only declared here for the benefit of
  * lookup_replace_object().  Please do not call it directly.
t/t5318-commit-graph.sh +0 -0
index 4f17d7701..c90626f5d 100755
@@ -259,6 +259,28 @@ test_expect_success 'check that gc computes commit-graph' '
 	test_cmp commit-graph-after-gc $objdir/info/commit-graph

t/t5318-commit-graph.sh test_expect_success 'check that gc computes commit-graph' ' · line 269

Quoted 10 lines
t/t5318-commit-graph.sh +8 -0
 '
 
+test_expect_success 'replace-objects invalidates commit-graph' '
+	cd "$TRASH_DIRECTORY" &&
+	test_when_finished rm -rf replace &&
+	git clone full replace &&
+	(
+		cd replace &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph &&

All right, this is preparation / setup; create commit-graph file, and check that it was created.

t/t5318-commit-graph.sh test_expect_success 'check that gc computes commit-graph' ' · line 270

Quoted 1 line
t/t5318-commit-graph.sh +1 -0
+		git replace HEAD~1 HEAD~2 &&

All right, so before replacement the history looks like this:

... <--- A <--- B <--- C <--- D <--- master <=== HEAD

~3 ~2 ~1 HEAD

While after the replacement the history view looks like this, if I understand it correctly:

     ... <--- A <--- B
               \
                \---[B] <--- D    <--- master  <=== HEAD

Wouldn't it be better (and more similar to the grafts test) to use git replace --graft, e.g.:

  +		git replace --graft HEAD~1 HEAD~3 &&

But that might be just a matter of taste.

t/t5318-commit-graph.sh test_expect_success 'check that gc computes commit-graph' ' · line 273

Quoted 3 lines
t/t5318-commit-graph.sh +3 -0
+		git -c core.commitGraph=false log >expect &&
+		git -c core.commitGraph=true log >actual &&
+		test_cmp expect actual &&

All right, so instead of testing if the output is what is expected, we test that the commit-graph feature did not change it. Good.

t/t5318-commit-graph.sh test_expect_success 'check that gc computes commit-graph' ' · line 274

Quoted 1 line
t/t5318-commit-graph.sh +1 -0
+		git commit-graph write --reachable &&

Is this necessary [see below]?

t/t5318-commit-graph.sh test_expect_success 'check that gc computes commit-graph' ' · line 276

Quoted 2 lines
t/t5318-commit-graph.sh +2 -0
+		git -c core.commitGraph=false --no-replace-objects log >expect &&
+		git -c core.commitGraph=true --no-replace-objects log >actual &&

All right, and here we test the same for --no-replace-objects case. Good.

By the way, is this git commit-graph write --reachable preceding this part of test necessary?

t/t5318-commit-graph.sh test_expect_success 'check that gc computes commit-graph' ' · line 280

Quoted 4 lines
t/t5318-commit-graph.sh +4 -0
+		test_cmp expect actual &&
+		rm -rf .git/objects/info/commit-graph &&
+		git commit-graph write --reachable &&
+		test_path_is_missing .git/objects/info/commit-graph

All right, so here we test that we wont write commit-graph file with altered view of history; which would store wrong information if the replace feature is turned off or if replace object is deleted (or if commit graph gets transferred in the furure, but the replacement refs do not).

Checking that the commit-graph file ws not created might be seen as testing implementation details, but full test would require testing with --no-replace-objects and after git replace -d; the above is simpler, so good enough.

Quoted 6 lines
t/t5318-commit-graph.sh +3 -0
+	)
+'
+
 # the verify tests below expect the commit-graph to contain
 # exactly the commits reachable from the commits/8 branch.
 # If the file changes the set of commits in the list, then the
#18 lore raw

PATCH 6/8 commit-graph: not compatible with grafts

In reply to Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Augment commit_graph_compatible(r) to return false when the given repository r has commit grafts or is a shallow clone. Test that in these situations we ignore existing commit-graph files and we do not write new commit-graph files.

---
 commit-graph.c          |  6 ++++++
 commit.c                |  2 +-
 commit.h                |  1 +
 t/t5318-commit-graph.sh | 36 ++++++++++++++++++++++++++++++++++++
 4 files changed, 44 insertions(+), 1 deletion(-)
commit-graph.c +6 -0
index 711099858..5097c7c12 100644
@@ -60,6 +60,12 @@ static struct commit_graph *alloc_commit_graph(void)
 
 static int commit_graph_compatible(struct repository *r)
 {
+	prepare_commit_graft(r);
+	if (r->parsed_objects && r->parsed_objects->grafts_nr)
+		return 0;
+	if (is_repository_shallow(r))
+		return 0;
+
 	prepare_replace_object(r);
 	if (hashmap_get_size(&r->objects->replace_map->map))
 		return 0;
commit.c +1 -1
index 39b80bd21..609adaf8a 100644
@@ -209,7 +209,7 @@ static int read_graft_file(struct repository *r, const char *graft_file)
 	return 0;
 }
 
-static void prepare_commit_graft(struct repository *r)
+void prepare_commit_graft(struct repository *r)
 {
 	char *graft_file;
 
commit.h +1 -0
index da0db36eb..5459e279f 100644
@@ -202,6 +202,7 @@ typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
 
 struct commit_graft *read_graft_line(struct strbuf *line);
 int register_commit_graft(struct repository *r, struct commit_graft *, int);
+void prepare_commit_graft(struct repository *r);
 struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid);
 
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2);
t/t5318-commit-graph.sh +36 -0
index c90626f5d..1d9f49af5 100755
@@ -281,6 +281,42 @@ test_expect_success 'replace-objects invalidates commit-graph' '
 	)
 '
 
+test_expect_success 'commit grafts invalidate commit-graph' '
+	cd "$TRASH_DIRECTORY" &&
+	test_when_finished rm -rf graft &&
+	git clone full graft &&
+	(
+		cd graft &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph &&
+		git replace --graft HEAD~1 HEAD~3 &&
+		git -c core.commitGraph=false log >expect &&
+		git -c core.commitGraph=true log >actual &&
+		test_cmp expect actual &&
+		git commit-graph write --reachable &&
+		git -c core.commitGraph=false --no-replace-objects log >expect &&
+		git -c core.commitGraph=true --no-replace-objects log >actual &&
+		test_cmp expect actual &&
+		rm -rf .git/objects/info/commit-graph &&
+		git commit-graph write --reachable &&
+		test_path_is_missing .git/objects/info/commit-graph
+	)
+'
+
+test_expect_success 'replace-objects invalidates commit-graph' '
+	cd "$TRASH_DIRECTORY" &&
+	test_when_finished rm -rf shallow &&
+	git clone --depth 2 "file://$TRASH_DIRECTORY/full" shallow &&
+	(
+		cd shallow &&
+		git commit-graph write --reachable &&
+		test_path_is_missing .git/objects/info/commit-graph &&
+		git fetch origin --unshallow &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph
+	)
+'
+
 # the verify tests below expect the commit-graph to contain
 # exactly the commits reachable from the commits/8 branch.
 # If the file changes the set of commits in the list, then the
Signature · 1 line
gitgitgadget
#19 lore raw

Re: [PATCH 6/8] commit-graph: not compatible with grafts

Quoted 6 lines · Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Augment commit_graph_compatible(r) to return false when the given repository r has commit grafts or is a shallow clone. Test that in these situations we ignore existing commit-graph files and we do not write new commit-graph files.

Tests for grafts are wrong, as they test replace objects.

Quoted 14 lines
---
 commit-graph.c          |  6 ++++++
 commit.c                |  2 +-
 commit.h                |  1 +
 t/t5318-commit-graph.sh | 36 ++++++++++++++++++++++++++++++++++++
 4 files changed, 44 insertions(+), 1 deletion(-)
commit-graph.c +0 -0
index 711099858..5097c7c12 100644

commit-graph.c static struct commit_graph *alloc_commit_graph(void) · line 67

Quoted 8 lines
commit-graph.c +5 -0
@@ -60,6 +60,12 @@ static struct commit_graph *alloc_commit_graph(void)
 
 static int commit_graph_compatible(struct repository *r)
 {
+	prepare_commit_graft(r);
+	if (r->parsed_objects && r->parsed_objects->grafts_nr)
+		return 0;
+	if (is_repository_shallow(r))
+		return 0;

Do I assume correctly that is_repository_shallow(r) needs prepare_commit_graph(r) done earlier? If it is so, all right.

If it is not so, then why put two separate history-"altering" features, namely grafts and shallow clones in single commit instead of in separate commits?

Quoted 22 lines
commit-graph.c +1 -0
+
 	prepare_replace_object(r);
 	if (hashmap_get_size(&r->objects->replace_map->map))
 		return 0;
commit.c +1 -1
index 39b80bd21..609adaf8a 100644
@@ -209,7 +209,7 @@ static int read_graft_file(struct repository *r, const char *graft_file)
 	return 0;
 }
 
-static void prepare_commit_graft(struct repository *r)
+void prepare_commit_graft(struct repository *r)
 {
 	char *graft_file;
 
commit.h +0 -0
index da0db36eb..5459e279f 100644

commit.h typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *); · line 207

Quoted 6 lines
commit.h +1 -0
@@ -202,6 +202,7 @@ typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
 
 struct commit_graft *read_graft_line(struct strbuf *line);
 int register_commit_graft(struct repository *r, struct commit_graft *, int);
+void prepare_commit_graft(struct repository *r);
 struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid);
 

I guess that prepare_commit_graft() was not needed outside commit.c before.

Quoted 8 lines
commit.h +0 -0
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2);
t/t5318-commit-graph.sh +0 -0
index c90626f5d..1d9f49af5 100755
@@ -281,6 +281,42 @@ test_expect_success 'replace-objects invalidates commit-graph' '
 	)
 '

t/t5318-commit-graph.sh test_expect_success 'replace-objects invalidates commit-graph' ' · line 292

Quoted 10 lines
t/t5318-commit-graph.sh +9 -0
 
+test_expect_success 'commit grafts invalidate commit-graph' '
+	cd "$TRASH_DIRECTORY" &&
+	test_when_finished rm -rf graft &&
+	git clone full graft &&
+	(
+		cd graft &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph &&
+		git replace --graft HEAD~1 HEAD~3 &&

Errr... this uses *replace objects* mechanism, not graft file!! The --graft option of git-remote command is here to make it easier to use replace objects to alter the view of history, and make if easy to convert from deprected grafts feature to replace objects.

Grafts file structure is described in gitrepository-layout(5) manpage. You would need to do something like the following:

  +		H1=$(git rev-parse --verify HEAD~1) &&
  +		H3=$(git rev-parse --verify HEAD~3) &&
  +		echo '$H1 $H3' >.git/info/grafts &&

You could have used "git replace --graft HEAD~1 HEAD~3" in previous commit test, like I wrote in reply to it.

t/t5318-commit-graph.sh test_expect_success 'replace-objects invalidates commit-graph' ' · line 295

Quoted 3 lines
t/t5318-commit-graph.sh +3 -0
+		git -c core.commitGraph=false log >expect &&
+		git -c core.commitGraph=true log >actual &&
+		test_cmp expect actual &&

All right, we test that we get the same result (when graft-file is written before altering the view of history) with and without the commit-graph feature.

Sidenote: shouldn't we use rev-list instead? Though here git-log is actually safe to use...

t/t5318-commit-graph.sh test_expect_success 'replace-objects invalidates commit-graph' ' · line 298

Quoted 3 lines
t/t5318-commit-graph.sh +3 -0
+		git commit-graph write --reachable &&
+		git -c core.commitGraph=false --no-replace-objects log >expect &&
+		git -c core.commitGraph=true --no-replace-objects log >actual &&

The --no-replace-objects does not affect the graph file!! You would want to remove graft file instead:

  +		git commit-graph write --reachable &&
  +		rm -f .git/info/grafts &&
  +		git -c core.commitGraph=false log >expect &&
  +		git -c core.commitGraph=true log >actual &&

And then check the results.

t/t5318-commit-graph.sh test_expect_success 'replace-objects invalidates commit-graph' ' · line 302

Quoted 4 lines
t/t5318-commit-graph.sh +4 -0
+		test_cmp expect actual &&
+		rm -rf .git/objects/info/commit-graph &&
+		git commit-graph write --reachable &&
+		test_path_is_missing .git/objects/info/commit-graph

All right.

Though, if it is the same for grafts and for replacements... nah. Adding a feature and deleting a feature is different, and it is only a single repetition.

t/t5318-commit-graph.sh test_expect_success 'replace-objects invalidates commit-graph' ' · line 309

Quoted 7 lines
t/t5318-commit-graph.sh +7 -0
+	)
+'
+
+test_expect_success 'replace-objects invalidates commit-graph' '
+	cd "$TRASH_DIRECTORY" &&
+	test_when_finished rm -rf shallow &&
+	git clone --depth 2 "file://$TRASH_DIRECTORY/full" shallow &&

Why do we need to use "file://..." URL, instead of simply "full"?

t/t5318-commit-graph.sh test_expect_success 'replace-objects invalidates commit-graph' ' · line 316

Quoted 7 lines
t/t5318-commit-graph.sh +7 -0
+	(
+		cd shallow &&
+		git commit-graph write --reachable &&
+		test_path_is_missing .git/objects/info/commit-graph &&
+		git fetch origin --unshallow &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph

All right, so in this case because you need to start (clone) with shallow feature, the test could be simplified.

Quoted 6 lines
t/t5318-commit-graph.sh +3 -0
+	)
+'
+
 # the verify tests below expect the commit-graph to contain
 # exactly the commits reachable from the commits/8 branch.
 # If the file changes the set of commits in the list, then the
#20 lore raw

PATCH 7/8 commit-graph: not compatible with uninitialized repo

In reply to Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

---
 commit-graph.c | 3 +++
 1 file changed, 3 insertions(+)
commit-graph.c +3 -0
index 5097c7c12..233958e10 100644
@@ -60,6 +60,9 @@ static struct commit_graph *alloc_commit_graph(void)
 
 static int commit_graph_compatible(struct repository *r)
 {
+	if (!r->gitdir)
+		return 0;
+
 	prepare_commit_graft(r);
 	if (r->parsed_objects && r->parsed_objects->grafts_nr)
 		return 0;
Signature · 1 line
gitgitgadget
#21 lore raw

Re: [PATCH 7/8] commit-graph: not compatible with uninitialized repo

"Derrick Stolee via GitGitGadget" <[email protected]> writes:

[I hope that the rest of replies would make it all right through GitGitGadget]

Quoted 12 lines

From: Derrick Stolee <[email protected]>

---
 commit-graph.c | 3 +++
 1 file changed, 3 insertions(+)
commit-graph.c +0 -0
index 5097c7c12..233958e10 100644

commit-graph.c static struct commit_graph *alloc_commit_graph(void) · line 65

Quoted 6 lines
commit-graph.c +3 -0
@@ -60,6 +60,9 @@ static struct commit_graph *alloc_commit_graph(void)
 
 static int commit_graph_compatible(struct repository *r)
 {
+	if (!r->gitdir)
+		return 0;
+

Nice catch.

I wonder if this is the problem because of the following tests (in which case this test should probably be earlier in this series), or not (and it doesn't need to).

I wonder how this issue was caught.

commit-graph.c static struct commit_graph *alloc_commit_graph(void) · line 68

Quoted 3 lines
commit-graph.c +0 -0
 	prepare_commit_graft(r);
 	if (r->parsed_objects && r->parsed_objects->grafts_nr)
 		return 0;

No tests?

#22 lore raw

PATCH 8/8 commit-graph: close_commit_graph before shallow walk

In reply to Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Make close_commit_graph() work for arbitrary repositories.

Call close_commit_graph() when about to start a rev-list walk that includes shallow commits. This is necessary in code paths that "fake" shallow commits for the sake of fetch. Specifically, test 351 in t5500-fetch-pack.sh runs

	git fetch --shallow-exclude one origin

with a file-based transfer. When the "remote" has a commit-graph, we do not prevent the commit-graph from being loaded, but then the commits are intended to be dynamically transferred into shallow commits during get_shallow_commits_by_rev_list(). By closing the commit-graph before this call, we prevent this interaction.

---
 commit-graph.c | 8 ++++----
 commit-graph.h | 1 +
 upload-pack.c  | 2 ++
 3 files changed, 7 insertions(+), 4 deletions(-)
commit-graph.c +4 -4
index 233958e10..237d4e7d1 100644
@@ -256,10 +256,10 @@ static int prepare_commit_graph(struct repository *r)
 	return !!r->objects->commit_graph;
 }
 
-static void close_commit_graph(void)
+void close_commit_graph(struct repository *r)
 {
-	free_commit_graph(the_repository->objects->commit_graph);
-	the_repository->objects->commit_graph = NULL;
+	free_commit_graph(r->objects->commit_graph);
+	r->objects->commit_graph = NULL;
 }
 
 static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos)
@@ -871,7 +871,7 @@ void write_commit_graph(const char *obj_dir,
 	write_graph_chunk_data(f, GRAPH_OID_LEN, commits.list, commits.nr);
 	write_graph_chunk_large_edges(f, commits.list, commits.nr);
 
-	close_commit_graph();
+	close_commit_graph(the_repository);
 	finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
 	commit_lock_file(&lk);
 
commit-graph.h +1 -0
index 76e098934..13d736cdd 100644
@@ -59,6 +59,7 @@ void write_commit_graph(const char *obj_dir,
 
 int verify_commit_graph(struct repository *r, struct commit_graph *g);
 
+void close_commit_graph(struct repository *);
 void free_commit_graph(struct commit_graph *);
 
 #endif
upload-pack.c +2 -0
index 4ca052d0b..52ad6c8e5 100644
@@ -24,6 +24,7 @@
 #include "quote.h"
 #include "upload-pack.h"
 #include "serve.h"
+#include "commit-graph.h"
 
 /* Remember to update object flag allocation in object.h */
 #define THEY_HAVE	(1u << 11)
@@ -739,6 +740,7 @@ static void deepen_by_rev_list(int ac, const char **av,
 {
 	struct commit_list *result;
 
+	close_commit_graph(the_repository);
 	result = get_shallow_commits_by_rev_list(ac, av, SHALLOW, NOT_SHALLOW);
 	send_shallow(result);
 	free_commit_list(result);
Signature · 1 line
gitgitgadget
#23 lore raw

Re: [PATCH 8/8] commit-graph: close_commit_graph before shallow walk

Quoted 3 lines · Derrick Stolee via GitGitGadget

From: Derrick Stolee <[email protected]>

Make close_commit_graph() work for arbitrary repositories.

The first line (above) does not match the rest of the commit message.

Quoted 79 lines

Call close_commit_graph() when about to start a rev-list walk that includes shallow commits. This is necessary in code paths that "fake" shallow commits for the sake of fetch. Specifically, test 351 in t5500-fetch-pack.sh runs

	git fetch --shallow-exclude one origin

with a file-based transfer. When the "remote" has a commit-graph, we do not prevent the commit-graph from being loaded, but then the commits are intended to be dynamically transferred into shallow commits during get_shallow_commits_by_rev_list(). By closing the commit-graph before this call, we prevent this interaction.

---
 commit-graph.c | 8 ++++----
 commit-graph.h | 1 +
 upload-pack.c  | 2 ++
 3 files changed, 7 insertions(+), 4 deletions(-)
commit-graph.c +4 -4
index 233958e10..237d4e7d1 100644
@@ -256,10 +256,10 @@ static int prepare_commit_graph(struct repository *r)
 	return !!r->objects->commit_graph;
 }
 
-static void close_commit_graph(void)
+void close_commit_graph(struct repository *r)
 {
-	free_commit_graph(the_repository->objects->commit_graph);
-	the_repository->objects->commit_graph = NULL;
+	free_commit_graph(r->objects->commit_graph);
+	r->objects->commit_graph = NULL;
 }
 
 static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos)
@@ -871,7 +871,7 @@ void write_commit_graph(const char *obj_dir,
 	write_graph_chunk_data(f, GRAPH_OID_LEN, commits.list, commits.nr);
 	write_graph_chunk_large_edges(f, commits.list, commits.nr);
 
-	close_commit_graph();
+	close_commit_graph(the_repository);
 	finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
 	commit_lock_file(&lk);
 
commit-graph.h +1 -0
index 76e098934..13d736cdd 100644
@@ -59,6 +59,7 @@ void write_commit_graph(const char *obj_dir,
 
 int verify_commit_graph(struct repository *r, struct commit_graph *g);
 
+void close_commit_graph(struct repository *);
 void free_commit_graph(struct commit_graph *);
 
 #endif
upload-pack.c +2 -0
index 4ca052d0b..52ad6c8e5 100644
@@ -24,6 +24,7 @@
 #include "quote.h"
 #include "upload-pack.h"
 #include "serve.h"
+#include "commit-graph.h"
 
 /* Remember to update object flag allocation in object.h */
 #define THEY_HAVE	(1u << 11)
@@ -739,6 +740,7 @@ static void deepen_by_rev_list(int ac, const char **av,
 {
 	struct commit_list *result;
 
+	close_commit_graph(the_repository);
 	result = get_shallow_commits_by_rev_list(ac, av, SHALLOW, NOT_SHALLOW);
 	send_shallow(result);
 	free_commit_list(result);
#24 lore raw

PATCH 1/1 DO-NOT-MERGE: write and read commit-graph always

In reply to Derrick Stolee via GitGitGadget

This commit is not intended to be merged, but is only to create a test environment to see what works with the commit-graph feature and what does not. The tests that fail are primarily related to corrupting the object store to remove a commit from visibility and testing that rev-list fails -- except it doesn't when there is a commit-graph that prevents parsing from the object database. The following tests will fail with this commit, but are not "real" bugs:

t0410-partial-clone.sh, Test 9
t5307-pack-missing-commit.sh, Tests 3-4
t6011-rev-list-with-bad-commit.sh, Test 4

The following test fails because the repo has ambiguous merge-bases, and the commit-graph changes the walk order so we select a different one. This alters the resulting merge from the expected result.

t6024-recursive-merge.sh, Test 4

The tests above are made to pass by deleting the commit-graph file before the necessary steps.

---
 builtin/commit.c                    |  2 ++
 builtin/gc.c                        |  3 +--
 commit-graph.c                      | 11 -----------
 t/t0410-partial-clone.sh            |  1 +
 t/t5307-pack-missing-commit.sh      |  2 ++
 t/t6011-rev-list-with-bad-commit.sh |  1 +
 t/t6024-recursive-merge.sh          |  1 +
 7 files changed, 8 insertions(+), 13 deletions(-)
builtin/commit.c +2 -0
index 158e3f843a..acc31252a9 100644
@@ -33,6 +33,7 @@
 #include "sequencer.h"
 #include "mailmap.h"
 #include "help.h"
+#include "commit-graph.h"
 
 static const char * const builtin_commit_usage[] = {
 	N_("git commit [<options>] [--] <pathspec>..."),
@@ -1652,6 +1653,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		     "not exceeded, and then \"git reset HEAD\" to recover."));
 
 	rerere(0);
+	write_commit_graph_reachable(get_object_directory(), 1);
 	run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
 	run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
 	if (amend && !no_post_rewrite) {
builtin/gc.c +1 -2
index e103f0f85d..60ab773087 100644
@@ -41,7 +41,7 @@ static int aggressive_depth = 50;
 static int aggressive_window = 250;
 static int gc_auto_threshold = 6700;
 static int gc_auto_pack_limit = 50;
-static int gc_write_commit_graph;
+static int gc_write_commit_graph = 1;
 static int detach_auto = 1;
 static timestamp_t gc_log_expire_time;
 static const char *gc_log_expire = "1.day.ago";
@@ -131,7 +131,6 @@ static void gc_config(void)
 	git_config_get_int("gc.aggressivedepth", &aggressive_depth);
 	git_config_get_int("gc.auto", &gc_auto_threshold);
 	git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
-	git_config_get_bool("gc.writecommitgraph", &gc_write_commit_graph);
 	git_config_get_bool("gc.autodetach", &detach_auto);
 	git_config_get_expiry("gc.pruneexpire", &prune_expire);
 	git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);
commit-graph.c +0 -11
index 237d4e7d1b..ed0d27c12e 100644
@@ -227,22 +227,11 @@ static int prepare_commit_graph(struct repository *r)
 {
 	struct alternate_object_database *alt;
 	char *obj_dir;
-	int config_value;
 
 	if (r->objects->commit_graph_attempted)
 		return !!r->objects->commit_graph;
 	r->objects->commit_graph_attempted = 1;
 
-	if (repo_config_get_bool(r, "core.commitgraph", &config_value) ||
-	    !config_value)
-		/*
-		 * This repository is not configured to use commit graphs, so
-		 * do not load one. (But report commit_graph_attempted anyway
-		 * so that commit graph loading is not attempted again for this
-		 * repository.)
-		 */
-		return 0;
-
 	if (!commit_graph_compatible(r))
 		return 0;
 
t/t0410-partial-clone.sh +1 -0
index 4984ca583d..c235672b03 100755
@@ -181,6 +181,7 @@ test_expect_success 'rev-list stops traversal at missing and promised commit' '
 
 	git -C repo config core.repositoryformatversion 1 &&
 	git -C repo config extensions.partialclone "arbitrary string" &&
+	rm -rf repo/.git/objects/info/commit-graph &&
 	git -C repo rev-list --exclude-promisor-objects --objects bar >out &&
 	grep $(git -C repo rev-parse bar) out &&
 	! grep $FOO out
t/t5307-pack-missing-commit.sh +2 -0
index ae52a1882d..0bb54ae227 100755
@@ -24,10 +24,12 @@ test_expect_success 'check corruption' '
 '
 
 test_expect_success 'rev-list notices corruption (1)' '
+	rm -rf .git/objects/info/commit-graph &&
 	test_must_fail git rev-list HEAD
 '
 
 test_expect_success 'rev-list notices corruption (2)' '
+	rm -rf .git/objects/info/commit-graph &&
 	test_must_fail git rev-list --objects HEAD
 '
 
t/t6011-rev-list-with-bad-commit.sh +1 -0
index e51eb41f4b..9c9cc4d540 100755
@@ -43,6 +43,7 @@ test_expect_success 'corrupt second commit object' \
 
 test_expect_success 'rev-list should fail' \
    '
+   rm -rf .git/objects/info/commit-graph &&
    test_must_fail git rev-list --all > /dev/null
    '
 
t/t6024-recursive-merge.sh +1 -0
index 3f59e58dfb..cec10983cd 100755
@@ -61,6 +61,7 @@ GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
 '
 
 test_expect_success "combined merge conflicts" "
+	rm -rf .git/objects/info/commit-graph &&
 	test_must_fail git merge -m final G
 "
 
Signature · 1 line
2.18.0.118.gd4f65b8d14
#25 lore raw

Re: [PATCH] DO-NOT-MERGE: write and read commit-graph always

Quoted 45 lines · Derrick Stolee

This commit is not intended to be merged, but is only to create a test environment to see what works with the commit-graph feature and what does not. The tests that fail are primarily related to corrupting the object store to remove a commit from visibility and testing that rev-list fails -- except it doesn't when there is a commit-graph that prevents parsing from the object database. The following tests will fail with this commit, but are not "real" bugs:

t0410-partial-clone.sh, Test 9
t5307-pack-missing-commit.sh, Tests 3-4
t6011-rev-list-with-bad-commit.sh, Test 4

The following test fails because the repo has ambiguous merge-bases, and the commit-graph changes the walk order so we select a different one. This alters the resulting merge from the expected result.

t6024-recursive-merge.sh, Test 4

The tests above are made to pass by deleting the commit-graph file before the necessary steps.

---
 builtin/commit.c                    |  2 ++
 builtin/gc.c                        |  3 +--
 commit-graph.c                      | 11 -----------
 t/t0410-partial-clone.sh            |  1 +
 t/t5307-pack-missing-commit.sh      |  2 ++
 t/t6011-rev-list-with-bad-commit.sh |  1 +
 t/t6024-recursive-merge.sh          |  1 +
 7 files changed, 8 insertions(+), 13 deletions(-)
builtin/commit.c +1 -0
index 158e3f843a..acc31252a9 100644
@@ -33,6 +33,7 @@
 #include "sequencer.h"
 #include "mailmap.h"
 #include "help.h"
+#include "commit-graph.h"
 
 static const char * const builtin_commit_usage[] = {
 	N_("git commit [<options>] [--] <pathspec>..."),

builtin/commit.c int cmd_commit(int argc, const char **argv, const char *prefix) · line 1659

Quoted 7 lines
builtin/commit.c +1 -0
@@ -1652,6 +1653,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		     "not exceeded, and then \"git reset HEAD\" to recover."));
 
 	rerere(0);
+	write_commit_graph_reachable(get_object_directory(), 1);
 	run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
 	run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
 	if (amend && !no_post_rewrite) {

This is certainly not for merging.

builtin/gc.c static int aggressive_depth = 50; · line 47

Quoted 13 lines
builtin/gc.c +1 -1
index e103f0f85d..60ab773087 100644
@@ -41,7 +41,7 @@ static int aggressive_depth = 50;
 static int aggressive_window = 250;
 static int gc_auto_threshold = 6700;
 static int gc_auto_pack_limit = 50;
-static int gc_write_commit_graph;
+static int gc_write_commit_graph = 1;
 static int detach_auto = 1;
 static timestamp_t gc_log_expire_time;
 static const char *gc_log_expire = "1.day.ago";

This is switching from default to off to default to on. I think with this feature we would default to off for a long time (wishful thinking:
maybe automatically enabling it for large repositories, or if commit-graph file exists already?).

builtin/gc.c static void gc_config(void) · line 136

Quoted 8 lines
builtin/gc.c +0 -1
@@ -131,7 +131,6 @@ static void gc_config(void)
 	git_config_get_int("gc.aggressivedepth", &aggressive_depth);
 	git_config_get_int("gc.auto", &gc_auto_threshold);
 	git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
-	git_config_get_bool("gc.writecommitgraph", &gc_write_commit_graph);
 	git_config_get_bool("gc.autodetach", &detach_auto);
 	git_config_get_expiry("gc.pruneexpire", &prune_expire);
 	git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire);

This is certainly not for merging, as it disables gc.writeCommitGraph config option entirely.

Quoted 17 lines
commit-graph.c +0 -4
index 237d4e7d1b..ed0d27c12e 100644
@@ -227,22 +227,11 @@ static int prepare_commit_graph(struct repository *r)
 {
 	struct alternate_object_database *alt;
 	char *obj_dir;
-	int config_value;
 
 	if (r->objects->commit_graph_attempted)
 		return !!r->objects->commit_graph;
 	r->objects->commit_graph_attempted = 1;
 
-	if (repo_config_get_bool(r, "core.commitgraph", &config_value) ||
-	    !config_value)
-		/*

commit-graph.c static int prepare_commit_graph(struct repository *r) · line 237

Quoted 10 lines
commit-graph.c +0 -7
-		 * This repository is not configured to use commit graphs, so
-		 * do not load one. (But report commit_graph_attempted anyway
-		 * so that commit graph loading is not attempted again for this
-		 * repository.)
-		 */
-		return 0;
-
 	if (!commit_graph_compatible(r))
 		return 0;
 

This is certainly not for merging, as it disables core.commitGraph config option entirely.

Quoted 46 lines
t/t0410-partial-clone.sh +1 -0
index 4984ca583d..c235672b03 100755
@@ -181,6 +181,7 @@ test_expect_success 'rev-list stops traversal at missing and promised commit' '
 
 	git -C repo config core.repositoryformatversion 1 &&
 	git -C repo config extensions.partialclone "arbitrary string" &&
+	rm -rf repo/.git/objects/info/commit-graph &&
 	git -C repo rev-list --exclude-promisor-objects --objects bar >out &&
 	grep $(git -C repo rev-parse bar) out &&
 	! grep $FOO out
t/t5307-pack-missing-commit.sh +2 -0
index ae52a1882d..0bb54ae227 100755
@@ -24,10 +24,12 @@ test_expect_success 'check corruption' '
 '
 
 test_expect_success 'rev-list notices corruption (1)' '
+	rm -rf .git/objects/info/commit-graph &&
 	test_must_fail git rev-list HEAD
 '
 
 test_expect_success 'rev-list notices corruption (2)' '
+	rm -rf .git/objects/info/commit-graph &&
 	test_must_fail git rev-list --objects HEAD
 '
 
t/t6011-rev-list-with-bad-commit.sh +1 -0
index e51eb41f4b..9c9cc4d540 100755
@@ -43,6 +43,7 @@ test_expect_success 'corrupt second commit object' \
 
 test_expect_success 'rev-list should fail' \
    '
+   rm -rf .git/objects/info/commit-graph &&
    test_must_fail git rev-list --all > /dev/null
    '
 
t/t6024-recursive-merge.sh +0 -0
index 3f59e58dfb..cec10983cd 100755

t/t6024-recursive-merge.sh GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F · line 66

Quoted 6 lines
t/t6024-recursive-merge.sh +1 -0
@@ -61,6 +61,7 @@ GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
 '
 
 test_expect_success "combined merge conflicts" "
+	rm -rf .git/objects/info/commit-graph &&
 	test_must_fail git merge -m final G
 "

I wonder though if all those changes to the testsuite shouldn't be merged.

Regards,

Signature · 1 line
Jakub Narębski
#26 lore raw

Re: [PATCH] DO-NOT-MERGE: write and read commit-graph always

Quoted 2 lines

I wonder though if all those changes to the testsuite shouldn't be merged.

I think Stolee doesn't want this to be merged after rereading subject and the commit message.

Thanks,
Stefan

#27 lore raw

Re: [PATCH] DO-NOT-MERGE: write and read commit-graph always

Quoted 5 lines · Stefan Beller
Quoted 2 lines

I wonder though if all those changes to the testsuite shouldn't be merged.

I think Stolee doesn't want this to be merged after rereading subject and the commit message.

Yes, I understand that, and for the most part I agree with it. This commit main purpose is to thoroughly exercise the new feature.

But I think that changes to the testsuite could have been extracted into separate commit, and merged (and only those changes). It could serve as note about the intent of the test. Well, perhaps after encapsulating it in some function with a good name... ;-)

Anyway, this is a minor insignificant thing.

Signature · 1 line
Jakub Narębski
#28 lore raw

Re: [PATCH] DO-NOT-MERGE: write and read commit-graph always

In reply to Derrick Stolee
Quoted 8 lines · Derrick Stolee

The following test fails because the repo has ambiguous merge-bases, and the commit-graph changes the walk order so we select a different one. This alters the resulting merge from the expected result.

t6024-recursive-merge.sh, Test 4

The tests above are made to pass by deleting the commit-graph file before the necessary steps.

I know you meant for these to not be merged, but perhaps the test in t6024 could be made to be less stringent about order of merge bases. In particular, instead of expecting a certain sha1 to be at stage 2 and a different one to be at stage 3, it could just check that both shas appear in the git ls-files --stage output.

Quoted 14 lines
t/t6024-recursive-merge.sh +1 -1
index 3f59e58dfb..cec10983cd 100755
@@ -61,6 +61,7 @@ GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
 '
 
 test_expect_success "combined merge conflicts" "
+       rm -rf .git/objects/info/commit-graph &&
        test_must_fail git merge -m final G
 "
 
--

2.18.0.118.gd4f65b8d14

#29 lore raw
In reply to Derrick Stolee via GitGitGadget

"Derrick Stolee via GitGitGadget" <[email protected]> writes:

Sidenote: the GitGitGadget doesn't fold/wrap lines properly in the cover-letter. Not that it is much of a problem.

Quoted 10 lines

One unresolved issue with the commit-graph feature is that it can cause issues when combined with replace objects, commit grafts, or shallow clones. These are not 100% incompatible, as one could be reasonably successful writing a commit-graph after replacing some objects and not have issues. The problems happen when commits that are already in the commit-graph file are replaced, or when git is run with the --no-replace-objects option; this can cause incorrect parents or incorrect generation numbers. Similar things occur with commit grafts and shallow clones, especially when running git fetch --unshallow in a shallow repo.

This means that these features can change the view of the history, and if the commit-graph was created before the change, it can have stale incorrect information -- which would lead to incorrect results if the commit-graph feature is used.

Quoted 5 lines

Instead of trying (and probably failing) to make these features work together, default to making the commit-graph feature unavailable in these situations. Create a new method 'commit_graph_compatible(r)' that checks if the repository 'r' has any of these features enabled.

Yes, the alternative would be trying to keep the commit-graph file up-to-date with the current view of history. But there are problems with this approach.

With grafts (which is a deprecated feature), there is no automated entry point. Best what one could do is to store some kind of fingerprint of graft file, and invalidate / recreate commit-graft file if it does not match what is in graft file at the time of running git command that may use commit-graft feature.

With replace objects we have two separate entry points: the git-replace command and fetching references in refs/replace/* namespace (and equivalent). While the first should be not difficult to do, the second one seems to be harder. Additionally, there is '--no-replace-objects' option to turn off the feature (fetch and push ignore replacement objects automatically); so we might want to have two versions of
commit-graph: one with replacement objects feature and one without (or something like that).

The shallow clone seems easiest, with only one automated entry points for changing the view of history this way, and no option to disable this feature -- but it is also the least interesting, as the intent of shallow clone is to have less history, so the commit-graph is less necessary, as you wrote.

We might want to revisit it later, but I agree that starting from this simple approach would be best.

One thing I would like to see added is for the user to know when commit-graph is not available (in manpages), and maybe even a way to see if it is on (e.g. with 'git commit-graph verify' and/or maybe in 'git status' output). But this is a separate issue.

Quoted 3 lines

I will send a follow-up patch that shows how I tested these interactions by computing the commit-graph on every 'git commit'.

Wouldn't it be enough to create commit-graph file, change the view of the history (via grafts, via replace objects, via shallow clone), and check that you still get correct results?

[cut]

Quoted 2 lines

This approach is very different from the previous RFC on the subject [1].

The previous RFC was in my opinion needlessly complicated. This one is much simpler, and better.

[...]

Quoted 51 lines

Thanks,
-Stolee

[1] https://public-inbox.org/git/[email protected]/
[RFC PATCH 0/6] Fix commit-graph/graft/replace/shallow combo

[2] https://public-inbox.org/git/[email protected]/T/#t
[PATCH 0/2] RFC ref store to repository migration

[3] https://public-inbox.org/git/[email protected]/T/#m966eac85fd58c66523654ddaf0bec72877d3295a
[PATCH] TO-SQUASH: replace the_repository with arbitrary r

Based-On: jt/commit-graph-per-object-store
Cc: [email protected]
Cc: [email protected]

Derrick Stolee (6):
commit-graph: update design document
test-repository: properly init repo
commit-graph: not compatible with replace objects
commit-graph: not compatible with grafts
commit-graph: not compatible with uninitialized repo
commit-graph: close_commit_graph before shallow walk

Stefan Beller (2):
refs.c: migrate internal ref iteration to pass thru repository
argument
refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback

 Documentation/technical/commit-graph.txt | 18 ++++++--
 builtin/replace.c                        |  8 ++--
 commit-graph.c                           | 34 ++++++++++++--
 commit-graph.h                           |  1 +
 commit.c                                 |  2 +-
 commit.h                                 |  1 +
 refs.c                                   | 48 +++++++++++++++++---
 refs.h                                   | 12 ++++-
 refs/iterator.c                          |  6 +--
 refs/refs-internal.h                     |  5 +-
 replace-object.c                         |  7 +--
 replace-object.h                         |  2 +
 t/helper/test-repository.c               | 10 +++-
 t/t5318-commit-graph.sh                  | 58 ++++++++++++++++++++++++
 upload-pack.c                            |  2 +
 15 files changed, 184 insertions(+), 30 deletions(-)

base-commit: dade47c06cf849b0ca180a8e6383b55ea6f75812
Published-As: https://github.com/gitgitgadget/git/releases/tags/pr-11%2Fderrickstolee%2Fshallow%2Fupstream-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-11/derrickstolee/shallow/upstream-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/11

#31 lore raw

v2 cover Clarify commit-graph and grafts/replace/shallow incompatibilities

In reply to Derrick Stolee via GitGitGadget

One unresolved issue with the commit-graph feature is that it can cause issues when combined with replace objects, commit grafts, or shallow clones. These are not 100% incompatible, as one could be reasonably successful writing a commit-graph after replacing some objects and not have issues. The problems happen when commits that are already in the commit-graph file are replaced, or when git is run with the --no-replace-objects option; this can cause incorrect parents or incorrect generation numbers. Similar things occur with commit grafts and shallow clones, especially when running git fetch --unshallow in a shallow repo.

Instead of trying (and probably failing) to make these features work together, default to making the commit-graph feature unavailable in these situations. Create a new method 'commit_graph_compatible(r)' that checks if the repository 'r' has any of these features enabled.

CHANGES IN V2:

* The first two commits regarding the ref iterators are unchanged, despite a lot of discussion on the subject [1].

* I included Peff's changes in jk/core-use-replace-refs, changing the base commit for the series to 1689c22c1c328e9135ed51458e9f9a5d224c5057 (the merge that brought that topic into 'msater').

* I fixed the tests for the interactions with the graft feature.

Because of the change of base, it is hard to provide a side-by-side diff from v1.

Thanks,
-Stolee

[1] https://public-inbox.org/git/CAGZ79kZ3PzqpGzXWcmxjzi98gA+LT2MBOf8KaA89hOa-Qig=Og@mail.gmail.com/
Stefan's response recommending we keep the first two commits.

Derrick Stolee (6):
commit-graph: update design document
test-repository: properly init repo
commit-graph: not compatible with replace objects
commit-graph: not compatible with grafts
commit-graph: not compatible with uninitialized repo
commit-graph: close_commit_graph before shallow walk

Stefan Beller (2):
refs.c: migrate internal ref iteration to pass thru repository
argument
refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback

 Documentation/technical/commit-graph.txt | 18 +++++--
 builtin/commit-graph.c                   |  4 ++
 builtin/replace.c                        |  8 ++--
 commit-graph.c                           | 38 +++++++++++++--
 commit-graph.h                           |  1 +
 commit.c                                 |  2 +-
 commit.h                                 |  1 +
 refs.c                                   | 48 ++++++++++++++++---
 refs.h                                   | 12 ++++-
 refs/iterator.c                          |  6 +--
 refs/refs-internal.h                     |  5 +-
 replace-object.c                         |  7 +--
 replace-object.h                         |  2 +
 t/helper/test-repository.c               | 10 +++-
 t/t5318-commit-graph.sh                  | 60 ++++++++++++++++++++++++
 upload-pack.c                            |  2 +
 16 files changed, 194 insertions(+), 30 deletions(-)

base-commit: 1689c22c1c328e9135ed51458e9f9a5d224c5057

Signature · 1 line
2.18.0.118.gd4f65b8d14
#32 lore raw

PATCH v2 1/8 refs.c: migrate internal ref iteration to pass thru repository argument

From: Stefan Beller <[email protected]>

In 60ce76d3581 (refs: add repository argument to for_each_replace_ref, 2018-04-11) and 0d296c57aec (refs: allow for_each_replace_ref to handle arbitrary repositories, 2018-04-11), for_each_replace_ref learned how to iterate over refs by a given arbitrary repository.
New attempts in the object store conversion have shown that it is useful to have the repository handle available that the refs iteration is currently iterating over.

To achieve this goal we will need to add a repository argument to each_ref_fn in refs.h. However as many callers rely on the signature such a patch would be too large.

So convert the internals of the ref subsystem first to pass through a repository argument without exposing the change to the user. Assume the_repository for the passed through repository, although it is not used anywhere yet.

---
 refs.c               | 39 +++++++++++++++++++++++++++++++++++++--
 refs.h               | 10 ++++++++++
 refs/iterator.c      |  6 +++---
 refs/refs-internal.h |  5 +++--
 4 files changed, 53 insertions(+), 7 deletions(-)
refs.c +37 -2
index 457fb78057..7cd76f72d2 100644
@@ -1386,17 +1386,50 @@ struct ref_iterator *refs_ref_iterator_begin(
  * non-zero value, stop the iteration and return that value;
  * otherwise, return 0.
  */
+static int do_for_each_repo_ref(struct repository *r, const char *prefix,
+				each_repo_ref_fn fn, int trim, int flags,
+				void *cb_data)
+{
+	struct ref_iterator *iter;
+	struct ref_store *refs = get_main_ref_store(r);
+
+	if (!refs)
+		return 0;
+
+	iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
+
+	return do_for_each_repo_ref_iterator(r, iter, fn, cb_data);
+}
+
+struct do_for_each_ref_help {
+	each_ref_fn *fn;
+	void *cb_data;
+};
+
+static int do_for_each_ref_helper(struct repository *r,
+				  const char *refname,
+				  const struct object_id *oid,
+				  int flags,
+				  void *cb_data)
+{
+	struct do_for_each_ref_help *hp = cb_data;
+
+	return hp->fn(refname, oid, flags, hp->cb_data);
+}
+
 static int do_for_each_ref(struct ref_store *refs, const char *prefix,
 			   each_ref_fn fn, int trim, int flags, void *cb_data)
 {
 	struct ref_iterator *iter;
+	struct do_for_each_ref_help hp = { fn, cb_data };
 
 	if (!refs)
 		return 0;
 
 	iter = refs_ref_iterator_begin(refs, prefix, trim, flags);
 
-	return do_for_each_ref_iterator(iter, fn, cb_data);
+	return do_for_each_repo_ref_iterator(the_repository, iter,
+					do_for_each_ref_helper, &hp);
 }
 
 int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
@@ -2025,10 +2058,12 @@ int refs_verify_refname_available(struct ref_store *refs,
 int refs_for_each_reflog(struct ref_store *refs, each_ref_fn fn, void *cb_data)
 {
 	struct ref_iterator *iter;
+	struct do_for_each_ref_help hp = { fn, cb_data };
 
 	iter = refs->be->reflog_iterator_begin(refs);
 
-	return do_for_each_ref_iterator(iter, fn, cb_data);
+	return do_for_each_repo_ref_iterator(the_repository, iter,
+					     do_for_each_ref_helper, &hp);
 }
 
 int for_each_reflog(each_ref_fn fn, void *cb_data)
refs.h +10 -0
index cc2fb4c68c..80eec8bbc6 100644
@@ -274,6 +274,16 @@ struct ref_transaction;
 typedef int each_ref_fn(const char *refname,
 			const struct object_id *oid, int flags, void *cb_data);
 
+/*
+ * The same as each_ref_fn, but also with a repository argument that
+ * contains the repository associated with the callback.
+ */
+typedef int each_repo_ref_fn(struct repository *r,
+			     const char *refname,
+			     const struct object_id *oid,
+			     int flags,
+			     void *cb_data);
+
 /*
  * The following functions invoke the specified callback function for
  * each reference indicated.  If the function ever returns a nonzero
refs/iterator.c +3 -3
index 2ac91ac340..629e00a122 100644
@@ -407,15 +407,15 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
 
 struct ref_iterator *current_ref_iter = NULL;
 
-int do_for_each_ref_iterator(struct ref_iterator *iter,
-			     each_ref_fn fn, void *cb_data)
+int do_for_each_repo_ref_iterator(struct repository *r, struct ref_iterator *iter,
+				  each_repo_ref_fn fn, void *cb_data)
 {
 	int retval = 0, ok;
 	struct ref_iterator *old_ref_iter = current_ref_iter;
 
 	current_ref_iter = iter;
 	while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
-		retval = fn(iter->refname, iter->oid, iter->flags, cb_data);
+		retval = fn(r, iter->refname, iter->oid, iter->flags, cb_data);
 		if (retval) {
 			/*
 			 * If ref_iterator_abort() returns ITER_ERROR,
refs/refs-internal.h +3 -2
index 04425d6d1e..89d7dd49cd 100644
@@ -474,8 +474,9 @@ extern struct ref_iterator *current_ref_iter;
  * adapter between the callback style of reference iteration and the
  * iterator style.
  */
-int do_for_each_ref_iterator(struct ref_iterator *iter,
-			     each_ref_fn fn, void *cb_data);
+int do_for_each_repo_ref_iterator(struct repository *r,
+				  struct ref_iterator *iter,
+				  each_repo_ref_fn fn, void *cb_data);
 
 /*
  * Only include per-worktree refs in a do_for_each_ref*() iteration.
Signature · 1 line
2.18.0.118.gd4f65b8d14
#33 lore raw

PATCH v2 2/8 refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback

In reply to Derrick Stolee

From: Stefan Beller <[email protected]>

---
 builtin/replace.c | 8 ++++----
 refs.c            | 9 ++++-----
 refs.h            | 2 +-
 replace-object.c  | 5 +++--
 4 files changed, 12 insertions(+), 12 deletions(-)
builtin/replace.c +4 -4
index e997a562f0..b5861a0ee9 100644
@@ -39,7 +39,8 @@ struct show_data {
 	enum replace_format format;
 };
 
-static int show_reference(const char *refname, const struct object_id *oid,
+static int show_reference(struct repository *r, const char *refname,
+			  const struct object_id *oid,
 			  int flag, void *cb_data)
 {
 	struct show_data *data = cb_data;
@@ -56,9 +57,8 @@ static int show_reference(const char *refname, const struct object_id *oid,
 			if (get_oid(refname, &object))
 				return error("Failed to resolve '%s' as a valid ref.", refname);
 
-			obj_type = oid_object_info(the_repository, &object,
-						   NULL);
-			repl_type = oid_object_info(the_repository, oid, NULL);
+			obj_type = oid_object_info(r, &object, NULL);
+			repl_type = oid_object_info(r, oid, NULL);
 
 			printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type),
 			       oid_to_hex(oid), type_name(repl_type));
refs.c +4 -5
index 7cd76f72d2..c5a5f727e8 100644
@@ -1474,12 +1474,11 @@ int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
 	return do_for_each_ref(refs, prefix, fn, 0, flag, cb_data);
 }
 
-int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data)
+int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data)
 {
-	return do_for_each_ref(get_main_ref_store(r),
-			       git_replace_ref_base, fn,
-			       strlen(git_replace_ref_base),
-			       DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+	return do_for_each_repo_ref(r, git_replace_ref_base, fn,
+				    strlen(git_replace_ref_base),
+				    DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 }
 
 int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
refs.h +1 -1
index 80eec8bbc6..a0a18223a1 100644
@@ -317,7 +317,7 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
 int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 int for_each_remote_ref(each_ref_fn fn, void *cb_data);
-int for_each_replace_ref(struct repository *r, each_ref_fn fn, void *cb_data);
+int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);
 int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
 int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 			 const char *prefix, void *cb_data);
replace-object.c +3 -2
index 4162df6324..3c17864eb7 100644
@@ -6,7 +6,8 @@
 #include "repository.h"
 #include "commit.h"
 
-static int register_replace_ref(const char *refname,
+static int register_replace_ref(struct repository *r,
+				const char *refname,
 				const struct object_id *oid,
 				int flag, void *cb_data)
 {
@@ -25,7 +26,7 @@ static int register_replace_ref(const char *refname,
 	oidcpy(&repl_obj->replacement, oid);
 
 	/* Register new object */
-	if (oidmap_put(the_repository->objects->replace_map, repl_obj))
+	if (oidmap_put(r->objects->replace_map, repl_obj))
 		die("duplicate replace ref: %s", refname);
 
 	return 0;
Signature · 1 line
2.18.0.118.gd4f65b8d14
#34 lore raw

PATCH v2 3/8 commit-graph: update design document

In reply to Derrick Stolee

As it exists right now, the commit-graph feature may provide inconsistent results when combined with commit grafts, replace objects, and shallow clones. Update the design document to discuss why these interactions are difficult to reconcile and how we will avoid errors by preventing updates to and reads from the commit-graph file when these other features exist.

---
 Documentation/technical/commit-graph.txt | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)
Documentation/technical/commit-graph.txt +15 -3
index c664acbd76..001395e950 100644
@@ -112,12 +112,24 @@ Design Details
 - The file format includes parameters for the object ID hash function,
   so a future change of hash algorithm does not require a change in format.
 
+- Commit grafts and replace objects can change the shape of the commit
+  history. The latter can also be enabled/disabled on the fly using
+  `--no-replace-objects`. This leads to difficultly storing both possible
+  interpretations of a commit id, especially when computing generation
+  numbers. The commit-graph will not be read or written when
+  replace-objects or grafts are present.
+
+- Shallow clones create grafts of commits by dropping their parents. This
+  leads the commit-graph to think those commits have generation number 1.
+  If and when those commits are made unshallow, those generation numbers
+  become invalid. Since shallow clones are intended to restrict the commit
+  history to a very small set of commits, the commit-graph feature is less
+  helpful for these clones, anyway. The commit-graph will not be read or
+  written when shallow commits are present.
+
 Future Work
 -----------
 
-- The commit graph feature currently does not honor commit grafts. This can
-  be remedied by duplicating or refactoring the current graft logic.
-
 - After computing and storing generation numbers, we must make graph
   walks aware of generation numbers to gain the performance benefits they
   enable. This will mostly be accomplished by swapping a commit-date-ordered
Signature · 1 line
2.18.0.118.gd4f65b8d14
#35 lore raw

PATCH v2 4/8 test-repository: properly init repo

In reply to Derrick Stolee
---
 t/helper/test-repository.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
t/helper/test-repository.c +8 -2
index 2762ca6562..6a84a53efb 100644
@@ -15,7 +15,10 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree,
 	struct commit *c;
 	struct commit_list *parent;
 
-	repo_init(&r, gitdir, worktree);
+	setup_git_env(gitdir);
+
+	if (repo_init(&r, gitdir, worktree))
+		die("Couldn't init repo");
 
 	c = lookup_commit(&r, commit_oid);
 
@@ -38,7 +41,10 @@ static void test_get_commit_tree_in_graph(const char *gitdir,
 	struct commit *c;
 	struct tree *tree;
 
-	repo_init(&r, gitdir, worktree);
+	setup_git_env(gitdir);
+
+	if (repo_init(&r, gitdir, worktree))
+		die("Couldn't init repo");
 
 	c = lookup_commit(&r, commit_oid);
 
Signature · 1 line
2.18.0.118.gd4f65b8d14
#36 lore raw

PATCH v2 5/8 commit-graph: not compatible with replace objects

In reply to Derrick Stolee

Create new method commit_graph_compatible(r) to check if a given repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist. Test this interaction succeeds, including ignoring an existing commit-graph and failing to write a new commit-graph. However, we do ensure that we write a new commit-graph by setting read_replace_refs to 0, thereby ignoring the replace refs.

---
 builtin/commit-graph.c  |  4 ++++
 commit-graph.c          | 21 +++++++++++++++++++++
 replace-object.c        |  2 +-
 replace-object.h        |  2 ++
 t/t5318-commit-graph.sh | 22 ++++++++++++++++++++++
 5 files changed, 50 insertions(+), 1 deletion(-)
builtin/commit-graph.c +4 -0
index 0bf0c48657..da737df321 100644
@@ -120,6 +120,8 @@ static int graph_read(int argc, const char **argv)
 	return 0;
 }
 
+extern int read_replace_refs;
+
 static int graph_write(int argc, const char **argv)
 {
 	struct string_list *pack_indexes = NULL;
@@ -150,6 +152,8 @@ static int graph_write(int argc, const char **argv)
 	if (!opts.obj_dir)
 		opts.obj_dir = get_object_directory();
 
+	read_replace_refs = 0;
+
 	if (opts.reachable) {
 		write_commit_graph_reachable(opts.obj_dir, opts.append);
 		return 0;
commit-graph.c +21 -0
index b0a55ad128..2c01fa433f 100644
@@ -13,6 +13,8 @@
 #include "commit-graph.h"
 #include "object-store.h"
 #include "alloc.h"
+#include "hashmap.h"
+#include "replace-object.h"
 
 #define GRAPH_SIGNATURE 0x43475048 /* "CGPH" */
 #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
@@ -56,6 +58,19 @@ static struct commit_graph *alloc_commit_graph(void)
 	return g;
 }
 
+extern int read_replace_refs;
+
+static int commit_graph_compatible(struct repository *r)
+{
+	if (read_replace_refs) {
+		prepare_replace_object(r);
+		if (hashmap_get_size(&r->objects->replace_map->map))
+			return 0;
+	}
+
+	return 1;
+}
+
 struct commit_graph *load_commit_graph_one(const char *graph_file)
 {
 	void *graph_map;
@@ -223,6 +238,9 @@ static int prepare_commit_graph(struct repository *r)
 		 */
 		return 0;
 
+	if (!commit_graph_compatible(r))
+		return 0;
+
 	obj_dir = r->objects->objectdir;
 	prepare_commit_graph_one(r, obj_dir);
 	prepare_alt_odb(r);
@@ -693,6 +711,9 @@ void write_commit_graph(const char *obj_dir,
 	int num_extra_edges;
 	struct commit_list *parent;
 
+	if (!commit_graph_compatible(the_repository))
+		return;
+
 	oids.nr = 0;
 	oids.alloc = approximate_object_count() / 4;
 
replace-object.c +1 -1
index 3c17864eb7..9821f1477e 100644
@@ -32,7 +32,7 @@ static int register_replace_ref(struct repository *r,
 	return 0;
 }
 
-static void prepare_replace_object(struct repository *r)
+void prepare_replace_object(struct repository *r)
 {
 	if (r->objects->replace_map)
 		return;
replace-object.h +2 -0
index 9345e105dd..16528df942 100644
@@ -10,6 +10,8 @@ struct replace_object {
 	struct object_id replacement;
 };
 
+void prepare_replace_object(struct repository *r);
+
 /*
  * This internal function is only declared here for the benefit of
  * lookup_replace_object().  Please do not call it directly.
t/t5318-commit-graph.sh +22 -0
index 4f17d7701e..e0c3c60f66 100755
@@ -259,6 +259,28 @@ test_expect_success 'check that gc computes commit-graph' '
 	test_cmp commit-graph-after-gc $objdir/info/commit-graph
 '
 
+test_expect_success 'replace-objects invalidates commit-graph' '
+	cd "$TRASH_DIRECTORY" &&
+	test_when_finished rm -rf replace &&
+	git clone full replace &&
+	(
+		cd replace &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph &&
+		git replace HEAD~1 HEAD~2 &&
+		git -c core.commitGraph=false log >expect &&
+		git -c core.commitGraph=true log >actual &&
+		test_cmp expect actual &&
+		git commit-graph write --reachable &&
+		git -c core.commitGraph=false --no-replace-objects log >expect &&
+		git -c core.commitGraph=true --no-replace-objects log >actual &&
+		test_cmp expect actual &&
+		rm -rf .git/objects/info/commit-graph &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph
+	)
+'
+
 # the verify tests below expect the commit-graph to contain
 # exactly the commits reachable from the commits/8 branch.
 # If the file changes the set of commits in the list, then the
Signature · 1 line
2.18.0.118.gd4f65b8d14
#37 lore raw

Re: [PATCH v2 5/8] commit-graph: not compatible with replace objects

Quoted 22 lines · Derrick Stolee

Create new method commit_graph_compatible(r) to check if a given repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist. Test this interaction succeeds, including ignoring an existing commit-graph and failing to write a new commit-graph. However, we do ensure that we write a new commit-graph by setting read_replace_refs to 0, thereby ignoring the replace refs.

---
 builtin/commit-graph.c  |  4 ++++
 commit-graph.c          | 21 +++++++++++++++++++++
 replace-object.c        |  2 +-
 replace-object.h        |  2 ++
 t/t5318-commit-graph.sh | 22 ++++++++++++++++++++++
 5 files changed, 50 insertions(+), 1 deletion(-)
builtin/commit-graph.c +0 -0
index 0bf0c48657..da737df321 100644

builtin/commit-graph.c static int graph_read(int argc, const char **argv) · line 124

Quoted 5 lines
builtin/commit-graph.c +2 -0
@@ -120,6 +120,8 @@ static int graph_read(int argc, const char **argv)
 	return 0;
 }
 
+extern int read_replace_refs;
+

Why do you need this (and also in commit-graph.c)? I thought cache.h includes it, which you can just make use of it.

Quoted 16 lines
builtin/commit-graph.c +10 -0
+static int commit_graph_compatible(struct repository *r)
+{
+	if (read_replace_refs) {
+		prepare_replace_object(r);
+		if (hashmap_get_size(&r->objects->replace_map->map))
+			return 0;
+	}
+
+	return 1;
+}
replace-object.c +0 -0
index 3c17864eb7..9821f1477e 100644

replace-object.c static int register_replace_ref(struct repository *r, · line 38

Quoted 8 lines
replace-object.c +1 -1
@@ -32,7 +32,7 @@ static int register_replace_ref(struct repository *r,
 	return 0;
 }
 
-static void prepare_replace_object(struct repository *r)
+void prepare_replace_object(struct repository *r)
 {
 	if (r->objects->replace_map)
 		return;

The way the new caller is written, I am wondering if this function should return "we are (or, are not) using the replace object feature", making it unnecessary for callers on the reading side to know about "read-replace-refs" external variable, for example.

	/*
	 * To be called on-demand from codepaths that want to make
	 * sure that replacement objects can be found as necessary.
	 * 
	 * Return number of replacement defined for the repository, or
	 * -1 when !read_replace_refs tells us not to use replacement
	 * mechanism at all.
	 */
	int prepare_replace_object(struct repository *r)
	{
		if (!read_replace_refs)
			return -1;

		if (!r->objects->replace_map) {
			r->objects->replace_map =
				xmalloc(...);
			oidmap_init(r->objects->replace_map, 0);
			for_each_refplace_ref(r, register_...);
		}
		return hashmap_get_size(&r->objects->replace_map->map);
	}

Then, the caller side can simply become something like:

	#define cgraph_compat(r) (prepare_replace_object(r) <= 0)

There are various writers to read_replace_refs variable, but I think they should first be replaced with calls to something like:

	void use_replace_refs(struct repository *r, int enable);

which allows us to hide the global variable and later make it per repository if we wanted to.

#38 lore raw

Re: [PATCH v2 5/8] commit-graph: not compatible with replace objects

Quoted 27 lines · Junio C Hamano
Quoted 25 lines · Derrick Stolee

Create new method commit_graph_compatible(r) to check if a given repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist. Test this interaction succeeds, including ignoring an existing commit-graph and failing to write a new commit-graph. However, we do ensure that we write a new commit-graph by setting read_replace_refs to 0, thereby ignoring the replace refs.

---
  builtin/commit-graph.c  |  4 ++++
  commit-graph.c          | 21 +++++++++++++++++++++
  replace-object.c        |  2 +-
  replace-object.h        |  2 ++
  t/t5318-commit-graph.sh | 22 ++++++++++++++++++++++
  5 files changed, 50 insertions(+), 1 deletion(-)
builtin/commit-graph.c +0 -0
index 0bf0c48657..da737df321 100644
@@ -120,6 +120,8 @@ static int graph_read(int argc, const char **argv)
  	return 0;
  }
 
Quoted 4 lines

builtin/commit-graph.c static int graph_read(int argc, const char **argv) · line 124

Quoted 2 lines
builtin/commit-graph.c +2 -0
+extern int read_replace_refs;
+

Why do you need this (and also in commit-graph.c)? I thought cache.h includes it, which you can just make use of it.

You're right. I don't know how I missed that.

Quoted 41 lines
Quoted 15 lines
builtin/commit-graph.c +10 -0
+static int commit_graph_compatible(struct repository *r)
+{
+	if (read_replace_refs) {
+		prepare_replace_object(r);
+		if (hashmap_get_size(&r->objects->replace_map->map))
+			return 0;
+	}
+
+	return 1;
+}
replace-object.c +0 -0
index 3c17864eb7..9821f1477e 100644

replace-object.c static int register_replace_ref(struct repository *r, · line 38

Quoted 8 lines
replace-object.c +1 -1
@@ -32,7 +32,7 @@ static int register_replace_ref(struct repository *r,
  	return 0;
  }
 
-static void prepare_replace_object(struct repository *r)
+void prepare_replace_object(struct repository *r)
  {
  	if (r->objects->replace_map)
  		return;

The way the new caller is written, I am wondering if this function should return "we are (or, are not) using the replace object feature", making it unnecessary for callers on the reading side to know about "read-replace-refs" external variable, for example.

	/*
	 * To be called on-demand from codepaths that want to make
	 * sure that replacement objects can be found as necessary.
	 *
	 * Return number of replacement defined for the repository, or
	 * -1 when !read_replace_refs tells us not to use replacement
	 * mechanism at all.
	 */
	int prepare_replace_object(struct repository *r)
	{
		if (!read_replace_refs)
			return -1;
Quoted 8 lines
		if (!r->objects->replace_map) {
			r->objects->replace_map =
				xmalloc(...);
			oidmap_init(r->objects->replace_map, 0);
			for_each_refplace_ref(r, register_...);
		}
		return hashmap_get_size(&r->objects->replace_map->map);
	}

This is a good idea. I can incorporate it into v3.

Quoted 3 lines

Then, the caller side can simply become something like:

	#define cgraph_compat(r) (prepare_replace_object(r) <= 0)

With the next two patches that add more conditions to commit_graph_compatible(), I'd prefer keeping it a method instead of a macro.

Quoted 7 lines

There are various writers to read_replace_refs variable, but I think they should first be replaced with calls to something like:

	void use_replace_refs(struct repository *r, int enable);

which allows us to hide the global variable and later make it per repository if we wanted to.

I will incorporate this into v3 as well.

Thanks,

-Stolee

#39 lore raw

PATCH v2 6/8 commit-graph: not compatible with grafts

In reply to Derrick Stolee

Augment commit_graph_compatible(r) to return false when the given repository r has commit grafts or is a shallow clone. Test that in these situations we ignore existing commit-graph files and we do not write new commit-graph files.

---
 commit-graph.c          |  6 ++++++
 commit.c                |  2 +-
 commit.h                |  1 +
 t/t5318-commit-graph.sh | 38 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 46 insertions(+), 1 deletion(-)
commit-graph.c +6 -0
index 2c01fa433f..c4eaedf4e5 100644
@@ -68,6 +68,12 @@ static int commit_graph_compatible(struct repository *r)
 			return 0;
 	}
 
+	prepare_commit_graft(r);
+	if (r->parsed_objects && r->parsed_objects->grafts_nr)
+		return 0;
+	if (is_repository_shallow(r))
+		return 0;
+
 	return 1;
 }
 
commit.c +1 -1
index 30d1af2b20..ef9a2cbb23 100644
@@ -209,7 +209,7 @@ static int read_graft_file(struct repository *r, const char *graft_file)
 	return 0;
 }
 
-static void prepare_commit_graft(struct repository *r)
+void prepare_commit_graft(struct repository *r)
 {
 	char *graft_file;
 
commit.h +1 -0
index da0db36eba..5459e279fe 100644
@@ -202,6 +202,7 @@ typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *);
 
 struct commit_graft *read_graft_line(struct strbuf *line);
 int register_commit_graft(struct repository *r, struct commit_graft *, int);
+void prepare_commit_graft(struct repository *r);
 struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid);
 
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2);
t/t5318-commit-graph.sh +38 -0
index e0c3c60f66..6aee861f78 100755
@@ -281,6 +281,44 @@ test_expect_success 'replace-objects invalidates commit-graph' '
 	)
 '
 
+test_expect_success 'commit grafts invalidate commit-graph' '
+	cd "$TRASH_DIRECTORY" &&
+	test_when_finished rm -rf graft &&
+	git clone full graft &&
+	(
+		cd graft &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph &&
+		H1=$(git rev-parse --verify HEAD~1) &&
+		H3=$(git rev-parse --verify HEAD~3) &&
+		echo "$H1 $H3" >.git/info/grafts &&
+		git -c core.commitGraph=false log >expect &&
+		git -c core.commitGraph=true log >actual &&
+		test_cmp expect actual &&
+		git commit-graph write --reachable &&
+		git -c core.commitGraph=false --no-replace-objects log >expect &&
+		git -c core.commitGraph=true --no-replace-objects log >actual &&
+		test_cmp expect actual &&
+		rm -rf .git/objects/info/commit-graph &&
+		git commit-graph write --reachable &&
+		test_path_is_missing .git/objects/info/commit-graph
+	)
+'
+
+test_expect_success 'replace-objects invalidates commit-graph' '
+	cd "$TRASH_DIRECTORY" &&
+	test_when_finished rm -rf shallow &&
+	git clone --depth 2 "file://$TRASH_DIRECTORY/full" shallow &&
+	(
+		cd shallow &&
+		git commit-graph write --reachable &&
+		test_path_is_missing .git/objects/info/commit-graph &&
+		git fetch origin --unshallow &&
+		git commit-graph write --reachable &&
+		test_path_is_file .git/objects/info/commit-graph
+	)
+'
+
 # the verify tests below expect the commit-graph to contain
 # exactly the commits reachable from the commits/8 branch.
 # If the file changes the set of commits in the list, then the
Signature · 1 line
2.18.0.118.gd4f65b8d14
#40 lore raw

PATCH v2 7/8 commit-graph: not compatible with uninitialized repo

In reply to Derrick Stolee
---
 commit-graph.c | 3 +++
 1 file changed, 3 insertions(+)
commit-graph.c +3 -0
index c4eaedf4e5..cee2caab5c 100644
@@ -62,6 +62,9 @@ extern int read_replace_refs;
 
 static int commit_graph_compatible(struct repository *r)
 {
+	if (!r->gitdir)
+		return 0;
+
 	if (read_replace_refs) {
 		prepare_replace_object(r);
 		if (hashmap_get_size(&r->objects->replace_map->map))
Signature · 1 line
2.18.0.118.gd4f65b8d14
#41 lore raw

PATCH v2 8/8 commit-graph: close_commit_graph before shallow walk

In reply to Derrick Stolee

Call close_commit_graph() when about to start a rev-list walk that includes shallow commits. This is necessary in code paths that "fake" shallow commits for the sake of fetch. Specifically, test 351 in t5500-fetch-pack.sh runs

	git fetch --shallow-exclude one origin

with a file-based transfer. When the "remote" has a commit-graph, we do not prevent the commit-graph from being loaded, but then the commits are intended to be dynamically transferred into shallow commits during get_shallow_commits_by_rev_list(). By closing the commit-graph before this call, we prevent this interaction.

---
 commit-graph.c | 8 ++++----
 commit-graph.h | 1 +
 upload-pack.c  | 2 ++
 3 files changed, 7 insertions(+), 4 deletions(-)
commit-graph.c +4 -4
index cee2caab5c..4bd1a4abbf 100644
@@ -260,10 +260,10 @@ static int prepare_commit_graph(struct repository *r)
 	return !!r->objects->commit_graph;
 }
 
-static void close_commit_graph(void)
+void close_commit_graph(struct repository *r)
 {
-	free_commit_graph(the_repository->objects->commit_graph);
-	the_repository->objects->commit_graph = NULL;
+	free_commit_graph(r->objects->commit_graph);
+	r->objects->commit_graph = NULL;
 }
 
 static int bsearch_graph(struct commit_graph *g, struct object_id *oid, uint32_t *pos)
@@ -875,7 +875,7 @@ void write_commit_graph(const char *obj_dir,
 	write_graph_chunk_data(f, GRAPH_OID_LEN, commits.list, commits.nr);
 	write_graph_chunk_large_edges(f, commits.list, commits.nr);
 
-	close_commit_graph();
+	close_commit_graph(the_repository);
 	finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_FSYNC);
 	commit_lock_file(&lk);
 
commit-graph.h +1 -0
index 76e098934a..13d736cdde 100644
@@ -59,6 +59,7 @@ void write_commit_graph(const char *obj_dir,
 
 int verify_commit_graph(struct repository *r, struct commit_graph *g);
 
+void close_commit_graph(struct repository *);
 void free_commit_graph(struct commit_graph *);
 
 #endif
upload-pack.c +2 -0
index 82b393ec31..2ae9d9bb47 100644
@@ -24,6 +24,7 @@
 #include "quote.h"
 #include "upload-pack.h"
 #include "serve.h"
+#include "commit-graph.h"
 
 /* Remember to update object flag allocation in object.h */
 #define THEY_HAVE	(1u << 11)
@@ -740,6 +741,7 @@ static void deepen_by_rev_list(int ac, const char **av,
 {
 	struct commit_list *result;
 
+	close_commit_graph(the_repository);
 	result = get_shallow_commits_by_rev_list(ac, av, SHALLOW, NOT_SHALLOW);
 	send_shallow(result);
 	free_commit_list(result);
Signature · 1 line
2.18.0.118.gd4f65b8d14
#42 lore raw
In reply to Derrick Stolee
Quoted 2 lines · Derrick Stolee

Because of the change of base, it is hard to provide a side-by-side diff from v1.

I thought that was the whole point of range-diff. ;-)

I'll take a look.

Thanks,
Stefan

#43 lore raw
Quoted 5 lines · Stefan Beller
Quoted 2 lines · Derrick Stolee

Because of the change of base, it is hard to provide a side-by-side diff from v1.

I thought that was the whole point of range-diff. ;-)

I thought so, too. Was there any change that confused range-diff machinery?

#44 lore raw
Quoted 8 lines · Junio C Hamano
Quoted 4 lines · Stefan Beller
Quoted 2 lines · Derrick Stolee

Because of the change of base, it is hard to provide a side-by-side diff from v1.

I thought that was the whole point of range-diff. ;-)

I thought so, too. Was there any change that confused range-diff machinery?

I've just been out of the loop and didn't realize what range-diff did. Here is the range-diff:

1:  43ddcc9ef9 = 1:  c8edae4179 refs.c: migrate internal ref iteration to pass thru repository argument 2:  22dc9ce836 = 2:  f89451e884 refs.c: upgrade for_each_replace_ref to be a each_repo_ref_fn callback 3:  5e90d36f84 ! 3:  d95aa3472c commit-graph: update design document
    @@ -19,7 +19,7 @@
        so a future change of hash algorithm does not require a change in format.

     +- Commit grafts and replace objects can change the shape of the commit -+  history. These can also be enabled/disabled on the fly using ++  history. The latter can also be enabled/disabled on the fly using +  --no-replace-objects. This leads to difficultly storing both possible +  interpretations of a commit id, especially when computing generation +  numbers. The commit-graph will not be read or written when 4:  88711a3cf4 = 4:  ec89c88e14 test-repository: properly init repo 5:  7f596c1718 ! 5:  0321a3cf10 commit-graph: not compatible with replace objects @@ -6,10 +6,34 @@
         repository r is compatible with the commit-graph feature. Fill the method with a check to see if replace-objects exist. Test this interaction succeeds, including ignoring an existing commit-graph and -    failing to write a new commit-graph.
    +    failing to write a new commit-graph. However, we do ensure that +    we write a new commit-graph by setting read_replace_refs to 0, thereby +    ignoring the replace refs.

    +diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c +--- a/builtin/commit-graph.c
    ++++ b/builtin/commit-graph.c
    +@@
    +   return 0;
    + }
    +
    ++extern int read_replace_refs;
    ++
    + static int graph_write(int argc, const char **argv)
    + {
    +   struct string_list *pack_indexes = NULL;
    +@@
    +   if (!opts.obj_dir)
    +           opts.obj_dir = get_object_directory();
    +
    ++  read_replace_refs = 0;
    ++
    +   if (opts.reachable) {
    +           write_commit_graph_reachable(opts.obj_dir, opts.append); +           return 0;
    +
     diff --git a/commit-graph.c b/commit-graph.c
     --- a/commit-graph.c
     +++ b/commit-graph.c
    @@ -26,11 +50,15 @@
        return g;
      }

    ++extern int read_replace_refs;
    ++
     +static int commit_graph_compatible(struct repository *r)
     +{
    -+  prepare_replace_object(r);
    -+  if (hashmap_get_size(&r->objects->replace_map->map))
    -+          return 0;
    ++  if (read_replace_refs) {
    ++          prepare_replace_object(r);
    ++          if (hashmap_get_size(&r->objects->replace_map->map)) ++                  return 0;
    ++  }
     +
     +  return 1;
     +}
    @@ -110,7 +138,7 @@
     +          test_cmp expect actual &&
     +          rm -rf .git/objects/info/commit-graph &&
     +          git commit-graph write --reachable &&
    -+          test_path_is_missing .git/objects/info/commit-graph ++          test_path_is_file .git/objects/info/commit-graph
     +  )
     +'
     +
6:  94dd91ac35 ! 6:  d18ecc0124 commit-graph: not compatible with grafts @@ -7,24 +7,25 @@
         situations we ignore existing commit-graph files and we do not write new commit-graph files.

    +    Helped-by: Jakub Narebski <[email protected]>

     diff --git a/commit-graph.c b/commit-graph.c
     --- a/commit-graph.c
     +++ b/commit-graph.c
     @@
    +                   return 0;
    +   }

    - static int commit_graph_compatible(struct repository *r)
    - {
     +  prepare_commit_graft(r);
     +  if (r->parsed_objects && r->parsed_objects->grafts_nr) +          return 0;
     +  if (is_repository_shallow(r))
     +          return 0;
     +
    -   prepare_replace_object(r);
    -   if (hashmap_get_size(&r->objects->replace_map->map)) -           return 0;
    +   return 1;
    + }
    +

     diff --git a/commit.c b/commit.c
     --- a/commit.c
    @@ -66,7 +67,9 @@
     +          cd graft &&
     +          git commit-graph write --reachable &&
     +          test_path_is_file .git/objects/info/commit-graph &&
    -+          git replace --graft HEAD~1 HEAD~3 &&
    ++          H1=$(git rev-parse --verify HEAD~1) &&
    ++          H3=$(git rev-parse --verify HEAD~3) &&
    ++          echo "$H1 $H3" >.git/info/grafts &&
     +          git -c core.commitGraph=false log >expect &&
     +          git -c core.commitGraph=true log >actual &&
     +          test_cmp expect actual &&
7:  5314a5a93d ! 7:  87d3397700 commit-graph: not compatible with uninitialized repo @@ -14,6 +14,6 @@
     +  if (!r->gitdir)
     +          return 0;
     +
    -   prepare_commit_graft(r);
    -   if (r->parsed_objects && r->parsed_objects->grafts_nr)
    -           return 0;
    +   if (read_replace_refs) {
    +           prepare_replace_object(r);
    +           if (hashmap_get_size(&r->objects->replace_map->map)) 8:  f4ab234ed2 ! 8:  a26c175897 commit-graph: close_commit_graph before shallow walk @@ -2,8 +2,6 @@

         commit-graph: close_commit_graph before shallow walk

    -    Make close_commit_graph() work for arbitrary repositories. -
         Call close_commit_graph() when about to start a rev-list walk that includes shallow commits. This is necessary in code paths that "fake" shallow commits for the sake of fetch. Specifically, test 351 in

#45 lore raw
In reply to Derrick Stolee
Quoted 28 lines · Derrick Stolee

One unresolved issue with the commit-graph feature is that it can cause issues when combined with replace objects, commit grafts, or shallow clones. These are not 100% incompatible, as one could be reasonably successful writing a commit-graph after replacing some objects and not have issues. The problems happen when commits that are already in the commit-graph file are replaced, or when git is run with the --no-replace-objects option; this can cause incorrect parents or incorrect generation numbers. Similar things occur with commit grafts and shallow clones, especially when running git fetch --unshallow in a shallow repo.

Instead of trying (and probably failing) to make these features work together, default to making the commit-graph feature unavailable in these situations. Create a new method 'commit_graph_compatible(r)' that checks if the repository 'r' has any of these features enabled.

CHANGES IN V2:

* The first two commits regarding the ref iterators are unchanged, despite a lot of discussion on the subject [1].

* I included Peff's changes in jk/core-use-replace-refs, changing the base commit for the series to 1689c22c1c328e9135ed51458e9f9a5d224c5057 (the merge that brought that topic into 'msater').

* I fixed the tests for the interactions with the graft feature.

Quoted 9 lines

Because of the change of base, it is hard to provide a side-by-side diff from v1.

Thanks,
-Stolee

[1] https://public-inbox.org/git/CAGZ79kZ3PzqpGzXWcmxjzi98gA+LT2MBOf8KaA89hOa-Qig=Og@mail.gmail.com/
Stefan's response recommending we keep the first two commits.

After reviewing my own patches, I flipped again (Sorry!) and would rather not see my patches be merged, but the very original solution by you, that you proposed at [1]. That said, I will not insist on it, and if this is merged as is, we can fix it up later.

With that said, I just read through the remaining patches, I think they are a valuable addition to Git and could be merged as-is.

[1] https://github.com/gitgitgadget/git/pull/11/commits/300db80140dacc927db0d46c804ca0ef4dcc1be1

Thanks,
Stefan

#46 lore raw
Quoted 42 lines · Stefan Beller
Quoted 27 lines · Derrick Stolee

One unresolved issue with the commit-graph feature is that it can cause issues when combined with replace objects, commit grafts, or shallow clones. These are not 100% incompatible, as one could be reasonably successful writing a commit-graph after replacing some objects and not have issues. The problems happen when commits that are already in the commit-graph file are replaced, or when git is run with the --no-replace-objects option; this can cause incorrect parents or incorrect generation numbers. Similar things occur with commit grafts and shallow clones, especially when running git fetch --unshallow in a shallow repo.

Instead of trying (and probably failing) to make these features work together, default to making the commit-graph feature unavailable in these situations. Create a new method 'commit_graph_compatible(r)' that checks if the repository 'r' has any of these features enabled.

CHANGES IN V2:

* The first two commits regarding the ref iterators are unchanged, despite a lot of discussion on the subject [1].

* I included Peff's changes in jk/core-use-replace-refs, changing the base commit for the series to 1689c22c1c328e9135ed51458e9f9a5d224c5057 (the merge that brought that topic into 'msater').

* I fixed the tests for the interactions with the graft feature.

Quoted 9 lines

Because of the change of base, it is hard to provide a side-by-side diff from v1.

Thanks,
-Stolee

[1] https://public-inbox.org/git/CAGZ79kZ3PzqpGzXWcmxjzi98gA+LT2MBOf8KaA89hOa-Qig=Og@mail.gmail.com/
Stefan's response recommending we keep the first two commits.

After reviewing my own patches, I flipped again (Sorry!) and would rather not see my patches be merged, but the very original solution by you, that you proposed at [1]. That said, I will not insist on it, and if this is merged as is, we can fix it up later.

Quoted 7 lines

With that said, I just read through the remaining patches, I think they are a valuable addition to Git and could be merged as-is.

[1] https://github.com/gitgitgadget/git/pull/11/commits/300db80140dacc927db0d46c804ca0ef4dcc1be1

Thanks,
Stefan

Just to keep things on-list as possible, here is the patch for the commit linked above. It would replace patches 1 & 2 from this series.

Junio: I can send a v3 that includes this commit if you need it, or if there are other edits to be made in this series.

commit 300db80140dacc927db0d46c804ca0ef4dcc1be1
Author: Derrick Stolee <[email protected]>
Date:   Fri Jul 20 15:39:06 2018 -0400

    replace-objects: use arbitrary repositories

    This is the smallest possible change that makes prepare_replace_objects work properly with arbitrary repositories. By supplying the repository as the cb_data, we do not need to modify any code in the ref iterator logic. We will likely want to do a full replacement of the ref iterator logic to provide a repository struct as a concrete parameter.

replace-object.c +0 -0
index 801b5c1678..e99fcd1ff6 100644
@@ -14,6 +14,7 @@ static int register_replace_ref(const char *refname,

        const char *slash = strrchr(refname, '/');
        const char *hash = slash ? slash + 1 : refname; struct replace_object *repl_obj = xmalloc(sizeof(*repl_obj)); +       struct repository *r = (struct repository *)cb_data;

        if (get_oid_hex(hash, &repl_obj->original.oid)) { free(repl_obj);
@@ -25,7 +26,7 @@ static int register_replace_ref(const char *refname, oidcpy(&repl_obj->replacement, oid);

        /* Register new object */
-       if (oidmap_put(the_repository->objects->replace_map, repl_obj)) +       if (oidmap_put(r->objects->replace_map, repl_obj)) die("duplicate replace ref: %s", refname);

        return 0;
@@ -40,7 +41,7 @@ static void prepare_replace_object(struct repository *r) xmalloc(sizeof(*r->objects->replace_map)); oidmap_init(r->objects->replace_map, 0);

-       for_each_replace_ref(r, register_replace_ref, NULL); +       for_each_replace_ref(r, register_replace_ref, r); }

 /* We allow "recursive" replacement. Only within reason, though */