Git development
 help / color / mirror / Atom feed
* [PATCHv4 08/12] Teach the notes lookup code to parse notes trees with various fanout schemes
From: Johan Herland @ 2009-08-27  1:43 UTC (permalink / raw)
  To: gitster
  Cc: git, johan, Johannes.Schindelin, trast, tavestbo, git, chriscool,
	spearce, Johannes Schindelin
In-Reply-To: <1251337437-16947-1-git-send-email-johan@herland.net>

The semantics used when parsing notes trees (with regards to fanout subtrees)
follow Dscho's proposal fairly closely:
- No concatenation/merging of notes is performed. If there are several notes
  objects referencing a given commit, only one of those objects are used.
- If a notes object for a given commit is present in the "root" notes tree,
  no subtrees are consulted; the object in the root tree is used directly.
- If there are more than one subtree that prefix-matches the given commit,
  only the subtree with the longest matching prefix is consulted. This
  means that if the given commit is e.g. "deadbeef", and the notes tree have
  subtrees "de" and "dead", then the following paths in the notes tree are
  searched: "deadbeef", "dead/beef". Note that "de/adbeef" is NOT searched.
- Fanout directories (subtrees) must references a whole number of bytes
  from the SHA1 sum they subdivide. E.g. subtrees "dead" and "de" are
  acceptable; "d" and "dea" are not.
- Multiple levels of fanout are allowed. All the above rules apply
  recursively. E.g. "de/adbeef" is preferred over "de/adbe/ef", etc.

This patch changes the in-memory datastructure for holding parsed notes:
Instead of holding all note (and subtree) entries in a hash table, a
simple 16-tree structure is used instead. The tree structure consists of
16-arrays as internal nodes, and note/subtree entries as leaf nodes. The
tree is traversed by indexing subsequent nibbles of the search key until
a leaf node is encountered. If a subtree entry is encountered while
searching for a note, the subtree is unpacked into the 16-tree structure,
and the search continues into that subtree.

The new algorithm performs significantly better in the cases where only
a fraction of the notes need to be looked up (this is assumed to be the
common case for notes lookup). The new code even performs marginally
better in the worst case (where _all_ the notes are looked up).

In addition to this, comes the massive performance win associated with
organizing the notes tree according to some fanout scheme. Even a simple
2/38 fanout scheme is dramatically quicker to traverse (going from tens of
seconds to sub-second runtimes).

As for memory usage, the new code is marginally better than the old code in
the worst case, but in the case of looking up only some notes from a notes
tree with proper fanout, the new code uses only a small fraction of the
memory needed to hold the entire notes tree.

However, there is one casualty of this patch. The old notes lookup code was
able to parse notes that were associated with non-SHA1s (e.g. refs). The new
code requires the referenced object to be named by a SHA1 sum. Still, this
is not considered a major setback, since the notes infrastructure was not
originally intended to annotate objects outside the Git object database.

Cc: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Johan Herland <johan@herland.net>
---
 notes.c |  294 +++++++++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 228 insertions(+), 66 deletions(-)

diff --git a/notes.c b/notes.c
index 9172154..a637056 100644
--- a/notes.c
+++ b/notes.c
@@ -6,103 +6,265 @@
 #include "strbuf.h"
 #include "tree-walk.h"
 
