#include "cache.h" #include "default.h" #include "../commit.h" #include "../fetch-negotiator.h" #include "../prio-queue.h" #include "../refs.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 (parse_commit(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, void *cb_data) { 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) { if (commit != NULL && !(commit->object.flags & COMMON)) { struct object *o = (struct object *)commit; if (!ancestors_only) o->flags |= COMMON; if (!(o->flags & SEEN)) rev_list_push(ns, commit, SEEN); else { struct commit_list *parents; if (!ancestors_only && !(o->flags & POPPED)) ns->non_common_revs--; if (!o->parsed && !dont_parse) if (parse_commit(commit)) return; for (parents = commit->parents; parents; parents = parents->next) mark_common(ns, parents->item, 0, dont_parse); } } } /* * 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); parse_commit(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 = ns = xcalloc(1, sizeof(*ns)); ns->rev_list.compare = compare_commits_by_commit_date; if (marked) for_each_ref(clear_marks, NULL); marked = 1; }