| #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 struct reference *ref, void *cb_data UNUSED) | |
| { | |
| struct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid), | |
| ref->name, 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 commit_stack stack = COMMIT_STACK_INIT; | |
| if (!commit || (commit->object.flags & COMMON)) | |
| return; | |
| commit_stack_push(&stack, commit); | |
| if (!ancestors_only) { | |
| commit->object.flags |= COMMON; | |
| if ((commit->object.flags & SEEN) && !(commit->object.flags & POPPED)) | |
| ns->non_common_revs--; | |
| } | |
| while ((commit = commit_stack_pop(&stack))) { | |
| 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--; | |
| commit_stack_push(&stack, parents->item); | |
| } | |
| } | |
| } | |
| commit_stack_clear(&stack); | |
| } | |
| /* | |
| * 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->non_common_revs == 0) | |
| return NULL; | |
| commit = prio_queue_get(&ns->rev_list); | |
| if (!commit) | |
| return NULL; | |
| 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 have_sent(struct fetch_negotiator *n, struct commit *c) | |
| { | |
| if (repo_parse_commit(the_repository, c)) | |
| return; | |
| mark_common(n->data, c, 0, 0); | |
| } | |
| 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->have_sent = have_sent; | |
| 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; | |
| } |