-struct entry {
-	unsigned char commit_sha1[20];
-	unsigned char notes_sha1[20];
+/*
+ * Use a non-balancing simple 16-tree structure with struct int_node as
+ * internal nodes, and struct leaf_node as leaf nodes. Each int_node has a
+ * 16-array of pointers to its children.
+ * The bottom 2 bits of each pointer is used to identify the pointer type
+ * - ptr & 3 == 0 - NULL pointer, assert(ptr == NULL)
+ * - ptr & 3 == 1 - pointer to next internal node - cast to struct int_node *
+ * - ptr & 3 == 2 - pointer to note entry - cast to struct leaf_node *
+ * - ptr & 3 == 3 - pointer to subtree entry - cast to struct leaf_node *
+ *
+ * The root node is a statically allocated struct int_node.
+ */
+struct int_node {
+	void *a[16];
 };
 
-struct hash_map {
-	struct entry *entries;
-	off_t count, size;
+/*
+ * Leaf nodes come in two variants, note entries and subtree entries,
+ * distinguished by the LSb of the leaf node pointer (see above).
+ * As a note entry, the key is the SHA1 of the referenced commit, and the
+ * value is the SHA1 of the note object.
+ * As a subtree entry, the key is the prefix SHA1 (w/trailing NULs) of the
+ * referenced commit, using the last byte of the key to store the length of
+ * the prefix. The value is the SHA1 of the tree object containing the notes
+ * subtree.
+ */
+struct leaf_node {
+	unsigned char key_sha1[20];
+	unsigned char val_sha1[20];
 };
 
+#define PTR_TYPE_NULL     0
+#define PTR_TYPE_INTERNAL 1
+#define PTR_TYPE_NOTE     2
+#define PTR_TYPE_SUBTREE  3
+
+#define GET_PTR_TYPE(ptr)       ((uintptr_t) (ptr) & 3)
+#define CLR_PTR_TYPE(ptr)       ((void *) ((uintptr_t) (ptr) & ~3))
+#define SET_PTR_TYPE(ptr, type) ((void *) ((uintptr_t) (ptr) | (type)))
+
+#define GET_NIBBLE(n, sha1) (((sha1[n >> 1]) >> ((n & 0x01) << 2)) & 0x0f)
+
+#define MATCHING_SUBTREE(key_sha1, subtree_sha1) \
+	(!memcmp(key_sha1, subtree_sha1, subtree_sha1[19]))
+
 static int initialized;
-static struct hash_map hash_map;
 
-static int hash_index(struct hash_map *map, const unsigned char *sha1)
-{
-	int i = ((*(unsigned int *)sha1) % map->size);
+static struct int_node root_node;
 
-	for (;;) {
-		unsigned char *current = map->entries[i].commit_sha1;
+static void load_subtree(struct leaf_node *subtree, struct int_node *node,
+		unsigned int n);
 
-		if (!hashcmp(sha1, current))
-			return i;
+/*
+ * To find a leaf_node:
+ * 1. Start at the root node, with n = 0
+ * 2. Use the nth nibble of the key as an index into a:
+ *    - If a[n] is an int_node, recurse into that node and increment n
+ *    - If a leaf_node with matching key, return leaf_node (assert note entry)
+ *    - If a matching subtree entry, unpack that subtree entry (and remove it);
+ *      restart search at the current level.
+ *    - Otherwise, we end up at a NULL pointer, or a non-matching leaf_node.
+ *      Backtrack out of the recursion, one level at a time and check a[0]:
+ *      - If a[0] at the current level is a matching subtree entry, unpack that
+ *        subtree entry (and remove it); restart search at the current level.
+ */
+static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,
+		const unsigned char *key_sha1)
+{
+	struct leaf_node *l;
+	unsigned char i = GET_NIBBLE(n, key_sha1);
+	void *p = tree->a[i];
 
-		if (is_null_sha1(current))
-			return -1 - i;
+	switch(GET_PTR_TYPE(p)) {
+	case PTR_TYPE_INTERNAL:
+		l = note_tree_find(CLR_PTR_TYPE(p), n + 1, key_sha1);
+		if (l)
+			return l;
+		break;
+	case PTR_TYPE_NOTE:
+		l = (struct leaf_node *) CLR_PTR_TYPE(p);
+		if (!hashcmp(key_sha1, l->key_sha1))
+			return l; /* return note object matching given key */
+		break;
+	case PTR_TYPE_SUBTREE:
+		l = (struct leaf_node *) CLR_PTR_TYPE(p);
+		if (MATCHING_SUBTREE(key_sha1, l->key_sha1)) {
+			/* unpack tree and resume search */
+			tree->a[i] = NULL;
+			load_subtree(l, tree, n);
+			free(l);
+			return note_tree_find(tree, n, key_sha1);
+		}
+		break;
+	case PTR_TYPE_NULL:
+	default:
+		assert(!p);
+		break;
+	}
 
-		if (++i == map->size)
-			i = 0;
+	/*
+	 * Did not find key at this (or any lower) level.
+	 * Check if there's a matching subtree entry in tree->a[0].
+	 * If so, unpack tree and resume search.
+	 */
+	p = tree->a[0];
+	if (GET_PTR_TYPE(p) != PTR_TYPE_SUBTREE)
+		return NULL;
+	l = (struct leaf_node *) CLR_PTR_TYPE(p);
+	if (MATCHING_SUBTREE(key_sha1, l->key_sha1)) {
+		/* unpack tree and resume search */
+		tree->a[0] = NULL;
+		load_subtree(l, tree, n);
+		free(l);
+		return note_tree_find(tree, n, key_sha1);
 	}
+	return NULL;
 }
 
-static void add_entry(const unsigned char *commit_sha1,
-		const unsigned char *notes_sha1)
+/*
+ * To insert a leaf_node:
+ * 1. Start at the root node, with n = 0
+ * 2. Use the nth nibble of the key as an index into a:
+ *    - If a[n] is NULL, store the tweaked pointer directly into a[n]
+ *    - If a[n] is an int_node, recurse into that node and increment n
+ *    - If a[n] is a leaf_node:
+ *      1. Check if they're equal, and handle that (abort? overwrite?)
+ *      2. Create a new int_node, and store both leaf_nodes there
+ *      3. Store the new int_node into a[n].
+ */
+static int note_tree_insert(struct int_node *tree, unsigned char n,
+		const struct leaf_node *entry, unsigned char type)
 {
-	int index;
-
-	if (hash_map.count + 1 > hash_map.size >> 1) {
-		int i, old_size = hash_map.size;
-		struct entry *old = hash_map.entries;
-
-		hash_map.size = old_size ? old_size << 1 : 64;
-		hash_map.entries = (struct entry *)
-			xcalloc(sizeof(struct entry), hash_map.size);
-
-		for (i = 0; i < old_size; i++)
-			if (!is_null_sha1(old[i].commit_sha1)) {
-				index = -1 - hash_index(&hash_map,
-						old[i].commit_sha1);
-				memcpy(hash_map.entries + index, old + i,
-					sizeof(struct entry));
-			}
-		free(old);
+	struct int_node *new_node;
+	const struct leaf_node *l;
+	int ret;
+	unsigned char i = GET_NIBBLE(n, entry->key_sha1);
+	void *p = tree->a[i];
+	assert(GET_PTR_TYPE(entry) == PTR_TYPE_NULL);
+	switch(GET_PTR_TYPE(p)) {
+	case PTR_TYPE_NULL:
+		assert(!p);
+		tree->a[i] = SET_PTR_TYPE(entry, type);
+		return 0;
+	case PTR_TYPE_INTERNAL:
+		return note_tree_insert(CLR_PTR_TYPE(p), n + 1, entry, type);
+	default:
+		assert(GET_PTR_TYPE(p) == PTR_TYPE_NOTE ||
+			GET_PTR_TYPE(p) == PTR_TYPE_SUBTREE);
+		l = (const struct leaf_node *) CLR_PTR_TYPE(p);
+		if (!hashcmp(entry->key_sha1, l->key_sha1))
+			return -1; /* abort insert on matching key */
+		new_node = (struct int_node *)
+			xcalloc(sizeof(struct int_node), 1);
+		ret = note_tree_insert(new_node, n + 1,
+			CLR_PTR_TYPE(p), GET_PTR_TYPE(p));
+		if (ret) {
+			free(new_node);
+			return -1;
+		}
+		tree->a[i] = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL);
+		return note_tree_insert(new_node, n + 1, entry, type);
 	}
+}
 
-	index = hash_index(&hash_map, commit_sha1);
-	if (index < 0) {
-		index = -1 - index;
-		hash_map.count++;
+/*
+ * Convert a partial SHA1 hex string to the corresponding partial SHA1 value.
+ * - hex      - Partial SHA1 segment in ASCII hex format
+ * - hex_len  - Length of above segment. Must be multiple of 2 between 0 and 40
+ * - sha1     - Partial SHA1 value is written here
+ * - sha1_len - Max #bytes to store in sha1, Must be >= hex_len / 2, and < 20
+ * Returns -1 on error (invalid arguments or invalid SHA1 (not in hex format).
+ * Otherwise, returns number of bytes written to sha1 (i.e. hex_len / 2).
+ * Pads sha1 with NULs up to sha1_len (not included in returned length).
+ */
+static int get_sha1_hex_segment(const char *hex, unsigned int hex_len,
+		unsigned char *sha1, unsigned int sha1_len)
+{
+	unsigned int i, len = hex_len >> 1;
+	if (hex_len % 2 != 0 || len > sha1_len)
+		return -1;
+	for (i = 0; i < len; i++) {
+		unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+		if (val & ~0xff)
+			return -1;
+		*sha1++ = val;
+		hex += 2;
 	}
+	for (; i < sha1_len; i++)
+		*sha1++ = 0;
+	return len;
+}
+
+static void load_subtree(struct leaf_node *subtree, struct int_node *node,
+		unsigned int n)
+{
+	unsigned char commit_sha1[20];
+	unsigned int prefix_len;
+	int status;
+	void *buf;
+	struct tree_desc desc;
+	struct name_entry entry;
+
+	buf = fill_tree_descriptor(&desc, subtree->val_sha1);
+	if (!buf)
+		die("Could not read %s for notes-index",
+		     sha1_to_hex(subtree->val_sha1));
+
+	prefix_len = subtree->key_sha1[19];
+	assert(prefix_len * 2 >= n);
+	memcpy(commit_sha1, subtree->key_sha1, prefix_len);
+	while (tree_entry(&desc, &entry)) {
+		int len = get_sha1_hex_segment(entry.path, strlen(entry.path),
+				commit_sha1 + prefix_len, 20 - prefix_len);
+		if (len < 0)
+			continue; /* entry.path is not a SHA1 sum. Skip */
+		len += prefix_len;
 
-	hashcpy(hash_map.entries[index].commit_sha1, commit_sha1);
-	hashcpy(hash_map.entries[index].notes_sha1, notes_sha1);
+		/*
+		 * If commit SHA1 is complete (len == 20), assume note object
+		 * If commit SHA1 is incomplete (len < 20), assume note subtree
+		 */
+		if (len <= 20) {
+			unsigned char type = PTR_TYPE_NOTE;
+			struct leaf_node *l = (struct leaf_node *)
+				xcalloc(sizeof(struct leaf_node), 1);
+			hashcpy(l->key_sha1, commit_sha1);
+			hashcpy(l->val_sha1, entry.sha1);
+			if (len < 20) {
+				l->key_sha1[19] = (unsigned char) len;
+				type = PTR_TYPE_SUBTREE;
+			}
+			status = note_tree_insert(node, n, l, type);
+			assert(!status);
+		}
+	}
+	free(buf);
 }
 
-static void initialize_hash_map(const char *notes_ref_name)
+static void initialize_notes(const char *notes_ref_name)
 {
 	unsigned char sha1[20], commit_sha1[20];
 	unsigned mode;
-	struct tree_desc desc;
-	struct name_entry entry;
-	void *buf;
+	struct leaf_node root_tree;
 
 	if (!notes_ref_name || read_ref(notes_ref_name, commit_sha1) ||
 	    get_tree_entry(commit_sha1, "", sha1, &mode))
 		return;
 
-	buf = fill_tree_descriptor(&desc, sha1);
-	if (!buf)
-		die("Could not read %s for notes-index", sha1_to_hex(sha1));
-
-	while (tree_entry(&desc, &entry))
-		if (!get_sha1(entry.path, commit_sha1))
-			add_entry(commit_sha1, entry.sha1);
-	free(buf);
+	hashclr(root_tree.key_sha1);
+	hashcpy(root_tree.val_sha1, sha1);
+	load_subtree(&root_tree, &root_node, 0);
 }
 
 static unsigned char *lookup_notes(const unsigned char *commit_sha1)
 {
-	int index;
-
-	if (!hash_map.size)
-		return NULL;
-
-	index = hash_index(&hash_map, commit_sha1);
-	if (index < 0)
-		return NULL;
-	return hash_map.entries[index].notes_sha1;
+	struct leaf_node *found = note_tree_find(&root_node, 0, commit_sha1);
+	if (found)
+		return found->val_sha1;
+	return NULL;
 }
 
 void get_commit_notes(const struct commit *commit, struct strbuf *sb,
@@ -120,7 +282,7 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb,
 			notes_ref_name = getenv(GIT_NOTES_REF_ENVIRONMENT);
 		else if (!notes_ref_name)
 			notes_ref_name = GIT_NOTES_DEFAULT_REF;
-		initialize_hash_map(notes_ref_name);
+		initialize_notes(notes_ref_name);
 		initialized = 1;
 	}
 
-- 
1.6.4.304.g1365c.dirty

^ permalink raw reply related

* [PATCHv4 10/12] notes.c: Implement simple memory pooling of leaf nodes
From: Johan Herland @ 2009-08-27  1:43 UTC (permalink / raw)
  To: gitster
  Cc: git, johan, Johannes.Schindelin, trast, tavestbo, git, chriscool,
	spearce
In-Reply-To: <1251337437-16947-1-git-send-email-johan@herland.net>

Allocate page-sized chunks for holding struct leaf_node objects.

This slightly (but consistently) improves runtime performance of notes
lookup, at a very slight increase (~2K on average) in memory usage.

When allocating a new memory pool, the older pool is leaked, but this is
no worse than the current situation, where (pretty much) all leaf_nodes
are leaked anyway.

Signed-off-by: Johan Herland <johan@herland.net>
---
 notes.c |   22 ++++++++++++++++++----
 1 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/notes.c b/notes.c
index a637056..5d1ee17 100644
--- a/notes.c
+++ b/notes.c
@@ -55,6 +55,23 @@ static int initialized;
 
 static struct int_node root_node;
 
+/* leaf nodes are allocated in simple memory pools */
+#define LEAF_NODE_POOL_SIZE 100
+static struct leaf_node *leaf_node_pool;
+static unsigned int leaf_node_pool_used;
+
+
+static struct leaf_node *new_leaf_node()
+{
+	if (!leaf_node_pool || leaf_node_pool_used >= LEAF_NODE_POOL_SIZE) {
+		/* MEMORY LEAK: */
+		leaf_node_pool = (struct leaf_node *)
+			xcalloc(sizeof(struct leaf_node), LEAF_NODE_POOL_SIZE);
+		leaf_node_pool_used = 0;
+	}
+	return leaf_node_pool + leaf_node_pool_used++;
+}
+
 static void load_subtree(struct leaf_node *subtree, struct int_node *node,
 		unsigned int n);
 
@@ -95,7 +112,6 @@ static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,
 			/* unpack tree and resume search */
 			tree->a[i] = NULL;
 			load_subtree(l, tree, n);
-			free(l);
 			return note_tree_find(tree, n, key_sha1);
 		}
 		break;
@@ -118,7 +134,6 @@ static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,
 		/* unpack tree and resume search */
 		tree->a[0] = NULL;
 		load_subtree(l, tree, n);
-		free(l);
 		return note_tree_find(tree, n, key_sha1);
 	}
 	return NULL;
@@ -229,8 +244,7 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,
 		 */
 		if (len <= 20) {
 			unsigned char type = PTR_TYPE_NOTE;
-			struct leaf_node *l = (struct leaf_node *)
-				xcalloc(sizeof(struct leaf_node), 1);
+			struct leaf_node *l = new_leaf_node();
 			hashcpy(l->key_sha1, commit_sha1);
 			hashcpy(l->val_sha1, entry.sha1);
 			if (len < 20) {
-- 
1.6.4.304.g1365c.dirty

^ permalink raw reply related

* [PATCHv4 09/12] Selftests verifying semantics when loading notes trees with various fanouts
From: Johan Herland @ 2009-08-27  1:43 UTC (permalink / raw)
  To: gitster
  Cc: git, johan, Johannes.Schindelin, trast, tavestbo, git, chriscool,
	spearce
In-Reply-To: <1251337437-16947-1-git-send-email-johan@herland.net>

Add selftests verifying:
- that we are able to parse notes trees with various fanout schemes
- that notes trees with conflicting fanout schemes are parsed as expected

Signed-off-by: Johan Herland <johan@herland.net>
---
 t/t3303-notes-subtrees.sh |  206 +++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 206 insertions(+), 0 deletions(-)
 create mode 100755 t/t3303-notes-subtrees.sh

diff --git a/t/t3303-notes-subtrees.sh b/t/t3303-notes-subtrees.sh
new file mode 100755
index 0000000..40bb3f4
--- /dev/null
+++ b/t/t3303-notes-subtrees.sh
@@ -0,0 +1,206 @@
+#!/bin/sh
+
+test_description='Test commit notes organized in subtrees'
+
+. ./test-lib.sh
+
+number_of_commits=100
+
+start_note_commit () {
+	test_tick &&
+	cat <<INPUT_END
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes
+COMMIT
+
+from refs/notes/commits^0
+deleteall
+INPUT_END
+
+}
+
+verify_notes () {
+	git log | grep "^    " > output &&
+	i=$number_of_commits &&
+	while [ $i -gt 0 ]; do
+		echo "    commit #$i" &&
+		echo "    note for commit #$i" &&
+		i=$(($i-1));
+	done > expect &&
+	test_cmp expect output
+}
+
+test_expect_success 'setup: create $number_of_commits commits' '
+
+	(
+		nr=0 &&
+		while [ $nr -lt $number_of_commits ]; do
+			nr=$(($nr+1)) &&
+			test_tick &&
+			cat <<INPUT_END
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #$nr
+COMMIT
+
+M 644 inline file
+data <<EOF
+file in commit #$nr
+EOF
+
+INPUT_END
+
+		done &&
+		test_tick &&
+		cat <<INPUT_END
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+no notes
+COMMIT
+
+deleteall
+
+INPUT_END
+
+	) |
+	git fast-import --quiet &&
+	git config core.notesRef refs/notes/commits
+'
+
+test_expect_success 'test notes in 2/38-fanout' '
+
+	(
+		start_note_commit &&
+		nr=$number_of_commits &&
+		git rev-list refs/heads/master |
+		while read sha1; do
+			note_path=$(echo "$sha1" | sed "s|^..|&/|")
+			cat <<INPUT_END &&
+M 100644 inline $note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+			nr=$(($nr-1))
+		done
+	) |
+	git fast-import --quiet
+'
+
+test_expect_success 'verify notes in 2/38-fanout' 'verify_notes'
+
+test_expect_success 'test notes in 4/36-fanout' '
+
+	(
+		start_note_commit &&
+		nr=$number_of_commits &&
+		git rev-list refs/heads/master |
+		while read sha1; do
+			note_path=$(echo "$sha1" | sed "s|^....|&/|")
+			cat <<INPUT_END &&
+M 100644 inline $note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+			nr=$(($nr-1))
+		done
+	) |
+	git fast-import --quiet
+'
+
+test_expect_success 'verify notes in 4/36-fanout' 'verify_notes'
+
+test_expect_success 'test notes in 4/36-fanout overriding 2/38-fanout' '
+
+	(
+		start_note_commit &&
+		nr=$number_of_commits &&
+		git rev-list refs/heads/master |
+		while read sha1; do
+			ignored_note_path=$(echo "$sha1" | sed "s|^..|&/|")
+			preferred_note_path=$(echo "$sha1" | sed "s|^....|&/|")
+			cat <<INPUT_END &&
+M 100644 inline $ignored_note_path
+data <<EOF
+IGNORED note for commit #$nr
+EOF
+
+M 100644 inline $preferred_note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+			nr=$(($nr-1))
+		done
+	) |
+	git fast-import --quiet
+'
+
+test_expect_success 'verify notes in 4/36-fanout overriding 2/38-fanout' 'verify_notes'
+
+test_expect_success 'test notes in 2/2/36-fanout' '
+
+	(
+		start_note_commit &&
+		nr=$number_of_commits &&
+		git rev-list refs/heads/master |
+		while read sha1; do
+			note_path=$(echo "$sha1" | sed "s|^\(..\)\(..\)|\1/\2/|")
+			cat <<INPUT_END &&
+M 100644 inline $note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+			nr=$(($nr-1))
+		done
+	) |
+	git fast-import --quiet
+'
+
+test_expect_success 'verify notes in 2/2/36-fanout' 'verify_notes'
+
+test_expect_success 'test notes in 2/38-fanout overriding 2/2/36-fanout' '
+
+	(
+		start_note_commit &&
+		nr=$number_of_commits &&
+		git rev-list refs/heads/master |
+		while read sha1; do
+			ignored_note_path=$(echo "$sha1" | sed "s|^\(..\)\(..\)|\1/\2/|")
+			preferred_note_path=$(echo "$sha1" | sed "s|^..|&/|")
+			cat <<INPUT_END &&
+M 100644 inline $ignored_note_path
+data <<EOF
+IGNORED note for commit #$nr
+EOF
+
+M 100644 inline $preferred_note_path
+data <<EOF
+note for commit #$nr
+EOF
+
+INPUT_END
+
+			nr=$(($nr-1))
+		done
+	) |
+	git fast-import --quiet
+'
+
+test_expect_success 'verify notes in 2/38-fanout overriding 2/2/36-fanout' 'verify_notes'
+
+test_done
-- 
1.6.4.304.g1365c.dirty

^ permalink raw reply related

* [PATCHv4 11/12] Add flags to get_commit_notes() to control the format of the note string
From: Johan Herland @ 2009-08-27  1:43 UTC (permalink / raw)
  To: gitster
  Cc: git, johan, Johannes.Schindelin, trast, tavestbo, git, chriscool,
	spearce
In-Reply-To: <1251337437-16947-1-git-send-email-johan@herland.net>

This patch adds the following flags to get_commit_notes() for adjusting the
format of the produced note string:
- NOTES_SHOW_HEADER: Print "Notes:" line before the notes contents
- NOTES_INDENT: Indent notes contents by 4 spaces

Suggested-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Johan Herland <johan@herland.net>
---
 notes.c  |    8 +++++---
 notes.h  |    5 ++++-
 pretty.c |    3 ++-
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/notes.c b/notes.c
index 5d1ee17..bf520ae 100644
--- a/notes.c
+++ b/notes.c
@@ -282,7 +282,7 @@ static unsigned char *lookup_notes(const unsigned char *commit_sha1)
 }
 
 void get_commit_notes(const struct commit *commit, struct strbuf *sb,
-		const char *output_encoding)
+		const char *output_encoding, int flags)
 {
 	static const char utf8[] = "utf-8";
 	unsigned char *sha1;
@@ -322,12 +322,14 @@ void get_commit_notes(const struct commit *commit, struct strbuf *sb,
 	if (msglen && msg[msglen - 1] == '\n')
 		msglen--;
 
-	strbuf_addstr(sb, "\nNotes:\n");
+	if (flags & NOTES_SHOW_HEADER)
+		strbuf_addstr(sb, "\nNotes:\n");
 
 	for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) {
 		linelen = strchrnul(msg_p, '\n') - msg_p;
 
-		strbuf_addstr(sb, "    ");
+		if (flags & NOTES_INDENT)
+			strbuf_addstr(sb, "    ");
 		strbuf_add(sb, msg_p, linelen);
 		strbuf_addch(sb, '\n');
 	}
diff --git a/notes.h b/notes.h
index 79d21b6..7f3eed4 100644
--- a/notes.h
+++ b/notes.h
@@ -1,7 +1,10 @@
 #ifndef NOTES_H
 #define NOTES_H
 
+#define NOTES_SHOW_HEADER 1
+#define NOTES_INDENT 2
+
 void get_commit_notes(const struct commit *commit, struct strbuf *sb,
-		const char *output_encoding);
+		const char *output_encoding, int flags);
 
 #endif
diff --git a/pretty.c b/pretty.c
index e25db81..01eadd0 100644
--- a/pretty.c
+++ b/pretty.c
@@ -978,7 +978,8 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 		strbuf_addch(sb, '\n');
 
 	if (fmt != CMIT_FMT_ONELINE)
-		get_commit_notes(commit, sb, encoding);
+		get_commit_notes(commit, sb, encoding,
+				 NOTES_SHOW_HEADER | NOTES_INDENT);
 
 	free(reencoded);
 }
-- 
1.6.4.304.g1365c.dirty

^ permalink raw reply related

* [PATCHv4 12/12] Add '%N'-format for pretty-printing commit notes
From: Johan Herland @ 2009-08-27  1:43 UTC (permalink / raw)
  To: gitster
  Cc: git, johan, Johannes.Schindelin, trast, tavestbo, git, chriscool,
	spearce
In-Reply-To: <1251337437-16947-1-git-send-email-johan@herland.net>

From: Johannes Schindelin <Johannes.Schindelin@gmx.de>

Signed-off-by: Johan Herland <johan@herland.net>
---
 Documentation/pretty-formats.txt |    1 +
 pretty.c                         |    4 ++++
 2 files changed, 5 insertions(+), 0 deletions(-)

diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index 2a845b1..5fb10b3 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -123,6 +123,7 @@ The placeholders are:
 - '%s': subject
 - '%f': sanitized subject line, suitable for a filename
 - '%b': body
+- '%N': commit notes
 - '%Cred': switch color to red
 - '%Cgreen': switch color to green
 - '%Cblue': switch color to blue
diff --git a/pretty.c b/pretty.c
index 01eadd0..7f350bb 100644
--- a/pretty.c
+++ b/pretty.c
@@ -702,6 +702,10 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
 	case 'd':
 		format_decoration(sb, commit);
 		return 1;
+	case 'N':
+		get_commit_notes(commit, sb, git_log_output_encoding ?
+			     git_log_output_encoding : git_commit_encoding, 0);
+		return 1;
 	}
 
 	/* For the rest we have to parse the commit header. */
