/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 | } |