git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/4] per-worktree bisection refs
@ 2015-08-26 19:46 David Turner
  2015-08-26 19:46 ` [PATCH v4 1/4] refs: clean up common_list David Turner
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: David Turner @ 2015-08-26 19:46 UTC (permalink / raw)
  To: git, mhagger, pclouds, sunshine

This reroll includes changes suggested by Duy Nguyen:

A. Path normalization (partial).
B. Rearrangement of common_list struct to make formatting prettier.

It also includes a test style fix suggested by Eric Sunshine and
others: a bogus test_must_fail on a non-git command has been replaced
by a two-step check.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v4 1/4] refs: clean up common_list
  2015-08-26 19:46 [PATCH v4 0/4] per-worktree bisection refs David Turner
@ 2015-08-26 19:46 ` David Turner
  2015-08-26 19:46 ` [PATCH v4 2/4] path: optimize common dir checking David Turner
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 13+ messages in thread
From: David Turner @ 2015-08-26 19:46 UTC (permalink / raw)
  To: git, mhagger, pclouds, sunshine; +Cc: David Turner

Instead of common_list having formatting like ! and /, use a struct to
hold common_list data in a structured form.

We don't use 'exclude' yet; instead, we keep the old codepath that
handles info/sparse-checkout and logs/HEAD.  Later, we will use exclude.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 path.c | 58 +++++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 37 insertions(+), 21 deletions(-)

diff --git a/path.c b/path.c
index 10f4cbf..d24bfa2 100644
--- a/path.c
+++ b/path.c
@@ -91,35 +91,51 @@ static void replace_dir(struct strbuf *buf, int len, const char *newdir)
 		buf->buf[newlen] = '/';
 }
 
-static const char *common_list[] = {
-	"/branches", "/hooks", "/info", "!/logs", "/lost-found",
-	"/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
-	"config", "!gc.pid", "packed-refs", "shallow",
-	NULL
+struct common_dir {
+	/* Not considered garbage for report_linked_checkout_garbage */
+	unsigned ignore_garbage:1;
+	unsigned is_dir:1;
+	/* Not common even though its parent is */
+	unsigned exclude:1;
+	const char *dirname;
+};
+
+struct common_dir common_list[] = {
+	{ 0, 1, 0, "branches" },
+	{ 0, 1, 0, "hooks" },
+	{ 0, 1, 0, "info" },
+	{ 0, 0, 1, "info/sparse-checkout" },
+	{ 1, 1, 0, "logs" },
+	{ 1, 1, 1, "logs/HEAD" },
+	{ 0, 1, 0, "lost-found" },
+	{ 0, 1, 0, "objects" },
+	{ 0, 1, 0, "refs" },
+	{ 0, 1, 0, "remotes" },
+	{ 0, 1, 0, "worktrees" },
+	{ 0, 1, 0, "rr-cache" },
+	{ 0, 1, 0, "svn" },
+	{ 0, 0, 0, "config" },
+	{ 1, 0, 0, "gc.pid" },
+	{ 0, 0, 0, "packed-refs" },
+	{ 0, 0, 0, "shallow" },
+	{ 0, 0, 0, NULL }
 };
 
 static void update_common_dir(struct strbuf *buf, int git_dir_len)
 {
 	char *base = buf->buf + git_dir_len;
-	const char **p;
+	const struct common_dir *p;
 
 	if (is_dir_file(base, "logs", "HEAD") ||
 	    is_dir_file(base, "info", "sparse-checkout"))
 		return;	/* keep this in $GIT_DIR */
-	for (p = common_list; *p; p++) {
-		const char *path = *p;
-		int is_dir = 0;
-		if (*path == '!')
-			path++;
-		if (*path == '/') {
-			path++;
-			is_dir = 1;
-		}
-		if (is_dir && dir_prefix(base, path)) {
+	for (p = common_list; p->dirname; p++) {
+		const char *path = p->dirname;
+		if (p->is_dir && dir_prefix(base, path)) {
 			replace_dir(buf, git_dir_len, get_git_common_dir());
 			return;
 		}
-		if (!is_dir && !strcmp(base, path)) {
+		if (!p->is_dir && !strcmp(base, path)) {
 			replace_dir(buf, git_dir_len, get_git_common_dir());
 			return;
 		}
@@ -129,16 +145,16 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len)
 void report_linked_checkout_garbage(void)
 {
 	struct strbuf sb = STRBUF_INIT;
-	const char **p;
+	const struct common_dir *p;
 	int len;
 
 	if (!git_common_dir_env)
 		return;
 	strbuf_addf(&sb, "%s/", get_git_dir());
 	len = sb.len;
-	for (p = common_list; *p; p++) {
-		const char *path = *p;
-		if (*path == '!')
+	for (p = common_list; p->dirname; p++) {
+		const char *path = p->dirname;
+		if (p->ignore_garbage)
 			continue;
 		strbuf_setlen(&sb, len);
 		strbuf_addstr(&sb, path);
-- 
2.4.2.622.gac67c30-twtrsrc

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v4 2/4] path: optimize common dir checking
  2015-08-26 19:46 [PATCH v4 0/4] per-worktree bisection refs David Turner
  2015-08-26 19:46 ` [PATCH v4 1/4] refs: clean up common_list David Turner
@ 2015-08-26 19:46 ` David Turner
  2015-08-26 21:15   ` Junio C Hamano
  2015-08-30  6:25   ` Torsten Bögershausen
  2015-08-26 19:46 ` [PATCH v4 3/4] refs: make refs/worktree/* per-worktree David Turner
  2015-08-26 19:46 ` [PATCH v4 4/4] bisect: make bisection refs per-worktree David Turner
  3 siblings, 2 replies; 13+ messages in thread
From: David Turner @ 2015-08-26 19:46 UTC (permalink / raw)
  To: git, mhagger, pclouds, sunshine; +Cc: David Turner

Instead of a linear search over common_list to check whether
a path is common, use a trie.  The trie search operates on
path prefixes, and handles excludes.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 path.c                | 226 ++++++++++++++++++++++++++++++++++++++++++++++----
 t/t0060-path-utils.sh |   1 +
 2 files changed, 213 insertions(+), 14 deletions(-)

diff --git a/path.c b/path.c
index d24bfa2..c7a4c40 100644
--- a/path.c
+++ b/path.c
@@ -121,25 +121,223 @@ struct common_dir common_list[] = {
 	{ 0, 0, 0, NULL }
 };
 
-static void update_common_dir(struct strbuf *buf, int git_dir_len)
+/*
+ * A compressed trie.  A trie node consists of zero or more characters that
+ * are common to all elements with this prefix, optionally followed by some
+ * children.  If value is not NULL, the trie node is a terminal node.
+ *
+ * For example, consider the following set of strings:
+ * abc
+ * def
+ * definite
+ * definition
+ *
+ * The trie would look look like:
+ * root: len = 0, value = (something), children a and d non-NULL.
+ *    a: len = 2, contents = bc
+ *    d: len = 2, contents = ef, children i non-NULL, value = (something)
+ *       i: len = 3, contents = nit, children e and i non-NULL, value = NULL
+ *           e: len = 0, children all NULL, value = (something)
+ *           i: len = 2, contents = on, children all NULL, value = (something)
+ */
+struct trie {
+	struct trie *children[256];
+	int len;
+	char *contents;
+	void *value;
+};
+
+static struct trie *make_trie_node(const char *key, void *value)
 {
-	char *base = buf->buf + git_dir_len;
-	const struct common_dir *p;
+	struct trie *new_node = xcalloc(1, sizeof(*new_node));
+	new_node->len = strlen(key);
+	if (new_node->len) {
+		new_node->contents = xmalloc(new_node->len);
+		memcpy(new_node->contents, key, new_node->len);
+	}
+	new_node->value = value;
+	return new_node;
+}
 
-	if (is_dir_file(base, "logs", "HEAD") ||
-	    is_dir_file(base, "info", "sparse-checkout"))
-		return;	/* keep this in $GIT_DIR */
-	for (p = common_list; p->dirname; p++) {
-		const char *path = p->dirname;
-		if (p->is_dir && dir_prefix(base, path)) {
-			replace_dir(buf, git_dir_len, get_git_common_dir());
-			return;
+/*
+ * Add a key/value pair to a trie.  The key is assumed to be \0-terminated.
+ * If there was an existing value for this key, return it.
+ */
+static void *add_to_trie(struct trie *root, const char *key, void *value)
+{
+	struct trie *child;
+	void *old;
+	int i;
+
+	if (!*key) {
+		/* we have reached the end of the key */
+		old = root->value;
+		root->value = value;
+		return old;
+	}
+
+	for (i = 0; i < root->len; ++i) {
+		if (root->contents[i] == key[i])
+			continue;
+
+		/*
+		 * Split this node: child will contain this node's
+		 * existing children.
+		 */
+		child = malloc(sizeof(*child));
+		memcpy(child->children, root->children, sizeof(root->children));
+
+		child->len = root->len - i - 1;
+		if (child->len) {
+			child->contents = strndup(root->contents + i + 1,
+						   child->len);
 		}
-		if (!p->is_dir && !strcmp(base, path)) {
-			replace_dir(buf, git_dir_len, get_git_common_dir());
-			return;
+		child->value = root->value;
+		root->value = NULL;
+		root->len = i;
+
+		memset(root->children, 0, sizeof(root->children));
+		root->children[(unsigned char)root->contents[i]] = child;
+
+		/* This is the newly-added child. */
+		root->children[(unsigned char)key[i]] =
+			make_trie_node(key + i + 1, value);
+		return NULL;
+	}
+
+	/* We have matched the entire compressed section */
+	if (key[i]) {
+		child = root->children[(unsigned char)key[root->len]];
+		if (child) {
+			return add_to_trie(child, key + root->len + 1, value);
+		} else {
+			child = make_trie_node(key + root->len + 1, value);
+			root->children[(unsigned char)key[root->len]] = child;
+			return NULL;
 		}
 	}
+
+	old = root->value;
+	root->value = value;
+	return old;
+}
+
+typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
+
+/*
+ * Search a trie for some key.  Find the longest /-or-\0-terminated
+ * prefix of the key for which the trie contains a value.  Call fn
+ * with the unmatched portion of the key and the found value, and
+ * return its return value.  If there is no such prefix, return -1.
+ *
+ * The key is partially normalized: consecutive slashes are skipped.
+ *
+ * For example, consider the trie containing only [refs,
+ * refs/worktree] (both with values).
+ *
+ * | key             | unmatched  | val from node | return value |
+ * |-----------------|------------|---------------|--------------|
+ * | a               | not called | n/a           | -1           |
+ * | refs            | \0         | refs          | as per fn    |
+ * | refs/           | /          | refs          | as per fn    |
+ * | refs/w          | /w         | refs          | as per fn    |
+ * | refs/worktree   | \0         | refs/worktree | as per fn    |
+ * | refs/worktree/  | /          | refs/worktree | as per fn    |
+ * | refs/worktree/a | /a         | refs/worktree | as per fn    |
+ * |-----------------|------------|---------------|--------------|
+ *
+ */
+static int trie_find(struct trie *root, const char *key, match_fn fn,
+		     void *baton)
+{
+	int i;
+	int result;
+	struct trie *child;
+
+	if (!*key) {
+		/* we have reached the end of the key */
+		if (root->value && !root->len)
+			return fn(key, root->value, baton);
+		else
+			return -1;
+	}
+
+	for (i = 0; i < root->len; ++i) {
+		/* Partial path normalization: skip consecutive slashes. */
+		if (key[i] == '/' && key[i+1] == '/') {
+			key++;
+			continue;
+		}
+		if (root->contents[i] != key[i])
+			return -1;
+	}
+
+	/* Matched the entire compressed section */
+	key += i;
+	if (!*key)
+		/* End of key */
+		return fn(key, root->value, baton);
+
+	/* Partial path normalization: skip consecutive slashes */
+	while (key[0] == '/' && key[1] == '/')
+		key ++;
+
+	child = root->children[(unsigned char)*key];
+	if (child)
+		result = trie_find(child, key + 1, fn, baton);
+	else
+		result = -1;
+
+	if (result >= 0 || (*key != '/' && *key != 0))
+		return result;
+	if (root->value)
+		return fn(key, root->value, baton);
+	else
+		return -1;
+}
+
+static struct trie common_trie;
+static int common_trie_done_setup;
+
+static void init_common_trie(void)
+{
+	struct common_dir *p;
+
+	if (common_trie_done_setup)
+		return;
+
+	for (p = common_list; p->dirname; p++)
+		add_to_trie(&common_trie, p->dirname, p);
+
+	common_trie_done_setup = 1;
+}
+
+/*
+ * Helper function for update_common_dir: returns 1 if the dir
+ * prefix is common.
+ */
+static int check_common(const char *unmatched, void *value, void *baton)
+{
+	struct common_dir *dir = value;
+
+	if (!dir)
+		return 0;
+
+	if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
+		return !dir->exclude;
+
+	if (!dir->is_dir && unmatched[0] == 0)
+		return !dir->exclude;
+
+	return 0;
+}
+
+static void update_common_dir(struct strbuf *buf, int git_dir_len)
+{
+	char *base = buf->buf + git_dir_len;
+	init_common_trie();
+	if (trie_find(&common_trie, base, check_common, NULL) > 0)
+		replace_dir(buf, git_dir_len, get_git_common_dir());
 }
 
 void report_linked_checkout_garbage(void)
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 93605f4..1ca49e1 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -271,6 +271,7 @@ test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
 test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
 test_git_path GIT_COMMON_DIR=bar info/grafts              bar/info/grafts
 test_git_path GIT_COMMON_DIR=bar info/sparse-checkout     .git/info/sparse-checkout
+test_git_path GIT_COMMON_DIR=bar info//sparse-checkout    .git/info//sparse-checkout
 test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
 test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
 test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master
-- 
2.4.2.622.gac67c30-twtrsrc

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v4 3/4] refs: make refs/worktree/* per-worktree
  2015-08-26 19:46 [PATCH v4 0/4] per-worktree bisection refs David Turner
  2015-08-26 19:46 ` [PATCH v4 1/4] refs: clean up common_list David Turner
  2015-08-26 19:46 ` [PATCH v4 2/4] path: optimize common dir checking David Turner
@ 2015-08-26 19:46 ` David Turner
  2015-08-26 19:46 ` [PATCH v4 4/4] bisect: make bisection refs per-worktree David Turner
  3 siblings, 0 replies; 13+ messages in thread
From: David Turner @ 2015-08-26 19:46 UTC (permalink / raw)
  To: git, mhagger, pclouds, sunshine; +Cc: David Turner

We need a place to stick refs for bisects in progress that is not
shared between worktrees.  So we use the refs/worktree/ hierarchy.

The is_per_worktree_ref function and associated docs learn that
refs/worktree/ is per-worktree, as does the git_path code in path.c

The ref-packing functions learn that per-worktree refs should not be
packed (since packed-refs is common rather than per-worktree).

Since refs/worktree is per-worktree, logs/refs/worktree should be too.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 Documentation/glossary-content.txt |  5 +++--
 path.c                             |  2 ++
 refs.c                             | 32 +++++++++++++++++++++++++++++++-
 t/t0060-path-utils.sh              |  5 +++++
 t/t1400-update-ref.sh              | 19 +++++++++++++++++++
 t/t3210-pack-refs.sh               |  7 +++++++
 6 files changed, 67 insertions(+), 3 deletions(-)

diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index 8c6478b..5c707e6 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -413,8 +413,9 @@ exclude;;
 
 [[def_per_worktree_ref]]per-worktree ref::
 	Refs that are per-<<def_working_tree,worktree>>, rather than
-	global.  This is presently only <<def_HEAD,HEAD>>, but might
-	later include other unusual refs.
+	global.  This is presently only <<def_HEAD,HEAD>> and any refs
+	that start with `refs/worktree/`, but might later include other
+	unusual refs.
 
 [[def_pseudoref]]pseudoref::
 	Pseudorefs are a class of files under `$GIT_DIR` which behave
diff --git a/path.c b/path.c
index c7a4c40..acac7b1 100644
--- a/path.c
+++ b/path.c
@@ -107,9 +107,11 @@ struct common_dir common_list[] = {
 	{ 0, 0, 1, "info/sparse-checkout" },
 	{ 1, 1, 0, "logs" },
 	{ 1, 1, 1, "logs/HEAD" },
+	{ 0, 1, 1,  "logs/refs/worktree" },
 	{ 0, 1, 0, "lost-found" },
 	{ 0, 1, 0, "objects" },
 	{ 0, 1, 0, "refs" },
+	{ 0, 1, 1, "refs/worktree", },
 	{ 0, 1, 0, "remotes" },
 	{ 0, 1, 0, "worktrees" },
 	{ 0, 1, 0, "rr-cache" },
diff --git a/refs.c b/refs.c
index e6fc3fe..30331bc 100644
--- a/refs.c
+++ b/refs.c
@@ -298,6 +298,11 @@ struct ref_entry {
 };
 
 static void read_loose_refs(const char *dirname, struct ref_dir *dir);
+static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len);
+static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache,
+					  const char *dirname, size_t len,
+					  int incomplete);
+static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
 
 static struct ref_dir *get_ref_dir(struct ref_entry *entry)
 {
@@ -306,6 +311,24 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)
 	dir = &entry->u.subdir;
 	if (entry->flag & REF_INCOMPLETE) {
 		read_loose_refs(entry->name, dir);
+
+		/*
+		 * Manually add refs/worktree, which, being
+		 * per-worktree, might not appear in the directory
+		 * listing for refs/ in the main repo.
+		 */
+		if (!strcmp(entry->name, "refs/")) {
+			int pos = search_ref_dir(dir, "refs/worktree/", 14);
+			if (pos < 0) {
+				struct ref_entry *child_entry;
+				child_entry = create_dir_entry(dir->ref_cache,
+							       "refs/worktree/",
+							       14, 1);
+				add_entry_to_dir(dir, child_entry);
+				read_loose_refs("refs/worktree",
+						&child_entry->u.subdir);
+			}
+		}
 		entry->flag &= ~REF_INCOMPLETE;
 	}
 	return dir;
@@ -2643,6 +2666,8 @@ struct pack_refs_cb_data {
 	struct ref_to_prune *ref_to_prune;
 };
 
+static int is_per_worktree_ref(const char *refname);
+
 /*
  * An each_ref_entry_fn that is run over loose references only.  If
  * the loose reference can be packed, add an entry in the packed ref
@@ -2656,6 +2681,10 @@ static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data)
 	struct ref_entry *packed_entry;
 	int is_tag_ref = starts_with(entry->name, "refs/tags/");
 
+	/* Do not pack per-worktree refs: */
+	if (is_per_worktree_ref(entry->name))
+		return 0;
+
 	/* ALWAYS pack tags */
 	if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
 		return 0;
@@ -2850,7 +2879,8 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
 
 static int is_per_worktree_ref(const char *refname)
 {
-	return !strcmp(refname, "HEAD");
+	return !strcmp(refname, "HEAD") ||
+		starts_with(refname, "refs/worktree/");
 }
 
 static int is_pseudoref_syntax(const char *refname)
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 1ca49e1..b2d2005 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -266,6 +266,10 @@ test_expect_success 'setup common repository' 'git --git-dir=bar init'
 test_git_path GIT_COMMON_DIR=bar index                    .git/index
 test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/refs/worktree/foo   .git/logs/refs/worktree/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs/worktre/foo    bar/logs/refs/worktre/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs/worktre        bar/logs/refs/worktre
+test_git_path GIT_COMMON_DIR=bar logs/refs/worktreefoo    bar/logs/refs/worktreefoo
 test_git_path GIT_COMMON_DIR=bar objects                  bar/objects
 test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
 test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
@@ -276,6 +280,7 @@ test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
 test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
 test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master
 test_git_path GIT_COMMON_DIR=bar refs/heads/master        bar/refs/heads/master
+test_git_path GIT_COMMON_DIR=bar refs/worktree/foo        .git/refs/worktree/foo
 test_git_path GIT_COMMON_DIR=bar hooks/me                 bar/hooks/me
 test_git_path GIT_COMMON_DIR=bar config                   bar/config
 test_git_path GIT_COMMON_DIR=bar packed-refs              bar/packed-refs
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 9d21c19..6dc29e6 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -1131,4 +1131,23 @@ test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches
 )
 '
 
+test_expect_success 'handle per-worktree refs in refs/worktree' '
+	git commit --allow-empty -m "initial commit" &&
+	git worktree add -b branch worktree &&
+	(
+		cd worktree &&
+		git commit --allow-empty -m "test commit"  &&
+		git for-each-ref >for-each-ref.out &&
+		! grep refs/worktree for-each-ref.out &&
+		git update-ref refs/worktree/something HEAD &&
+		git rev-parse refs/worktree/something >../worktree-head &&
+		git for-each-ref | grep refs/worktree/something
+	) &&
+	test_path_is_missing .git/refs/worktree &&
+	test_must_fail git rev-parse refs/worktree/something &&
+	git update-ref refs/worktree/something HEAD &&
+	git rev-parse refs/worktree/something >main-head &&
+	! test_cmp main-head worktree-head
+'
+
 test_done
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
index 8aae98d..c54cd29 100755
--- a/t/t3210-pack-refs.sh
+++ b/t/t3210-pack-refs.sh
@@ -160,6 +160,13 @@ test_expect_success 'pack ref directly below refs/' '
 	test_path_is_missing .git/refs/top
 '
 
+test_expect_success 'do not pack ref in refs/worktree' '
+	git update-ref refs/worktree/local HEAD &&
+	git pack-refs --all --prune &&
+	! grep refs/worktree/local .git/packed-refs >/dev/null &&
+	test_path_is_file .git/refs/worktree/local
+'
+
 test_expect_success 'disable reflogs' '
 	git config core.logallrefupdates false &&
 	rm -rf .git/logs