-- 
1.6.4.304.g1365c.dirty

^ permalink raw reply related

* Re: [PATCH v3] import-tars: Allow per-tar author and commit message.
From: Junio C Hamano @ 2009-08-27  4:57 UTC (permalink / raw)
  To: Peter Krefting; +Cc: git
In-Reply-To: <20090826193015.962A7189B12@perkele>

Peter Krefting <peter@softwolves.pp.se> writes:

> +			while (<MSG>)
> +			{
> +				if (/^Committer:\s+([^<>]*)\s+<(.*)>\s*$/i)
> +				{
> +					$this_committer_name = $1;
> +					$this_committer_email = $2;
> +				}
> +				elsif (/^Author:\s+([^<>]*)\s+<(.*)>\s*$/i)
> +				{
> +					$this_author_name = $1;
> +					$this_author_email = $2;
> +				}
> +				else
> +				{
> +					$commit_msg .= $_;
> +				}

Do you really want to slurp Committer:/Author: lines from _anywhere_ in
the file?  Wouldn't it make more sense to vaguely emulate e-mail message
format with headers, empty-line and then body that is free form?

^ permalink raw reply

* Re: [PATCHv4 08/12] Teach the notes lookup code to parse notes trees with various fanout schemes
From: Junio C Hamano @ 2009-08-27  5:00 UTC (permalink / raw)
  To: Johan Herland
  Cc: git, Johannes.Schindelin, trast, tavestbo, git, chriscool,
	spearce
In-Reply-To: <1251337437-16947-9-git-send-email-johan@herland.net>

Johan Herland <johan@herland.net> writes:

> The semantics used when parsing notes trees (with regards to fanout subtrees)
> follow Dscho's proposal fairly closely:
> - No concatenation/merging of notes is performed. If there are several notes
>   objects referencing a given commit, only one of those objects are used.
> - If a notes object for a given commit is present in the "root" notes tree,
>   no subtrees are consulted; the object in the root tree is used directly.
> - If there are more than one subtree that prefix-matches the given commit,
>   only the subtree with the longest matching prefix is consulted. This
>   means that if the given commit is e.g. "deadbeef", and the notes tree have
>   subtrees "de" and "dead", then the following paths in the notes tree are
>   searched: "deadbeef", "dead/beef". Note that "de/adbeef" is NOT searched.
> - Fanout directories (subtrees) must references a whole number of bytes
>   from the SHA1 sum they subdivide. E.g. subtrees "dead" and "de" are
>   acceptable; "d" and "dea" are not.
> - Multiple levels of fanout are allowed. All the above rules apply
>   recursively. E.g. "de/adbeef" is preferred over "de/adbe/ef", etc.

If I am reading this correctly, the earlier parts of the series were
aiming to let multiple people to add notes to the same commit more or less
uncordinated while still allowing to merge them sensibly, but now such a
workflow becomes impossible with this change.  

The above claims notes trees with different levels of fan-out are allowed,
but what it really means is that merging notes trees with different levels
of fan-out will produce a useless result that records notes for the same
commit in different blobs all over the notes tree, and asking the notes
mechanism to give the notes for one commit will give only one piece that
originates in the tree whose creator happened to have used the longest
prefix while ignoring all others.  It may _allow_ such a layout, but how
would such semantics be useful in the first place?

I suspect that I am missing something but my gut feeling is that this
change turns an interesting hack (even though it might be expensive) into
a hack that is not useful at all in the real world, without some order,
discipline, or guideline is applied.

What's the recommended way to work with this system from the end user's
point of view in a distirbuted environment?  Somebody up in the project is
supposed to decide what fan-out is to be used for the whole project and
everybody should follow that structure?  If a participant in the project
forgets that rule (or makes a mistake), a notes tree that mistakenly
merges his notes tree becomes practically useless?  If so, perhaps we
would need a mechanism to avoid such a mistake from happening?

^ permalink raw reply

* Re: [PATCH] Re: Teach mailinfo to ignore everything before -- >8 -- mark
From: Junio C Hamano @ 2009-08-27  5:46 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Jakub Narebski, git
In-Reply-To: <alpine.DEB.1.00.0908261059530.4713@intel-tinevez-2-302>

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

>> mailsplit.scissors
>
> Sorry, did not have time to read this thread properly, but has anybody put 
> thought into the interaction between this patch and "git rebase" (which 
> uses "git am", and therefore mailsplit, internally)?

I was looking around this area tonight (I promised I won't touch the
definition of scissors, but I never said I won't work on making it
usable), as I originally shared the same worry with you.

It turns out that "rebase" invokes "am" with the "--rebasing" option.
Under this option, "am" uses an equivalent of "commit -C $commit"
internally to port the message forward.  So our worries are unfounded.

^ permalink raw reply

* Re: [PATCH v3] import-tars: Allow per-tar author and commit message.
From: Peter Krefting @ 2009-08-27  6:42 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7viqg96ehf.fsf@alter.siamese.dyndns.org>

Junio C Hamano:

> Do you really want to slurp Committer:/Author: lines from _anywhere_ in 
> the file?  Wouldn't it make more sense to vaguely emulate e-mail message 
> format with headers, empty-line and then body that is free form?

I just tried not to overdo it, and keep the parsing code as simple as 
possible. I wasn't trying to implement an RFC 5322 compliant parser...

-- 
\\// Peter - http://www.softwolves.pp.se/

^ permalink raw reply

* [PATCH 1/2 tr/reset-checkout-patch] Make test case number unique
From: Johannes Sixt @ 2009-08-27  7:35 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

From: Johannes Sixt <j6t@kdbg.org>

Signed-off-by: Johannes Sixt <j6t@kdbg.org>
---
 ...5-checkout-patch.sh => t2016-checkout-patch.sh} |    0
 1 files changed, 0 insertions(+), 0 deletions(-)
 rename t/{t2015-checkout-patch.sh => t2016-checkout-patch.sh} (100%)

