Re: Git is not scalable with too many refs/*

12 messages, 3 authors, 2016-06-15 · open the first message on its own page

Re: Git is not scalable with too many refs/*

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:52:09

René Scharfe [off-list ref] writes:
quoted hunk
Hi Martin,

Am 29.09.2011 22:11, schrieb Martin Fick:
quoted
Your patch works well for me.  It achieves about the same 
gains as Julian's patch. Thanks!
OK, and what happens if you apply the following patch on top of my first
one?  It avoids going through all the refs a second time during cleanup,
at the cost of going through the list of all known objects.  I wonder if
that's any faster in your case.
...
 static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
@@ -690,8 +689,7 @@ static void orphaned_commit_warning(struct commit *commit)
 	else
 		describe_detached_head(_("Previous HEAD position was"), commit);
 
-	clear_commit_marks(commit, -1);
-	for_each_ref(clear_commit_marks_from_one_ref, NULL);
+	clear_commit_marks_for_all(ALL_REV_FLAGS);
 }
The function already clears all the flag bits from commits near the tip of
all the refs (i.e. whatever commit it traverses until it gets to the fork
point), so it cannot be reused in other contexts where the caller

 - first marks commit objects with some flag bits for its own purpose,
   unrelated to the "orphaned"-ness check;
 - calls this function to issue a warning; and then
 - use the flag it earlier set to do something useful.

which requires "cleaning after yourself, by clearing only the bits you
used without disturbing other bits that you do not use" pattern.

It might be a better solution to not bother to clear the marks at all;
would it break anything in this codepath?

Re: Git is not scalable with too many refs/*

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

Am 30.09.2011 18:52, schrieb Junio C Hamano:
René Scharfe [off-list ref] writes:
quoted
Hi Martin,

Am 29.09.2011 22:11, schrieb Martin Fick:
quoted
Your patch works well for me.  It achieves about the same 
gains as Julian's patch. Thanks!
OK, and what happens if you apply the following patch on top of my first
one?  It avoids going through all the refs a second time during cleanup,
at the cost of going through the list of all known objects.  I wonder if
that's any faster in your case.
...
 static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
@@ -690,8 +689,7 @@ static void orphaned_commit_warning(struct commit *commit)
 	else
 		describe_detached_head(_("Previous HEAD position was"), commit);
 
-	clear_commit_marks(commit, -1);
-	for_each_ref(clear_commit_marks_from_one_ref, NULL);
+	clear_commit_marks_for_all(ALL_REV_FLAGS);
 }
The function already clears all the flag bits from commits near the tip of
all the refs (i.e. whatever commit it traverses until it gets to the fork
point), so it cannot be reused in other contexts where the caller

 - first marks commit objects with some flag bits for its own purpose,
   unrelated to the "orphaned"-ness check;
 - calls this function to issue a warning; and then
 - use the flag it earlier set to do something useful.

which requires "cleaning after yourself, by clearing only the bits you
used without disturbing other bits that you do not use" pattern.
Yes, clear_commit_marks_for_all is a bit brutal.  Callers could clear
specfic bits (e.g. SEEN|UNINTERESTING) instead of ALL_REV_FLAGS, though.
It might be a better solution to not bother to clear the marks at all;
would it break anything in this codepath?
Unfortunately, yes; the cleanup part was added by 5c08dc48 later, when
it become apparent that it's really needed.

However, since the patch only buys us a 5% speedup I'm not sure it's
worth it in its current form.

René

Re: Git is not scalable with too many refs/*

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

Am 30.09.2011 20:17, schrieb René Scharfe:
Am 30.09.2011 18:52, schrieb Junio C Hamano:
quoted
It might be a better solution to not bother to clear the marks at
all; would it break anything in this codepath?
Unfortunately, yes; the cleanup part was added by 5c08dc48 later,
when it become apparent that it's really needed.

However, since the patch only buys us a 5% speedup I'm not sure it's 
worth it in its current form.
I found something better: A trick used by bisect and bundle.  They copy
the list of pending objects from rev_info before calling
prepare_revision_walk and then go through it to clean up the commit
marks without going through the refs again.  And I think we can even
improve it a little.

The following patches tighten some orphan/detached head tests a little,
then comes a resend of my first patch on this topic, only split up into
two, then four patches that introduce the trick mentioned above (which
could be squashed together perhaps) and the last one is a bonus
refactoring patch.

 bisect.c                   |   20 +++++++-------
 builtin/checkout.c         |   58 +++++++++++++------------------------------
 bundle.c                   |   11 +++-----
 commit.c                   |   14 ++++++++++
 commit.h                   |    1 +
 revision.c                 |   14 +++++++---
 revision.h                 |    2 +
 t/t2020-checkout-detach.sh |    7 ++++-
 8 files changed, 64 insertions(+), 63 deletions(-)

René

[PATCH 1/8] checkout: check for "Previous HEAD" notice in t2020

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

If we leave a detached head, exactly one of two things happens: either
checkout warns about it being an orphan or describes it as a courtesy.
Test t2020 already checked that the warning is shown as needed.  This
patch also checks for the description.

Signed-off-by: Rene Scharfe <redacted>
---
 t/t2020-checkout-detach.sh |    7 +++++--
 1 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 2366f0f..068fba4 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -12,11 +12,14 @@ check_not_detached () {
 }
 
 ORPHAN_WARNING='you are leaving .* commit.*behind'
+PREV_HEAD_DESC='Previous HEAD position was'
 check_orphan_warning() {
-	test_i18ngrep "$ORPHAN_WARNING" "$1"
+	test_i18ngrep "$ORPHAN_WARNING" "$1" &&
+	test_i18ngrep ! "$PREV_HEAD_DESC" "$1"
 }
 check_no_orphan_warning() {
-	test_i18ngrep ! "$ORPHAN_WARNING" "$1"
+	test_i18ngrep ! "$ORPHAN_WARNING" "$1" &&
+	test_i18ngrep "$PREV_HEAD_DESC" "$1"
 }
 
 reset () {
-- 
1.7.7

[PATCH 2/8] revision: factor out add_pending_sha1

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

This function is a combination of the static get_reference and
add_pending_object.  It can be used to easily queue objects by hash.

Signed-off-by: Rene Scharfe <redacted>
---
The next patch is going to use it in checkout.

 revision.c |   11 ++++++++---
 revision.h |    1 +
 2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/revision.c b/revision.c
index c46cfaa..2e8aa33 100644
--- a/revision.c
+++ b/revision.c
@@ -185,6 +185,13 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con
 	return object;
 }
 
+void add_pending_sha1(struct rev_info *revs, const char *name,
+		      const unsigned char *sha1, unsigned int flags)
+{
+	struct object *object = get_reference(revs, name, sha1, flags);
+	add_pending_object(revs, object, name);
+}
+
 static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
 {
 	unsigned long flags = object->flags;
@@ -832,9 +839,7 @@ struct all_refs_cb {
 static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
 	struct all_refs_cb *cb = cb_data;
-	struct object *object = get_reference(cb->all_revs, path, sha1,
-					      cb->all_flags);
-	add_pending_object(cb->all_revs, object, path);
+	add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags);
 	return 0;
 }
 
diff --git a/revision.h b/revision.h
index 3d64ada..4541265 100644
--- a/revision.h
+++ b/revision.h
@@ -191,6 +191,7 @@ extern void add_object(struct object *obj,
 		       const char *name);
 
 extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
+extern void add_pending_sha1(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags);
 
 extern void add_head_to_pending(struct rev_info *);
 
-- 
1.7.7

[PATCH 3/8] checkout: use add_pending_{object,sha1} in orphan check

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

Instead of building a list of textual arguments for setup_revisions, use
add_pending_object and add_pending_sha1 to queue the objects directly.
This is both faster and simpler.

Signed-off-by: Rene Scharfe <redacted>
---
 builtin/checkout.c |   39 ++++++++++++---------------------------
 1 files changed, 12 insertions(+), 27 deletions(-)
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 5e356a6..84e0cdc 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -588,24 +588,11 @@ static void update_refs_for_switch(struct checkout_opts *opts,
 		report_tracking(new);
 }
 
-struct rev_list_args {
-	int argc;
-	int alloc;
-	const char **argv;
-};
-
-static void add_one_rev_list_arg(struct rev_list_args *args, const char *s)
-{
-	ALLOC_GROW(args->argv, args->argc + 1, args->alloc);
-	args->argv[args->argc++] = s;
-}
-
-static int add_one_ref_to_rev_list_arg(const char *refname,
-				       const unsigned char *sha1,
-				       int flags,
-				       void *cb_data)
+static int add_pending_uninteresting_ref(const char *refname,
+					 const unsigned char *sha1,
+					 int flags, void *cb_data)
 {
-	add_one_rev_list_arg(cb_data, refname);
+	add_pending_sha1(cb_data, refname, sha1, flags | UNINTERESTING);
 	return 0;
 }
 
@@ -685,19 +672,17 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
  */
 static void orphaned_commit_warning(struct commit *commit)
 {
-	struct rev_list_args args = { 0, 0, NULL };
 	struct rev_info revs;
-
-	add_one_rev_list_arg(&args, "(internal)");
-	add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1));
-	add_one_rev_list_arg(&args, "--not");
-	for_each_ref(add_one_ref_to_rev_list_arg, &args);
-	add_one_rev_list_arg(&args, "--");
-	add_one_rev_list_arg(&args, NULL);
+	struct object *object = &commit->object;
 
 	init_revisions(&revs, NULL);
