Coverage Report

Created: 2025-12-31 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/object-file-convert.c
Line
Count
Source
1
#define DISABLE_SIGN_COMPARE_WARNINGS
2
3
#include "git-compat-util.h"
4
#include "gettext.h"
5
#include "strbuf.h"
6
#include "hex.h"
7
#include "repository.h"
8
#include "hash.h"
9
#include "hash.h"
10
#include "object.h"
11
#include "loose.h"
12
#include "commit.h"
13
#include "gpg-interface.h"
14
#include "object-file-convert.h"
15
16
int repo_oid_to_algop(struct repository *repo, const struct object_id *src,
17
          const struct git_hash_algo *to, struct object_id *dest)
18
0
{
19
  /*
20
   * If the source algorithm is not set, then we're using the
21
   * default hash algorithm for that object.
22
   */
23
0
  const struct git_hash_algo *from =
24
0
    src->algo ? &hash_algos[src->algo] : repo->hash_algo;
25
26
0
  if (from == to) {
27
0
    if (src != dest)
28
0
      oidcpy(dest, src);
29
0
    return 0;
30
0
  }
31
0
  if (repo_loose_object_map_oid(repo, src, to, dest)) {
32
    /*
33
     * We may have loaded the object map at repo initialization but
34
     * another process (perhaps upstream of a pipe from us) may have
35
     * written a new object into the map.  If the object is missing,
36
     * let's reload the map to see if the object has appeared.
37
     */
38
0
    repo_read_loose_object_map(repo);
39
0
    if (repo_loose_object_map_oid(repo, src, to, dest))
40
0
      return -1;
41
0
  }
42
0
  return 0;
43
0
}
44
45
static int decode_tree_entry_raw(struct object_id *oid, const char **path,
46
         size_t *len, const struct git_hash_algo *algo,
47
         const char *buf, unsigned long size)
48
0
{
49
0
  uint16_t mode;
50
0
  const unsigned hashsz = algo->rawsz;
51
52
0
  if (size < hashsz + 3 || buf[size - (hashsz + 1)]) {
53
0
    return -1;
54
0
  }
55
56
0
  *path = parse_mode(buf, &mode);
57
0
  if (!*path || !**path)
58
0
    return -1;
59
0
  *len = strlen(*path) + 1;
60
61
0
  oidread(oid, (const unsigned char *)*path + *len, algo);
62
0
  return 0;
63
0
}
64
65
static int convert_tree_object(struct repository *repo,
66
             struct strbuf *out,
67
             const struct git_hash_algo *from,
68
             const struct git_hash_algo *to,
69
             const char *buffer, size_t size)
70
0
{
71
0
  const char *p = buffer, *end = buffer + size;
72
73
0
  while (p < end) {
74
0
    struct object_id entry_oid, mapped_oid;
75
0
    const char *path = NULL;
76
0
    size_t pathlen;
77
78
0
    if (decode_tree_entry_raw(&entry_oid, &path, &pathlen, from, p,
79
0
            end - p))
80
0
      return error(_("failed to decode tree entry"));
81
0
    if (repo_oid_to_algop(repo, &entry_oid, to, &mapped_oid))
82
0
      return error(_("failed to map tree entry for %s"), oid_to_hex(&entry_oid));
83
0
    strbuf_add(out, p, path - p);
84
0
    strbuf_add(out, path, pathlen);
85
0
    strbuf_add(out, mapped_oid.hash, to->rawsz);
86
0
    p = path + pathlen + from->rawsz;
87
0
  }
88
0
  return 0;
89
0
}
90
91
static int convert_tag_object(struct repository *repo,
92
            struct strbuf *out,
93
            const struct git_hash_algo *from,
94
            const struct git_hash_algo *to,
95
            const char *buffer, size_t size)