diff --git a/t/t2015-checkout-patch.sh b/t/t2016-checkout-patch.sh
similarity index 100%
rename from t/t2015-checkout-patch.sh
rename to t/t2016-checkout-patch.sh
-- 
1.6.4.1.1371.g221f

^ permalink raw reply

* [PATCH 2/2 jc/1.7.0-diff-whitespace-only-status] Make test case number unique
From: Johannes Sixt @ 2009-08-27  7:38 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Git Mailing List

From: Johannes Sixt <j6t@kdbg.org>

Signed-off-by: Johannes Sixt <j6t@kdbg.org>
---
 ...espace-status.sh => t4040-whitespace-status.sh} |    0
 1 files changed, 0 insertions(+), 0 deletions(-)
 rename t/{t4037-whitespace-status.sh => t4040-whitespace-status.sh} (100%)

diff --git a/t/t4037-whitespace-status.sh b/t/t4040-whitespace-status.sh
similarity index 100%
rename from t/t4037-whitespace-status.sh
rename to t/t4040-whitespace-status.sh
-- 
1.6.4.1.1371.g221f

^ permalink raw reply

* Re: [PATCHv4 10/12] notes.c: Implement simple memory pooling of leaf  nodes
From: Alex Riesen @ 2009-08-27  7:39 UTC (permalink / raw)
  To: Johan Herland
  Cc: gitster, git, Johannes.Schindelin, trast, tavestbo, git,
	chriscool, spearce
