* Re: fatal: git write-tree failed to write a tree
From: Johannes Schindelin @ 2009-03-01 21:10 UTC (permalink / raw)
To: Caleb Cushing; +Cc: git
In-Reply-To: <81bfc67a0902280825t507e385bvd25c846add2a299c@mail.gmail.com>
Hi,
On Sat, 28 Feb 2009, Caleb Cushing wrote:
> profiles/package.mask/java-overlay: unmerged
> (f67eeb638e2593b2bee5c9b476e3044a8404916a)
> fatal: git write-tree failed to write a tree
A tree cannot contain unmerged files.
Ciao,
Dscho
^ permalink raw reply
* Re: "warning: no common commits" triggered due to change of remote's IP address?
From: Thomas Rast @ 2009-03-01 21:20 UTC (permalink / raw)
To: Brent Goodrick; +Cc: git
In-Reply-To: <e38bce640903011001p2d705707o9f7145ab5ab68929@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 2099 bytes --]
Brent Goodrick wrote:
> My expectation at this point is that, since I've changed only the IP
> address, and kept everything else the same, git should be smart enough
> to compare SHA1 values only and not download the entire remote repo
> just to do that comparison.
>
> But I was quite surprised to find that it was pulling down tons of data:
Git doesn't care about the details of the transport; during its
handshake, lists of the available refs are exchanged, and then used to
determine the common commits. So I'm also rather surprised.
However, your use of + refspecs in
> gitw fetch 88.99.100.101:git.repos/environ.git
> +refs/heads/home:refs/remotes/origin/home
makes me wonder: have you rewritten the repo hosting 'home' between
two fetches? Using (especially, but not only) git-filter-branch can
easily render your history disjoint from the pre-filtering state.
> warning: no common commits
Either your history is very short and really has no common commits
whatsoever, or it gave up because of the 256 revision limit during
find_common().
IIRC it walks by date, so it is enough to make 256 local commits with
a new timestamp to hit that limit. I posted some experimental code
two months ago that would use a bisection algorithm, so that it is
harder to hit the limit and faster at detecting disjoint history, but
nobody had the time to review it.
I'll follow up with the patch if you want to try it. The problem is
it's quite large _and_ I don't run any git servers where I could give
it a good testing. You'll have to apply it to both sides.
(It does have the nice side-effect of saving the uploading side a bit
of work.)
> 1. Will terminating the git fetch like I did leave the satellite repo
> in an inconsistent state? If so, is my only choice to start
> a new repo from scratch on the satellite machine, or is there some
> repair mechanism?
It will just leave a temporary pack file that git-gc will eventually
remove. You can just try another fetch later.
--
Thomas Rast
trast@{inf,student}.ethz.ch
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply
* [PATCH 2/2 RFC] fetch-pack: log(n)-transmission find_common()
From: Thomas Rast @ 2009-03-01 21:24 UTC (permalink / raw)
To: Brent Goodrick; +Cc: git
Replaces the existing simple history search with a more sophisticated
algorithm:
1) Walk history with exponentially increasing stride lengths; i.e.,
send the 1st commit, then the 2nd after that, then the 4th after
that, and so on.
2) Bisect the resulting intervals.
Combined with tracking the "outstanding haves" so that we can detect
which sha1s were never ACKed by upload-pack (and hence not common),
this gives O(log(n)) required "have" lines.
Unfortunately this cannot work if the server sends fake ACKs, so we
introduce a capability 'no-giveup' which instructs upload-pack to
disable ok_to_give_up(). (Which incidentally saves the server a lot
of work.)
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
builtin-fetch-pack.c | 807 +++++++++++++++++++++++++++++++++++++++++++++++++-
upload-pack.c | 7 +-
2 files changed, 805 insertions(+), 9 deletions(-)
diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c
index ae0a67a..6d637ec 100644
--- a/builtin-fetch-pack.c
+++ b/builtin-fetch-pack.c
@@ -9,6 +9,7 @@
#include "fetch-pack.h"
#include "remote.h"
#include "run-command.h"
+#include <string.h>
static int transfer_unpack_limit = -1;
static int fetch_unpack_limit = -1;
@@ -25,6 +26,14 @@
#define COMMON_REF (1U << 2)
#define SEEN (1U << 3)
#define POPPED (1U << 4)
+#define BISECTED_FW (1U << 5)
+#define BISECTED_BW (1U << 6)
+#define NOTCOMMON (1U << 7)
+#define HAS_INFO (1U << 8)
+#define PARSED_WITH_PARENTS (1U << 9)
+#define CLEAN_MARKS (COMMON | COMMON_REF | SEEN | POPPED | BISECTED_FW \
+ | BISECTED_BW | NOTCOMMON | HAS_INFO \
+ | PARSED_WITH_PARENTS)
static int marked;
@@ -34,8 +43,61 @@
*/
#define MAX_IN_VAIN 256
+struct work_heap
+{
+ struct work_heap *next;
+ struct work_heap *prev;
+ struct work_item *item;
+};
+
+struct work_item
+{
+ /* Fibonacci heap data */
+ /* we don't need a parent pointer because we don't support changing keys */
+ struct work_item *child;
+ struct work_item *sibling;
+ int rank;
+ int marked;
+
+ /* actual content */
+ struct commit *commit;
+ unsigned int stride; /* for stride mode */
+ unsigned int steps;
+ unsigned int depth;
+ unsigned int bisect; /* for bisection mode */
+
+ /*
+ * Helper array to generate the bisection order. A size of 32
+ * is ok as long as the history length is less than 2**32.
+ */
+ struct commit **ladder;
+};
+
+/*
+ * "Recursion stack" of the exponential-stride algorithm
+ *
+ * This is a priority queue implemented as a Fibonacci heap since we
+ * expect to stuff a lot of commits into it.
+ */
+static struct work_heap *work_heap = NULL;
+static struct work_heap *work_min = NULL;
+
+/* list of commits not ACKed yet */
+static struct commit_list *outstanding = NULL;
+static struct commit_list *outstanding_end = NULL;
+
+struct commit_info
+{
+ struct commit_list *children;
+ struct commit_list *bisect_forward;
+ struct commit_list *bisect_backward;
+ struct commit_list *forward_bound;
+ struct commit_list *backward_bound;
+};
+
static struct commit_list *rev_list;
static int non_common_revs, multi_ack, use_sideband;
+static int exp_stride_algorithm;
static void rev_list_push(struct commit *commit, int mark)
{
@@ -68,8 +130,7 @@ static int clear_marks(const char *path, const unsigned char *sha1, int flag, vo
struct object *o = deref_tag(parse_object(sha1), path, 0);
if (o && o->type == OBJ_COMMIT)
- clear_commit_marks((struct commit *)o,
- COMMON | COMMON_REF | SEEN | POPPED);
+ clear_commit_marks((struct commit *)o, CLEAN_MARKS);
return 0;
}
@@ -107,6 +168,361 @@ static void mark_common(struct commit *commit,
}
}
+static struct commit_info *get_commit_info(struct commit *commit)
+{
+ if (commit->object.flags & HAS_INFO)
+ return commit->util;
+
+ struct commit_info *info = xmalloc(sizeof(struct commit_info));
+ info->children = NULL;
+ info->bisect_forward = NULL;
+ info->bisect_backward = NULL;
+ info->forward_bound = NULL;
+ info->backward_bound = NULL;
+ commit->util = info;
+ commit->object.flags |= HAS_INFO;
+ return info;
+}
+
+static void add_child(struct commit *parent, struct commit *child)
+{
+ struct commit_info *info = get_commit_info(parent);
+ struct commit_list *item;
+ for (item = info->children; item; item = item->next)
+ if (item->item == child)
+ break;
+ if (!item)
+ commit_list_insert(child, &(info->children));
+}
+
+/* implemented further below */
+static void mark_not_common(struct commit *commit);
+
+static void check_parsed_and_mark(struct commit *commit)
+{
+ struct commit_list *parent, *child;
+ struct commit_info *info;
+
+ if (!commit || commit->object.flags & PARSED_WITH_PARENTS)
+ return;
+
+ parse_commit(commit);
+
+ commit->object.flags |= PARSED_WITH_PARENTS;
+
+ info = get_commit_info(commit);
+ for (child = info->children; child; child = child->next) {
+ if (child->item->object.flags & (COMMON|COMMON_REF))
+ commit->object.flags |= COMMON;
+ }
+
+ for (parent = commit->parents; parent; parent = parent->next) {
+ add_child(parent->item, commit);
+ if (parent->item->object.flags & NOTCOMMON)
+ mark_not_common(commit);
+ }
+}
+
+static void work_item_free(struct work_item *item)
+{
+ if (!item->bisect)
+ free(item->ladder);
+ free(item);
+}
+
+/*
+ * Ordering imposed on the "recursion" that generates the commits.
+ * The smallest item is processed first. To maintain the O(log n)
+ * transmissions bound we want to achieve, bisections must come after
+ * (compare >0) strides.
+ */
+
+static int work_item_cmp(struct work_item *a, struct work_item *b)
+{
+ if (a->bisect || b->bisect) {
+ /* bisections last */
+ if (a->bisect > b->bisect)
+ return 1;
+ else if (a->bisect < b->bisect)
+ return -1;
+
+ /* older commits later */
+ if (a->commit->date < b->commit->date)
+ return 1;
+ else if (a->commit->date == b->commit->date)
+ return 0;
+ else
+ return -1;
+ } else {
+ /* deeper sidelines of history later */
+ if (a->depth > b->depth)
+ return 1;
+ else if (a->depth < b->depth)
+ return -1;
+
+ /* older commits later */
+ if (a->commit->date < b->commit->date)
+ return 1;
+ else if (a->commit->date == b->commit->date)
+ return 0;
+ else
+ return -1;
+ }
+}
+
+/*
+ * Insert/remove items from the work_heap. _pop() always returns the
+ * smallest one.
+ */
+
+static void work_heap_insert_internal(struct work_item *item)
+{
+ struct work_heap *entry = xmalloc(sizeof(struct work_heap));
+ entry->item = item;
+
+ if (!work_heap) {
+ entry->prev = entry;
+ entry->next = entry;
+ work_heap = entry;
+ work_min = work_heap;
+ return;
+ }
+
+ entry->next = work_heap;
+ entry->prev = work_heap->prev;
+ work_heap->prev = entry;
+ entry->prev->next = entry;
+ if (!work_min || work_item_cmp(item, work_min->item) < 0)
+ work_min = entry;
+ work_heap = entry;
+}
+
+static void work_heap_insert(struct work_item *item)
+{
+ item->marked = 0;
+ item->child = NULL;
+ item->sibling = NULL;
+ item->rank = 0;
+
+ work_heap_insert_internal(item);
+}
+
+static struct work_item *work_heap_pop(void)
+{
+ struct work_heap *entry = work_min;
+ struct work_item *x, *y, *t;
+ struct work_item *child, *item;
+ /* The rank of any child in a Fib heap of size n can be at
+ * most log_{3/2}(n), and we only support 2**32 items
+ * anyway */
+ struct work_item *ranks[55] = {0,};
+ int i;
+
+ if (!entry)
+ return NULL;
+
+ if (entry == entry->prev) {
+ work_heap = NULL;
+ work_min = NULL;
+ item = entry->item;
+ free(entry);
+ for (child = item->child; child; child = child->sibling) {
+ child->marked = 0;
+ work_heap_insert_internal(child);
+ }
+ return item;
+ }
+
+ work_heap = entry->next;
+ entry->next->prev = entry->prev;
+ entry->prev->next = entry->next;
+ item = entry->item;
+ free(entry);
+ work_min = NULL;
+
+ for (child = item->child; child; child = child->sibling) {
+ child->marked = 0;
+ work_heap_insert_internal(child);
+ }
+
+ while (work_heap) {
+ entry = work_heap;
+ if (entry == entry->prev) {
+ work_heap = NULL;
+ } else {
+ work_heap = entry->next;
+ entry->next->prev = entry->prev;
+ entry->prev->next = entry->next;
+ }
+ x = entry->item;
+ free(entry);
+ while ((y = ranks[x->rank])) {
+ ranks[x->rank] = NULL;
+ if (work_item_cmp(x, y) > 0) {
+ t = x;
+ x = y;
+ y = t;
+ }
+ y->sibling = x->child;
+ x->child = y;
+ x->rank++;
+ }
+ ranks[x->rank] = x;
+ }
+
+ for (i = 0; i < 55; i++)
+ if (ranks[i])
+ work_heap_insert_internal(ranks[i]);
+
+ return item;
+}
+
+static void work_insert_stride(struct commit *commit, int stride,
+ int steps, int depth, struct commit **ladder)
+{
+ struct work_item *item = xmalloc(sizeof(struct work_item));
+ check_parsed_and_mark(commit);
+ item->commit = commit;
+ item->stride = stride;
+ item->steps = steps;
+ item->depth = depth;
+ item->bisect = 0;
+ item->ladder = xmalloc(32*sizeof(struct commit *));
+ if (ladder)
+ memcpy(item->ladder, ladder, 32*sizeof(struct commit *));
+ work_heap_insert(item);
+}
+
+static void work_insert_bisect(struct commit *commit, int bisect)
+{
+ struct work_item *item = xmalloc(sizeof(struct work_item));
+ item->commit = commit;
+ item->stride = 0;
+ item->steps = 0;
+ item->depth = 0;
+ item->bisect = bisect;
+ item->ladder = NULL;
+ work_heap_insert(item);
+}
+
+static void work_insert_strides(struct commit_list *commits,
+ int stride, int steps, int depth, int depth2,
+ struct commit **ladder,
+ struct work_item *reusable_item)
+{
+ struct work_item *item = reusable_item;
+
+ while (commits) {
+ if (!(commits->item->object.flags &= SEEN)) {
+ if (!item)
+ item = xmalloc(sizeof(struct work_item));
+
+ item->commit = commits->item;
+ item->stride = stride;
+ item->steps = steps;
+ item->depth = depth;
+ item->bisect = 0;
+ item->ladder = xmalloc(32*sizeof(struct commit *));
+ if (ladder)
+ memcpy(item->ladder, ladder, 32*sizeof(struct commit *));
+ work_heap_insert(item);
+ }
+ commits = commits->next;
+ item = NULL;
+ depth = depth2;
+ }
+
+ if (item)
+ work_item_free(item);
+}
+
+static void work_insert_bisects(struct commit_list *commits,
+ int bisect,
+ struct work_item *reusable_item)
+{
+ struct work_item *item = reusable_item;
+
+ while (commits) {
+ if (!item)
+ item = xmalloc(sizeof(struct work_item));
+
+ item->commit = commits->item;
+ item->stride = 0;
+ item->steps = 0;
+ item->depth = 0;
+ item->bisect = bisect;
+ work_heap_insert(item);
+
+ commits = commits->next;
+ item = NULL;
+ }
+
+ if (item)
+ work_item_free(item);
+}
+
+static int work_heap_insert_ref(const char *path, const unsigned char *sha1,
+ int flag, void *cb_data)
+{
+ struct object *o = deref_tag(parse_object(sha1), path, 0);
+
+ if (o && o->type == OBJ_COMMIT && !(o->flags & SEEN))
+ work_insert_stride((struct commit *)o, 0, 0, -1, NULL);
+
+ return 0;
+}
+
+
+/*
+ * Marks all (known) children of this commit NOTCOMMON. We call this
+ * for all sha1s the server did not ACK.
+ */
+
+static void mark_not_common(struct commit *commit)
+{
+ struct commit_info *info = get_commit_info(commit);
+ struct commit_list *child;
+
+ assert(!(commit->object.flags & COMMON));
+
+ if (commit->object.flags & NOTCOMMON)
+ return;
+
+ commit->object.flags |= NOTCOMMON;
+
+ for (child = info->children; child; child = child->next)
+ mark_not_common(child->item);
+}
+
+/*
+ * Similar for the ACKed ones. We have a _new() version because the
+ * old one interacts with the work queue.
+ */
+
+static void mark_common_new(struct commit *commit, int ancestors_only, int dont_parse)
+{
+ struct commit_list *parents;
+ struct object *o;
+
+ if (commit == NULL || commit->object.flags & (COMMON_REF|COMMON))
+ return;
+
+ if (!dont_parse && !(commit->object.parsed))
+ check_parsed_and_mark(commit);
+
+ o = (struct object *)commit;
+
+ if (!ancestors_only)
+ o->flags |= COMMON;
+
+ for (parents = commit->parents; parents; parents = parents->next) {
+ add_child(parents->item, commit);
+ mark_common_new(parents->item, 0, dont_parse);
+ }
+}
+
+
+
/*
Get the next rev to send, ignoring the common.
*/
@@ -156,6 +572,341 @@ static void mark_common(struct commit *commit,
return commit;
}
+
+int flag_in_list(int flag, struct commit_list *list)
+{
+ while (list) {
+ if (list->item->object.flags & flag)
+ return 1;
+ list = list->next;
+ }
+ return 0;
+}
+
+
+/*
+ * Find the second bit set in x, similar to ffs() from string.h.
+ * Assumes that x>0.
+ */
+
+int fss(int x)
+{
+ return ffs(x - (1<<(ffs(x)-1)));
+}
+
+
+/*
+ * The exponential-stride algorithm, part 1 and 1.5.
+ *
+ * We take exponential strides through history, that is we emit the
+ * 1st commit, the 2nd, the 4th, etc. But we also simultaneously
+ * build pointers that help us bisect later: Every commit has a
+ * bisect_forward and bisect_backward list (if history is linear, both
+ * have at most 1 element) that tells us where to continue bisection
+ * after this.
+ *
+ * To achieve this with good running time, we think of the commits in
+ * a line of history as being numbered in binary:
+ *
+ * o 1000
+ * o 111
+ * o 110 <- current [1]
+ * o 101 [0]
+ * t | o 100 [2]
+ * i | o 11
+ * m | o 10
+ * e v o 1
+ *
+ * Every commit of the form 10...0 is emitted, since it is an
+ * exponential step. Between that, we want to have
+ * (100).bisect_backward=[110], (110).bisect_forward=[101],
+ * (110).bisect_backward=[111], etc. Define the lowest bit set (as
+ * per ffs()-1) in a commit as its "order", then we can achieve this
+ * by keeping track of the last commit of every order (marked with [n]
+ * in the diagram) as we go through history. Then [n] must be on
+ * [n+1].bisect_backward and [n-1] must be on [n].bisect_forward.
+ *
+ * Similarly, we track the boundaries forward_bound and
+ * backward_bound, which indicate the furthest commit(s) that can be
+ * reached by bisecting this one. We use them to detect when it
+ * becomes pointless to pursue this branch of the bisection because
+ * the far end is already COMMON (NOTCOMMON) in forward (backward)
+ * mode. In the scheme chosen, a current commit [n] numbered N is a
+ * backward boundary of [i] for all i=0..n, and [fss(N)-1] is a
+ * forward boundary of the current [n].
+ *
+ * The tough problem is if we stop (because we hit a root, or a part
+ * of history that we've already seen) at a non-exp-stride commit.
+ * Then we must ensure that all history up to the current commit can
+ * be reached by bisecting. Consider
+ *
+ * o 1000
+ * o 111
+ * o 110
+ * o 101 <- current [0]
+ * t | o 100 [2]
+ * i | o 11
+ * m | o 10 [1]
+ * e v o 1
+ *
+ * then stopping at the commit 101 without any corrective measures
+ * would leave it unreachable because the intermediate step 110 has
+ * not been seen yet.
+ *
+ * Observe that any given commit x10z, where x is arbitrary and z only
+ * zeros, has outgoing pointers to x01z (forward) and x11z (backward).
+ * Conversely this means x01z needs an incoming forward pointer from
+ * x10z, even if we haven't seen it yet. So there are two cases:
+ *
+ * SEEN CASE: We stopped because we hit a SEEN commit. Assume that
+ * the last commit not SEEN is numbered x1y1z, where x is arbitrary
+ * and y and z are only zeros (z possibly empty). Then unless y is
+ * empty, x1y'01z needs an incoming forward pointer from x1y'10z
+ * (where y'0=y), which we haven't seen yet. Repeat until y is empty.
+ * Note that during this repetition, we might hit a root commit.
+ *
+ * ROOT CASE: We cannot fix this with forward pointers since there are
+ * no commits left to put them in. Assume that the root commit is
+ * numbered x1y1z as in the SEEN case. Then we fix by pointing a
+ * backwards pointer from x1y'00z to x1y'01z, then iterate with
+ * x1y'00z. Note that x1y'00z is the last seen commit of order
+ * fss(x1y1z).
+ *
+ * Note that we only need to bisect (first level) exp-stride commits
+ * in one direction. We choose backward. This choice needs to be
+ * consistent among get_rev_new_stride() and get_rev_new(), and it
+ * happens to match the faster code path of get_rev_new_bisect().
+ */
+
+
+/* Helper for the root case */
+static void fix_pointers_at_root(struct commit *commit, int steps, struct commit **ladder)
+{
+ struct commit_info *info;
+ struct commit *commit2;
+ int order;
+ int order2;
+
+ order = ffs(steps)-1;
+ /* order2 tracks the second bit, commit2 the commit of that order */
+ order2 = fss(steps)-1;
+ while (order != order2-1 && order2 > 0) {
+ /* ladder[order2] is x1y'00z, commit2 is x1y'01z */
+ commit2 = ladder[order2];
+ info = get_commit_info(commit2);
+ commit_list_insert(commit, &info->bisect_backward);
+ /* forward bound was already handled when treating
+ * commit2 itself */
+ steps -= 1 << order;
+ order = order2;
+ commit = commit2;
+ order2 = fss(steps)-1;
+ }
+}
+
+static struct commit *get_rev_new_stride(struct work_item *item)
+{
+ struct commit_info *info;
+ struct commit *commit = item->commit;
+ int steps = item->steps;
+ int stride = item->stride;
+ int depth = item->depth;
+ int order = -1;
+ struct commit **ladder = item->ladder;
+ int i;
+ /* The next two are for the pointer-fixing iterations */
+ int order2;
+ struct commit *commit2;
+
+ if (depth == -1)
+ depth = 0;
+
+ while (!(commit->object.flags & (SEEN|COMMON))) {
+ steps++;
+ order = ffs(steps)-1;
+
+ commit->object.flags |= SEEN;
+
+ if (order > 0 && stride == order) {
+ info = get_commit_info(ladder[order-1]);
+ commit_list_insert(commit, &info->backward_bound);
+ for (i = order-2; i>=0; i--) {
+ info = get_commit_info(ladder[i]);
+ commit_list_insert(commit, &info->backward_bound);
+ }
+ }
+
+ ladder[order] = commit;
+
+ if (stride == order) {
+ /* this is precisely a 2**k'th commit, emit it */
+ work_insert_strides(commit->parents, stride+1, steps,
+ depth, depth+1, ladder, item);
+ return commit;
+ }
+
+ /*
+ * We are the next step in the higher-order commit's
+ * backward (in time) bisection
+ */
+ info = get_commit_info(ladder[order+1]);
+ commit_list_insert(commit, &info->bisect_backward);
+ info = get_commit_info(commit);
+ commit_list_insert(ladder[fss(steps)-1], &info->forward_bound);
+
+ if (order > 0) {
+ /*
+ * The lower-order commit is our next step for
+ * forward (in time) bisection
+ */
+ /* info = get_commit_info(commit); from above */
+ commit_list_insert(ladder[order-1], &info->bisect_forward);
+
+ /*
+ * This commit bounds _all_ lower-order commits backward
+ */
+ for (i = order-1; i >= 0; i--) {
+ info = get_commit_info(ladder[i]);
+ commit_list_insert(commit, &info->backward_bound);
+ }
+ }
+
+ if (commit->parents) {
+ work_insert_strides(commit->parents->next, stride,
+ steps, depth+1, depth+1, ladder,
+ NULL);
+ commit = commit->parents->item;
+ check_parsed_and_mark(commit);
+ } else {
+ /* ROOT CASE: fix missing pointers */
+ fix_pointers_at_root(commit, steps, ladder);
+ work_item_free(item);
+ return commit;
+ }
+ }
+
+ if (order == -1) {
+ /* this just means we hit a SEEN commit immediately */
+ work_item_free(item);
+ return NULL;
+ }
+
+ /* SEEN CASE: fix missing pointers w.r.t. the _last_ commit
+ * before this */
+
+ /* unless/until we hit a root, 'commit' tracks the steps
+ * backward through the SEEN commits that we stick the
+ * pointers in. 'order2' only serves to figure out if the 'y'
+ * (see comment at function head) of 'commit' is empty. */
+ order2 = fss(steps)-1;
+ assert(order2 > order || order2 == 0);
+ while (order != order2-1 && order2 > 0) {
+ int i;
+ /* skip ahead to the next commit2 of order 'order+1' */
+ for (i = (1<<order); i > 0; i--) {
+ steps++;
+ if (commit->parents) {
+ commit = commit->parents->item;
+ check_parsed_and_mark(commit);
+ continue;
+ }
+ /* we hit a root commit! use the root strategy
+ * for the rest */
+ fix_pointers_at_root(commit, steps, ladder);
+ /* short-cut out of the big loop */
+ work_item_free(item);
+ return NULL;
+ }
+ info = get_commit_info(commit);
+ commit_list_insert(ladder[order], &info->bisect_forward);
+ for (i = order; i >= 0; i--) {
+ info = get_commit_info(ladder[i]);
+ commit_list_insert(commit, &info->backward_bound);
+ }
+ order++;
+ /* this has not changed order2. we still keep track
+ * of 'steps' carefully in case we hit a root
+ * commit. */
+ }
+
+ work_item_free(item);
+ return NULL;
+}
+
+/*
+ * The exponential-stride algorithm, part 2.
+ *
+ * Bisecting is now easy: just follow the forward/backward pointers,
+ * stopping when we already know enough to not search any further.
+ */
+
+static struct commit *get_rev_new_bisect(struct work_item *item)
+{
+ struct commit *commit = item->commit;
+ struct commit_info *info = get_commit_info(commit);
+ int flags = commit->object.flags;
+
+ if (!(flags & (BISECTED_FW|NOTCOMMON))
+ && !flag_in_list(COMMON, info->forward_bound)) {
+ commit->object.flags |= BISECTED_FW;
+ work_insert_bisects(info->bisect_forward, item->bisect+1, NULL);
+ if (!(flags & (POPPED|COMMON))) {
+ /* re-queue for backward bisection */
+ work_heap_insert(item);
+ item = NULL;
+ return commit;
+ }
+ }
+
+ if (!(flags & (BISECTED_BW|COMMON))
+ && !flag_in_list(NOTCOMMON, info->backward_bound)) {
+ commit->object.flags |= BISECTED_BW;
+ work_insert_bisects(info->bisect_backward, item->bisect+1, item);
+ item = NULL;
+ if (!(flags & (POPPED|NOTCOMMON)))
+ return commit;
+ }
+
+ if (item)
+ free(item);
+ return NULL;
+}
+
+
+static struct commit* get_rev_new(void)
+{
+ struct commit *commit = NULL;
+ struct work_item *item;
+
+ while (commit == NULL) {
+ item = work_heap_pop();
+ if (!item)
+ return NULL;
+ check_parsed_and_mark(item->commit);
+ if (item->bisect) {
+ commit = get_rev_new_bisect(item);
+ } else {
+ commit = get_rev_new_stride(item);
+ if (commit) {
+ /* there's no point in bisecting the
+ * first level both ways */
+ commit->object.flags |= BISECTED_FW;
+ work_insert_bisect(commit, 1);
+ }
+ }
+ }
+
+ commit->object.flags |= POPPED;
+ return commit;
+}
+
+static void pop_outstanding(void)
+{
+ struct commit_list *item = outstanding;
+ outstanding = item->next;
+ free(item);
+}
+
/*
* Send 'have' for the next batch of revisions. Returns 0 if we ran
* out of commits to send, 1 otherwise.
@@ -167,7 +918,10 @@ static int send_have_lines(int fd[2], int *flushes, unsigned *in_vain)
int i;
for (i = 0; i < 32; i++) {
- commit = get_rev();
+ if (exp_stride_algorithm)
+ commit = get_rev_new();
+ else
+ commit = get_rev();
if (!commit)
return 0;
packet_write(fd[1], "have %s\n",
@@ -175,6 +929,14 @@ static int send_have_lines(int fd[2], int *flushes, unsigned *in_vain)
if (args.verbose)
fprintf(stderr, "have %s\n",
sha1_to_hex(commit->object.sha1));
+
+ if (outstanding) {
+ commit_list_insert(commit, &(outstanding_end->next));
+ outstanding_end = outstanding_end->next;
+ } else {
+ commit_list_insert(commit, &outstanding);
+ outstanding_end = outstanding;
+ }
}
packet_flush(fd[1]);
*flushes += 1;
@@ -195,7 +957,10 @@ static int find_common(int fd[2], unsigned char *result_sha1,
for_each_ref(clear_marks, NULL);
marked = 1;
- for_each_ref(rev_list_insert_ref, NULL);
+ if (exp_stride_algorithm)
+ for_each_ref(work_heap_insert_ref, NULL);
+ else
+ for_each_ref(rev_list_insert_ref, NULL);
fetching = 0;
for ( ; refs ; refs = refs->next) {
@@ -218,7 +983,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
if (!fetching)
- packet_write(fd[1], "want %s%s%s%s%s%s%s%s\n",
+ packet_write(fd[1], "want %s%s%s%s%s%s%s%s%s\n",
sha1_to_hex(remote),
(multi_ack ? " multi_ack" : ""),
(use_sideband == 2 ? " side-band-64k" : ""),
@@ -226,6 +991,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
(args.use_thin_pack ? " thin-pack" : ""),
(args.no_progress ? " no-progress" : ""),
(args.include_tag ? " include-tag" : ""),
+ (exp_stride_algorithm ? " no-giveup" : ""),
" ofs-delta");
else
packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
@@ -294,12 +1060,31 @@ static int find_common(int fd[2], unsigned char *result_sha1,
} else if (ack == 2) {
struct commit *commit =
lookup_commit(result_sha1);
- mark_common(commit, 0, 1);
+ if (exp_stride_algorithm) {
+ while (commit != outstanding->item) {
+ if (args.verbose)
+ fprintf(stderr, "unwinding: %s\n", sha1_to_hex(outstanding->item->object.sha1));
+ mark_not_common(outstanding->item);
+ pop_outstanding();
+ unwound++;
+ }
+ pop_outstanding();
+ unwound++;
+ mark_common_new(commit, 0, 1);
+ } else {
+ mark_common(commit, 0, 1);
+ }
retval = 0;
in_vain = 0;
got_continue = 1;
}
} while (ack);
+ while (exp_stride_algorithm && unwound++ < 32) {
+ if (args.verbose)
+ fprintf(stderr, "unwinding: %s\n", sha1_to_hex(outstanding->item->object.sha1));
+ mark_not_common(outstanding->item);
+ pop_outstanding();
+ }
flushes--;
if (got_continue && MAX_IN_VAIN < in_vain) {
if (args.verbose)
@@ -467,7 +1252,10 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
if (!(o->flags & SEEN)) {
rev_list_push((struct commit *)o, COMMON_REF | SEEN);
- mark_common((struct commit *)o, 1, 1);
+ if (exp_stride_algorithm)
+ mark_common_new((struct commit *)o, 1, 1);
+ else
+ mark_common((struct commit *)o, 1, 1);
}
}
@@ -617,6 +1405,11 @@ static int get_pack(int xd[2], char **pack_lockfile)
fprintf(stderr, "Server supports side-band\n");
use_sideband = 1;
}
+ if (server_supports("no-giveup")) {
+ if (args.verbose)
+ fprintf(stderr, "Server supports no-giveup\n");
+ exp_stride_algorithm = 1;
+ }
if (everything_local(&ref, nr_match, match)) {
packet_flush(fd[1]);
goto all_done;
diff --git a/upload-pack.c b/upload-pack.c
index e5adbc0..8f05dbf 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -37,6 +37,7 @@
*/
static int use_sideband;
static int debug_fd;
+static int disable_give_up;
static void reset_timeout(void)
{
@@ -414,7 +415,7 @@ static int get_common_commits(void)
if (!prefixcmp(line, "have ")) {
switch (got_sha1(line+5, sha1)) {
case -1: /* they have what we do not */
- if (multi_ack && ok_to_give_up())
+ if (multi_ack && !disable_give_up && ok_to_give_up())
packet_write(1, "ACK %s continue\n",
sha1_to_hex(sha1));
break;
@@ -501,6 +502,8 @@ static void receive_needs(void)
no_progress = 1;
if (strstr(line+45, "include-tag"))
use_include_tag = 1;
+ if (strstr(line+45, "no-giveup"))
+ disable_give_up = 1;
/* We have sent all our refs already, and the other end
* should have chosen out of them; otherwise they are
@@ -573,7 +576,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo
{
static const char *capabilities = "multi_ack thin-pack side-band"
" side-band-64k ofs-delta shallow no-progress"
- " include-tag";
+ " include-tag no-giveup";
struct object *o = parse_object(sha1);
if (!o)
--
tg: (73ef856..) t/fp-speedup (depends on: origin/master t/fp-refactor)
^ permalink raw reply related
* [PATCH 0/2] git-pull: Allow --stat and --no-stat to be used with --rebase
From: Tor Arne Vestbø @ 2009-03-01 21:28 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
These two small patches teach git-rebase to understand --stat, and git-pull
to forward --stat to git-rebase when used with --rebase. Comments welcome!
Tor Arne Vestbø (2):
git-rebase: Add --stat and --no-stat for producing diffstat on rebase
git-pull: Allow --stat and --no-stat to be used with --rebase
Documentation/git-rebase.txt | 17 ++++++++++++++++-
git-pull.sh | 10 +++++-----
git-rebase.sh | 25 ++++++++++++++++++-------
t/t3406-rebase-message.sh | 23 ++++++++++++++++++++++-
4 files changed, 61 insertions(+), 14 deletions(-)
^ permalink raw reply
* [PATCH 2/2] git-pull: Allow --stat and --no-stat to be used with --rebase
From: Tor Arne Vestbø @ 2009-03-01 21:28 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <1235942908-5419-2-git-send-email-torarnv@gmail.com>
Forwards the --stat, --no-stat, and --summary options on to git-rebase.
Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
git-pull.sh | 10 +++++-----
1 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/git-pull.sh b/git-pull.sh
index 25adddf..8a26763 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -16,7 +16,7 @@ cd_to_toplevel
test -z "$(git ls-files -u)" ||
die "You are in the middle of a conflicted merge."
-strategy_args= no_stat= no_commit= squash= no_ff= log_arg= verbosity=
+strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity=
curr_branch=$(git symbolic-ref -q HEAD)
curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
rebase=$(git config --bool branch.$curr_branch_short.rebase)
@@ -28,9 +28,9 @@ do
-v|--verbose)
verbosity="$verbosity -v" ;;
-n|--no-stat|--no-summary)
- no_stat=-n ;;
+ diffstat=--no-stat ;;
--stat|--summary)
- no_stat=$1 ;;
+ diffstat=--stat ;;
--log|--no-log)
log_arg=$1 ;;
--no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
@@ -188,7 +188,7 @@ fi
merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
test true = "$rebase" &&
- exec git-rebase $strategy_args --onto $merge_head \
+ exec git-rebase $diffstat $strategy_args --onto $merge_head \
${oldremoteref:-$merge_head}
-exec git-merge $no_stat $no_commit $squash $no_ff $log_arg $strategy_args \
+exec git-merge $diffstat $no_commit $squash $no_ff $log_arg $strategy_args \
"$merge_name" HEAD $merge_head $verbosity
--
1.6.2.rc2.11.g80931
^ permalink raw reply related
* [PATCH 1/2] git-rebase: Add --stat and --no-stat for producing diffstat on rebase
From: Tor Arne Vestbø @ 2009-03-01 21:28 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <1235942908-5419-1-git-send-email-torarnv@gmail.com>
The behavior of --verbose is unchanged, but uses a different state
variable internally, so that the meaning of verbose output may be
expanded without affecting the diffstat. This is also reflected in
the documentation.
The configuration option rebase.stat works the same was as merg.stat,
but the default is currently false.
Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
Documentation/git-rebase.txt | 17 ++++++++++++++++-
git-rebase.sh | 25 ++++++++++++++++++-------
t/t3406-rebase-message.sh | 23 ++++++++++++++++++++++-
3 files changed, 56 insertions(+), 9 deletions(-)
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index da3c38c..57bd333 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -192,6 +192,13 @@ Alternatively, you can undo the 'git-rebase' with
git rebase --abort
+CONFIGURATION
+-------------
+
+rebase.stat::
+ Whether to show a diffstat of what changed upstream since the last
+ rebase. False by default.
+
OPTIONS
-------
<newbase>::
@@ -232,7 +239,15 @@ OPTIONS
-v::
--verbose::
- Display a diffstat of what changed upstream since the last rebase.
+ Be verbose. Implies --stat.
+
+--stat::
+ Show a diffstat of what changed upstream since the last rebase. The
+ diffstat is also controlled by the configuration option rebase.stat.
+
+-n::
+--no-stat::
+ Do not show a diffstat as part of the rebase process.
--no-verify::
This option bypasses the pre-rebase hook. See also linkgit:githooks[5].
diff --git a/git-rebase.sh b/git-rebase.sh
index 368c0ef..26d7566 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -46,6 +46,7 @@ do_merge=
dotest="$GIT_DIR"/rebase-merge
prec=4
verbose=
+diffstat=$(git config --bool rebase.stat)
git_am_opt=
rebase_root=
@@ -289,8 +290,15 @@ do
esac
do_merge=t
;;
+ -n|--no-stat)
+ diffstat=
+ ;;
+ --stat)
+ diffstat=t
+ ;;
-v|--verbose)
verbose=t
+ diffstat=t
;;
--whitespace=*)
git_am_opt="$git_am_opt $1"
@@ -426,18 +434,21 @@ then
exit 0
fi
-if test -n "$verbose"
-then
- echo "Changes from $mb to $onto:"
- # We want color (if set), but no pager
- GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
-fi
-
# Detach HEAD and reset the tree
echo "First, rewinding head to replay your work on top of it..."
git checkout -q "$onto^0" || die "could not detach HEAD"
git update-ref ORIG_HEAD $branch
+if test -n "$diffstat"
+then
+ if test -n "$verbose"
+ then
+ echo "Changes from $mb to $onto:"
+ fi
+ # We want color (if set), but no pager
+ GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
+fi
+
# If the $onto is a proper descendant of the tip of the branch, then
# we just fast forwarded.
if test "$mb" = "$branch"
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 5391080..85fc7c4 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -22,7 +22,8 @@ test_expect_success setup '
git checkout topic &&
quick_one A &&
quick_one B &&
- quick_one Z
+ quick_one Z &&
+ git tag start
'
@@ -41,4 +42,24 @@ test_expect_success 'rebase -m' '
'
+test_expect_success 'rebase --stat' '
+ git reset --hard start
+ git rebase --stat master >diffstat.txt &&
+ grep "^ fileX | *1 +$" diffstat.txt
+'
+
+test_expect_success 'rebase w/config rebase.stat' '
+ git reset --hard start
+ git config rebase.stat true &&
+ git rebase master >diffstat.txt &&
+ grep "^ fileX | *1 +$" diffstat.txt
+'
+
+test_expect_success 'rebase -n overrides config rebase.stat config' '
+ git reset --hard start
+ git config rebase.stat true &&
+ git rebase -n master >diffstat.txt &&
+ ! grep "^ fileX | *1 +$" diffstat.txt
+'
+
test_done
--
1.6.2.rc2.11.g80931
^ permalink raw reply related
* Re: jgit and ignore
From: Robin Rosenberg @ 2009-03-01 20:47 UTC (permalink / raw)
To: Jon Smirl
Cc: Tor Arne Vestbø, Ferry Huberts (Pelagic), Shawn O. Pearce,
Git Mailing List
In-Reply-To: <9e4733910903010631p51f9d4a7xddf9d823ff848bde@mail.gmail.com>
Jon Smirl writes:
> My .git got created in my workspace root. Is there ever a case where
> you would want .git in your workspace root? If not, remove this choice
> when the parent directory is the workspace root.
Sure there is. If you create your projects in the workspace, and in particular,
directly beneath the workspace root, having the .git directory there is a logical
choice. I generally try to create the projects outside the workspace, but Eclipse
seems keen on creating projects in the workspace, but that is just my personal
preferences and partly for historical reasons.
-- robin
^ permalink raw reply
* Yesss! sourceforge.net and Git
From: Johannes Schindelin @ 2009-03-01 21:38 UTC (permalink / raw)
To: git
Hi,
check this out:
http://apps.sourceforge.net/trac/sitedocs/wiki/Git
Ciao,
Dscho
^ permalink raw reply
* Re: [PATCH 2/2 RFC] fetch-pack: log(n)-transmission find_common()
From: Thomas Rast @ 2009-03-01 21:37 UTC (permalink / raw)
To: Brent Goodrick; +Cc: git
In-Reply-To: <1235942640-2370-1-git-send-email-trast@student.ethz.ch>
[-- Attachment #1: Type: text/plain, Size: 528 bytes --]
I wrote:
> Subject: [PATCH 2/2 RFC] fetch-pack: log(n)-transmission find_common()
Sorry for the out-of-thread reply. This is almost exactly the same
patch as
http://article.gmane.org/gmane.comp.version-control.git/102485
I forget to send along 1/2. I'll tack it on 2/2 shortly, but that one
really is unchanged from the above thread IIRC.
But it also turns out, as you can see, that git-send-email happily
ignores --in-reply-to if threading is disabled. :-(
--
Thomas Rast
trast@{inf,student}.ethz.ch
[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply
* [PATCH 1/2] fetch-pack: rearrange main loop
From: Thomas Rast @ 2009-03-01 21:39 UTC (permalink / raw)
To: Brent Goodrick; +Cc: git
In-Reply-To: <1235942640-2370-1-git-send-email-trast@student.ethz.ch>
This patch does not change the results (nor any of the semantics
except for the get_rev return type), but we need the changed layout
for the exponential-stride feature.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
builtin-fetch-pack.c | 108 +++++++++++++++++++++++++++++--------------------
1 files changed, 64 insertions(+), 44 deletions(-)
diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c
index 372bfa2..ae0a67a 100644
--- a/builtin-fetch-pack.c
+++ b/builtin-fetch-pack.c
@@ -111,7 +111,7 @@ static void mark_common(struct commit *commit,
Get the next rev to send, ignoring the common.
*/
-static const unsigned char* get_rev(void)
+static struct commit* get_rev(void)
{
struct commit *commit = NULL;
@@ -153,15 +153,41 @@ static void mark_common(struct commit *commit,
rev_list = rev_list->next;
}
- return commit->object.sha1;
+ return commit;
}
+/*
+ * Send 'have' for the next batch of revisions. Returns 0 if we ran
+ * out of commits to send, 1 otherwise.
+ */
+
+static int send_have_lines(int fd[2], int *flushes, unsigned *in_vain)
+{
+ struct commit *commit;
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ commit = get_rev();
+ if (!commit)
+ return 0;
+ packet_write(fd[1], "have %s\n",
+ sha1_to_hex(commit->object.sha1));
+ if (args.verbose)
+ fprintf(stderr, "have %s\n",
+ sha1_to_hex(commit->object.sha1));
+ }
+ packet_flush(fd[1]);
+ *flushes += 1;
+ *in_vain += 32;
+ return 1;
+}
+
+
static int find_common(int fd[2], unsigned char *result_sha1,
struct ref *refs)
{
int fetching;
int count = 0, flushes = 0, retval;
- const unsigned char *sha1;
unsigned in_vain = 0;
int got_continue = 0;
@@ -243,51 +269,45 @@ static int find_common(int fd[2], unsigned char *result_sha1,
flushes = 0;
retval = -1;
- while ((sha1 = get_rev())) {
- packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
- if (args.verbose)
- fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
- in_vain++;
- if (!(31 & ++count)) {
- int ack;
- packet_flush(fd[1]);
- flushes++;
+ /*
+ * We keep one window "ahead" of the other side, and
+ * will wait for an ACK only on the next one
+ */
+ if (!send_have_lines(fd, &flushes, &in_vain))
+ goto done;
- /*
- * We keep one window "ahead" of the other side, and
- * will wait for an ACK only on the next one
- */
- if (count == 32)
- continue;
+ while (send_have_lines(fd, &flushes, &in_vain)) {
+ int ack;
+ int unwound = 0;
- do {
- ack = get_ack(fd[0], result_sha1);
- if (args.verbose && ack)
- fprintf(stderr, "got ack %d %s\n", ack,
- sha1_to_hex(result_sha1));
- if (ack == 1) {
- flushes = 0;
- multi_ack = 0;
- retval = 0;
- goto done;
- } else if (ack == 2) {
- struct commit *commit =
- lookup_commit(result_sha1);
- mark_common(commit, 0, 1);
- retval = 0;
- in_vain = 0;
- got_continue = 1;
- }
- } while (ack);
- flushes--;
- if (got_continue && MAX_IN_VAIN < in_vain) {
- if (args.verbose)
- fprintf(stderr, "giving up\n");
- break; /* give up */
+ do {
+ ack = get_ack(fd[0], result_sha1);
+ if (args.verbose && ack)
+ fprintf(stderr, "got ack %d %s\n", ack,
+ sha1_to_hex(result_sha1));
+ if (ack == 1) {
+ flushes = 0;
+ multi_ack = 0;
+ retval = 0;
+ goto done;
+ } else if (ack == 2) {
+ struct commit *commit =
+ lookup_commit(result_sha1);
+ mark_common(commit, 0, 1);
+ retval = 0;
+ in_vain = 0;
+ got_continue = 1;
}
+ } while (ack);
+ flushes--;
+ if (got_continue && MAX_IN_VAIN < in_vain) {
+ if (args.verbose)
+ fprintf(stderr, "giving up\n");
+ break; /* give up */
}
}
+
done:
packet_write(fd[1], "done\n");
if (args.verbose)
--
tg: (7f705dc..) t/fp-refactor (depends on: origin/master)
^ permalink raw reply related
* Re: Yesss! sourceforge.net and Git
From: Sverre Rabbelier @ 2009-03-01 21:44 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: git
In-Reply-To: <alpine.DEB.1.00.0903012237360.10279@pacific.mpi-cbg.de>
Heya,
On Sun, Mar 1, 2009 at 22:38, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> http://apps.sourceforge.net/trac/sitedocs/wiki/Git
About time! Totally awesome though.
Now, code.google.com and others have no excuse anymore, right? ;)
--
Cheers,
Sverre Rabbelier
^ permalink raw reply
* Re: [PATCH 1/2] git-rebase: Add --stat and --no-stat for producing diffstat on rebase
From: Jakub Narebski @ 2009-03-01 21:47 UTC (permalink / raw)
To: Tor Arne Vestbø; +Cc: git
In-Reply-To: <1235942908-5419-2-git-send-email-torarnv@gmail.com>
Tor Arne Vestbø <torarnv@gmail.com> writes:
> The behavior of --verbose is unchanged, but uses a different state
> variable internally, so that the meaning of verbose output may be
> expanded without affecting the diffstat. This is also reflected in
> the documentation.
>
> The configuration option rebase.stat works the same was as merg.stat,
> but the default is currently false.
>
> Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
> ---
> Documentation/git-rebase.txt | 17 ++++++++++++++++-
> git-rebase.sh | 25 ++++++++++++++++++-------
> t/t3406-rebase-message.sh | 23 ++++++++++++++++++++++-
> 3 files changed, 56 insertions(+), 9 deletions(-)
>
You have to update also Documentation/config.txt
--
Jakub Narebski
Poland
ShadeHawk on #git
^ permalink raw reply
* Re: [PATCH] rebase -i: avoid 'git reset' when possible
From: Johannes Schindelin @ 2009-03-01 21:57 UTC (permalink / raw)
To: Junio C Hamano
Cc: Sverre Rabbelier, Stephen Haberman, Shawn O. Pearce, Thomas Rast,
Git Mailing List, Stephan Beyer, Christian Couder,
Daniel Barkalow
In-Reply-To: <7vvdqt8wob.fsf@gitster.siamese.dyndns.org>
Hi,
On Sun, 1 Mar 2009, Junio C Hamano wrote:
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
>
> > diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
> > index 3dc659d..a9617a2 100755
> > --- a/git-rebase--interactive.sh
> > +++ b/git-rebase--interactive.sh
> > @@ -442,6 +442,27 @@ do_rest () {
> > done
> > }
> >
> > +# skip picking commits whose parents are unchanged
> > +skip_unnecessary_picks () {
> > + current=$ONTO
> > + i=0
> > + while read command sha1 rest
> > + do
> > + test pick = "$command" &&
> > + test $current = "$(git rev-parse $sha1^)" ||
> > + break
> > + current=$(git rev-parse $sha1)
> > + i=$(($i+1))
> > + done < "$TODO"
> > + test $i = 0 || {
> > + sed -e "${i}q" < "$TODO" >> "$DONE" &&
> > + sed -e "1,${i}d" < "$TODO" >> "$TODO".new &&
> > + mv -f "$TODO".new "$TODO" &&
> > + ONTO=$current ||
> > + die "Could not skip $i pick commands"
> > + }
> > +}
>
> I do not think you have to count and then run two sed.
>
> this=$ONTO
> skip_pick=t
> while read command sha1 rest
> do
> sha1=$(git rev-parse "$sha1")
> case "$skip_pick,$command" in
> t,pick | t,p)
> if test "$this" = "$sha1"
> then
> echo >&3 "$command $sha1 $rest"
> this="$sha1"
> continue
> fi
> esac
> skip_pick=f
> echo "$command $sha1 $rest"
> done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
> ...
Or even
current=$ONTO
fd=3
while read command sha1 rest
do
case "$fd,$command,$current" in
3,pick,"$sha1"*|t,p,"$sha1"*)
current=$sha1
;;
*)
fd=1
;;
esac
echo "$command $sha1 $rest" >&$fd
done < "$TODO" > "$TODO.new" 3>> "$DONE" &&
mv "$TODO.new" "$TODO"
Hmm?
Ciao,
Dscho
^ permalink raw reply
* Re: Yesss! sourceforge.net and Git
From: Grzegorz Kossakowski @ 2009-03-01 21:47 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: git
In-Reply-To: <alpine.DEB.1.00.0903012237360.10279@pacific.mpi-cbg.de>
Johannes Schindelin pisze:
> Hi,
>
> check this out:
>
> http://apps.sourceforge.net/trac/sitedocs/wiki/Git
Great news.
I have some more. At Apache we are in the process of setting up official Git mirror of our SVN repository.
It may sound like nothing really big but first step (even small) is important. We are already discussing how we could
change our work-flow if we have more powerful tools in place.
Personally, I would like to thank Git community for producing such great piece of software and supporting it here in
very professional and vivid way.
--
Best regards,
Grzegorz Kossakowski
^ permalink raw reply
* Re: Yesss! sourceforge.net and Git
From: Johannes Schindelin @ 2009-03-01 22:01 UTC (permalink / raw)
To: Sverre Rabbelier; +Cc: git
In-Reply-To: <fabb9a1e0903011344r2a094283ge95e29d674858213@mail.gmail.com>
[-- Attachment #1: Type: TEXT/PLAIN, Size: 448 bytes --]
Hi,
On Sun, 1 Mar 2009, Sverre Rabbelier wrote:
> On Sun, Mar 1, 2009 at 22:38, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> > http://apps.sourceforge.net/trac/sitedocs/wiki/Git
>
> About time! Totally awesome though.
> Now, code.google.com and others have no excuse anymore, right? ;)
Except that it is run by Subversion people, and therefore it by like
asking git.or.cz to serve Mercurial repositories ;-)
Ciao,
Dscho
^ permalink raw reply
* Re: Yesss! sourceforge.net and Git
From: Sverre Rabbelier @ 2009-03-01 22:02 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: git
In-Reply-To: <alpine.DEB.1.00.0903012259390.10279@pacific.mpi-cbg.de>
Heya,
On Sun, Mar 1, 2009 at 23:01, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
> On Sun, 1 Mar 2009, Sverre Rabbelier wrote:
>> On Sun, Mar 1, 2009 at 22:38, Johannes Schindelin wrote:
>> > http://apps.sourceforge.net/trac/sitedocs/wiki/Git
It seems there's a bunch of sillyness in there, such as them
recommending backup over rsync?! They should be telling people to just
make a clone, and in the case of a crash they can push the clone back
up!
Also, maybe we can tell them git learned how to clone empty repo's recently?
>> About time! Totally awesome though.
>> Now, code.google.com and others have no excuse anymore, right? ;)
>
> Except that it is run by Subversion people, and therefore it by like
> asking git.or.cz to serve Mercurial repositories ;-)
Heh, speaking of Hg, I heard they're adding DVCS support to
code.google.com soon, if they're already adding a non-svn VCS, why not
git? :D
--
Cheers,
Sverre Rabbelier
^ permalink raw reply
* Re: Yesss! sourceforge.net and Git
From: Johannes Schindelin @ 2009-03-01 22:07 UTC (permalink / raw)
To: Sverre Rabbelier; +Cc: git
In-Reply-To: <fabb9a1e0903011402y3d2878b1h7e29f720dbfe1c82@mail.gmail.com>
[-- Attachment #1: Type: TEXT/PLAIN, Size: 1130 bytes --]
Hi,
On Sun, 1 Mar 2009, Sverre Rabbelier wrote:
> On Sun, Mar 1, 2009 at 23:01, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
> > On Sun, 1 Mar 2009, Sverre Rabbelier wrote:
> >> On Sun, Mar 1, 2009 at 22:38, Johannes Schindelin wrote:
> >> > http://apps.sourceforge.net/trac/sitedocs/wiki/Git
>
> It seems there's a bunch of sillyness in there, such as them
> recommending backup over rsync?! They should be telling people to just
> make a clone, and in the case of a crash they can push the clone back
> up!
> Also, maybe we can tell them git learned how to clone empty repo's recently?
It's not in any release yet, is it?
> >> About time! Totally awesome though.
> >> Now, code.google.com and others have no excuse anymore, right? ;)
> >
> > Except that it is run by Subversion people, and therefore it by like
> > asking git.or.cz to serve Mercurial repositories ;-)
>
> Heh, speaking of Hg, I heard they're adding DVCS support to
> code.google.com soon, if they're already adding a non-svn VCS, why not
> git? :D
Well, you work in the shop. Lots of politics, my son, lots of politics.
Ciao,
Dscho
^ permalink raw reply
* [PATCH v2] git-rebase: Add --stat and --no-stat for producing diffstat on rebase
From: Tor Arne Vestbø @ 2009-03-01 22:11 UTC (permalink / raw)
To: Junio C Hamano; +Cc: jnareb, git
In-Reply-To: <m3zlg4ud9v.fsf@localhost.localdomain>
The behavior of --verbose is unchanged, but uses a different state
variable internally, so that the meaning of verbose output may be
expanded without affecting the diffstat. This is also reflected in
the documentation.
The configuration option rebase.stat works the same was as merg.stat,
but the default is currently false.
Signed-off-by: Tor Arne Vestbø <torarnv@gmail.com>
---
Thanks Jakub, I knew there was a reason that git-config.txt was so empty ;)
And sorry for the double post, --cc to git-send-email seems to override
what's in the config, not compliment it.
Documentation/config.txt | 4 ++++
Documentation/git-rebase.txt | 17 ++++++++++++++++-
git-rebase.sh | 25 ++++++++++++++++++-------
t/t3406-rebase-message.sh | 23 ++++++++++++++++++++++-
4 files changed, 60 insertions(+), 9 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index f5152c5..6be2e99 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1160,6 +1160,10 @@ pull.octopus::
pull.twohead::
The default merge strategy to use when pulling a single branch.
+rebase.stat::
+ Whether to show a diffstat of what changed upstream since the last
+ rebase. False by default.
+
receive.fsckObjects::
If it is set to true, git-receive-pack will check all received
objects. It will abort in the case of a malformed object or a
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index da3c38c..57bd333 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -192,6 +192,13 @@ Alternatively, you can undo the 'git-rebase' with
git rebase --abort
+CONFIGURATION
+-------------
+
+rebase.stat::
+ Whether to show a diffstat of what changed upstream since the last
+ rebase. False by default.
+
OPTIONS
-------
<newbase>::
@@ -232,7 +239,15 @@ OPTIONS
-v::
--verbose::
- Display a diffstat of what changed upstream since the last rebase.
+ Be verbose. Implies --stat.
+
+--stat::
+ Show a diffstat of what changed upstream since the last rebase. The
+ diffstat is also controlled by the configuration option rebase.stat.
+
+-n::
+--no-stat::
+ Do not show a diffstat as part of the rebase process.
--no-verify::
This option bypasses the pre-rebase hook. See also linkgit:githooks[5].
diff --git a/git-rebase.sh b/git-rebase.sh
index 368c0ef..26d7566 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -46,6 +46,7 @@ do_merge=
dotest="$GIT_DIR"/rebase-merge
prec=4
verbose=
+diffstat=$(git config --bool rebase.stat)
git_am_opt=
rebase_root=
@@ -289,8 +290,15 @@ do
esac
do_merge=t
;;
+ -n|--no-stat)
+ diffstat=
+ ;;
+ --stat)
+ diffstat=t
+ ;;
-v|--verbose)
verbose=t
+ diffstat=t
;;
--whitespace=*)
git_am_opt="$git_am_opt $1"
@@ -426,18 +434,21 @@ then
exit 0
fi
-if test -n "$verbose"
-then
- echo "Changes from $mb to $onto:"
- # We want color (if set), but no pager
- GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
-fi
-
# Detach HEAD and reset the tree
echo "First, rewinding head to replay your work on top of it..."
git checkout -q "$onto^0" || die "could not detach HEAD"
git update-ref ORIG_HEAD $branch
+if test -n "$diffstat"
+then
+ if test -n "$verbose"
+ then
+ echo "Changes from $mb to $onto:"
+ fi
+ # We want color (if set), but no pager
+ GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
+fi
+
# If the $onto is a proper descendant of the tip of the branch, then
# we just fast forwarded.
if test "$mb" = "$branch"
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 5391080..85fc7c4 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -22,7 +22,8 @@ test_expect_success setup '
git checkout topic &&
quick_one A &&
quick_one B &&
- quick_one Z
+ quick_one Z &&
+ git tag start
'
@@ -41,4 +42,24 @@ test_expect_success 'rebase -m' '
'
+test_expect_success 'rebase --stat' '
+ git reset --hard start
+ git rebase --stat master >diffstat.txt &&
+ grep "^ fileX | *1 +$" diffstat.txt
+'
+
+test_expect_success 'rebase w/config rebase.stat' '
+ git reset --hard start
+ git config rebase.stat true &&
+ git rebase master >diffstat.txt &&
+ grep "^ fileX | *1 +$" diffstat.txt
+'
+
+test_expect_success 'rebase -n overrides config rebase.stat config' '
+ git reset --hard start
+ git config rebase.stat true &&
+ git rebase -n master >diffstat.txt &&
+ ! grep "^ fileX | *1 +$" diffstat.txt
+'
+
test_done
--
1.6.2.rc2.11.g80931
^ permalink raw reply related
* Re: Yesss! sourceforge.net and Git
From: Felipe Contreras @ 2009-03-01 22:10 UTC (permalink / raw)
To: Sverre Rabbelier; +Cc: Johannes Schindelin, git
In-Reply-To: <fabb9a1e0903011402y3d2878b1h7e29f720dbfe1c82@mail.gmail.com>
On Mon, Mar 2, 2009 at 12:02 AM, Sverre Rabbelier <srabbelier@gmail.com> wrote:
> Heya,
>
>
> On Sun, Mar 1, 2009 at 23:01, Johannes Schindelin
> <Johannes.Schindelin@gmx.de> wrote:
>> On Sun, 1 Mar 2009, Sverre Rabbelier wrote:
>>> About time! Totally awesome though.
>>> Now, code.google.com and others have no excuse anymore, right? ;)
>>
>> Except that it is run by Subversion people, and therefore it by like
>> asking git.or.cz to serve Mercurial repositories ;-)
>
> Heh, speaking of Hg, I heard they're adding DVCS support to
> code.google.com soon, if they're already adding a non-svn VCS, why not
> git? :D
Well, git has become so freakingly popular that supporting any DVCS
that is not git would generate lots of complaints.
--
Felipe Contreras
^ permalink raw reply
* Re: Yesss! sourceforge.net and Git
From: Sverre Rabbelier @ 2009-03-01 22:11 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: git
In-Reply-To: <alpine.DEB.1.00.0903012306220.10279@pacific.mpi-cbg.de>
Heya,
On Sun, Mar 1, 2009 at 23:07, Johannes Schindelin
<Johannes.Schindelin@gmx.de> wrote:
>> Also, maybe we can tell them git learned how to clone empty repo's recently?
>
> It's not in any release yet, is it?
True, that. But when it is we can? What with 1.6.2 soon-to-be-released and all?
> Well, you work in the shop. Lots of politics, my son, lots of politics.
Yeah, it was mostly wishful thinking from my side. There's some work
being done on git and hg interoperability though, so if worst comes to
worst, I could live with using something like git-hg ;).
--
Cheers,
Sverre Rabbelier
^ permalink raw reply
* Re: Yesss! sourceforge.net and Git
From: Sverre Rabbelier @ 2009-03-01 22:13 UTC (permalink / raw)
To: Felipe Contreras; +Cc: Johannes Schindelin, git
In-Reply-To: <94a0d4530903011410u67b20a38nc81d8b00b1c251f8@mail.gmail.com>
Heya,
On Sun, Mar 1, 2009 at 23:10, Felipe Contreras
<felipe.contreras@gmail.com> wrote:
> Well, git has become so freakingly popular that supporting any DVCS
> that is not git would generate lots of complaints.
Hehe, since when have big company's ever bothered with 'lots of
complaints'? ;) I mean, they're offering a free service, they can
always hide behind "if you don't like it, use something else".
--
Cheers,
Sverre Rabbelier
^ permalink raw reply
* [PATCH v5] Test fsck a bit harder
From: Thomas Rast @ 2009-03-01 22:32 UTC (permalink / raw)
To: Johannes Sixt, Junio C Hamano; +Cc: git
In-Reply-To: <200902212021.37807.j6t@kdbg.org>
git-fsck, of all tools, has very few tests. This adds some more:
* a corrupted object;
* a branch pointing to a non-commit;
* a tag pointing to a nonexistent object;
* and a tag pointing to an object of a type other than what the tag
itself claims.
Some of the involved shell programming is due to Johannes Sixt.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Sorry for taking so long to fix this.
Johannes Sixt wrote:
> This is wrong: It does not test the exist status. In a pipeline, the shell
> looks only at the exit status of the last command. You really want this as
>
> test_must_fail git fsck >out 2>&1 &&
You're right.
> If you want to have it more verbose, add 'cat out &&'. But IMHO that is
> overengineered. If the test detects a regression in the future, it is easy to
> inspect the file out if necessary.
I usually try to write the tests such that the cause of failure should
be reasonably obvious from the verbose output. But I don't really
care enough to insert stray cats either...
t/t1450-fsck.sh | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 69 insertions(+), 0 deletions(-)
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 4597af0..d4a83a1 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -28,4 +28,73 @@ test_expect_success 'loose objects borrowed from alternate are not missing' '
)
'
+# Corruption tests follow. Make sure to remove all traces of the
+# specific corruption you test afterwards, lest a later test trip over
+# it.
+
+test_expect_success 'object with bad sha1' '
+ sha=$(echo blob | git hash-object -w --stdin) &&
+ echo $sha &&
+ old=$(echo $sha | sed "s+^..+&/+") &&
+ new=${old%/*}/ffffffffffffffffffffffffffffffffffffff &&
+ sha=${new%/*}${new##*/} &&
+ mv .git/objects/$old .git/objects/$new &&
+ git update-index --add --cacheinfo 100644 $sha foo &&
+ tree=$(git write-tree) &&
+ cmt=$(echo bogus | git commit-tree $tree) &&
+ git update-ref refs/heads/bogus $cmt &&
+ test_must_fail git fsck >out 2>&1 &&
+ grep "$sha.*corrupt" out
+'
+
+rm -f .git/objects/$new
+git update-ref -d refs/heads/bogus
+git read-tree -u --reset HEAD
+
+test_expect_success 'branch pointing to non-commit' '
+ git rev-parse HEAD^{tree} > .git/refs/heads/invalid &&
+ git fsck 2>&1 | tee out &&
+ grep "not a commit" out
+'
+
+git update-ref -d refs/heads/invalid
+
+cat > invalid-tag <<EOF
+object ffffffffffffffffffffffffffffffffffffffff
+type commit
+tag invalid
+tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+This is an invalid tag.
+EOF
+
+test_expect_success 'tag pointing to nonexistent' '
+ tag=$(git hash-object -t tag -w --stdin < invalid-tag) &&
+ echo $tag > .git/refs/tags/invalid &&
+ test_must_fail git fsck >out 2>&1 &&
+ grep "missing commit ffffffffffffffffffffffffffffffffffffffff" out
+'
+
+rm .git/refs/tags/invalid
+rm -f .git/objects/$(echo $tag | sed "s#^..#&/#")
+
+cat > wrong-tag <<EOF
+object $(echo blob | git hash-object -w --stdin)
+type commit
+tag wrong
+tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+This is an invalid tag.
+EOF
+
+test_expect_success 'tag pointing to something else than its type' '
+ tag=$(git hash-object -t tag -w --stdin < wrong-tag) &&
+ echo $tag > .git/refs/tags/wrong &&
+ test_must_fail git fsck >out 2>&1 &&
+ grep "Object.*is a blob, not a commit" out
+'
+
+rm .git/refs/tags/wrong
+
+
test_done
--
1.6.2.rc2.289.g2fa25
^ permalink raw reply related
* [PATCH] send-email: respect in-reply-to regardless of threading
From: Thomas Rast @ 2009-03-01 22:45 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano
In-Reply-To: <200903012237.40891.trast@student.ethz.ch>
git-send-email supports the --in-reply-to option even with
--no-thread. However, the code that adds the relevant mail headers
was guarded by a test for --thread.
Remove the test, so that the user's choice is respected.
Signed-off-by: Thomas Rast <trast@student.ethz.ch>
---
Thomas Rast wrote:
> But it also turns out, as you can see, that git-send-email happily
> ignores --in-reply-to if threading is disabled. :-(
This is the minimally intrusive fix. It would be more consistent to
ask for the in-reply-to regardless of thread setting, but it would
also be less of a fix and more of a behaviour change.
git-send-email.perl | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/git-send-email.perl b/git-send-email.perl
index adf7ecb..09fe3d9 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -821,7 +821,7 @@ sub get_patch_subject($) {
Message-Id: $message_id
X-Mailer: git-send-email $gitversion
";
- if ($thread && $reply_to) {
+ if ($reply_to) {
$header .= "In-Reply-To: $reply_to\n";
$header .= "References: $references\n";
--
1.6.2.rc2.340.g83918
^ permalink raw reply related
* Re: gitk endless loop
From: Paul Mackerras @ 2009-03-01 22:48 UTC (permalink / raw)
To: Johannes Sixt; +Cc: Git Mailing List
In-Reply-To: <4992B5DE.3010207@viscovery.net>
Johannes Sixt writes:
> This recipe sends gitk into an endless loop. In git.git do:
I just pushed out a fix. Thanks for the recipe for reproducing the
problem.
Paul.
^ permalink raw reply
* Re: "warning: no common commits" triggered due to change of remote's IP address?
From: Brent Goodrick @ 2009-03-01 23:01 UTC (permalink / raw)
To: Thomas Rast; +Cc: git
In-Reply-To: <200903012221.03662.trast@student.ethz.ch>
On Sun, Mar 1, 2009 at 1:20 PM, Thomas Rast <trast@student.ethz.ch> wrote:
> However, your use of + refspecs in
>
>> gitw fetch 88.99.100.101:git.repos/environ.git
>> +refs/heads/home:refs/remotes/origin/home
>
> makes me wonder: have you rewritten the repo hosting 'home' between
> two fetches? Using (especially, but not only) git-filter-branch can
> easily render your history disjoint from the pre-filtering state.
>
>> warning: no common commits
>
> Either your history is very short and really has no common commits
> whatsoever, or it gave up because of the 256 revision limit during
> find_common().
Hmmm, maybe, without knowing it. Originally, that section of the
.git/config file had "*"'s where "home" was. To clarify, the original
was:
[remote "origin"]
url = <some_ip_address>:git.repos/environ.git
fetch = +refs/heads/*:refs/remotes/origin/*
and the current one is now:
[remote "origin"]
url = <some_ip_address>:git.repos/environ.git
fetch = +refs/heads/home:refs/remotes/origin/home
Maybe I had made that change and this is the first time I am doing a
fetch to using that change. I thinking that was the cause of this,
because I retried doing a fetch into a separate throw-away repo with
just the change of IP address, and it did not need to fetch anything
more. I had not executed git-filter-branch at all.
>> 1. Will terminating the git fetch like I did leave the satellite repo
>> in an inconsistent state? If so, is my only choice to start
>> a new repo from scratch on the satellite machine, or is there some
>> repair mechanism?
>
> It will just leave a temporary pack file that git-gc will eventually
> remove. You can just try another fetch later.
Good, that is what I would have expected.
Brent
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox