Coverage Report

Created: 2026-10-17 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/negotiator/default.c
Line
Count
Source
1
#define USE_THE_REPOSITORY_VARIABLE
2
3
#include "git-compat-util.h"
4
#include "default.h"
5
#include "../commit.h"
6
#include "../fetch-negotiator.h"
7
#include "../prio-queue.h"
8
#include "../refs.h"
9
#include "../repository.h"
10
#include "../tag.h"
11
12
/* Remember to update object flag allocation in object.h */
13
0
#define COMMON    (1U << 2)
14
0
#define COMMON_REF  (1U << 3)
15
0
#define SEEN    (1U << 4)
16
0
#define POPPED    (1U << 5)
17
18
static int marked;
19
20
struct negotiation_state {
21
  struct prio_queue rev_list;
22
  int non_common_revs;
23
};
24
25
static void rev_list_push(struct negotiation_state *ns,
26
        struct commit *commit, int mark)
27
0
{
28
0
  if (!(commit->object.flags & mark)) {
29
0
    commit->object.flags |= mark;
30
31
0
    if (repo_parse_commit(the_repository, commit))
32
0
      return;
33
34
0
    prio_queue_put(&ns->rev_list, commit);
35
36
0
    if (!(commit->object.flags & COMMON))
37
0
      ns->non_common_revs++;
38
0
  }
39
0
}
40
41
static int clear_marks(const struct reference *ref, void *cb_data UNUSED)
42
0
{
43
0
  struct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),
44
0
             ref->name, 0);
45
46
0
  if (o && o->type == OBJ_COMMIT)
47
0
    clear_commit_marks((struct commit *)o,
48
0
           COMMON | COMMON_REF | SEEN | POPPED);
49
0
  return 0;
50
0
}
51
52
/*
53
 * This function marks a rev and its ancestors as common.
54
 * In some cases, it is desirable to mark only the ancestors (for example
55
 * when only the server does not yet know that they are common).
56
 */
57
static void mark_common(struct negotiation_state *ns, struct commit *commit,
58
    int ancestors_only, int dont_parse)
59
0
{
60
0
  struct commit_stack stack = COMMIT_STACK_INIT;
61
62
0
  if (!commit || (commit->object.flags & COMMON))
63
0
    return;
64
65
0
  commit_stack_push(&stack, commit);
66
0
  if (!ancestors_only) {
67
0
    commit->object.flags |= COMMON;
68
69
0
    if ((commit->object.flags & SEEN) && !(commit->object.flags & POPPED))
70
0
      ns->non_common_revs--;
71
0
  }
72
0
  while ((commit = commit_stack_pop(&stack))) {
73
0
    struct object *o = (struct object *)commit;
74
75
0
    if (!(o->flags & SEEN))
76
0
      rev_list_push(ns, commit, SEEN);
77
0
    else {
78
0
      struct commit_list *parents;
79
80
0
      if (!o->parsed && !dont_parse)
81
0
        if (repo_parse_commit(the_repository, commit))
82
0
          continue;
83
84
0
      for (parents = commit->parents;
85
0
          parents;
86
0
          parents = parents->next) {
87
0
        struct commit *p = parents->item;
88
89
0
        if (p->object.flags & COMMON)
90
0
          continue;
91
92
0
        p->object.flags |= COMMON;
93
94
0
        if ((p->object.flags & SEEN) && !(p->object.flags & POPPED))
95
0
          ns->non_common_revs--;
96
97
0
        commit_stack_push(&stack, parents->item);
98
0
      }
99
0
    }
100
0
  }
101
102
0
  commit_stack_clear(&stack);
103
0
}
104
105
/*
106
 * Get the next rev to send, ignoring the common.
107
 */
108
static const struct object_id *get_rev(struct negotiation_state *ns)
109
0
{
110
0
  struct commit *commit = NULL;
111
112
0
  while (commit == NULL) {
113
0
    unsigned int mark;
114
0
    struct commit_list *parents;
115
116
0
    if (ns->non_common_revs == 0)
117
0
      return NULL;
118
119
0
    commit = prio_queue_get(&ns->rev_list);
120
0
    if (!commit)
121
0
      return NULL;
122
0
    repo_parse_commit(the_repository, commit);
123
0
    parents = commit->parents;
124
125
0
    commit->object.flags |= POPPED;
126
0
    if (!(commit->object.flags & COMMON))
127
0
      ns->non_common_revs--;
128
129
0
    if (commit->object.flags & COMMON) {
130
      /* do not send "have", and ignore ancestors */
131
0
      commit = NULL;
132
0
      mark = COMMON | SEEN;
133
0
    } else if (commit->object.flags & COMMON_REF)
134
      /* send "have", and ignore ancestors */
135
0
      mark = COMMON | SEEN;
136
0
    else
137
      /* send "have", also for its ancestors */
138
0
      mark = SEEN;
139
140
0
    while (parents) {
141
0
      if (!(parents->item->object.flags & SEEN))
142
0
        rev_list_push(ns, parents->item, mark);
143
0
      if (mark & COMMON)
144
0
        mark_common(ns, parents->item, 1, 0);
145
0
      parents = parents->next;
146
0
    }
147
0
  }
148
149
0
  return &commit->object.oid;
150
0
}
151
152
static void known_common(struct fetch_negotiator *n, struct commit *c)
153
0
{
154
0
  if (!(c->object.flags & SEEN)) {
155
0
    rev_list_push(n->data, c, COMMON_REF | SEEN);
156
0
    mark_common(n->data, c, 1, 1);
157
0
  }
158
0
}
159
160
static void add_tip(struct fetch_negotiator *n, struct commit *c)
161
0
{
162
0
  n->known_common = NULL;
163
0
  rev_list_push(n->data, c, SEEN);
164
0
}
165
166
static const struct object_id *next(struct fetch_negotiator *n)
167
0
{
168
0
  n->known_common = NULL;
169
0
  n->add_tip = NULL;
170
0
  return get_rev(n->data);
171
0
}
172
173
static int ack(struct fetch_negotiator *n, struct commit *c)
174
0
{
175
0
  int known_to_be_common = !!(c->object.flags & COMMON);
176
0
  mark_common(n->data, c, 0, 1);
177
0
  return known_to_be_common;
178
0
}
179
180
static void have_sent(struct fetch_negotiator *n, struct commit *c)
181
0
{
182
0
  if (repo_parse_commit(the_repository, c))
183
0
    return;
184
0
  mark_common(n->data, c, 0, 0);
185
0
}
186
187
static void release(struct fetch_negotiator *n)
188
0
{
189
0
  clear_prio_queue(&((struct negotiation_state *)n->data)->rev_list);
190
0
  FREE_AND_NULL(n->data);
191
0
}
192
193
void default_negotiator_init(struct fetch_negotiator *negotiator)
194
0
{
195
0
  struct negotiation_state *ns;
196
0
  negotiator->known_common = known_common;
197
0
  negotiator->add_tip = add_tip;
198
0
  negotiator->next = next;
199
0
  negotiator->ack = ack;
200
0
  negotiator->have_sent = have_sent;
201
0
  negotiator->release = release;
202
0
  negotiator->data = CALLOC_ARRAY(ns, 1);
203
0
  ns->rev_list.compare = compare_commits_by_commit_date;
204
205
0
  if (marked)
206
0
    refs_for_each_ref(get_main_ref_store(the_repository),
207
          clear_marks, NULL);
208
0
  marked = 1;
209
0
}