Coverage Report

Created: 2025-12-31 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/negotiator/skipping.c
Line
Count
Source
1
#define USE_THE_REPOSITORY_VARIABLE
2
3
#include "git-compat-util.h"
4
#include "skipping.h"
5
#include "../commit.h"
6
#include "../fetch-negotiator.h"
7
#include "../hex.h"
8
#include "../prio-queue.h"
9
#include "../refs.h"
10
#include "../repository.h"
11
#include "../tag.h"
12
13
/* Remember to update object flag allocation in object.h */
14
/*
15
 * Both us and the server know that both parties have this object.
16
 */
17
0
#define COMMON    (1U << 2)
18
/*
19
 * The server has told us that it has this object. We still need to tell the
20
 * server that we have this object (or one of its descendants), but since we are
21
 * going to do that, we do not need to tell the server about its ancestors.
22
 */
23
0
#define ADVERTISED  (1U << 3)
24
/*
25
 * This commit has entered the priority queue.
26
 */
27
0
#define SEEN    (1U << 4)
28
/*
29
 * This commit has left the priority queue.
30
 */
31
0
#define POPPED    (1U << 5)
32
33
static int marked;
34
35
/*
36
 * An entry in the priority queue.
37
 */
38
struct entry {
39
  struct commit *commit;
40
41
  /*
42
   * Used only if commit is not COMMON.
43
   */
44
  uint16_t original_ttl;
45
  uint16_t ttl;
46
};
47
48
struct data {
49
  struct prio_queue rev_list;
50
51
  /*
52
   * The number of non-COMMON commits in rev_list.
53
   */
54
  int non_common_revs;
55
};
56
57
static int compare(const void *a_, const void *b_, void *data UNUSED)
58
0
{
59
0
  const struct entry *a = a_;
60
0
  const struct entry *b = b_;
61
0
  return compare_commits_by_commit_date(a->commit, b->commit, NULL);
62
0
}
63
64
static struct entry *rev_list_push(struct data *data, struct commit *commit, int mark)
65
0
{
66
0
  struct entry *entry;
67
0
  commit->object.flags |= mark | SEEN;
68
69
0
  CALLOC_ARRAY(entry, 1);
70
0
  entry->commit = commit;
71
0
  prio_queue_put(&data->rev_list, entry);
72
73
0
  if (!(mark & COMMON))
74
0
    data->non_common_revs++;
75
0
  return entry;
76
0
}
77
78
static int clear_marks(const struct reference *ref, void *cb_data UNUSED)
79
0
{
80
0
  struct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),
81
0
             ref->name, 0);
82
83
0
  if (o && o->type == OBJ_COMMIT)
84
0
    clear_commit_marks((struct commit *)o,
85
0
           COMMON | ADVERTISED | SEEN | POPPED);
86
0
  return 0;
87
0
}
88
89
/*
90
 * Mark this SEEN commit and all its parsed SEEN ancestors as COMMON.
91
 */
92
static void mark_common(struct data *data, struct commit *seen_commit)
93
0
{
94
0
  struct prio_queue queue = { NULL };
95
0
  struct commit *c;
96
97
0
  if (seen_commit->object.flags & COMMON)
98
0
    return;
99
100
0
  prio_queue_put(&queue, seen_commit);
101
0
  seen_commit->object.flags |= COMMON;
102
0
  while ((c = prio_queue_get(&queue))) {
103
0
    struct commit_list *p;
104
105
0
    if (!(c->object.flags & POPPED))
106
0
      data->non_common_revs--;
107
108
0
    if (!c->object.parsed)
109
0
      continue;
110
0
    for (p = c->parents; p; p = p->next) {
111
0
      if (!(p->item->object.flags & SEEN) ||
112
0
          (p->item->object.flags & COMMON))
113
0
        continue;
114
115
0
      p->item->object.flags |= COMMON;
116
0
      prio_queue_put(&queue, p->item);
117
0
    }
118
0
  }
119
120
0
  clear_prio_queue(&queue);
121
0
}
122
123
/*
124
 * Ensure that the priority queue has an entry for to_push, and ensure that the
125
 * entry has the correct flags and ttl.
126
 *
127
 * This function returns 1 if an entry was found or created, and 0 otherwise
128
 * (because the entry for this commit had already been popped).
129
 */
130
static int push_parent(struct data *data, struct entry *entry,
131
           struct commit *to_push)
132
0
{
133
0
  struct entry *parent_entry;
134
135
0
  if (to_push->object.flags & SEEN) {
136
0
    if (to_push->object.flags & POPPED)
137
      /*
138
       * The entry for this commit has already been popped,
139
       * due to clock skew. Pretend that this parent does not
140
       * exist.
141
       */
142
0
      return 0;
143
    /*
144
     * Find the existing entry and use it.
145
     */
146
0
    for (size_t i = 0; i < data->rev_list.nr; i++) {
147
0
      parent_entry = data->rev_list.array[i].data;
148
0
      if (parent_entry->commit == to_push)
149
0
        goto parent_found;
150
0
    }
151
0
    BUG("missing parent in priority queue");
152
0
parent_found:
153
0
    ;
154
0
  } else {
155
0
    parent_entry = rev_list_push(data, to_push, 0);
156
0
  }
157
158
0
  if (entry->commit->object.flags & (COMMON | ADVERTISED)) {
159
0
    mark_common(data, to_push);
160
0
  } else {
161
0
    uint16_t new_original_ttl = entry->ttl
162
0
      ? entry->original_ttl : entry->original_ttl * 3 / 2 + 1;
163
0
    uint16_t new_ttl = entry->ttl
164
0
      ? entry->ttl - 1 : new_original_ttl;
165
0
    if (parent_entry->original_ttl < new_original_ttl) {
166
0
      parent_entry->original_ttl = new_original_ttl;
167
0
      parent_entry->ttl = new_ttl;
168
0
    }
169
0
  }
170
171
0
  return 1;
172
0
}
173
174
static const struct object_id *get_rev(struct data *data)
175
0
{
176
0
  struct commit *to_send = NULL;
177
178
0
  while (to_send == NULL) {
179
0
    struct entry *entry;
180
0
    struct commit *commit;
181
0
    struct commit_list *p;
182
0
    int parent_pushed = 0;
183
184
0
    if (data->rev_list.nr == 0 || data->non_common_revs == 0)
185
0
      return NULL;
186
187
0
    entry = prio_queue_get(&data->rev_list);
188
0
    commit = entry->commit;
189
0
    commit->object.flags |= POPPED;
190
0
    if (!(commit->object.flags & COMMON))
191
0
      data->non_common_revs--;
192
193
0
    if (!(commit->object.flags & COMMON) && !entry->ttl)
194
0
      to_send = commit;
195
196
0
    repo_parse_commit(the_repository, commit);
197
0
    for (p = commit->parents; p; p = p->next)
198
0
      parent_pushed |= push_parent(data, entry, p->item);
199
200
0
    if (!(commit->object.flags & COMMON) && !parent_pushed)
201
      /*
202
       * This commit has no parents, or all of its parents
203
       * have already been popped (due to clock skew), so send
204
       * it anyway.
205
       */
206
0
      to_send = commit;
207
208
0
    free(entry);
209
0
  }
210
211
0
  return &to_send->object.oid;
212
0
}
213
214
static void known_common(struct fetch_negotiator *n, struct commit *c)
215
0
{
216
0
  if (c->object.flags & SEEN)
217
0
    return;
218
0
  rev_list_push(n->data, c, ADVERTISED);
219
0
}
220
221
static void add_tip(struct fetch_negotiator *n, struct commit *c)
222
0
{
223
0
  n->known_common = NULL;
224
0
  if (c->object.flags & SEEN)
225
0
    return;
226
0
  rev_list_push(n->data, c, 0);
227
0
}
228
229
static const struct object_id *next(struct fetch_negotiator *n)
230
0
{
231
0
  n->known_common = NULL;
232
0
  n->add_tip = NULL;
233
0
  return get_rev(n->data);
234
0
}
235
236
static int ack(struct fetch_negotiator *n, struct commit *c)
237
0
{
238
0
  int known_to_be_common = !!(c->object.flags & COMMON);
239
0
  if (!(c->object.flags & SEEN))
240
0
    die("received ack for commit %s not sent as 'have'",
241
0
        oid_to_hex(&c->object.oid));
242
0
  mark_common(n->data, c);
243
0
  return known_to_be_common;
244
0
}
245
246
static void release(struct fetch_negotiator *n)
247
0
{
248
0
  struct data *data = n->data;
249
0
  for (size_t i = 0; i < data->rev_list.nr; i++)
250
0
    free(data->rev_list.array[i].data);
251
0
  clear_prio_queue(&data->rev_list);
252
0
  FREE_AND_NULL(data);
253
0
}
254
255
void skipping_negotiator_init(struct fetch_negotiator *negotiator)
256
0
{
257
0
  struct data *data;
258
0
  negotiator->known_common = known_common;
259
0
  negotiator->add_tip = add_tip;
260
0
  negotiator->next = next;
261
0
  negotiator->ack = ack;
262
0
  negotiator->release = release;
263
0
  negotiator->data = CALLOC_ARRAY(data, 1);
264
0
  data->rev_list.compare = compare;
265
266
0
  if (marked)
267
0
    refs_for_each_ref(get_main_ref_store(the_repository),
268
          clear_marks, NULL);
269
0
  marked = 1;
270
0
}