| #define DISABLE_SIGN_COMPARE_WARNINGS | |
| #include "git-compat-util.h" | |
| #include "gettext.h" | |
| #include "strbuf.h" | |
| #include "hex.h" | |
| #include "repository.h" | |
| #include "hash.h" | |
| #include "hash.h" | |
| #include "object.h" | |
| #include "loose.h" | |
| #include "commit.h" | |
| #include "gpg-interface.h" | |
| #include "object-file-convert.h" | |
| int repo_oid_to_algop(struct repository *repo, const struct object_id *src, | |
| const struct git_hash_algo *to, struct object_id *dest) | |
| { | |
| /* | |
| * If the source algorithm is not set, then we're using the | |
| * default hash algorithm for that object. | |
| */ | |
| const struct git_hash_algo *from = | |
| src->algo ? &hash_algos[src->algo] : repo->hash_algo; | |
| if (from == to) { | |
| if (src != dest) | |
| oidcpy(dest, src); | |
| return 0; | |
| } | |
| if (repo_loose_object_map_oid(repo, src, to, dest)) { | |
| /* | |
| * We may have loaded the object map at repo initialization but | |
| * another process (perhaps upstream of a pipe from us) may have | |
| * written a new object into the map. If the object is missing, | |
| * let's reload the map to see if the object has appeared. | |
| */ | |
| repo_read_loose_object_map(repo); | |
| if (repo_loose_object_map_oid(repo, src, to, dest)) | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int decode_tree_entry_raw(struct object_id *oid, const char **path, | |
| size_t *len, const struct git_hash_algo *algo, | |
| const char *buf, unsigned long size) | |
| { | |
| uint16_t mode; | |
| const unsigned hashsz = algo->rawsz; | |
| if (size < hashsz + 3 || buf[size - (hashsz + 1)]) { | |
| return -1; | |
| } | |
| *path = parse_mode(buf, &mode); | |
| if (!*path || !**path) | |
| return -1; | |
| *len = strlen(*path) + 1; | |
| oidread(oid, (const unsigned char *)*path + *len, algo); | |
| return 0; | |
| } | |
| static int convert_tree_object(struct repository *repo, | |
| struct strbuf *out, | |
| const struct git_hash_algo *from, | |
| const struct git_hash_algo *to, | |
| const char *buffer, size_t size) | |
| { | |
| const char *p = buffer, *end = buffer + size; | |
| while (p < end) { | |
| struct object_id entry_oid, mapped_oid; | |
| const char *path = NULL; | |
| size_t pathlen; | |
| if (decode_tree_entry_raw(&entry_oid, &path, &pathlen, from, p, | |
| end - p)) | |
| return error(_("failed to decode tree entry")); | |
| if (repo_oid_to_algop(repo, &entry_oid, to, &mapped_oid)) | |
| return error(_("failed to map tree entry for %s"), oid_to_hex(&entry_oid)); | |
| strbuf_add(out, p, path - p); | |
| strbuf_add(out, path, pathlen); | |
| strbuf_add(out, mapped_oid.hash, to->rawsz); | |
| p = path + pathlen + from->rawsz; | |
| } | |
| return 0; | |
| } | |
| static int convert_tag_object(struct repository *repo, | |
| struct strbuf *out, | |
| const struct git_hash_algo *from, | |
| const struct git_hash_algo *to, | |
| const char *buffer, size_t size) | |
| { | |
| struct strbuf payload = STRBUF_INIT, oursig = STRBUF_INIT, othersig = STRBUF_INIT; | |
| const int entry_len = from->hexsz + 7; | |
| size_t payload_size; | |
| struct object_id oid, mapped_oid; | |
| const char *p; | |
| /* Consume the object line */ | |
| if ((entry_len >= size) || | |
| memcmp(buffer, "object ", 7) || buffer[entry_len] != '\n') | |
| return error("bogus tag object"); | |
| if (parse_oid_hex_algop(buffer + 7, &oid, &p, from) < 0) | |
| return error("bad tag object ID"); | |
| if (repo_oid_to_algop(repo, &oid, to, &mapped_oid)) | |
| return error("unable to map tree %s in tag object", | |
| oid_to_hex(&oid)); | |
| size -= ((p + 1) - buffer); | |
| buffer = p + 1; | |
| /* Is there a signature for our algorithm? */ | |
| payload_size = parse_signed_buffer(buffer, size); | |
| if (payload_size != size) { | |
| /* Yes, there is. */ | |
| strbuf_add(&oursig, buffer + payload_size, size - payload_size); | |
| } | |
| /* Now, is there a signature for the other algorithm? */ | |
| parse_buffer_signed_by_header(buffer, payload_size, &payload, &othersig, to); | |
| /* | |
| * Our payload is now in payload and we may have up to two signatrures | |
| * in oursig and othersig. | |
| */ | |
| /* Add some slop for longer signature header in the new algorithm. */ | |
| strbuf_grow(out, (7 + to->hexsz + 1) + size + 7); | |
| strbuf_addf(out, "object %s\n", oid_to_hex(&mapped_oid)); | |
| strbuf_addbuf(out, &payload); | |
| if (oursig.len) | |
| add_header_signature(out, &oursig, from); | |
| strbuf_addbuf(out, &othersig); | |
| strbuf_release(&payload); | |
| strbuf_release(&othersig); | |
| strbuf_release(&oursig); | |
| return 0; | |
| } | |
| static int convert_commit_object(struct repository *repo, | |
| struct strbuf *out, | |
| const struct git_hash_algo *from, | |
| const struct git_hash_algo *to, | |
| const char *buffer, size_t size) | |
| { | |
| const char *tail = buffer; | |
| const char *bufptr = buffer; | |
| const int tree_entry_len = from->hexsz + 5; | |
| const int parent_entry_len = from->hexsz + 7; | |
| struct object_id oid, mapped_oid; | |
| const char *p, *eol; | |
| tail += size; | |
| while ((bufptr < tail) && (*bufptr != '\n')) { | |
| eol = memchr(bufptr, '\n', tail - bufptr); | |
| if (!eol) | |
| return error(_("bad %s in commit"), "line"); | |
| if (((bufptr + 5) < eol) && !memcmp(bufptr, "tree ", 5)) | |
| { | |
| if (((bufptr + tree_entry_len) != eol) || | |
| parse_oid_hex_algop(bufptr + 5, &oid, &p, from) || | |
| (p != eol)) | |
| return error(_("bad %s in commit"), "tree"); | |
| if (repo_oid_to_algop(repo, &oid, to, &mapped_oid)) | |
| return error(_("unable to map %s %s in commit object"), | |
| "tree", oid_to_hex(&oid)); | |
| strbuf_addf(out, "tree %s\n", oid_to_hex(&mapped_oid)); | |
| } | |
| else if (((bufptr + 7) < eol) && !memcmp(bufptr, "parent ", 7)) | |
| { | |
| if (((bufptr + parent_entry_len) != eol) || | |
| parse_oid_hex_algop(bufptr + 7, &oid, &p, from) || | |
| (p != eol)) | |
| return error(_("bad %s in commit"), "parent"); | |
| if (repo_oid_to_algop(repo, &oid, to, &mapped_oid)) | |
| return error(_("unable to map %s %s in commit object"), | |
| "parent", oid_to_hex(&oid)); | |
| strbuf_addf(out, "parent %s\n", oid_to_hex(&mapped_oid)); | |
| } | |
| else if (((bufptr + 9) < eol) && !memcmp(bufptr, "mergetag ", 9)) | |
| { | |
| struct strbuf tag = STRBUF_INIT, new_tag = STRBUF_INIT; | |
| /* Recover the tag object from the mergetag */ | |
| strbuf_add(&tag, bufptr + 9, (eol - (bufptr + 9)) + 1); | |
| bufptr = eol + 1; | |
| while ((bufptr < tail) && (*bufptr == ' ')) { | |
| eol = memchr(bufptr, '\n', tail - bufptr); | |
| if (!eol) { | |
| strbuf_release(&tag); | |
| return error(_("bad %s in commit"), "mergetag continuation"); | |
| } | |
| strbuf_add(&tag, bufptr + 1, (eol - (bufptr + 1)) + 1); | |
| bufptr = eol + 1; | |
| } | |
| /* Compute the new tag object */ | |
| if (convert_tag_object(repo, &new_tag, from, to, tag.buf, tag.len)) { | |
| strbuf_release(&tag); | |
| strbuf_release(&new_tag); | |
| return -1; | |
| } | |
| /* Write the new mergetag */ | |
| strbuf_addstr(out, "mergetag"); | |
| strbuf_add_lines(out, " ", new_tag.buf, new_tag.len); | |
| strbuf_release(&tag); | |
| strbuf_release(&new_tag); | |
| } | |
| else if (((bufptr + 7) < tail) && !memcmp(bufptr, "author ", 7)) | |
| strbuf_add(out, bufptr, (eol - bufptr) + 1); | |
| else if (((bufptr + 10) < tail) && !memcmp(bufptr, "committer ", 10)) | |
| strbuf_add(out, bufptr, (eol - bufptr) + 1); | |
| else if (((bufptr + 9) < tail) && !memcmp(bufptr, "encoding ", 9)) | |
| strbuf_add(out, bufptr, (eol - bufptr) + 1); | |
| else if (((bufptr + 6) < tail) && !memcmp(bufptr, "gpgsig", 6)) | |
| strbuf_add(out, bufptr, (eol - bufptr) + 1); | |
| else { | |
| /* Unknown line fail it might embed an oid */ | |
| return -1; | |
| } | |
| /* Consume any trailing continuation lines */ | |
| bufptr = eol + 1; | |
| while ((bufptr < tail) && (*bufptr == ' ')) { | |
| eol = memchr(bufptr, '\n', tail - bufptr); | |
| if (!eol) | |
| return error(_("bad %s in commit"), "continuation"); | |
| strbuf_add(out, bufptr, (eol - bufptr) + 1); | |
| bufptr = eol + 1; | |
| } | |
| } | |
| if (bufptr < tail) | |
| strbuf_add(out, bufptr, tail - bufptr); | |
| return 0; | |
| } | |
| int convert_object_file(struct repository *repo, | |
| struct strbuf *outbuf, | |
| const struct git_hash_algo *from, | |
| const struct git_hash_algo *to, | |
| const void *buf, size_t len, | |
| enum object_type type, | |
| int gentle) | |
| { | |
| int ret; | |
| /* Don't call this function when no conversion is necessary */ | |
| if ((from == to) || (type == OBJ_BLOB)) | |
| BUG("Refusing noop object file conversion"); | |
| switch (type) { | |
| case OBJ_COMMIT: | |
| ret = convert_commit_object(repo, outbuf, from, to, buf, len); | |
| break; | |
| case OBJ_TREE: | |
| ret = convert_tree_object(repo, outbuf, from, to, buf, len); | |
| break; | |
| case OBJ_TAG: | |
| ret = convert_tag_object(repo, outbuf, from, to, buf, len); | |
| break; | |
| default: | |
| /* Not implemented yet, so fail. */ | |
| ret = -1; | |
| break; | |
| } | |
| if (!ret) | |
| return 0; | |
| if (gentle) { | |
| strbuf_release(outbuf); | |
| return ret; | |
| } | |
| die(_("Failed to convert object from %s to %s"), | |
| from->name, to->name); | |
| } |