diff --git a/upload-pack.c b/upload-pack.c
index ddaa72f0a9..4572fff07c 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -12,9 +12,15 @@
static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn]
";
-#define THEY_HAVE (1U << 0)
-#define OUR_REF (1U << 1)
-#define WANTED (1U << 2)
+/* bits #0..7 in revision.h, #8..10 in commit.c */
+#define THEY_HAVE (1u << 11)
+#define OUR_REF (1u << 12)
+#define WANTED (1u << 13)
+#define COMMON_KNOWN (1u << 14)
+#define REACHABLE (1u << 15)
+
+static unsigned long oldest_have;
+
static int multi_ack, nr_our_refs;
static int use_thin_pack, use_ofs_delta;
static struct object_array have_obj;
@@ -303,11 +309,12 @@ static void create_pack_file(void)
static int got_sha1(char *hex, unsigned char *sha1)
{
struct object *o;
+ int we_knew_they_have = 0;
if (get_sha1_hex(hex, sha1))
die("git-upload-pack: expected SHA1 object, got '%s'", hex);
if (!has_sha1_file(sha1))
- return 0;
+ return -1;
o = lookup_object(sha1);
if (!(o && o->parsed))
@@ -316,15 +323,84 @@ static int got_sha1(char *hex, unsigned char *sha1)
die("oops (%s)", sha1_to_hex(sha1));
if (o->type == OBJ_COMMIT) {
struct commit_list *parents;
+ struct commit *commit = (struct commit *)o;
if (o->flags & THEY_HAVE)
- return 0;
- o->flags |= THEY_HAVE;
- for (parents = ((struct commit*)o)->parents;
+ we_knew_they_have = 1;
+ else
+ o->flags |= THEY_HAVE;
+ if (!oldest_have || (commit->date < oldest_have))
+ oldest_have = commit->date;
+ for (parents = commit->parents;
parents;
parents = parents->next)
parents->item->object.flags |= THEY_HAVE;
}
- add_object_array(o, NULL, &have_obj);
+ if (!we_knew_they_have) {
+ add_object_array(o, NULL, &have_obj);
+ return 1;
+ }
+ return 0;
+}
+
+static int reachable(struct commit *want)
+{
+ struct commit_list *work = NULL;
+
+ insert_by_date(want, &work);
+ while (work) {
+ struct commit_list *list = work->next;
+ struct commit *commit = work->item;
+ free(work);
+ work = list;
+
+ if (commit->object.flags & THEY_HAVE) {
+ want->object.flags |= COMMON_KNOWN;
+ break;
+ }
+ if (!commit->object.parsed)
+ parse_object(commit->object.sha1);
+ if (commit->object.flags & REACHABLE)
+ continue;
+ commit->object.flags |= REACHABLE;
+ if (commit->date < oldest_have)
+ continue;
+ for (list = commit->parents; list; list = list->next) {
+ struct commit *parent = list->item;
+ if (!(parent->object.flags & REACHABLE))
+ insert_by_date(parent, &work);
+ }
+ }
+ want->object.flags |= REACHABLE;
+ clear_commit_marks(want, REACHABLE);
+ free_commit_list(work);
+ return (want->object.flags & COMMON_KNOWN);
+}
+
+static int ok_to_give_up(void)
+{
+ int i;
+
+ if (!have_obj.nr)
+ return 0;
+
+ for (i = 0; i < want_obj.nr; i++) {
+ struct object *want = want_obj.objects[i].item;
+
+ if (want->flags & COMMON_KNOWN)
+ continue;
+ want = deref_tag(want, "a want line", 0);
+ if (!want || want->type != OBJ_COMMIT) {
+ /* no way to tell if this is reachable by
+ * looking at the ancestry chain alone, so
+ * leave a note to ourselves not to worry about
+ * this object anymore.
+ */
+ want_obj.objects[i].item->flags |= COMMON_KNOWN;
+ continue;
+ }
+ if (!reachable((struct commit *)want))
+ return 0;
+ }
return 1;
}
@@ -349,7 +425,13 @@ static int get_common_commits(void)
}
len = strip(line, len);
if (!strncmp(line, "have ", 5)) {
- if (got_sha1(line+5, sha1)) {
+ switch (got_sha1(line+5, sha1)) {
+ case -1: /* they have what we do not */
+ if (multi_ack && ok_to_give_up())
+ packet_write(1, "ACK %s continue\n",
+ sha1_to_hex(sha1));
+ break;
+ default:
memcpy(hex, sha1_to_hex(sha1), 41);
if (multi_ack) {
const char *msg = "ACK %s continue\n";
@@ -358,6 +440,7 @@ static int get_common_commits(void)
}
else if (have_obj.nr == 1)
packet_write(1, "ACK %s\n", hex);
+ break;
}
continue;
}