Coverage Report

Created: 2026-03-31 06:24

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