-	if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1)
-		die(_("internal error: only -- alone should have been left"));
+	setup_revisions(0, NULL, &revs, NULL);
+
+	object->flags &= ~UNINTERESTING;
+	add_pending_object(&revs, object, sha1_to_hex(object->sha1));
+
+	for_each_ref(add_pending_uninteresting_ref, &revs);
+
 	if (prepare_revision_walk(&revs))
 		die(_("internal error in revision walk"));
 	if (!(commit->object.flags & UNINTERESTING))
-- 
1.7.7

[PATCH 4/8] revision: add leak_pending flag

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

The new flag leak_pending in struct rev_info can be used to prevent
prepare_revision_walk from freeing the list of pending objects.  It
will still forget about them, so it really is leaked.  This behaviour
may look weird at first, but it can be useful if the pointer to the
list is saved before calling prepare_revision_walk.

Signed-off-by: Rene Scharfe <redacted>
---
The next three patches are going to use this flag.

 revision.c |    3 ++-
 revision.h |    1 +
 2 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/revision.c b/revision.c
index 2e8aa33..6d329b4 100644
--- a/revision.c
+++ b/revision.c
@@ -1974,7 +1974,8 @@ int prepare_revision_walk(struct rev_info *revs)
 		}
 		e++;
 	}
-	free(list);
+	if (!revs->leak_pending)
+		free(list);
 
 	if (revs->no_walk)
 		return 0;