-- 
2.4.2.622.gac67c30-twtrsrc

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v4 4/4] bisect: make bisection refs per-worktree
  2015-08-26 19:46 [PATCH v4 0/4] per-worktree bisection refs David Turner
                   ` (2 preceding siblings ...)
  2015-08-26 19:46 ` [PATCH v4 3/4] refs: make refs/worktree/* per-worktree David Turner
@ 2015-08-26 19:46 ` David Turner
  3 siblings, 0 replies; 13+ messages in thread
From: David Turner @ 2015-08-26 19:46 UTC (permalink / raw)
  To: git, mhagger, pclouds, sunshine; +Cc: David Turner

Using the new refs/worktree/ refs, make bisection per-worktree.

Signed-off-by: David Turner <dturner@twopensource.com>
---
 Documentation/git-bisect.txt       |  4 ++--
 Documentation/rev-list-options.txt | 14 +++++++-------
 bisect.c                           |  2 +-
 builtin/rev-parse.c                |  6 ++++--
 git-bisect.sh                      | 14 +++++++-------
 revision.c                         |  2 +-
 t/t6030-bisect-porcelain.sh        | 20 ++++++++++----------
 7 files changed, 32 insertions(+), 30 deletions(-)

diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
index e97f2de..f0c52d1 100644
--- a/Documentation/git-bisect.txt
+++ b/Documentation/git-bisect.txt
@@ -82,7 +82,7 @@ to ask for the next commit that needs testing.
 
 Eventually there will be no more revisions left to inspect, and the
 command will print out a description of the first bad commit. The
-reference `refs/bisect/bad` will be left pointing at that commit.
+reference `refs/worktree/bisect/bad` will be left pointing at that commit.
 
 
 Bisect reset
@@ -373,7 +373,7 @@ on a single line.
 ------------
 $ git bisect start HEAD <known-good-commit> [ <boundary-commit> ... ] --no-checkout
 $ git bisect run sh -c '
-	GOOD=$(git for-each-ref "--format=%(objectname)" refs/bisect/good-*) &&
+	GOOD=$(git for-each-ref "--format=%(objectname)" refs/worktree/bisect/good-*) &&
 	git rev-list --objects BISECT_HEAD --not $GOOD >tmp.$$ &&
 	git pack-objects --stdout >/dev/null <tmp.$$
 	rc=$?
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index a9b808f..1175960 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -183,9 +183,9 @@ explicitly.
 
 ifndef::git-rev-list[]
 --bisect::
-	Pretend as if the bad bisection ref `refs/bisect/bad`
+	Pretend as if the bad bisection ref `refs/worktree/bisect/bad`
 	was listed and as if it was followed by `--not` and the good
-	bisection refs `refs/bisect/good-*` on the command
+	bisection refs `refs/worktree/bisect/good-*` on the command
 	line. Cannot be combined with --first-parent.
 endif::git-rev-list[]
 
@@ -548,10 +548,10 @@ Bisection Helpers
 --bisect::
 	Limit output to the one commit object which is roughly halfway between
 	included and excluded commits. Note that the bad bisection ref
-	`refs/bisect/bad` is added to the included commits (if it
-	exists) and the good bisection refs `refs/bisect/good-*` are
+	`refs/worktree/bisect/bad` is added to the included commits (if it
+	exists) and the good bisection refs `refs/worktree/bisect/good-*` are
 	added to the excluded commits (if they exist). Thus, supposing there
-	are no refs in `refs/bisect/`, if
+	are no refs in `refs/worktree/bisect/`, if
 +
 -----------------------------------------------------------------------
 	$ git rev-list --bisect foo ^bar ^baz
@@ -571,7 +571,7 @@ one. Cannot be combined with --first-parent.
 
 --bisect-vars::
 	This calculates the same as `--bisect`, except that refs in
-	`refs/bisect/` are not used, and except that this outputs
+	`refs/worktree/bisect/` are not used, and except that this outputs
 	text ready to be eval'ed by the shell. These lines will assign the
 	name of the midpoint revision to the variable `bisect_rev`, and the
 	expected number of commits to be tested after `bisect_rev` is tested
@@ -584,7 +584,7 @@ one. Cannot be combined with --first-parent.
 --bisect-all::
 	This outputs all the commit objects between the included and excluded
 	commits, ordered by their distance to the included and excluded
-	commits. Refs in `refs/bisect/` are not used. The farthest
+	commits. Refs in `refs/worktree/bisect/` are not used. The farthest
 	from them is displayed first. (This is the only one displayed by
 	`--bisect`.)
 +
diff --git a/bisect.c b/bisect.c
index 33ac88d..dbe3461 100644
--- a/bisect.c
+++ b/bisect.c
@@ -425,7 +425,7 @@ static int register_ref(const char *refname, const struct object_id *oid,
 
 static int read_bisect_refs(void)
 {
-	return for_each_ref_in("refs/bisect/", register_ref, NULL);
+	return for_each_ref_in("refs/worktree/bisect/", register_ref, NULL);
 }
 
 static void read_bisect_paths(struct argv_array *array)
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 02d747d..3240ddf 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -663,8 +663,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 				continue;
 			}
 			if (!strcmp(arg, "--bisect")) {
-				for_each_ref_in("refs/bisect/bad", show_reference, NULL);
-				for_each_ref_in("refs/bisect/good", anti_reference, NULL);
+				for_each_ref_in("refs/worktree/bisect/bad",
+						show_reference, NULL);
+				for_each_ref_in("refs/worktree/bisect/good",
+						anti_reference, NULL);
 				continue;
 			}
 			if (starts_with(arg, "--branches=")) {
diff --git a/git-bisect.sh b/git-bisect.sh
index ea63223..c16433d 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -210,7 +210,7 @@ bisect_write() {
 		*)
 			die "$(eval_gettext "Bad bisect_write argument: \$state")" ;;
 	esac
-	git update-ref "refs/bisect/$tag" "$rev" || exit
+	git update-ref "refs/worktree/bisect/$tag" "$rev" || exit
 	echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG"
 	test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG"
 }
@@ -282,8 +282,8 @@ bisect_state() {
 
 bisect_next_check() {
 	missing_good= missing_bad=
-	git show-ref -q --verify refs/bisect/$TERM_BAD || missing_bad=t
-	test -n "$(git for-each-ref "refs/bisect/$TERM_GOOD-*")" || missing_good=t
+	git show-ref -q --verify refs/worktree/bisect/$TERM_BAD || missing_bad=t
+	test -n "$(git for-each-ref "refs/worktree/bisect/$TERM_GOOD-*")" || missing_good=t
 
 	case "$missing_good,$missing_bad,$1" in
 	,,*)
@@ -341,15 +341,15 @@ bisect_next() {
 	# Check if we should exit because bisection is finished
 	if test $res -eq 10
 	then
-		bad_rev=$(git show-ref --hash --verify refs/bisect/$TERM_BAD)
+		bad_rev=$(git show-ref --hash --verify refs/worktree/bisect/$TERM_BAD)
 		bad_commit=$(git show-branch $bad_rev)
 		echo "# first $TERM_BAD commit: $bad_commit" >>"$GIT_DIR/BISECT_LOG"
 		exit 0
 	elif test $res -eq 2
 	then
 		echo "# only skipped commits left to test" >>"$GIT_DIR/BISECT_LOG"
-		good_revs=$(git for-each-ref --format="%(objectname)" "refs/bisect/$TERM_GOOD-*")
-		for skipped in $(git rev-list refs/bisect/$TERM_BAD --not $good_revs)
+		good_revs=$(git for-each-ref --format="%(objectname)" "refs/worktree/bisect/$TERM_GOOD-*")
+		for skipped in $(git rev-list refs/worktree/bisect/$TERM_BAD --not $good_revs)
 		do
 			skipped_commit=$(git show-branch $skipped)
 			echo "# possible first $TERM_BAD commit: $skipped_commit" >>"$GIT_DIR/BISECT_LOG"
@@ -412,7 +412,7 @@ Try 'git bisect reset <commit>'.")"
 
 bisect_clean_state() {
 	# There may be some refs packed during bisection.
-	git for-each-ref --format='%(refname) %(objectname)' refs/bisect/\* |
+	git for-each-ref --format='%(refname) %(objectname)' refs/worktree/bisect/\* |
 	while read ref hash
 	do
 		git update-ref -d $ref $hash || exit
diff --git a/revision.c b/revision.c
index b6b2cf7..974a11f 100644
--- a/revision.c
+++ b/revision.c
@@ -2084,7 +2084,7 @@ extern void read_bisect_terms(const char **bad, const char **good);
 static int for_each_bisect_ref(const char *submodule, each_ref_fn fn, void *cb_data, const char *term) {
 	struct strbuf bisect_refs = STRBUF_INIT;
 	int status;
-	strbuf_addf(&bisect_refs, "refs/bisect/%s", term);
+	strbuf_addf(&bisect_refs, "refs/worktree/bisect/%s", term);
 	status = for_each_ref_in_submodule(submodule, bisect_refs.buf, fn, cb_data);
 	strbuf_release(&bisect_refs);
 	return status;
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 9e2c203..bfd5463 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -68,7 +68,7 @@ test_expect_success 'bisect fails if given any junk instead of revs' '
 	git bisect reset &&
 	test_must_fail git bisect start foo $HASH1 -- &&
 	test_must_fail git bisect start $HASH4 $HASH1 bar -- &&
-	test -z "$(git for-each-ref "refs/bisect/*")" &&
+	test -z "$(git for-each-ref "refs/worktree/bisect/*")" &&
 	test -z "$(ls .git/BISECT_* 2>/dev/null)" &&
 	git bisect start &&
 	test_must_fail git bisect good foo $HASH1 &&
@@ -77,7 +77,7 @@ test_expect_success 'bisect fails if given any junk instead of revs' '
 	test_must_fail git bisect bad $HASH3 $HASH4 &&
 	test_must_fail git bisect skip bar $HASH3 &&
 	test_must_fail git bisect skip $HASH1 foo &&
-	test -z "$(git for-each-ref "refs/bisect/*")" &&
+	test -z "$(git for-each-ref "refs/worktree/bisect/*")" &&
 	git bisect good $HASH1 &&
 	git bisect bad $HASH4
 '
@@ -115,7 +115,7 @@ test_expect_success 'bisect reset removes packed refs' '
 	git pack-refs --all --prune &&
 	git bisect next &&
 	git bisect reset &&
-	test -z "$(git for-each-ref "refs/bisect/*")" &&
+	test -z "$(git for-each-ref "refs/worktree/bisect/*")" &&
 	test -z "$(git for-each-ref "refs/heads/bisect")"
 '
 
@@ -126,7 +126,7 @@ test_expect_success 'bisect reset removes bisect state after --no-checkout' '
 	git bisect bad $HASH3 &&
 	git bisect next &&
 	git bisect reset &&
-	test -z "$(git for-each-ref "refs/bisect/*")" &&
+	test -z "$(git for-each-ref "refs/worktree/bisect/*")" &&
 	test -z "$(git for-each-ref "refs/heads/bisect")" &&
 	test -z "$(git for-each-ref "BISECT_HEAD")"
 '
@@ -176,7 +176,7 @@ test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' '
 	git branch > branch.output &&
 	grep "* other" branch.output > /dev/null &&
 	test_must_fail test -e .git/BISECT_START &&
-	test -z "$(git for-each-ref "refs/bisect/*")" &&
+	test -z "$(git for-each-ref "refs/worktree/bisect/*")" &&
 	git checkout HEAD hello
 '
 
@@ -671,7 +671,7 @@ test_expect_success 'bisect: --no-checkout - target before breakage' '
 	git bisect bad BISECT_HEAD &&
 	check_same BROKEN_HASH5 BISECT_HEAD &&
 	git bisect bad BISECT_HEAD &&
-	check_same BROKEN_HASH5 bisect/bad &&
+	check_same BROKEN_HASH5 refs/worktree/bisect/bad &&
 	git bisect reset
 '
 
@@ -682,7 +682,7 @@ test_expect_success 'bisect: --no-checkout - target in breakage' '
 	git bisect bad BISECT_HEAD &&
 	check_same BROKEN_HASH5 BISECT_HEAD &&
 	git bisect good BISECT_HEAD &&
-	check_same BROKEN_HASH6 bisect/bad &&
+	check_same BROKEN_HASH6 refs/worktree/bisect/bad &&
 	git bisect reset
 '
 
@@ -693,7 +693,7 @@ test_expect_success 'bisect: --no-checkout - target after breakage' '
 	git bisect good BISECT_HEAD &&
 	check_same BROKEN_HASH8 BISECT_HEAD &&
 	git bisect good BISECT_HEAD &&
-	check_same BROKEN_HASH9 bisect/bad &&
+	check_same BROKEN_HASH9 refs/worktree/bisect/bad &&
 	git bisect reset
 '
 
@@ -702,13 +702,13 @@ test_expect_success 'bisect: demonstrate identification of damage boundary' "
 	git checkout broken &&
 	git bisect start broken master --no-checkout &&
 	git bisect run \"\$SHELL_PATH\" -c '
-		GOOD=\$(git for-each-ref \"--format=%(objectname)\" refs/bisect/good-*) &&
+		GOOD=\$(git for-each-ref \"--format=%(objectname)\" refs/worktree/bisect/good-*) &&
 		git rev-list --objects BISECT_HEAD --not \$GOOD >tmp.\$\$ &&
 		git pack-objects --stdout >/dev/null < tmp.\$\$
 		rc=\$?
 		rm -f tmp.\$\$
 		test \$rc = 0' &&
-	check_same BROKEN_HASH6 bisect/bad &&
+	check_same BROKEN_HASH6 refs/worktree/bisect/bad &&
 	git bisect reset
 "
 
-- 
2.4.2.622.gac67c30-twtrsrc

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] path: optimize common dir checking
  2015-08-26 19:46 ` [PATCH v4 2/4] path: optimize common dir checking David Turner
@ 2015-08-26 21:15   ` Junio C Hamano
  2015-08-26 22:10     ` David Turner
  2015-08-30  6:25   ` Torsten Bögershausen
  1 sibling, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2015-08-26 21:15 UTC (permalink / raw)
  To: David Turner; +Cc: git, mhagger, pclouds, sunshine

David Turner <dturner@twopensource.com> writes:

> Instead of a linear search over common_list to check whether
> a path is common, use a trie.  The trie search operates on
> path prefixes, and handles excludes.
>
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  path.c                | 226 ++++++++++++++++++++++++++++++++++++++++++++++----
>  t/t0060-path-utils.sh |   1 +
>  2 files changed, 213 insertions(+), 14 deletions(-)
>
> diff --git a/path.c b/path.c
> index d24bfa2..c7a4c40 100644
> --- a/path.c
> +++ b/path.c
> @@ -121,25 +121,223 @@ struct common_dir common_list[] = {
>  	{ 0, 0, 0, NULL }
>  };
>  
> -static void update_common_dir(struct strbuf *buf, int git_dir_len)
> +/*
> + * A compressed trie.  A trie node consists of zero or more characters that
> + * are common to all elements with this prefix, optionally followed by some
> + * children.  If value is not NULL, the trie node is a terminal node.
> + *
> + * For example, consider the following set of strings:
> + * abc
> + * def
> + * definite
> + * definition
> + *
> + * The trie would look look like:
> + * root: len = 0, value = (something), children a and d non-NULL.

"value = NULL", as there is no empty string registered in the trie?

> + *    a: len = 2, contents = bc

"value = NULL" here, too (just showing I am following along, not
just skimming)?

> + *    d: len = 2, contents = ef, children i non-NULL, value = (something)
> + *       i: len = 3, contents = nit, children e and i non-NULL, value = NULL
> + *           e: len = 0, children all NULL, value = (something)
> + *           i: len = 2, contents = on, children all NULL, value = (something)
> + */
> +struct trie {
> +	struct trie *children[256];
> +	int len;
> +	char *contents;
> +	void *value;
> +};
> +
> +static struct trie *make_trie_node(const char *key, void *value)
>  {
> -	char *base = buf->buf + git_dir_len;
> -	const struct common_dir *p;
> +	struct trie *new_node = xcalloc(1, sizeof(*new_node));
> +	new_node->len = strlen(key);
> +	if (new_node->len) {
> +		new_node->contents = xmalloc(new_node->len);
> +		memcpy(new_node->contents, key, new_node->len);
> +	}
> +	new_node->value = value;
> +	return new_node;
> +}
>  
> -	if (is_dir_file(base, "logs", "HEAD") ||
> -	    is_dir_file(base, "info", "sparse-checkout"))
> -		return;	/* keep this in $GIT_DIR */
> -	for (p = common_list; p->dirname; p++) {
> -		const char *path = p->dirname;
> -		if (p->is_dir && dir_prefix(base, path)) {
> -			replace_dir(buf, git_dir_len, get_git_common_dir());
> -			return;
> +/*
> + * Add a key/value pair to a trie.  The key is assumed to be \0-terminated.
> + * If there was an existing value for this key, return it.
> + */
> +static void *add_to_trie(struct trie *root, const char *key, void *value)
> +{
> +	struct trie *child;
> +	void *old;
> +	int i;
> +
> +	if (!*key) {
> +		/* we have reached the end of the key */
> +		old = root->value;
> +		root->value = value;
> +		return old;
> +	}
> +
> +	for (i = 0; i < root->len; ++i) {
> +		if (root->contents[i] == key[i])
> +			continue;
> +
> +		/*
> +		 * Split this node: child will contain this node's
> +		 * existing children.
> +		 */
> +		child = malloc(sizeof(*child));
> +		memcpy(child->children, root->children, sizeof(root->children));
> +
> +		child->len = root->len - i - 1;
> +		if (child->len) {
> +			child->contents = strndup(root->contents + i + 1,
> +						   child->len);
>  		}
> -		if (!p->is_dir && !strcmp(base, path)) {
> -			replace_dir(buf, git_dir_len, get_git_common_dir());
> -			return;
> +		child->value = root->value;
> +		root->value = NULL;
> +		root->len = i;
> +
> +		memset(root->children, 0, sizeof(root->children));
> +		root->children[(unsigned char)root->contents[i]] = child;
> +
> +		/* This is the newly-added child. */
> +		root->children[(unsigned char)key[i]] =
> +			make_trie_node(key + i + 1, value);
> +		return NULL;
> +	}
> +
> +	/* We have matched the entire compressed section */
> +	if (key[i]) {
> +		child = root->children[(unsigned char)key[root->len]];
> +		if (child) {
> +			return add_to_trie(child, key + root->len + 1, value);
> +		} else {
> +			child = make_trie_node(key + root->len + 1, value);
> +			root->children[(unsigned char)key[root->len]] = child;
> +			return NULL;
>  		}
>  	}
> +
> +	old = root->value;
> +	root->value = value;
> +	return old;
> +}
> +
> +typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
> +
> +/*
> + * Search a trie for some key.  Find the longest /-or-\0-terminated
> + * prefix of the key for which the trie contains a value.  Call fn
> + * with the unmatched portion of the key and the found value, and
> + * return its return value.  If there is no such prefix, return -1.
> + *
> + * The key is partially normalized: consecutive slashes are skipped.
> + *
> + * For example, consider the trie containing only [refs,
> + * refs/worktree] (both with values).
> + *
> + * | key             | unmatched  | val from node | return value |
> + * |-----------------|------------|---------------|--------------|
> + * | a               | not called | n/a           | -1           |
> + * | refs            | \0         | refs          | as per fn    |
> + * | refs/           | /          | refs          | as per fn    |
> + * | refs/w          | /w         | refs          | as per fn    |
> + * | refs/worktree   | \0         | refs/worktree | as per fn    |
> + * | refs/worktree/  | /          | refs/worktree | as per fn    |
> + * | refs/worktree/a | /a         | refs/worktree | as per fn    |
> + * |-----------------|------------|---------------|--------------|
> + *
> + */
> +static int trie_find(struct trie *root, const char *key, match_fn fn,
> +		     void *baton)
> +{
> +	int i;
> +	int result;
> +	struct trie *child;
> +
> +	if (!*key) {
> +		/* we have reached the end of the key */
> +		if (root->value && !root->len)
> +			return fn(key, root->value, baton);
> +		else
> +			return -1;
> +	}
> +
> +	for (i = 0; i < root->len; ++i) {
> +		/* Partial path normalization: skip consecutive slashes. */
> +		if (key[i] == '/' && key[i+1] == '/') {
> +			key++;
> +			continue;
> +		}
> +		if (root->contents[i] != key[i])
> +			return -1;
> +	}
> +
> +	/* Matched the entire compressed section */
> +	key += i;
> +	if (!*key)
> +		/* End of key */
> +		return fn(key, root->value, baton);
> +
> +	/* Partial path normalization: skip consecutive slashes */
> +	while (key[0] == '/' && key[1] == '/')
> +		key ++;

These "skipping consecutive slashes" inside this function is cute.
I would have expected me to react "Eek, Ugly" to it, but somehow I
didn't find them ugly.

> +	child = root->children[(unsigned char)*key];
> +	if (child)
> +		result = trie_find(child, key + 1, fn, baton);
> +	else
> +		result = -1;
> +
> +	if (result >= 0 || (*key != '/' && *key != 0))
> +		return result;
> +	if (root->value)
> +		return fn(key, root->value, baton);
> +	else
> +		return -1;
> +}
> +
> +static struct trie common_trie;
> +static int common_trie_done_setup;
> +
> +static void init_common_trie(void)
> +{
> +	struct common_dir *p;
> +
> +	if (common_trie_done_setup)
> +		return;
> +
> +	for (p = common_list; p->dirname; p++)
> +		add_to_trie(&common_trie, p->dirname, p);
> +
> +	common_trie_done_setup = 1;
> +}

I somehow wonder if we'd want to precomute the trie at the build
time, though ;-)

> +/*
> + * Helper function for update_common_dir: returns 1 if the dir
> + * prefix is common.
> + */
> +static int check_common(const char *unmatched, void *value, void *baton)
> +{
> +	struct common_dir *dir = value;
> +
> +	if (!dir)
> +		return 0;
> +
> +	if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
> +		return !dir->exclude;
> +
> +	if (!dir->is_dir && unmatched[0] == 0)
> +		return !dir->exclude;
> +
> +	return 0;
> +}
> +
> +static void update_common_dir(struct strbuf *buf, int git_dir_len)
> +{
> +	char *base = buf->buf + git_dir_len;
> +	init_common_trie();
> +	if (trie_find(&common_trie, base, check_common, NULL) > 0)
> +		replace_dir(buf, git_dir_len, get_git_common_dir());
>  }

Thanks for a pleasant read.

>  
>  void report_linked_checkout_garbage(void)
> diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
> index 93605f4..1ca49e1 100755
> --- a/t/t0060-path-utils.sh
> +++ b/t/t0060-path-utils.sh
> @@ -271,6 +271,7 @@ test_git_path GIT_COMMON_DIR=bar objects/bar              bar/objects/bar
>  test_git_path GIT_COMMON_DIR=bar info/exclude             bar/info/exclude
>  test_git_path GIT_COMMON_DIR=bar info/grafts              bar/info/grafts
>  test_git_path GIT_COMMON_DIR=bar info/sparse-checkout     .git/info/sparse-checkout
> +test_git_path GIT_COMMON_DIR=bar info//sparse-checkout    .git/info//sparse-checkout
>  test_git_path GIT_COMMON_DIR=bar remotes/bar              bar/remotes/bar
>  test_git_path GIT_COMMON_DIR=bar branches/bar             bar/branches/bar
>  test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master   bar/logs/refs/heads/master

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] path: optimize common dir checking
  2015-08-26 21:15   ` Junio C Hamano
@ 2015-08-26 22:10     ` David Turner
  2015-08-26 22:15       ` Junio C Hamano
  2015-08-26 22:19       ` David Turner
  0 siblings, 2 replies; 13+ messages in thread
From: David Turner @ 2015-08-26 22:10 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, mhagger, pclouds, sunshine

On Wed, 2015-08-26 at 14:15 -0700, Junio C Hamano wrote:
> > + * For example, consider the following set of strings:
> > + * abc
> > + * def
> > + * definite
> > + * definition
> > + *
> > + * The trie would look look like:
> > + * root: len = 0, value = (something), children a and d non-NULL.
> 
> "value = NULL", as there is no empty string registered in the trie?

Indeed.

> > + *    a: len = 2, contents = bc
> 
> "value = NULL" here, too (just showing I am following along, not
> just skimming)?

Yep.

<snip>
> > +	/* Partial path normalization: skip consecutive slashes */
> > +	while (key[0] == '/' && key[1] == '/')
> > +		key ++;
> 
> These "skipping consecutive slashes" inside this function is cute.
> I would have expected me to react "Eek, Ugly" to it, but somehow I
> didn't find them ugly.

It's right on the cute/ugly edge for me too.

<snip>

> I somehow wonder if we'd want to precomute the trie at the build
> time, though ;-)