In-Reply-To: <1251337437-16947-11-git-send-email-johan@herland.net>

On Thu, Aug 27, 2009 at 03:43, Johan Herland<johan@herland.net> wrote:
> When allocating a new memory pool, the older pool is leaked, but this is
> no worse than the current situation, where (pretty much) all leaf_nodes
> are leaked anyway.

Could you return the unused nodes back into tghe mempool?
By making the pool a preallocated list, perhaps?

And then it is trivial to provide a deallocation function for the mempool,
which something really concerned about the memleak can call (like when
or if libgit get more usable in an application context).

> @@ -95,7 +112,6 @@ static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,
>                        /* unpack tree and resume search */
>                        tree->a[i] = NULL;
>                        load_subtree(l, tree, n);
> -                       free(l);

free_leaf_node(l), which returns the node into mempool

>                        return note_tree_find(tree, n, key_sha1);
>                }
>                break;
> @@ -118,7 +134,6 @@ static struct leaf_node *note_tree_find(struct int_node *tree, unsigned char n,
>                /* unpack tree and resume search */
>                tree->a[0] = NULL;
>                load_subtree(l, tree, n);
> -               free(l);

free_leaf_node(l);

>                return note_tree_find(tree, n, key_sha1);
>        }
>        return NULL;

^ permalink raw reply

* Re: git clone http://git.savannah.gnu.org/cgit/xboard.git segfaults
From: Johannes Schindelin @ 2009-08-27  7:39 UTC (permalink / raw)
  To: Ali Polatel; +Cc: Tay Ray Chuan, git
In-Reply-To: <20090826131235.GF16486@harikalardiyari>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 857 bytes --]

Hi,

On Wed, 26 Aug 2009, Ali Polatel wrote:

> Tay Ray Chuan yazmış:
> 
> > On Mon, Aug 17, 2009 at 10:22 PM, Johannes Schindelin<Johannes.Schindelin@gmx.de> wrote:
> > > Seems that an object request is aborted, but the slot, and therefore 
> > > the callback, is called nevertheless.  Tay, does that ring a bell?
> > 
> > thanks Johannes, your diagnosis was a vital clue.
> > 
> > Ali, could you see if this patch fixes it for you? On my side, I had
> > some difficulty reproducing your problem reliably (it happened
> > sometimes but not on other times).
> > 
> 
> It works, I don't get any segfaults after applying this patch.

Great!

But why did you drop me from the Cc: list?  It's not every day that I can 
pay that close attention to the mails I get; mails which are not addressed 
to me directly fall off the plate on other days...

Ciao,
Dscho

^ permalink raw reply

* git-svn-Cloning repository with complicate nesting
From: Daniele Segato @ 2009-08-27  8:32 UTC (permalink / raw)
  To: Git Mailing List

Hi, this is my first message in the list: this may be a newbie
question and my English may not be very good.

I've an SVN repository structured like this:

http://<url>/path/to/repo
    |
  HEAD
    |----- root
    |
  BRANCHES
    |----- V1.0
    |         |----- root
    |
    |----- V1.1
    |         |----- root
    |
    |----- V1.2
    |         |----- root
    |
    |----- DEV
    |         |----- FEATURE1
    |         |            |----- root
    |         |
    |         |----- FEATURE2
    |         |            |----- root
    |         |
    |         |----- FEATURE3
    |                      |----- root
    |
    |----- BUILDS
              |----- BUILD1
              |            |----- root
              |
              |----- BUILD2
              |            |----- root
              |
              |----- BUILD3
                           |----- root

the same for TAGS.

I did this:

git init
git svn init <url>
vim .git/config