diff --git a/revision.h b/revision.h
index 4541265..366a9b4 100644
--- a/revision.h
+++ b/revision.h
@@ -97,6 +97,7 @@ struct rev_info {
 			date_mode_explicit:1,
 			preserve_subject:1;
 	unsigned int	disable_stdin:1;
+	unsigned int	leak_pending:1;
 
 	enum date_mode date_mode;
 
-- 
1.7.7

[PATCH 5/8] bisect: use leak_pending flag

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

Instead of creating a copy of the list of pending objects, copy the
struct object_array that points to it, turn on leak_pending, and thus
cause prepare_revision_walk to leave it to us.  And free it once
we're done.

Signed-off-by: Rene Scharfe <redacted>
---
 bisect.c |   13 ++++++++-----
 1 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/bisect.c b/bisect.c
index c7b7d79..a05504f 100644
--- a/bisect.c
+++ b/bisect.c
@@ -831,12 +831,14 @@ static int check_ancestors(const char *prefix)
 	bisect_rev_setup(&revs, prefix, "^%s", "%s", 0);
 
 	/* Save pending objects, so they can be cleaned up later. */
-	memset(&pending_copy, 0, sizeof(pending_copy));
-	for (i = 0; i < revs.pending.nr; i++)
-		add_object_array(revs.pending.objects[i].item,
-				 revs.pending.objects[i].name,
-				 &pending_copy);
+	pending_copy = revs.pending;
+	revs.leak_pending = 1;
 
+	/*
+	 * bisect_common calls prepare_revision_walk right away, which
+	 * (together with .leak_pending = 1) makes us the sole owner of
+	 * the list of pending objects.
+	 */
 	bisect_common(&revs);
 	res = (revs.commits != NULL);
 
@@ -845,6 +847,7 @@ static int check_ancestors(const char *prefix)
 		struct object *o = pending_copy.objects[i].item;
 		clear_commit_marks((struct commit *)o, ALL_REV_FLAGS);
 	}
