Coverage Report

Created: 2026-11-12 06:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/connected.c
Line
Count
Source
1
#include "git-compat-util.h"
2
#include "config.h"
3
#include "gettext.h"
4
#include "hex.h"
5
#include "odb.h"
6
#include "parse.h"
7
#include "run-command.h"
8
#include "sigchain.h"
9
#include "connected.h"
10
#include "transport.h"
11
#include "packfile.h"
12
#include "promisor-remote.h"
13
14
static int promised_object_cb(const struct object_id *oid UNUSED,
15
            struct object_info *oi UNUSED,
16
            void *payload)
17
0
{
18
0
  bool *found = payload;
19
0
  *found = true;
20
0
  return 1;
21
0
}
22
23
/*
24
 * For partial clones, we don't want to have to do a regular connectivity check
25
 * because we have to enumerate and exclude all promisor objects (slow), and
26
 * then the connectivity check itself becomes a no-op because in a partial
27
 * clone every object is a promisor object. Instead, just make sure we
28
 * received, in a promisor packfile, the objects pointed to by each wanted ref.
29
 *
30
 * Before checking for promisor packs, be sure we have the latest pack-files
31
 * loaded into memory.
32
 *
33
 * Returns 1 when all object IDs have been found in promisor packs, in which
34
 * case we're fully connected and thus done. Returns 0 when we have found
35
 * objects in non-promisor packs, in which case we'll have to fall back to the
36
 * rev-list-based connectivity checks. Returns a negative error code on error.
37
 */
38
static int check_connected_promisor(struct repository *repo,
39
            oid_iterate_fn fn,
40
            void *cb_data,
41
            const struct object_id **oid)
42
0
{
43
0
  struct odb_for_each_object_options opts = {
44
0
    .flags = ODB_FOR_EACH_OBJECT_PROMISOR_ONLY,
45
0
    .prefix_hex_len = repo->hash_algo->hexsz,
46
0
  };
47
0
  int err;
48
49
0
  odb_reprepare(repo->objects);
50
0
  do {
51
0
    bool found = false;
52
53
0
    opts.prefix = *oid;
54
55
0
    err = odb_for_each_object_ext(repo->objects, NULL,
56
0
                promised_object_cb, &found, &opts);
57
0
    if (err < 0)
58
0
      return err;
59
60
    /*
61
     * We have found an object that is not part of a promisor pack,
62
     * and thus we cannot skip the full connectivity check.
63
     */
64
0
    if (!found)
65
0
      return 0;
66
0
  } while ((*oid = fn(cb_data)) != NULL);
67
68
0
  return 1;
69
0
}
70
71
/*
72
 * Fills in the --connectivity-check mode to pass to rev-list.
73
 */
74
static void connectivity_check_mode(struct repository *repo,
75
            struct check_connected_options *opt,
76
            struct strbuf *mode)
77
0
{
78
0
  const char *algorithm = NULL, *cutoff;
79
0
  unsigned long max_commits;
80
81
  /*
82
   * Deepening fetches do not have the usual boundary of
83
   * existing references, so use the full check for them.
84
   */
85
0
  if (opt->is_deepening_fetch) {
86
0
    strbuf_addstr(mode, "full");
87
0
    return;
88
0
  }
89
90
0
  if (repo_config_get_string_tmp(repo,
91
0
               "transfer.connectivitycheck",
92
0
               &algorithm))
93
0
    algorithm = "full";
94
0
  if (!strcasecmp(algorithm, "full"))
95
0
    strbuf_addstr(mode, "full");
96
0
  else if (!strcasecmp(algorithm, "incremental"))
97
0
    strbuf_addstr(mode, "incremental");
98
0
  else if (!strcasecmp(algorithm, "auto"))
99
0
    strbuf_addstr(mode, "auto");
100
0
  else if (skip_iprefix(algorithm, "auto=", &cutoff) &&
101
0
     git_parse_ulong(cutoff, &max_commits))
102
0
    strbuf_addf(mode, "auto=%lu", max_commits);
103
0
  else {
104
0
    warning(_("unknown transfer.connectivityCheck algorithm '%s', "
105
0
        "using 'full'"), algorithm);
106
0
    strbuf_addstr(mode, "full");
107
0
  }
108
0
}
109
110
/*
111
 * If we feed all the commits we want to verify to this command
112
 *
113
 *  $ git rev-list --objects --stdin --not --all
114
 *
115
 * and if it does not error out, that means everything reachable from
116
 * these commits locally exists and is connected to our existing refs.
117
 * Note that this does _not_ validate the individual objects.
118
 *
119
 * Returns 0 if everything is connected, non-zero otherwise.
120
 */
121
int check_connected(struct repository *repo,
122
        oid_iterate_fn fn, void *cb_data,
123
        struct check_connected_options *opt)
