In a future change, we will introduce a capability to choose specific commit
OIDs as 'have's in fetch negotiation, with the ability to have the
negotiator choose more 'have's to increase coverage beyond that required
core set. The negotiator works to avoid emitting 'have's that can reach each
other, but that logic is hidden beneath the negotiator's iterator function
pointer ('next'). We need a way to communicate to the negotiator that we
have picked a 'have' so it could incorporate that into its logic.
Add a have_sent() method to the fetch_negotiator interface. This is the
signal that allows the negotiator to track the commit as already shown and
can perform the proper bookkeeping to avoid emitting those objects or
anything they can reach.
For our non-trivial negotiators, it is sufficient to mark these commits as
common, so the implementation is quite simple. This logic will be exercised
in the next change.
Reviewed-by: Matthew John Cheetham <mjcheetham@outlook.com>
Signed-off-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
208 lines
4.9 KiB
C
208 lines
4.9 KiB
C
#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 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 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;
|
|
}
|