96
0
{
97
0
  struct strbuf payload = STRBUF_INIT, oursig = STRBUF_INIT, othersig = STRBUF_INIT;
98
0
  const int entry_len = from->hexsz + 7;
99
0
  size_t payload_size;
100
0
  struct object_id oid, mapped_oid;
101
0
  const char *p;
102
103
  /* Consume the object line */
104
0
  if ((entry_len >= size) ||
105
0
      memcmp(buffer, "object ", 7) || buffer[entry_len] != '\n')
106
0
    return error("bogus tag object");
107
0
  if (parse_oid_hex_algop(buffer + 7, &oid, &p, from) < 0)
108
0
    return error("bad tag object ID");
109
0
  if (repo_oid_to_algop(repo, &oid, to, &mapped_oid))
110
0
    return error("unable to map tree %s in tag object",
111
0
           oid_to_hex(&oid));
112
0
  size -= ((p + 1) - buffer);
113
0
  buffer = p + 1;
114
115
  /* Is there a signature for our algorithm? */
116
0
  payload_size = parse_signed_buffer(buffer, size);
117
0
  if (payload_size != size) {
118
    /* Yes, there is. */
119
0
    strbuf_add(&oursig, buffer + payload_size, size - payload_size);
120
0
  }
121
122
  /* Now, is there a signature for the other algorithm? */
123
0
  parse_buffer_signed_by_header(buffer, payload_size, &payload, &othersig, to);
124
  /*
125
   * Our payload is now in payload and we may have up to two signatrures
126
   * in oursig and othersig.
127
   */
128
129
  /* Add some slop for longer signature header in the new algorithm. */
130
0
  strbuf_grow(out, (7 + to->hexsz + 1) + size + 7);
131
0
  strbuf_addf(out, "object %s\n", oid_to_hex(&mapped_oid));
132
0
  strbuf_addbuf(out, &payload);
133
0
  if (oursig.len)
134
0
    add_header_signature(out, &oursig, from);
135
0
  strbuf_addbuf(out, &othersig);
136
137
0
  strbuf_release(&payload);
138
0
  strbuf_release(&othersig);
139
0
  strbuf_release(&oursig);
140
0
  return 0;
141
0
}
142
143
static int convert_commit_object(struct repository *repo,
144
         struct strbuf *out,
145
         const struct git_hash_algo *from,
146
         const struct git_hash_algo *to,
147
         const char *buffer, size_t size)