LOL.

<snip>
> Thanks for a pleasant read.

Thank you!  I'll re-roll with those last two fixes (re value=NULL)
tomorrow-ish.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] path: optimize common dir checking
  2015-08-26 22:10     ` David Turner
@ 2015-08-26 22:15       ` Junio C Hamano
  2015-08-26 22:19       ` David Turner
  1 sibling, 0 replies; 13+ messages in thread
From: Junio C Hamano @ 2015-08-26 22:15 UTC (permalink / raw)
  To: David Turner; +Cc: git, mhagger, pclouds, sunshine

David Turner <dturner@twopensource.com> writes:

> On Wed, 2015-08-26 at 14:15 -0700, Junio C Hamano wrote:
>
>> Thanks for a pleasant read.
>
> Thank you!  I'll re-roll with those last two fixes (re value=NULL)
> tomorrow-ish.

If these two "value = NULL" are the only things, I can locally fix
them up.  No need to resend.

Thanks.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] path: optimize common dir checking
  2015-08-26 22:10     ` David Turner
  2015-08-26 22:15       ` Junio C Hamano
@ 2015-08-26 22:19       ` David Turner
  2015-08-28 16:39         ` Junio C Hamano
  1 sibling, 1 reply; 13+ messages in thread
From: David Turner @ 2015-08-26 22:19 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, mhagger, pclouds, sunshine

On Wed, 2015-08-26 at 18:10 -0400, David Turner wrote:
> On Wed, 2015-08-26 at 14:15 -0700, Junio C Hamano wrote:
> > > + * For example, consider the following set of strings:
> > > + * abc
> > > + * def
> > > + * definite
> > > + * definition
> > > + *
> > > + * The trie would look look like:
> > > + * root: len = 0, value = (something), children a and d non-NULL.
> > 
> > "value = NULL", as there is no empty string registered in the trie?
> 
> Indeed.
> 
> > > + *    a: len = 2, contents = bc
> > 
> > "value = NULL" here, too (just showing I am following along, not
> > just skimming)?
> 
> Yep.

No, wait. value should be non-NULL, since abc is in the string set. 

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] path: optimize common dir checking
  2015-08-26 22:19       ` David Turner