[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[svn-remote "svn"]
	url = <url>
	fetch = <url>/HEAD/root:refs/remotes/trunk
	branches = <url>/BRANCHES/*/root:refs/remotes/branches/*
	branches = <url>/BRANCHES/*/*/root:refs/remotes/devel/*
	tags = <url>/TAGS/*/root:refs/remotes/tags/*


git svn fetch


It is now cloning the repo (it is a really big repo)


It is my configuration ok for the repository structure?

if from another terminal I execute "git branch -r" I get:
  tags/V1.3.0
  tags/V1.3.0@3260
  tags/V1.3.1
  tags/V1.3.1@3359
  tags/V1.3.2
  tags/V1.3.2@4256
  tags/V1.4.0-COMMUNITY-FINAL
  tags/V1.4.0-ENTERPRISE-BETA@4241
  trunk
  trunk@4475

It should have already created some branch but I don't see any...

what are those @XXXX number for some of those branches?

Is the syntax of the svn-remote configuration correct?

with this:
branches = <url>/BRANCHES/*/*/root:refs/remotes/devel/*
how does git choose the name of the branch? (
refs/remotes/devel/WHAT_GOES_HERE ? )

I would like it to use the tuple */* of the directory


If the syntax of my configuration is not correct, where can I found a
documentation about it? I couldn't find one.


Thanks
Regards,
Daniele

^ permalink raw reply

* Re: [PATCHv4 08/12] Teach the notes lookup code to parse notes trees with various fanout schemes
From: Johan Herland @ 2009-08-27  9:35 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Johannes.Schindelin, trast, tavestbo, git, chriscool,
	spearce
In-Reply-To: <7v7hwp6ebb.fsf@alter.siamese.dyndns.org>

On Thursday 27 August 2009, Junio C Hamano wrote:
> Johan Herland <johan@herland.net> writes:
> > The semantics used when parsing notes trees (with regards to fanout
> > subtrees) follow Dscho's proposal fairly closely:
> > - No concatenation/merging of notes is performed. If there are several
> > notes objects referencing a given commit, only one of those objects are
> > used. - If a notes object for a given commit is present in the "root"
> > notes tree, no subtrees are consulted; the object in the root tree is
> > used directly. - If there are more than one subtree that prefix-matches
> > the given commit, only the subtree with the longest matching prefix is
> > consulted. This means that if the given commit is e.g. "deadbeef", and
> > the notes tree have subtrees "de" and "dead", then the following paths
> > in the notes tree are searched: "deadbeef", "dead/beef". Note that
> > "de/adbeef" is NOT searched. - Fanout directories (subtrees) must
> > references a whole number of bytes from the SHA1 sum they subdivide.
> > E.g. subtrees "dead" and "de" are acceptable; "d" and "dea" are not.
> > - Multiple levels of fanout are allowed. All the above rules apply
> >   recursively. E.g. "de/adbeef" is preferred over "de/adbe/ef", etc.
>
> If I am reading this correctly, the earlier parts of the series were
> aiming to let multiple people to add notes to the same commit more or
> less uncordinated while still allowing to merge them sensibly, but now
> such a workflow becomes impossible with this change.
>
> The above claims notes trees with different levels of fan-out are
> allowed, but what it really means is that merging notes trees with
> different levels of fan-out will produce a useless result that records
> notes for the same commit in different blobs all over the notes tree, and
> asking the notes mechanism to give the notes for one commit will give
> only one piece that originates in the tree whose creator happened to have
> used the longest prefix while ignoring all others.  It may _allow_ such a
> layout, but how would such semantics be useful in the first place?
>
> I suspect that I am missing something but my gut feeling is that this
> change turns an interesting hack (even though it might be expensive) into
> a hack that is not useful at all in the real world, without some order,
> discipline, or guideline is applied.

As you may remember, the major, remaining problem with the jh/notes patch 
series was that as the number of notes in a repo grew, the runtime cost of 
displaying (even a single one of) them became prohibitive. This was the 
major reason why Dscho and me did NOT want you to merge this series earlier.
The solution to this performance problem (which has been discussed since 
almost a year ago), is to use some fanout scheme in the notes tree, so that 
we can load individual notes without necessarily parsing the entire notes 
tree. This patch implements the _reading_ of a notes trees with fanout.

However, as you correctly identify, allowing fanout makes it possible to add 
multiple notes for the same commit in the same notes tree.

Therefore, we must now create the order/discipline/guideline you request by 
taking away this extra freedom.

This will take the form of enforcing a chosen fanout scheme when _writing_ 
notes. This code (yet to be written) will include:

- refactoring the notes tree when the number of notes call for a different 
fanout scheme (e.g. create a 2/38 fanout scheme when the number of notes in 
a (sub)tree exceed some threshold).

- adding and editing notes while following the current fanout scheme.

- adding a custom merge strategy for note trees, which reads notes trees 


Currently I have a two different ideas on a suitable fanout scheme:

1. Define a threshold where the number of notes in a (sub)tree becomes large 
enough warrant a fanout. For now, let's assume that threshold is 1024. Start 
with en empty notes tree with no fanout. When we reach 1024 notes, split the 
notes tree into 256 subdirs (2/38 fanout). As each of the subdirs reach 1024 
notes, split those subdirs further (2/2/36 fanout), and so on. In practice 
(since SHA1 gives us a uniform distribution), notes trees with up to:
- < 1024 notes will have no fanout
- < ~256K notes will have 2/38 fanout
- < ~64M notes will have 2/2/36 fanout
- < ~16G notes will have 2/2/2/34 fanout

2. Simply decide on a constant 2/2/36 fanout. For the case with < 256K 
notes, this is somewhat wasteful, but not prohibitively expensive. For the 
case with > 64M notes, performance will start to degrade. The big advantage 
with this approach is that when this is hardcoded into the notes code, we 
have regained the property that notes for a given commit have exactly _one_ 
unique position in the notes tree across all installations (enabling us to 
fall back on the regular merge strategy).

> What's the recommended way to work with this system from the end user's
> point of view in a distirbuted environment?

The end user should not know or care about what fanout scheme is used. 
Everything should be handled seamlessly by the code.

> Somebody up in the project is supposed to decide what fan-out is to be
> used for the whole project and everybody should follow that structure?

Nope, the code should decide which fanout scheme to use.

> If a participant in the project forgets that rule (or makes a mistake), a
> notes tree that mistakenly merges his notes tree becomes practically
> useless?

No. Again this should be invisible to the user.

> If so, perhaps we would need a mechanism to avoid such a mistake from
> happening?

The notes code should prevent notes that violate the fanout scheme from 
being created.

The fanout scheme is not something the user should have to worry about at 
all.


I totally understand that you don't want to merge the notes feature before 
the "writing" side is taken care of. As such, this iteration is simply yet 
another iteration towards that final goal.


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply

* Re: [PATCHv4 10/12] notes.c: Implement simple memory pooling of leaf nodes
From: Johan Herland @ 2009-08-27  9:49 UTC (permalink / raw)
  To: git
  Cc: Alex Riesen, gitster, Johannes.Schindelin, trast, tavestbo, git,
	chriscool, spearce
In-Reply-To: <81b0412b0908270039l7a937c3bmd745274c71526ce1@mail.gmail.com>

On Thursday 27 August 2009, Alex Riesen wrote:
> On Thu, Aug 27, 2009 at 03:43, Johan Herland<johan@herland.net> wrote:
> > When allocating a new memory pool, the older pool is leaked, but this
> > is no worse than the current situation, where (pretty much) all
> > leaf_nodes are leaked anyway.
>
> Could you return the unused nodes back into the mempool?
> By making the pool a preallocated list, perhaps?

Yes, maintaining a free-list is certainly possible. However, the number of 
free()d leaf_nodes is relatively small (only subtree entries are free()d 
after unpacking them into the tree structure), so I'm not sure it pays off, 
runtime-wise.

> And then it is trivial to provide a deallocation function for the
> mempool, which something really concerned about the memleak can call
> (like when or if libgit get more usable in an application context).

Yes, I plan to provide a free_notes() function that free()s all the memory 
associated with the notes data structure. This would of course keep 
references to all the mempools, and deallocate them (along with all the 
int_nodes).


...Johan

-- 
Johan Herland, <johan@herland.net>
www.herland.net

^ permalink raw reply

* [SCuMD]
From: Christian Senkowski @ 2009-08-27  9:53 UTC (permalink / raw)
  To: git

[-- Attachment #1: Type: text/plain, Size: 1301 bytes --]

Hi,

I cloned SCuMD directly via git and started it but got following error:

~/scumd$ ./run.sh
Exception in thread "main" java.lang.NoClassDefFoundError:
com.asolutions.scmsshd.SCuMD
   at gnu.java.lang.MainThread.run(libgcj.so.90)
Caused by: java.lang.ClassNotFoundException:
com.asolutions.scmsshd.SCuMD not found in
gnu.gcj.runtime.SystemClassLoader{urls=[file:depend/lib/jgit.jar,file:depend/lib/minasshd.jar,file:lib/aopalliance-1.0.jar,file:lib/bcprov-jdk15-140.jar,file:lib/commons-io-1.4.jar,file:lib/commons-logging-1.0.4.jar,file:./,file:lib/jline-0.9.1.jar,file:lib/jline-0.9.94.jar,file:lib/jpam-1.1.jar,file:lib/jsch-0.1.40.jar,file:lib/jzlib-1.0.7.jar,file:lib/log4j-1.2.13.jar,file:lib/slf4j-api-1.5.2.jar,file:lib/slf4j-log4j12-1.4.3.jar,file:lib/spring-beans-2.5.5.jar,file:lib/spring-context-2.5.5.jar,file:lib/spring-core-2.5.5.jar],
parent=gnu.gcj.runtime.ExtensionClassLoader{urls=[], parent=null}}
   at java.net.URLClassLoader.findClass(libgcj.so.90)
   at gnu.gcj.runtime.SystemClassLoader.findClass(libgcj.so.90)
   at java.lang.ClassLoader.loadClass(libgcj.so.90)
   at java.lang.ClassLoader.loadClass(libgcj.so.90)
   at gnu.java.lang.MainThread.run(libgcj.so.90)


Please help me out :)


Kind regards,
C. Senkowski

-- 
Christian Senkowski



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 260 bytes --]

^ permalink raw reply

* Re: [SCuMD]
From: Andreas Ericsson @ 2009-08-27 10:04 UTC (permalink / raw)
  To: Christian Senkowski; +Cc: git
In-Reply-To: <4A965799.6050204@gmx.de>

Christian Senkowski wrote:
> Hi,
> 
> I cloned SCuMD directly via git and started it but got following error:
> 
> ~/scumd$ ./run.sh
> Exception in thread "main" java.lang.NoClassDefFoundError:
> com.asolutions.scmsshd.SCuMD
>    at gnu.java.lang.MainThread.run(libgcj.so.90)
> Caused by: java.lang.ClassNotFoundException:
> com.asolutions.scmsshd.SCuMD not found in
> gnu.gcj.runtime.SystemClassLoader{urls=[file:depend/lib/jgit.jar,file:depend/lib/minasshd.jar,file:lib/aopalliance-1.0.jar,file:lib/bcprov-jdk15-140.jar,file:lib/commons-io-1.4.jar,file:lib/commons-logging-1.0.4.jar,file:./,file:lib/jline-0.9.1.jar,file:lib/jline-0.9.94.jar,file:lib/jpam-1.1.jar,file:lib/jsch-0.1.40.jar,file:lib/jzlib-1.0.7.jar,file:lib/log4j-1.2.13.jar,file:lib/slf4j-api-1.5.2.jar,file:lib/slf4j-log4j12-1.4.3.jar,file:lib/spring-beans-2.5.5.jar,file:lib/spring-context-2.5.5.jar,file:lib/spring-core-2.5.5.jar],
> parent=gnu.gcj.runtime.ExtensionClassLoader{urls=[], parent=null}}
>    at java.net.URLClassLoader.findClass(libgcj.so.90)
>    at gnu.gcj.runtime.SystemClassLoader.findClass(libgcj.so.90)
>    at java.lang.ClassLoader.loadClass(libgcj.so.90)
>    at java.lang.ClassLoader.loadClass(libgcj.so.90)
>    at gnu.java.lang.MainThread.run(libgcj.so.90)
> 
> 
> Please help me out :)
> 

Please refer to the SCuMD mailing list for this SCuMD related question.

-- 
Andreas Ericsson                   andreas.ericsson@op5.se
OP5 AB                             www.op5.se
Tel: +46 8-230225                  Fax: +46 8-230231

Considering the successes of the wars on alcohol, poverty, drugs and
terror, I think we should give some serious thought to declaring war
on peace.

^ permalink raw reply

* Re: [PATCHv4 08/12] Teach the notes lookup code to parse notes trees with various fanout schemes
From: Johannes Schindelin @ 2009-08-27 10:42 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Johan Herland, git, trast, tavestbo, git, chriscool, spearce
In-Reply-To: <7v7hwp6ebb.fsf@alter.siamese.dyndns.org>