148
0
{
149
0
  const char *tail = buffer;
150
0
  const char *bufptr = buffer;
151
0
  const int tree_entry_len = from->hexsz + 5;
152
0
  const int parent_entry_len = from->hexsz + 7;
153
0
  struct object_id oid, mapped_oid;
154
0
  const char *p, *eol;
155
156
0
  tail += size;
157
158
0
  while ((bufptr < tail) && (*bufptr != '\n')) {
159
0
    eol = memchr(bufptr, '\n', tail - bufptr);
160
0
    if (!eol)
161
0
      return error(_("bad %s in commit"), "line");
162
163
0
    if (((bufptr + 5) < eol) && !memcmp(bufptr, "tree ", 5))
164
0
    {
165
0
      if (((bufptr + tree_entry_len) != eol) ||
166
0
          parse_oid_hex_algop(bufptr + 5, &oid, &p, from) ||
167
0
          (p != eol))
168
0
        return error(_("bad %s in commit"), "tree");
169
170
0
      if (repo_oid_to_algop(repo, &oid, to, &mapped_oid))
171
0
        return error(_("unable to map %s %s in commit object"),
172
0
               "tree", oid_to_hex(&oid));
173
0
      strbuf_addf(out, "tree %s\n", oid_to_hex(&mapped_oid));
174
0
    }
175
0
    else if (((bufptr + 7) < eol) && !memcmp(bufptr, "parent ", 7))
176
0
    {
177
0
      if (((bufptr + parent_entry_len) != eol) ||
178
0
          parse_oid_hex_algop(bufptr + 7, &oid, &p, from) ||
179
0
          (p != eol))
180
0
        return error(_("bad %s in commit"), "parent");
181
182
0
      if (repo_oid_to_algop(repo, &oid, to, &mapped_oid))
183
0
        return error(_("unable to map %s %s in commit object"),
184
0
               "parent", oid_to_hex(&oid));
185
186
0
      strbuf_addf(out, "parent %s\n", oid_to_hex(&mapped_oid));
187
0
    }
188
0
    else if (((bufptr + 9) < eol) && !memcmp(bufptr, "mergetag ", 9))
189
0
    {
190
0
      struct strbuf tag = STRBUF_INIT, new_tag = STRBUF_INIT;
191
192
      /* Recover the tag object from the mergetag */
193
0
      strbuf_add(&tag, bufptr + 9, (eol - (bufptr + 9)) + 1);
194
195
0
      bufptr = eol + 1;
196
0
      while ((bufptr < tail) && (*bufptr == ' ')) {
197
0
        eol = memchr(bufptr, '\n', tail - bufptr);
198
0
        if (!eol) {
199
0
          strbuf_release(&tag);
200
0
          return error(_("bad %s in commit"), "mergetag continuation");
201
0
        }
202
0
        strbuf_add(&tag, bufptr + 1, (eol - (bufptr + 1)) + 1);
203
0
        bufptr = eol + 1;
204
0
      }
205
206
      /* Compute the new tag object */
207
0
      if (convert_tag_object(repo, &new_tag, from, to, tag.buf, tag.len)) {
208
0
        strbuf_release(&tag);
209
0
        strbuf_release(&new_tag);
210
0
        return -1;
211
0
      }
212
213
      /* Write the new mergetag */
214
0
      strbuf_addstr(out, "mergetag");
215
0
      strbuf_add_lines(out, " ", new_tag.buf, new_tag.len);
216
0
      strbuf_release(&tag);
217
0
      strbuf_release(&new_tag);
218
0
    }
219
0
    else if (((bufptr + 7) < tail) && !memcmp(bufptr, "author ", 7))
220
0
      strbuf_add(out, bufptr, (eol - bufptr) + 1);
221
0
    else if (((bufptr + 10) < tail) && !memcmp(bufptr, "committer ", 10))
222
0
      strbuf_add(out, bufptr, (eol - bufptr) + 1);
223
0
    else if (((bufptr + 9) < tail) && !memcmp(bufptr, "encoding ", 9))
224
0
      strbuf_add(out, bufptr, (eol - bufptr) + 1);
225
0
    else if (((bufptr + 6) < tail) && !memcmp(bufptr, "gpgsig", 6))
226
0
      strbuf_add(out, bufptr, (eol - bufptr) + 1);
227
0
    else {
228
      /* Unknown line fail it might embed an oid */
229
0
      return -1;
230
0
    }
231
    /* Consume any trailing continuation lines */
232
0
    bufptr = eol + 1;
233
0
    while ((bufptr < tail) && (*bufptr == ' ')) {
234
0
      eol = memchr(bufptr, '\n', tail - bufptr);
235
0
      if (!eol)
236
0
        return error(_("bad %s in commit"), "continuation");
237
0
      strbuf_add(out, bufptr, (eol - bufptr) + 1);
238
0
      bufptr = eol + 1;
239
0
    }
240
0
  }
241
0
  if (bufptr < tail)
242
0
    strbuf_add(out, bufptr, tail - bufptr);
243
0
  return 0;
244
0
}
245
246
int convert_object_file(struct repository *repo,
247
      struct strbuf *outbuf,
248
      const struct git_hash_algo *from,
249
      const struct git_hash_algo *to,
250
      const void *buf, size_t len,
251
      enum object_type type,
252
      int gentle)
253
0
{
254
0
  int ret;
255
256
  /* Don't call this function when no conversion is necessary */
257
0
  if ((from == to) || (type == OBJ_BLOB))
258
0
    BUG("Refusing noop object file conversion");
259
260
0
  switch (type) {
261
0
  case OBJ_COMMIT:
262
0
    ret = convert_commit_object(repo, outbuf, from, to, buf, len);
263
0
    break;
264
0
  case OBJ_TREE:
265
0
    ret = convert_tree_object(repo, outbuf, from, to, buf, len);
266
0
    break;
267
0
  case OBJ_TAG:
268
0
    ret = convert_tag_object(repo, outbuf, from, to, buf, len);
269
0
    break;
270
0
  default:
271
    /* Not implemented yet, so fail. */
272
0
    ret = -1;
273
0
    break;
274
0
  }
275
0
  if (!ret)
276
0
    return 0;
277
0
  if (gentle) {
278
0
    strbuf_release(outbuf);
279
0
    return ret;
280
0
  }
281
0
  die(_("Failed to convert object from %s to %s"),
282
0
    from->name, to->name);
283
0
}