Coverage Report

Created: 2026-02-26 06:44

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