mirror of
https://github.com/git/git
synced 2024-08-27 19:49:21 +00:00
![Junio C Hamano](/assets/img/avatar_default.png)
The implementation of the default "negotiator", used to find common ancestor over the network for object tranfer, used to be recursive; it was updated to be iterative to conserve stackspace usage. * hx/negotiator-non-recursive: negotiator/skipping: fix some problems in mark_common() negotiator/default: avoid stack overflow
198 lines
4.7 KiB
C
198 lines
4.7 KiB
C
#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)
|
|
for_each_ref(clear_marks, NULL);
|
|
marked = 1;
|
|
}
|