@ 2015-08-28 16:39         ` Junio C Hamano
  2015-08-28 18:36           ` David Turner
  0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2015-08-28 16:39 UTC (permalink / raw)
  To: David Turner; +Cc: git, mhagger, pclouds, sunshine

David Turner <dturner@twopensource.com> writes:

> On Wed, 2015-08-26 at 18:10 -0400, David Turner wrote:
>> On Wed, 2015-08-26 at 14:15 -0700, Junio C Hamano wrote:
>> > > + * For example, consider the following set of strings:
>> > > + * abc
>> > > + * def
>> > > + * definite
>> > > + * definition
>> > > + *
>> > > + * The trie would look look like:
>> > > + * root: len = 0, value = (something), children a and d non-NULL.
>> > 
>> > "value = NULL", as there is no empty string registered in the trie?
>> 
>> Indeed.
>> 
>> > > + *    a: len = 2, contents = bc
>> > 
>> > "value = NULL" here, too (just showing I am following along, not
>> > just skimming)?
>> 
>> Yep.
>
> No, wait. value should be non-NULL, since abc is in the string set. 

True.  Here is what I came up with on top of your original.  



 path.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/path.c b/path.c
index 4100ba6..ce0530b 100644
--- a/path.c
+++ b/path.c
@@ -133,12 +133,13 @@ struct common_dir common_list[] = {
  * definition
  *
  * The trie would look look like:
- * root: len = 0, value = (something), children a and d non-NULL.
- *    a: len = 2, contents = bc
- *    d: len = 2, contents = ef, children i non-NULL, value = (something)
+ * root: len = 0, children a and d non-NULL, value = NULL.
+ *    a: len = 2, contents = bc, value = (data for "abc")
+ *    d: len = 2, contents = ef, children i non-NULL, value = (data for "def")
  *       i: len = 3, contents = nit, children e and i non-NULL, value = NULL
- *           e: len = 0, children all NULL, value = (something)
- *           i: len = 2, contents = on, children all NULL, value = (something)
+ *           e: len = 0, children all NULL, value = (data for "definite")
+ *           i: len = 2, contents = on, children all NULL,
+ *              value = (data for "definition")
  */
 struct trie {
 	struct trie *children[256];

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] path: optimize common dir checking
  2015-08-28 16:39         ` Junio C Hamano
@ 2015-08-28 18:36           ` David Turner
  0 siblings, 0 replies; 13+ messages in thread
From: David Turner @ 2015-08-28 18:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, mhagger, pclouds, sunshine

On Fri, 2015-08-28 at 09:39 -0700, Junio C Hamano wrote:
> David Turner <dturner@twopensource.com> writes:
> 
> > On Wed, 2015-08-26 at 18:10 -0400, David Turner wrote:
> >> On Wed, 2015-08-26 at 14:15 -0700, Junio C Hamano wrote:
> >> > > + * For example, consider the following set of strings:
> >> > > + * abc
> >> > > + * def
> >> > > + * definite
> >> > > + * definition
> >> > > + *
> >> > > + * The trie would look look like:
> >> > > + * root: len = 0, value = (something), children a and d non-NULL.
> >> > 
> >> > "value = NULL", as there is no empty string registered in the trie?
> >> 
> >> Indeed.
> >> 
> >> > > + *    a: len = 2, contents = bc
> >> > 
> >> > "value = NULL" here, too (just showing I am following along, not
> >> > just skimming)?
> >> 
> >> Yep.
> >
> > No, wait. value should be non-NULL, since abc is in the string set. 
> 
> True.  Here is what I came up with on top of your original.  
> 
> 
> 
>  path.c | 11 ++++++-----
>  1 file changed, 6 insertions(+), 5 deletions(-)
> 
> diff --git a/path.c b/path.c
> index 4100ba6..ce0530b 100644
> --- a/path.c
> +++ b/path.c
> @@ -133,12 +133,13 @@ struct common_dir common_list[] = {
>   * definition
>   *
>   * The trie would look look like:
> - * root: len = 0, value = (something), children a and d non-NULL.
> - *    a: len = 2, contents = bc
> - *    d: len = 2, contents = ef, children i non-NULL, value = (something)
> + * root: len = 0, children a and d non-NULL, value = NULL.
> + *    a: len = 2, contents = bc, value = (data for "abc")
> + *    d: len = 2, contents = ef, children i non-NULL, value = (data for "def")
>   *       i: len = 3, contents = nit, children e and i non-NULL, value = NULL
> - *           e: len = 0, children all NULL, value = (something)
> - *           i: len = 2, contents = on, children all NULL, value = (something)
> + *           e: len = 0, children all NULL, value = (data for "definite")
> + *           i: len = 2, contents = on, children all NULL,
> + *              value = (data for "definition")
>   */
>  struct trie {
>  	struct trie *children[256];

LGTM.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] path: optimize common dir checking
  2015-08-26 19:46 ` [PATCH v4 2/4] path: optimize common dir checking David Turner
  2015-08-26 21:15   ` Junio C Hamano
@ 2015-08-30  6:25   ` Torsten Bögershausen
  2015-08-31 18:56     ` David Turner
  1 sibling, 1 reply; 13+ messages in thread
From: Torsten Bögershausen @ 2015-08-30  6:25 UTC (permalink / raw)
  To: David Turner, git, mhagger, pclouds, sunshine

On 26.08.15 21:46, David Turner wrote:
> Instead of a linear search over common_list to check whether
> a path is common, use a trie.  The trie search operates on
> path prefixes, and handles excludes.
> 
> Signed-off-by: David Turner <dturner@twopensource.com>
> ---
>  path.c                | 226 ++++++++++++++++++++++++++++++++++++++++++++++----
>  t/t0060-path-utils.sh |   1 +
>  2 files changed, 213 insertions(+), 14 deletions(-)
> 
> diff --git a/path.c b/path.c


> +		child->len = root->len - i - 1;
> +		if (child->len) {
> +			child->contents = strndup(root->contents + i + 1,
> +						   child->len);
>  		}
Could we use xtrndup() instead of strndup() ?
(Otherwise it won't compile under Mac OS here)

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v4 2/4] path: optimize common dir checking
  2015-08-30  6:25   ` Torsten Bögershausen
@ 2015-08-31 18:56     ` David Turner
  0 siblings, 0 replies; 13+ messages in thread
From: David Turner @ 2015-08-31 18:56 UTC (permalink / raw)
  To: Torsten Bögershausen; +Cc: git, mhagger, pclouds, sunshine

On Sun, 2015-08-30 at 08:25 +0200, Torsten Bögershausen wrote:
> On 26.08.15 21:46, David Turner wrote:
> > Instead of a linear search over common_list to check whether
> > a path is common, use a trie.  The trie search operates on
> > path prefixes, and handles excludes.
> > 
> > Signed-off-by: David Turner <dturner@twopensource.com>
> > ---
> >  path.c                | 226 ++++++++++++++++++++++++++++++++++++++++++++++----
> >  t/t0060-path-utils.sh |   1 +
> >  2 files changed, 213 insertions(+), 14 deletions(-)
> > 
> > diff --git a/path.c b/path.c
> 
> 
> > +		child->len = root->len - i - 1;
> > +		if (child->len) {
> > +			child->contents = strndup(root->contents + i + 1,
> > +						   child->len);
> >  		}
> Could we use xtrndup() instead of strndup() ?
> (Otherwise it won't compile under Mac OS here)

Thanks.  Junio, can we squash in the following?  (Or let me know and I
can resend the series with both this and your patch squashed)

---
 path.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/path.c b/path.c
index 777af35..21306ab 100644
--- a/path.c
+++ b/path.c
@@ -191,7 +191,7 @@ static void *add_to_trie(struct trie *root, const
char *key, void *value)
 
 		child->len = root->len - i - 1;
 		if (child->len) {
-			child->contents = strndup(root->contents + i + 1,
+			child->contents = xstrndup(root->contents + i + 1,
 						   child->len);
 		}
 		child->value = root->value;

^ permalink raw reply related	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2015-08-31 18:56 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-08-26 19:46 [PATCH v4 0/4] per-worktree bisection refs David Turner
2015-08-26 19:46 ` [PATCH v4 1/4] refs: clean up common_list David Turner
2015-08-26 19:46 ` [PATCH v4 2/4] path: optimize common dir checking David Turner
2015-08-26 21:15   ` Junio C Hamano
2015-08-26 22:10     ` David Turner
2015-08-26 22:15       ` Junio C Hamano
2015-08-26 22:19       ` David Turner
2015-08-28 16:39         ` Junio C Hamano
2015-08-28 18:36           ` David Turner
2015-08-30  6:25   ` Torsten Bögershausen
2015-08-31 18:56     ` David Turner
2015-08-26 19:46 ` [PATCH v4 3/4] refs: make refs/worktree/* per-worktree David Turner
2015-08-26 19:46 ` [PATCH v4 4/4] bisect: make bisection refs per-worktree David Turner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).