#define USE_THE_REPOSITORY_VARIABLE #include "git-compat-util.h" #include "default.h" #include "../commit.h" #include "../fetch-negotiator.h" #include "../prio-queue.h" #include "../refs.h" #include "../repository.h" #include "../tag.h" /* Remember to update object flag allocation in object.h */ #define COMMON (1U << 2) #define COMMON_REF (1U << 3) #define SEEN (1U << 4) #define POPPED (1U << 5) static int marked; struct negotiation_state { struct prio_queue rev_list; int non_common_revs; }; static void rev_list_push(struct negotiation_state *ns, struct commit *commit, int mark) { if (!(commit->object.flags & mark)) { commit->object.flags |= mark; if (repo_parse_commit(the_repository, commit)) return; prio_queue_put(&ns->rev_list, commit); if (!(commit->object.flags & COMMON)) ns->non_common_revs++; } } static int clear_marks(const char *refname, const struct object_id *oid, int flag UNUSED, void *cb_data UNUSED) { struct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0); if (o && o->type == OBJ_COMMIT) clear_commit_marks((struct commit *)o, COMMON | COMMON_REF | SEEN | POPPED); return 0; } /* * This function marks a rev and its ancestors as common. * In some cases, it is desirable to mark only the ancestors (for example * when only the server does not yet know that they are common). */ static void mark_common(struct negotiation_state *ns, struct commit *commit, int ancestors_only, int dont_parse) { struct prio_queue queue = { NULL }; if (!commit || (commit->object.flags & COMMON)) return; prio_queue_put(&queue, commit); if (!ancestors_only) { commit->object.flags |= COMMON; if ((commit->object.flags & SEEN) && !(commit->object.flags & POPPED)) ns->non_common_revs--; } while ((commit = prio_queue_get(&queue))) { struct object *o = (struct object *)commit; if (!(o->flags & SEEN)) rev_list_push(ns, commit, SEEN); else { struct commit_list *parents; if (!o->parsed && !dont_parse) if (repo_parse_commit(the_repository, commit)) continue; for (parents = commit->parents; parents; parents = parents->next) { struct commit *p = parents->item; if (p->object.flags & COMMON) continue; p->object.flags |= COMMON; if ((p->object.flags & SEEN) && !(p->object.flags & POPPED)) ns->non_common_revs--; prio_queue_put(&queue, parents->item); } } } clear_prio_queue(&queue); } /* * Get the next rev to send, ignoring the common. */ static const struct object_id *get_rev(struct negotiation_state *ns) { struct commit *commit = NULL; while (commit == NULL) { unsigned int mark; struct commit_list *parents; if (ns->rev_list.nr == 0 || ns->non_common_revs == 0) return NULL; commit = prio_queue_get(&ns->rev_list); repo_parse_commit(the_repository, commit); parents = commit->parents; commit->object.flags |= POPPED; if (!(commit->object.flags & COMMON)) ns->non_common_revs--; if (commit->object.flags & COMMON) { /* do not send "have", and ignore ancestors */ commit = NULL; mark = COMMON | SEEN; } else if (commit->object.flags & COMMON_REF) /* send "have", and ignore ancestors */ mark = COMMON | SEEN; else /* send "have", also for its ancestors */ mark = SEEN; while (parents) { if (!(parents->item->object.flags & SEEN)) rev_list_push(ns, parents->item, mark); if (mark & COMMON) mark_common(ns, parents->item, 1, 0); parents = parents->next; } } return &commit->object.oid; } static void known_common(struct fetch_negotiator *n, struct commit *c) { if (!(c->object.flags & SEEN)) { rev_list_push(n->data, c, COMMON_REF | SEEN); mark_common(n->data, c, 1, 1); } } static void add_tip(struct fetch_negotiator *n, struct commit *c) { n->known_common = NULL; rev_list_push(n->data, c, SEEN); } static const struct object_id *next(struct fetch_negotiator *n) { n->known_common = NULL; n->add_tip = NULL; return get_rev(n->data); } static int ack(struct fetch_negotiator *n, struct commit *c) { int known_to_be_common = !!(c->object.flags & COMMON); mark_common(n->data, c, 0, 1); return known_to_be_common; } static void release(struct fetch_negotiator *n) { clear_prio_queue(&((struct negotiation_state *)n->data)->rev_list); FREE_AND_NULL(n->data); } void default_negotiator_init(struct fetch_negotiator *negotiator) { struct negotiation_state *ns; negotiator->known_common = known_common; negotiator->add_tip = add_tip; negotiator->next = next; negotiator->ack = ack; negotiator->release = release; negotiator->data = CALLOC_ARRAY(ns, 1); ns->rev_list.compare = compare_commits_by_commit_date; if (marked) refs_for_each_ref(get_main_ref_store(the_repository), clear_marks, NULL); marked = 1; }