Coverage Report

Created: 2024-09-08 06:23

/src/git/negotiator/skipping.c
Line
Count
Source (jump to first uncovered line)
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 char *refname, const char *referent UNUSED, const struct object_id *oid,
79
           int flag UNUSED,
80
           void *cb_data UNUSED)
81
0
{
82
0
  struct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);
83
84
0
  if (o && o->type == OBJ_COMMIT)
85
0
    clear_commit_marks((struct commit *)o,
86
0
           COMMON | ADVERTISED | SEEN | POPPED);
87
0
  return 0;
88
0
}
89
90
/*
91
 * Mark this SEEN commit and all its parsed SEEN ancestors as COMMON.
92
 */
93
static void mark_common(struct data *data, struct commit *seen_commit)
94
0
{
95
0
  struct prio_queue queue = { NULL };
96
0
  struct commit *c;
97
98
0
  if (seen_commit->object.flags & COMMON)
99
0
    return;
100
101
0
  prio_queue_put(&queue, seen_commit);
102
0
  seen_commit->object.flags |= COMMON;
103
0
  while ((c = prio_queue_get(&queue))) {
104
0
    struct commit_list *p;
105
106
0
    if (!(c->object.flags & POPPED))
107
0
      data->non_common_revs--;
108
109
0
    if (!c->object.parsed)
110
0
      continue;
111
0
    for (p = c->parents; p; p = p->next) {
112
0
      if (!(p->item->object.flags & SEEN) ||
113
0
          (p->item->object.flags & COMMON))
114
0
        continue;
115
116
0
      p->item->object.flags |= COMMON;
117
0
      prio_queue_put(&queue, p->item);
118
0
    }
119
0
  }
120
121
0
  clear_prio_queue(&queue);
122
0
}
123
124
/*
125
 * Ensure that the priority queue has an entry for to_push, and ensure that the
126
 * entry has the correct flags and ttl.
127
 *
128
 * This function returns 1 if an entry was found or created, and 0 otherwise
129
 * (because the entry for this commit had already been popped).
130
 */
131
static int push_parent(struct data *data, struct entry *entry,
132
           struct commit *to_push)
133
0
{
134
0
  struct entry *parent_entry;
135
136
0
  if (to_push->object.flags & SEEN) {
137
0
    int i;
138
0
    if (to_push->object.flags & POPPED)
139
      /*
140
       * The entry for this commit has already been popped,
141
       * due to clock skew. Pretend that this parent does not
142
       * exist.
143
       */
144
0
      return 0;
145
    /*
146
     * Find the existing entry and use it.
147
     */
148
0
    for (i = 0; i < data->rev_list.nr; i++) {
149
0
      parent_entry = data->rev_list.array[i].data;
150
0
      if (parent_entry->commit == to_push)
151
0
        goto parent_found;
152
0
    }
153
0
    BUG("missing parent in priority queue");
154
0
parent_found:
155
0
    ;
156
0
  } else {
157
0
    parent_entry = rev_list_push(data, to_push, 0);
158
0
  }
159
160
0
  if (entry->commit->object.flags & (COMMON | ADVERTISED)) {
161
0
    mark_common(data, to_push);
162
0
  } else {
163
0
    uint16_t new_original_ttl = entry->ttl
164
0
      ? entry->original_ttl : entry->original_ttl * 3 / 2 + 1;
165
0
    uint16_t new_ttl = entry->ttl
166
0
      ? entry->ttl - 1 : new_original_ttl;
167
0
    if (parent_entry->original_ttl < new_original_ttl) {
168
0
      parent_entry->original_ttl = new_original_ttl;
169
0
      parent_entry->ttl = new_ttl;
170
0
    }
171
0
  }
172
173
0
  return 1;
174
0
}
175
176
static const struct object_id *get_rev(struct data *data)
177
0
{
178
0
  struct commit *to_send = NULL;
179
180
0
  while (to_send == NULL) {
181
0
    struct entry *entry;
182
0
    struct commit *commit;
183
0
    struct commit_list *p;
184
0
    int parent_pushed = 0;
185
186
0
    if (data->rev_list.nr == 0 || data->non_common_revs == 0)
187
0
      return NULL;
188
189
0
    entry = prio_queue_get(&data->rev_list);
190
0
    commit = entry->commit;
191
0
    commit->object.flags |= POPPED;
192
0
    if (!(commit->object.flags & COMMON))
193
0
      data->non_common_revs--;
194
195
0
    if (!(commit->object.flags & COMMON) && !entry->ttl)
196
0
      to_send = commit;
197
198
0
    repo_parse_commit(the_repository, commit);
199
0
    for (p = commit->parents; p; p = p->next)
200
0
      parent_pushed |= push_parent(data, entry, p->item);
201
202
0
    if (!(commit->object.flags & COMMON) && !parent_pushed)
203
      /*
204
       * This commit has no parents, or all of its parents
205
       * have already been popped (due to clock skew), so send
206
       * it anyway.
207
       */
208
0
      to_send = commit;
209
210
0
    free(entry);
211
0
  }
212
213
0
  return &to_send->object.oid;
214
0
}
215
216
static void known_common(struct fetch_negotiator *n, struct commit *c)
217
0
{
218
0
  if (c->object.flags & SEEN)
219
0
    return;
220
0
  rev_list_push(n->data, c, ADVERTISED);
221
0
}
222
223
static void add_tip(struct fetch_negotiator *n, struct commit *c)
224
0
{
225
0
  n->known_common = NULL;
226
0
  if (c->object.flags & SEEN)
227
0
    return;
228
0
  rev_list_push(n->data, c, 0);
229
0
}
230
231
static const struct object_id *next(struct fetch_negotiator *n)
232
0
{
233
0
  n->known_common = NULL;
234
0
  n->add_tip = NULL;
235
0
  return get_rev(n->data);
236
0
}
237
238
static int ack(struct fetch_negotiator *n, struct commit *c)
239
0
{
240
0
  int known_to_be_common = !!(c->object.flags & COMMON);
241
0
  if (!(c->object.flags & SEEN))
242
0
    die("received ack for commit %s not sent as 'have'",
243
0
        oid_to_hex(&c->object.oid));
244
0
  mark_common(n->data, c);
245
0
  return known_to_be_common;
246
0
}
247
248
static void release(struct fetch_negotiator *n)
249
0
{
250
0
  clear_prio_queue(&((struct data *)n->data)->rev_list);
251
0
  FREE_AND_NULL(n->data);
252
0
}
253
254
void skipping_negotiator_init(struct fetch_negotiator *negotiator)
255
0
{
256
0
  struct data *data;
257
0
  negotiator->known_common = known_common;
258
0
  negotiator->add_tip = add_tip;
259
0
  negotiator->next = next;
260
0
  negotiator->ack = ack;
261
0
  negotiator->release = release;
262
0
  negotiator->data = CALLOC_ARRAY(data, 1);
263
0
  data->rev_list.compare = compare;
264
265
0
  if (marked)
266
0
    refs_for_each_ref(get_main_ref_store(the_repository),
267
0
          clear_marks, NULL);
268
0
  marked = 1;
269
0
}