+	free(pending_copy.objects);
 
 	return res;
 }
-- 
1.7.7

[PATCH 6/8] bundle: use leak_pending flag

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

Instead of creating a copy of the list of pending objects, copy the
struct object_array that points to it, turn on leak_pending, and thus
cause prepare_revision_walk to leave it to us.  And free it once
we're done.

Signed-off-by: Rene Scharfe <redacted>
---
 bundle.c |    8 +++-----
 1 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/bundle.c b/bundle.c
index f48fd7d..26cc9ab 100644
--- a/bundle.c
+++ b/bundle.c
@@ -122,11 +122,8 @@ int verify_bundle(struct bundle_header *header, int verbose)
 	req_nr = revs.pending.nr;
 	setup_revisions(2, argv, &revs, NULL);
 
-	memset(&refs, 0, sizeof(struct object_array));
-	for (i = 0; i < revs.pending.nr; i++) {
-		struct object_array_entry *e = revs.pending.objects + i;
-		add_object_array(e->item, e->name, &refs);
-	}
+	refs = revs.pending;
+	revs.leak_pending = 1;
 
 	if (prepare_revision_walk(&revs))
 		die("revision walk setup failed");
@@ -146,6 +143,7 @@ int verify_bundle(struct bundle_header *header, int verbose)
 
 	for (i = 0; i < refs.nr; i++)
 		clear_commit_marks((struct commit *)refs.objects[i].item, -1);
+	free(refs.objects);
 
 	if (verbose) {
 		struct ref_list *r;
-- 
1.7.7

[PATCH 7/8] checkout: use leak_pending flag

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

Instead of going through all the references again when we clear the
commit marks, do it like bisect and bundle and gain ownership of the
list of pending objects which we constructed from those references.

We simply copy the struct object_array that points to the list, set
the flag leak_pending and then prepare_revision_walk won't destroy
it and it's ours.  We use it to clear the marks and  free it at the
end.

Signed-off-by: Rene Scharfe <redacted>
---
 builtin/checkout.c |   25 ++++++++++++-------------
 1 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 84e0cdc..cfd7e59 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -596,17 +596,6 @@ static int add_pending_uninteresting_ref(const char *refname,
 	return 0;
 }
 
-static int clear_commit_marks_from_one_ref(const char *refname,
-				      const unsigned char *sha1,
-				      int flags,
-				      void *cb_data)
-{
-	struct commit *commit = lookup_commit_reference_gently(sha1, 1);
-	if (commit)
-		clear_commit_marks(commit, -1);
-	return 0;
-}
-
 static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
 {
 	parse_commit(commit);
@@ -674,6 +663,8 @@ static void orphaned_commit_warning(struct commit *commit)
 {
 	struct rev_info revs;
 	struct object *object = &commit->object;
+	struct object_array refs;
+	unsigned int i;
 
 	init_revisions(&revs, NULL);
 	setup_revisions(0, NULL, &revs, NULL);
@@ -683,6 +674,9 @@ static void orphaned_commit_warning(struct commit *commit)
 
 	for_each_ref(add_pending_uninteresting_ref, &revs);
 
+	refs = revs.pending;
+	revs.leak_pending = 1;
+
 	if (prepare_revision_walk(&revs))
 		die(_("internal error in revision walk"));
 	if (!(commit->object.flags & UNINTERESTING))
@@ -690,8 +684,13 @@ static void orphaned_commit_warning(struct commit *commit)
 	else
 		describe_detached_head(_("Previous HEAD position was"), commit);
 
-	clear_commit_marks(commit, -1);
-	for_each_ref(clear_commit_marks_from_one_ref, NULL);
+	for (i = 0; i < refs.nr; i++) {
+		struct object *o = refs.objects[i].item;
+		struct commit *c = lookup_commit_reference_gently(o->sha1, 1);
+		if (c)
+			clear_commit_marks(c, ALL_REV_FLAGS);
+	}
+	free(refs.objects);
 }
 
 static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
-- 
1.7.7

[PATCH 8/8] commit: factor out clear_commit_marks_for_object_array

From: René Scharfe <hidden>
Date: 2016-06-15 22:52:09

Factor out the code to clear the commit marks for a whole struct
object_array from builtin/checkout.c into its own exported function
clear_commit_marks_for_object_array and use it in bisect and bundle
as well.  It handles tags and commits and ignores objects of any
other type.

Signed-off-by: Rene Scharfe <redacted>
---
 bisect.c           |    7 ++-----
 builtin/checkout.c |    8 +-------
 bundle.c           |    3 +--
 commit.c           |   14 ++++++++++++++
 commit.h           |    1 +
 5 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/bisect.c b/bisect.c
index a05504f..b4547b9 100644
--- a/bisect.c
+++ b/bisect.c
@@ -826,7 +826,7 @@ static int check_ancestors(const char *prefix)
 {
 	struct rev_info revs;
 	struct object_array pending_copy;
-	int i, res;
+	int res;
 
 	bisect_rev_setup(&revs, prefix, "^%s", "%s", 0);
 
@@ -843,10 +843,7 @@ static int check_ancestors(const char *prefix)
 	res = (revs.commits != NULL);
 
 	/* Clean up objects used, as they will be reused. */
-	for (i = 0; i < pending_copy.nr; i++) {
-		struct object *o = pending_copy.objects[i].item;
-		clear_commit_marks((struct commit *)o, ALL_REV_FLAGS);
-	}
+	clear_commit_marks_for_object_array(&pending_copy, ALL_REV_FLAGS);
 	free(pending_copy.objects);
 
 	return res;
diff --git a/builtin/checkout.c b/builtin/checkout.c
index cfd7e59..683819b 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -664,7 +664,6 @@ static void orphaned_commit_warning(struct commit *commit)
 	struct rev_info revs;
 	struct object *object = &commit->object;
 	struct object_array refs;
-	unsigned int i;
 
 	init_revisions(&revs, NULL);
 	setup_revisions(0, NULL, &revs, NULL);
@@ -684,12 +683,7 @@ static void orphaned_commit_warning(struct commit *commit)
 	else
 		describe_detached_head(_("Previous HEAD position was"), commit);
 
-	for (i = 0; i < refs.nr; i++) {
-		struct object *o = refs.objects[i].item;
-		struct commit *c = lookup_commit_reference_gently(o->sha1, 1);
-		if (c)
-			clear_commit_marks(c, ALL_REV_FLAGS);
-	}
+	clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
 	free(refs.objects);
 }
 
diff --git a/bundle.c b/bundle.c
index 26cc9ab..a8ea918 100644
--- a/bundle.c
+++ b/bundle.c
@@ -141,8 +141,7 @@ int verify_bundle(struct bundle_header *header, int verbose)
 				refs.objects[i].name);
 		}
 