Hi,

On Wed, 26 Aug 2009, Junio C Hamano wrote:

> Johan Herland <johan@herland.net> writes:
> 
> > The semantics used when parsing notes trees (with regards to fanout subtrees)
> > follow Dscho's proposal fairly closely:
> > - No concatenation/merging of notes is performed. If there are several notes
> >   objects referencing a given commit, only one of those objects are used.
> > - If a notes object for a given commit is present in the "root" notes tree,
> >   no subtrees are consulted; the object in the root tree is used directly.
> > - If there are more than one subtree that prefix-matches the given commit,
> >   only the subtree with the longest matching prefix is consulted. This
> >   means that if the given commit is e.g. "deadbeef", and the notes tree have
> >   subtrees "de" and "dead", then the following paths in the notes tree are
> >   searched: "deadbeef", "dead/beef". Note that "de/adbeef" is NOT searched.
> > - Fanout directories (subtrees) must references a whole number of bytes
> >   from the SHA1 sum they subdivide. E.g. subtrees "dead" and "de" are
> >   acceptable; "d" and "dea" are not.
> > - Multiple levels of fanout are allowed. All the above rules apply
> >   recursively. E.g. "de/adbeef" is preferred over "de/adbe/ef", etc.
> 
> If I am reading this correctly, the earlier parts of the series were
> aiming to let multiple people to add notes to the same commit more or less
> uncordinated while still allowing to merge them sensibly, but now such a
> workflow becomes impossible with this change.  
> 
> The above claims notes trees with different levels of fan-out are allowed,
> but what it really means is that merging notes trees with different levels
> of fan-out will produce a useless result that records notes for the same
> commit in different blobs all over the notes tree, and asking the notes
> mechanism to give the notes for one commit will give only one piece that
> originates in the tree whose creator happened to have used the longest
> prefix while ignoring all others.  It may _allow_ such a layout, but how
> would such semantics be useful in the first place?
> 
> I suspect that I am missing something but my gut feeling is that this
> change turns an interesting hack (even though it might be expensive) into
> a hack that is not useful at all in the real world, without some order,
> discipline, or guideline is applied.
> 
> What's the recommended way to work with this system from the end user's
> point of view in a distirbuted environment?  Somebody up in the project is
> supposed to decide what fan-out is to be used for the whole project and
> everybody should follow that structure?  If a participant in the project
> forgets that rule (or makes a mistake), a notes tree that mistakenly
> merges his notes tree becomes practically useless?  If so, perhaps we
> would need a mechanism to avoid such a mistake from happening?

Hmm...

Maybe you're right (and mugwump was right all along) that _all_ matching 
notes should be shown...

Ciao,
Dscho

^ permalink raw reply

* Re: [PATCHv4 08/12] Teach the notes lookup code to parse notes trees with various fanout schemes
From: Johannes Schindelin @ 2009-08-27 10:47 UTC (permalink / raw)
  To: Johan Herland
  Cc: Junio C Hamano, git, trast, tavestbo, git, chriscool, spearce
In-Reply-To: <200908271135.31794.johan@herland.net>

Hi,

On Thu, 27 Aug 2009, Johan Herland wrote:

> On Thursday 27 August 2009, Junio C Hamano wrote:

> > Somebody up in the project is supposed to decide what fan-out is to be 
> > used for the whole project and everybody should follow that structure?
> 
> Nope, the code should decide which fanout scheme to use.

I half-agree, the code should decide which fanout scheme to use, but 
_only_ when producing new notes.

I imagine that it could merge the existing notes, and try to make sure 
that there are no more blobs in a given subtree than a certain threshold; 
if that threshold is reached, it could fan-out using 2-digit subtrees, 
merging what needs merging (by concatenation) along the way.

The natural precedence of shallower paths/longer basenames should cope 
well with that (i.e. prefer to show abcd/... over ab/cd/...).

Ciao,
Dscho

^ permalink raw reply

* Re: [PATCH] Re: Teach mailinfo to ignore everything before -- >8 -- mark
From: Johannes Schindelin @ 2009-08-27 10:49 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jakub Narebski, git
In-Reply-To: <7vprahyfk4.fsf@alter.siamese.dyndns.org>

Hi,

On Wed, 26 Aug 2009, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> >> mailsplit.scissors
> >
> > Sorry, did not have time to read this thread properly, but has anybody put 
> > thought into the interaction between this patch and "git rebase" (which 
> > uses "git am", and therefore mailsplit, internally)?
> 
> I was looking around this area tonight (I promised I won't touch the
> definition of scissors, but I never said I won't work on making it
> usable), as I originally shared the same worry with you.
> 
> It turns out that "rebase" invokes "am" with the "--rebasing" option.
> Under this option, "am" uses an equivalent of "commit -C $commit"
> internally to port the message forward.  So our worries are unfounded.

Thank you very much!  This relieves me, indeed.

Ciao,
Dscho

^ permalink raw reply

* Re: [RFC] Use a 16-tree instead of a 256-tree for storing notes
From: Johannes Schindelin @ 2009-08-27 11:56 UTC (permalink / raw)
  To: Andreas Ericsson
  Cc: Alex Riesen, Johan Herland, git, gitster, trast, tavestbo, git,
	chriscool, spearce
In-Reply-To: <4A95383A.4080104@op5.se>

Hi,

On Wed, 26 Aug 2009, Andreas Ericsson wrote:


> If it's to be squashed in, why mention the 256-tree at all

It was labeled RFC, so I think it is perfectly fine to compare with other 
contenders.

Thanks,
Dscho

^ permalink raw reply

* Re: [PATCH] upload-pack: add a trigger for post-upload-pack hook
From: Johan Sørensen @ 2009-08-27 12:09 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jeff King, Tom Werner, Tom Preston-Werner, git
In-Reply-To: <7vy6p69j6a.fsf@alter.siamese.dyndns.org>

On Thu, Aug 27, 2009 at 2:47 AM, Junio C Hamano<gitster@pobox.com> wrote:
> After upload-pack successfully finishes its operation, post-upload-pack
> hook can be called for logging purposes.
>
> The hook is passed various pieces of information, one per line, from its
> standard input.  Currently the following items can be fed to the hook, but
> more types of information may be added in the future:
>
>    want SHA-1::
>        40-byte hexadecimal object name the client asked to include in the
>        resulting pack.  Can occur one or more times in the input.
>
>    have SHA-1::
>        40-byte hexadecimal object name the client asked to exclude from
>        the resulting pack, claiming to have them already.  Can occur zero
>        or more times in the input.
>
>    time float::
>        Number of seconds spent for creating the packfile.
>
>    size decimal::
>        Size of the resulting packfile in bytes.

Neat. And feeding it lines gives more room for future additions.

I'd like to suggest the following line from the original patch:

   full-pack integer::
        1 if the request was considered a full clone, 0 if it was a
partial update (fetch)


Also, on a similar note; in the little git-daemon (a tiny fork+exec
server in ruby) included with Gitorious there's a geo-ip lookup based
on the client addr. It would be fun if the client ip could be passed
along to this hook as well, but that would require passing it along
all the way from before fetch-pack is invoked as far as I can see..?

JS