124
0
{
125
0
  struct child_process rev_list = CHILD_PROCESS_INIT;
126
0
  FILE *rev_list_in;
127
0
  struct check_connected_options defaults = CHECK_CONNECTED_INIT;
128
0
  struct strbuf mode = STRBUF_INIT;
129
0
  const struct object_id *oid;
130
0
  int err = 0;
131
0
  struct packed_git *new_pack = NULL;
132
0
  struct transport *transport;
133
0
  size_t base_len;
134
135
0
  if (!opt)
136
0
    opt = &defaults;
137
0
  transport = opt->transport;
138
139
0
  oid = fn(cb_data);
140
0
  if (!oid) {
141
0
    if (opt->err_fd)
142
0
      close(opt->err_fd);
143
0
    return err;
144
0
  }
145
146
0
  if (repo_has_promisor_remote(repo)) {
147
0
    err = check_connected_promisor(repo, fn, cb_data, &oid);
148
0
    if (err) {
149
0
      if (opt->err_fd)
150
0
        close(opt->err_fd);
151
0
      if (err > 0)
152
0
        err = 0;
153
0
      return err;
154
0
    }
155
0
  }
156
157
0
  if (opt->shallow_file) {
158
0
    strvec_push(&rev_list.args, "--shallow-file");
159
0
    strvec_push(&rev_list.args, opt->shallow_file);
160
0
  }
161
0
  strvec_push(&rev_list.args,"rev-list");
162
0
  strvec_push(&rev_list.args, "--objects");
163
0
  strvec_push(&rev_list.args, "--stdin");
164
0
  if (repo_has_promisor_remote(repo))
165
0
    strvec_push(&rev_list.args, "--exclude-promisor-objects");
166
167
  /* define where the traversal should stop */
168
0
  strvec_push(&rev_list.args, "--not");
169
0
  if (!opt->is_deepening_fetch) {
170
0
    if (opt->exclude_hidden_refs_section)
171
0
      strvec_pushf(&rev_list.args, "--exclude-hidden=%s",
172
0
             opt->exclude_hidden_refs_section);
173
0
    strvec_push(&rev_list.args, "--all");
174
0
  }
175
0
  strvec_push(&rev_list.args, "--alternate-refs");
176
0
  strvec_push(&rev_list.args, "--quiet");
177
0
  if (opt->progress)
178
0
    strvec_pushf(&rev_list.args, "--progress=%s",
179
0
           _("Checking connectivity"));
180
0
  connectivity_check_mode(repo, opt, &mode);
181
0
  strvec_pushf(&rev_list.args, "--connectivity-check=%s", mode.buf);
182
0
  strbuf_release(&mode);
183
184
0
  rev_list.git_cmd = 1;
185
0
  if (opt->env)
186
0
    strvec_pushv(&rev_list.env, opt->env);
187
0
  rev_list.in = -1;
188
0
  rev_list.no_stdout = 1;
189
0
  if (opt->err_fd)
190
0
    rev_list.err = opt->err_fd;
191
0
  else
192
0
    rev_list.no_stderr = opt->quiet;
193
194
0
  if (start_command(&rev_list))
195
0
    return error(_("Could not run 'git rev-list'"));
196
197
0
  sigchain_push(SIGPIPE, SIG_IGN);
198
199
0
  rev_list_in = xfdopen(rev_list.in, "w");
200
201
0
  if (transport && transport->smart_options &&
202
0
      transport->smart_options->self_contained_and_connected &&
203
0
      transport->pack_lockfiles.nr == 1 &&
204
0
      strip_suffix(transport->pack_lockfiles.items[0].string,
205
0
       ".keep", &base_len)) {
206
0
    struct strbuf idx_file = STRBUF_INIT;
207
0
    strbuf_add(&idx_file, transport->pack_lockfiles.items[0].string,
208
0
         base_len);
209
0
    strbuf_addstr(&idx_file, ".idx");
210
0
    new_pack = add_packed_git(repo, idx_file.buf,
211
0
            idx_file.len, 1);
212
0
    strbuf_release(&idx_file);
213
0
  }
214
215
0
  do {
216
    /*
217
     * If index-pack already checked that:
218
     * - there are no dangling pointers in the new pack
219
     * - the pack is self contained
220
     * Then if the updated ref is in the new pack, then we
221
     * are sure the ref is good and not sending it to
222
     * rev-list for verification.
223
     */
224
0
    if (new_pack && find_pack_entry_one(oid, new_pack))
225
0
      continue;
226
227
0
    if (fprintf(rev_list_in, "%s\n", oid_to_hex(oid)) < 0)
228
0
      break;
229
0
  } while ((oid = fn(cb_data)) != NULL);
230
231
0
  if (ferror(rev_list_in) || fflush(rev_list_in)) {
232
0
    if (errno != EPIPE && errno != EINVAL)
233
0
      error_errno(_("failed write to rev-list"));
234
0
    err = -1;
235
0
  }
236
237
0
  if (fclose(rev_list_in))
238
0
    err = error_errno(_("failed to close rev-list's stdin"));
239
240
0
  sigchain_pop(SIGPIPE);
241
0
  if (new_pack) {
242
0
    close_pack(new_pack);
243
0
    free(new_pack);
244
0
  }
245
0
  return finish_command(&rev_list) || err;
246
0
}