-	for (i = 0; i < refs.nr; i++)
-		clear_commit_marks((struct commit *)refs.objects[i].item, -1);
+	clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
 	free(refs.objects);
 
 	if (verbose) {
diff --git a/commit.c b/commit.c
index 97b4327..50af007 100644
--- a/commit.c
+++ b/commit.c
@@ -430,6 +430,20 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)
 	}
 }
 
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
+{
+	struct object *object;
+	struct commit *commit;
+	unsigned int i;
+
+	for (i = 0; i < a->nr; i++) {
+		object = a->objects[i].item;
+		commit = lookup_commit_reference_gently(object->sha1, 1);
+		if (commit)
+			clear_commit_marks(commit, mark);
+	}
+}
+
 struct commit *pop_commit(struct commit_list **stack)
 {
 	struct commit_list *top = *stack;
diff --git a/commit.h b/commit.h
index 12d100b..0a4c730 100644
--- a/commit.h
+++ b/commit.h
@@ -126,6 +126,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
 struct commit *pop_commit(struct commit_list **stack);
 
 void clear_commit_marks(struct commit *commit, unsigned int mark);
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
 
 /*
  * Performs an in-place topological sort of list supplied.
-- 
1.7.7

Re: [PATCH 1/8] checkout: check for "Previous HEAD" notice in t2020

From: Sverre Rabbelier <hidden>
Date: 2016-06-15 22:52:09

Heya,

On Sat, Oct 1, 2011 at 17:38, René Scharfe [off-list ref] wrote:
If we leave a detached head, exactly one of two things happens: either
checkout warns about it being an orphan or describes it as a courtesy.
Test t2020 already checked that the warning is shown as needed.  This
patch also checks for the description.
A cover letter would have been nice for such a long series :).

-- 
Cheers,

Sverre Rabbelier
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help