>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>
>  > Here is an illustration patch.
>
>  And here is a bit more polished one with necessary supporting material.
>
>  Documentation/git-upload-pack.txt |    2 +
>  Documentation/githooks.txt        |   25 +++++++++++++
>  t/t5501-post-upload-pack.sh       |   49 ++++++++++++++++++++++++++
>  upload-pack.c                     |   68 +++++++++++++++++++++++++++++++++++-
>  4 files changed, 142 insertions(+), 2 deletions(-)
>  create mode 100755 t/t5501-post-upload-pack.sh
>
> diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
> index b8e49dc..63f3b5c 100644
> --- a/Documentation/git-upload-pack.txt
> +++ b/Documentation/git-upload-pack.txt
> @@ -20,6 +20,8 @@ The UI for the protocol is on the 'git-fetch-pack' side, and the
>  program pair is meant to be used to pull updates from a remote
>  repository.  For push operations, see 'git-send-pack'.
>
> +After finishing the operation successfully, `post-upload-pack`
> +hook is called (see linkgit:githooks[5]).
>
>  OPTIONS
>  -------
> diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
> index 1c73673..036f6c7 100644
> --- a/Documentation/githooks.txt
> +++ b/Documentation/githooks.txt
> @@ -307,6 +307,31 @@ Both standard output and standard error output are forwarded to
>  'git-send-pack' on the other end, so you can simply `echo` messages
>  for the user.
>
> +post-upload-pack
> +----------------
> +
> +After upload-pack successfully finishes its operation, this hook is called
> +for logging purposes.
> +
> +The hook is passed various pieces of information, one per line, from its
> +standard input.  Currently the following items can be fed to the hook, but
> +more types of information may be added in the future:
> +
> +want SHA-1::
> +    40-byte hexadecimal object name the client asked to include in the
> +    resulting pack.  Can occur one or more times in the input.
> +
> +have SHA-1::
> +    40-byte hexadecimal object name the client asked to exclude from
> +    the resulting pack, claiming to have them already.  Can occur zero
> +    or more times in the input.
> +
> +time float::
> +    Number of seconds spent for creating the packfile.
> +
> +size decimal::
> +    Size of the resulting packfile in bytes.
> +
>  pre-auto-gc
>  -----------
>
> diff --git a/t/t5501-post-upload-pack.sh b/t/t5501-post-upload-pack.sh
> new file mode 100755
> index 0000000..2cb63f8
> --- /dev/null
> +++ b/t/t5501-post-upload-pack.sh
> @@ -0,0 +1,49 @@
> +#!/bin/sh
> +
> +test_description='post upload-hook'
> +
> +. ./test-lib.sh
> +
> +LOGFILE=".git/post-upload-pack-log"
> +
> +test_expect_success setup '
> +       test_commit A &&
> +       test_commit B &&
> +       git reset --hard A &&
> +       test_commit C &&
> +       git branch prev B &&
> +       mkdir -p .git/hooks &&
> +       {
> +               echo "#!$SHELL_PATH" &&
> +               echo "cat >post-upload-pack-log"
> +       } >".git/hooks/post-upload-pack" &&
> +       chmod +x .git/hooks/post-upload-pack
> +'
> +
> +: test_expect_success initial '
> +       rm -fr sub &&
> +       git init sub &&
> +       (
> +               cd sub &&
> +               git fetch --no-tags .. prev
> +       ) &&
> +       want=$(sed -n "s/^want //p" "$LOGFILE") &&
> +       test "$want" = "$(git rev-parse --verify B)" &&
> +       ! grep "^have " "$LOGFILE"
> +'
> +
> +test_expect_success second '
> +       rm -fr sub &&
> +       git init sub &&
> +       (
> +               cd sub &&
> +               git fetch --no-tags .. prev:refs/remotes/prev &&
> +               git fetch --no-tags .. master
> +       ) &&
> +       want=$(sed -n "s/^want //p" "$LOGFILE") &&
> +       test "$want" = "$(git rev-parse --verify C)" &&
> +       have=$(sed -n "s/^have //p" "$LOGFILE") &&
> +       test "$have" = "$(git rev-parse --verify B)"
> +'
> +
> +test_done
> diff --git a/upload-pack.c b/upload-pack.c
> index 4d8be83..69a6f46 100644
> --- a/upload-pack.c
> +++ b/upload-pack.c
> @@ -141,8 +141,60 @@ static int do_rev_list(int fd, void *create_full_pack)
>        return 0;
>  }
>
> +static int feed_obj_to_hook(const char *label, struct object_array *oa, int i, int fd)
> +{
> +       int cnt;
> +       char buf[512];
> +
> +       cnt = sprintf(buf, "%s %s\n", label,
> +                     sha1_to_hex(oa->objects[i].item->sha1));
> +       return write_in_full(fd, buf, cnt) != cnt;
> +}
> +
> +static int run_post_upload_pack_hook(size_t total, struct timeval *tv)
> +{
> +       const char *argv[2];
> +       struct child_process proc;
> +       int err, i;
> +       int cnt;
> +       char buf[512];
> +
> +       argv[0] = "hooks/post-upload-pack";
> +       argv[1] = NULL;
> +
> +       if (access(argv[0], X_OK) < 0)
> +               return 0;
> +
> +       memset(&proc, 0, sizeof(proc));
> +       proc.argv = argv;
> +       proc.in = -1;
> +       proc.stdout_to_stderr = 1;
> +       err = start_command(&proc);
> +       if (err)
> +               return err;
> +       for (i = 0; !err && i < want_obj.nr; i++)
> +               err |= feed_obj_to_hook("want", &want_obj, i, proc.in);
> +       for (i = 0; !err && i < have_obj.nr; i++)
> +               err |= feed_obj_to_hook("have", &have_obj, i, proc.in);
> +       if (!err) {
> +               cnt = sprintf(buf, "time %ld.%06ld\n",
> +                             (long)tv->tv_sec, (long)tv->tv_usec);
> +               err |= (write_in_full(proc.in, buf, cnt) != cnt);
> +       }
> +       if (!err) {
> +               cnt = sprintf(buf, "size %ld\n", (long)total);
> +               err |= (write_in_full(proc.in, buf, cnt) != cnt);
> +       }
> +       if (close(proc.in))
> +               err = 1;
> +       if (finish_command(&proc))
> +               err = 1;
> +       return err;
> +}
> +
>  static void create_pack_file(void)
>  {
> +       struct timeval start_tv, tv;
>        struct async rev_list;
>        struct child_process pack_objects;
>        int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr);
> @@ -150,10 +202,12 @@ static void create_pack_file(void)
>        char abort_msg[] = "aborting due to possible repository "
>                "corruption on the remote side.";
>        int buffered = -1;
> -       ssize_t sz;
> +       ssize_t sz, total_sz;
>        const char *argv[10];
>        int arg = 0;
>
> +       gettimeofday(&start_tv, NULL);
> +       total_sz = 0;
>        if (shallow_nr) {
>                rev_list.proc = do_rev_list;
>                rev_list.data = 0;
> @@ -262,7 +316,7 @@ static void create_pack_file(void)
>                        sz = xread(pack_objects.out, cp,
>                                  sizeof(data) - outsz);
>                        if (0 < sz)
> -                                       ;
> +                               total_sz += sz;
>                        else if (sz == 0) {
>                                close(pack_objects.out);
>                                pack_objects.out = -1;
> @@ -314,6 +368,16 @@ static void create_pack_file(void)
>        }
>        if (use_sideband)
>                packet_flush(1);
> +
> +       gettimeofday(&tv, NULL);
> +       tv.tv_sec -= start_tv.tv_sec;
> +       if (tv.tv_usec < start_tv.tv_usec) {
> +               tv.tv_sec--;
> +               tv.tv_usec += 1000000;
> +       }
> +       tv.tv_usec -= start_tv.tv_usec;
> +       if (run_post_upload_pack_hook(total_sz, &tv))
> +               warning("post-upload-hook failed");
>        return;
>
>  fail:
> --
> 1.6.4.1.288.g10d22
>
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply

* Re: [PATCH 14/14] Add README and gitignore file for MSVC build
From: Frank Li @ 2009-08-27 13:16 UTC (permalink / raw)
  To: Thiago Farina
  Cc: Marius Storm-Olsen, Reece Dunn, Johannes.Schindelin, msysgit, git
In-Reply-To: <a4c8a6d00908251024o24380f7ue409ac5f164c085e@mail.gmail.com>

>
> [submodule "ext\\zlib"]
>       path = ext\\zlib
>       url = git://repo.or.cz/zlib.git
>
I fixed this problem
It should be ext/zlib

^ permalink raw reply

* Re: [PATCH] upload-pack: add a trigger for post-upload-pack hook
From: Jakub Narebski @ 2009-08-27 13:33 UTC (permalink / raw)
  To: Johan Sorensen
  Cc: Junio C Hamano, Jeff King, Tom Werner, Tom Preston-Werner, git
In-Reply-To: <9e0f31700908270509o1031a027y1b49efe7ea9a4fd3@mail.gmail.com>

Johan Sorensen <johan@johansorensen.com> writes:
> On Thu, Aug 27, 2009 at 2:47 AM, Junio C Hamano <gitster@pobox.com> wrote:

> > After upload-pack successfully finishes its operation, post-upload-pack
> > hook can be called for logging purposes.
> >
> > The hook is passed various pieces of information, one per line, from its
> > standard input.  Currently the following items can be fed to the hook, but
> > more types of information may be added in the future:
> >
> >    want SHA-1::
> >        40-byte hexadecimal object name the client asked to include in the
> >        resulting pack.  Can occur one or more times in the input.
> >
> >    have SHA-1::
> >        40-byte hexadecimal object name the client asked to exclude from
> >        the resulting pack, claiming to have them already.  Can occur zero
> >        or more times in the input.
> >
> >    time float::
> >        Number of seconds spent for creating the packfile.
> >
> >    size decimal::
> >        Size of the resulting packfile in bytes.
> 
> Neat. And feeding it lines gives more room for future additions.
> 
> I'd like to suggest the following line from the original patch:
> 
>    full-pack integer::
>         1 if the request was considered a full clone, 0 if it was a
> partial update (fetch)
 
If it is all "want" and no "have", it is clone or fetch into empty
repository.  If additionaly "want"s cover all refs, it is a clone.
No need to pass this information: it can be derived.

> Also, on a similar note; in the little git-daemon (a tiny fork+exec
> server in ruby) included with Gitorious there's a geo-ip lookup based
> on the client addr. It would be fun if the client ip could be passed
> along to this hook as well, but that would require passing it along
> all the way from before fetch-pack is invoked as far as I can see..?

Well, we can pass at least `client-ip`...

[please don't quote what is not needed]
-- 
Jakub Narebski
Poland
ShadeHawk on #git

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox