diff options
Diffstat (limited to 'builtin/fast-export.c')
-rw-r--r-- | builtin/fast-export.c | 309 |
1 files changed, 221 insertions, 88 deletions
diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 9e283482ef..d2e33f5005 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -33,16 +33,19 @@ static const char *fast_export_usage[] = { static int progress; static enum { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT; static enum { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT; +static enum { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT; static int fake_missing_tagger; static int use_done_feature; static int no_data; static int full_tree; static int reference_excluded_commits; static int show_original_ids; +static int mark_tags; static struct string_list extra_refs = STRING_LIST_INIT_NODUP; static struct string_list tag_refs = STRING_LIST_INIT_NODUP; static struct refspec refspecs = REFSPEC_INIT_FETCH; static int anonymize; +static struct hashmap anonymized_seeds; static struct revision_sources revision_sources; static int parse_opt_signed_tag_mode(const struct option *opt, @@ -77,6 +80,31 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt, return 0; } +static int parse_opt_reencode_mode(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + reencode_mode = REENCODE_ABORT; + return 0; + } + + switch (git_parse_maybe_bool(arg)) { + case 0: + reencode_mode = REENCODE_NO; + break; + case 1: + reencode_mode = REENCODE_YES; + break; + default: + if (!strcasecmp(arg, "abort")) + reencode_mode = REENCODE_ABORT; + else + return error("Unknown reencoding mode: %s", arg); + } + + return 0; +} + static struct decoration idnums; static uint32_t last_idnum; @@ -93,19 +121,33 @@ static int has_unshown_parent(struct commit *commit) struct anonymized_entry { struct hashmap_entry hash; + const char *anon; + const char orig[FLEX_ARRAY]; +}; + +struct anonymized_entry_key { + struct hashmap_entry hash; const char *orig; size_t orig_len; - const char *anon; - size_t anon_len; }; static int anonymized_entry_cmp(const void *unused_cmp_data, - const void *va, const void *vb, - const void *unused_keydata) + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata) { - const struct anonymized_entry *a = va, *b = vb; - return a->orig_len != b->orig_len || - memcmp(a->orig, b->orig, a->orig_len); + const struct anonymized_entry *a, *b; + + a = container_of(eptr, const struct anonymized_entry, hash); + if (keydata) { + const struct anonymized_entry_key *key = keydata; + int equal = !strncmp(a->orig, key->orig, key->orig_len) && + !a->orig[key->orig_len]; + return !equal; + } + + b = container_of(entry_or_key, const struct anonymized_entry, hash); + return strcmp(a->orig, b->orig); } /* @@ -113,31 +155,39 @@ static int anonymized_entry_cmp(const void *unused_cmp_data, * the same anonymized string with another. The actual generation * is farmed out to the generate function. */ -static const void *anonymize_mem(struct hashmap *map, - void *(*generate)(const void *, size_t *), - const void *orig, size_t *len) +static const char *anonymize_str(struct hashmap *map, + char *(*generate)(void *), + const char *orig, size_t len, + void *data) { - struct anonymized_entry key, *ret; + struct anonymized_entry_key key; + struct anonymized_entry *ret; if (!map->cmpfn) hashmap_init(map, anonymized_entry_cmp, NULL, 0); - hashmap_entry_init(&key, memhash(orig, *len)); + hashmap_entry_init(&key.hash, memhash(orig, len)); key.orig = orig; - key.orig_len = *len; - ret = hashmap_get(map, &key, NULL); + key.orig_len = len; + + /* First check if it's a token the user configured manually... */ + if (anonymized_seeds.cmpfn) + ret = hashmap_get_entry(&anonymized_seeds, &key, hash, &key); + else + ret = NULL; + + /* ...otherwise check if we've already seen it in this context... */ + if (!ret) + ret = hashmap_get_entry(map, &key, hash, &key); + /* ...and finally generate a new mapping if necessary */ if (!ret) { - ret = xmalloc(sizeof(*ret)); + FLEX_ALLOC_MEM(ret, orig, orig, len); hashmap_entry_init(&ret->hash, key.hash.hash); - ret->orig = xstrdup(orig); - ret->orig_len = *len; - ret->anon = generate(orig, len); - ret->anon_len = *len; - hashmap_put(map, ret); + ret->anon = generate(data); + hashmap_put(map, &ret->hash); } - *len = ret->anon_len; return ret->anon; } @@ -149,13 +199,13 @@ static const void *anonymize_mem(struct hashmap *map, */ static void anonymize_path(struct strbuf *out, const char *path, struct hashmap *map, - void *(*generate)(const void *, size_t *)) + char *(*generate)(void *)) { while (*path) { const char *end_of_component = strchrnul(path, '/'); size_t len = end_of_component - path; - const char *c = anonymize_mem(map, generate, path, &len); - strbuf_add(out, c, len); + const char *c = anonymize_str(map, generate, path, len, NULL); + strbuf_addstr(out, c); path = end_of_component; if (*path) strbuf_addch(out, *path++); @@ -249,7 +299,7 @@ static void export_blob(const struct object_id *oid) if (is_null_oid(oid)) return; - object = lookup_object(the_repository, oid->hash); + object = lookup_object(the_repository, oid); if (object && object->flags & SHOWN) return; @@ -261,7 +311,8 @@ static void export_blob(const struct object_id *oid) buf = read_object_file(oid, &type, &size); if (!buf) die("could not read blob %s", oid_to_hex(oid)); - if (check_object_signature(oid, buf, size, type_name(type)) < 0) + if (check_object_signature(the_repository, oid, buf, size, + type_name(type)) < 0) die("oid mismatch in blob %s", oid_to_hex(oid)); object = parse_object_buffer(the_repository, oid, type, size, buf, &eaten); @@ -328,12 +379,12 @@ static void print_path_1(const char *path) printf("%s", path); } -static void *anonymize_path_component(const void *path, size_t *len) +static char *anonymize_path_component(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "path%d", counter++); - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } static void print_path(const char *path) @@ -350,20 +401,23 @@ static void print_path(const char *path) } } -static void *generate_fake_oid(const void *old, size_t *len) +static char *generate_fake_oid(void *data) { static uint32_t counter = 1; /* avoid null oid */ const unsigned hashsz = the_hash_algo->rawsz; - unsigned char *out = xcalloc(hashsz, 1); - put_be32(out + hashsz - 4, counter++); - return out; + struct object_id oid; + char *hex = xmallocz(GIT_MAX_HEXSZ); + + oidclr(&oid); + put_be32(oid.hash + hashsz - 4, counter++); + return oid_to_hex_r(hex, &oid); } -static const struct object_id *anonymize_oid(const struct object_id *oid) +static const char *anonymize_oid(const char *oid_hex) { static struct hashmap objs; - size_t len = the_hash_algo->rawsz; - return anonymize_mem(&objs, generate_fake_oid, oid, &len); + size_t len = strlen(oid_hex); + return anonymize_str(&objs, generate_fake_oid, oid_hex, len, NULL); } static void show_filemodify(struct diff_queue_struct *q, @@ -422,12 +476,12 @@ static void show_filemodify(struct diff_queue_struct *q, */ if (no_data || S_ISGITLINK(spec->mode)) printf("M %06o %s ", spec->mode, - oid_to_hex(anonymize ? - anonymize_oid(&spec->oid) : - &spec->oid)); + anonymize ? + anonymize_oid(oid_to_hex(&spec->oid)) : + oid_to_hex(&spec->oid)); else { struct object *object = lookup_object(the_repository, - spec->oid.hash); + &spec->oid); printf("M %06o :%d ", spec->mode, get_object_mark(object)); } @@ -453,19 +507,19 @@ static const char *find_encoding(const char *begin, const char *end) bol = memmem(begin, end ? end - begin : strlen(begin), needle, strlen(needle)); if (!bol) - return git_commit_encoding; + return NULL; bol += strlen(needle); eol = strchrnul(bol, '\n'); *eol = '\0'; return bol; } -static void *anonymize_ref_component(const void *old, size_t *len) +static char *anonymize_ref_component(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "ref%d", counter++); - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } static const char *anonymize_refname(const char *refname) @@ -484,13 +538,6 @@ static const char *anonymize_refname(const char *refname) static struct strbuf anon = STRBUF_INIT; int i; - /* - * We also leave "master" as a special case, since it does not reveal - * anything interesting. - */ - if (!strcmp(refname, "refs/heads/master")) - return refname; - strbuf_reset(&anon); for (i = 0; i < ARRAY_SIZE(prefixes); i++) { if (skip_prefix(refname, prefixes[i], &refname)) { @@ -513,14 +560,13 @@ static char *anonymize_commit_message(const char *old) return xstrfmt("subject %d\n\nbody\n", counter++); } -static struct hashmap idents; -static void *anonymize_ident(const void *old, size_t *len) +static char *anonymize_ident(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "User %d <user%d@example.com>", counter, counter); counter++; - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } /* @@ -530,6 +576,7 @@ static void *anonymize_ident(const void *old, size_t *len) */ static void anonymize_ident_line(const char **beg, const char **end) { + static struct hashmap idents; static struct strbuf buffers[] = { STRBUF_INIT, STRBUF_INIT }; static unsigned which_buffer; @@ -555,9 +602,9 @@ static void anonymize_ident_line(const char **beg, const char **end) size_t len; len = split.mail_end - split.name_begin; - ident = anonymize_mem(&idents, anonymize_ident, - split.name_begin, &len); - strbuf_add(out, ident, len); + ident = anonymize_str(&idents, anonymize_ident, + split.name_begin, len, NULL); + strbuf_addstr(out, ident); strbuf_addch(out, ' '); strbuf_add(out, split.date_begin, split.tz_end - split.date_begin); } else { @@ -633,18 +680,32 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, } mark_next_object(&commit->object); - if (anonymize) + if (anonymize) { reencoded = anonymize_commit_message(message); - else if (!is_encoding_utf8(encoding)) - reencoded = reencode_string(message, "UTF-8", encoding); + } else if (encoding) { + switch(reencode_mode) { + case REENCODE_YES: + reencoded = reencode_string(message, "UTF-8", encoding); + break; + case REENCODE_NO: + break; + case REENCODE_ABORT: + die("Encountered commit-specific encoding %s in commit " + "%s; use --reencode=[yes|no] to handle it", + encoding, oid_to_hex(&commit->object.oid)); + } + } if (!commit->parents) printf("reset %s\n", refname); printf("commit %s\nmark :%"PRIu32"\n", refname, last_idnum); if (show_original_ids) printf("original-oid %s\n", oid_to_hex(&commit->object.oid)); - printf("%.*s\n%.*s\ndata %u\n%s", + printf("%.*s\n%.*s\n", (int)(author_end - author), author, - (int)(committer_end - committer), committer, + (int)(committer_end - committer), committer); + if (!reencoded && encoding) + printf("encoding %s\n", encoding); + printf("data %u\n%s", (unsigned)(reencoded ? strlen(reencoded) : message ? strlen(message) : 0), @@ -665,9 +726,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, if (mark) printf(":%d\n", mark); else - printf("%s\n", oid_to_hex(anonymize ? - anonymize_oid(&obj->oid) : - &obj->oid)); + printf("%s\n", + anonymize ? + anonymize_oid(oid_to_hex(&obj->oid)) : + oid_to_hex(&obj->oid)); i++; } @@ -682,12 +744,12 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, show_progress(); } -static void *anonymize_tag(const void *old, size_t *len) +static char *anonymize_tag(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "tag message %d", counter++); - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } static void handle_tail(struct object_array *commits, struct rev_info *revs, @@ -757,8 +819,8 @@ static void handle_tag(const char *name, struct tag *tag) name = anonymize_refname(name); if (message) { static struct hashmap tags; - message = anonymize_mem(&tags, anonymize_tag, - message, &message_size); + message = anonymize_str(&tags, anonymize_tag, + message, message_size, NULL); } } @@ -802,25 +864,39 @@ static void handle_tag(const char *name, struct tag *tag) free(buf); return; case REWRITE: - if (tagged->type != OBJ_COMMIT) { - die("tag %s tags unexported %s!", - oid_to_hex(&tag->object.oid), - type_name(tagged->type)); - } - p = rewrite_commit((struct commit *)tagged); - if (!p) { - printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(&null_oid)); - free(buf); - return; + if (tagged->type == OBJ_TAG && !mark_tags) { + die(_("Error: Cannot export nested tags unless --mark-tags is specified.")); + } else if (tagged->type == OBJ_COMMIT) { + p = rewrite_commit((struct commit *)tagged); + if (!p) { + printf("reset %s\nfrom %s\n\n", + name, oid_to_hex(&null_oid)); + free(buf); + return; + } + tagged_mark = get_object_mark(&p->object); + } else { + /* tagged->type is either OBJ_BLOB or OBJ_TAG */ + tagged_mark = get_object_mark(tagged); } - tagged_mark = get_object_mark(&p->object); } } - if (starts_with(name, "refs/tags/")) - name += 10; - printf("tag %s\nfrom :%d\n", name, tagged_mark); + if (tagged->type == OBJ_TAG) { + printf("reset %s\nfrom %s\n\n", + name, oid_to_hex(&null_oid)); + } + skip_prefix(name, "refs/tags/", &name); + printf("tag %s\n", name); + if (mark_tags) { + mark_next_object(&tag->object); + printf("mark :%"PRIu32"\n", last_idnum); + } + if (tagged_mark) + printf("from :%d\n", tagged_mark); + else + printf("from %s\n", oid_to_hex(&tagged->oid)); + if (show_original_ids) printf("original-oid %s\n", oid_to_hex(&tag->object.oid)); printf("%.*s%sdata %d\n%.*s\n", @@ -867,7 +943,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) if (e->flags & UNINTERESTING) continue; - if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1) + if (dwim_ref(e->name, strlen(e->name), &oid, &full_name, 0) != 1) continue; if (refspecs.nr) { @@ -950,7 +1026,7 @@ static void handle_tags_and_duplicates(struct string_list *extras) /* * Getting here means we have a commit which * was excluded by a negative refspec (e.g. - * fast-export ^master master). If we are + * fast-export ^HEAD HEAD). If we are * referencing excluded commits, set the ref * to the exact commit. Otherwise, the user * wants the branch exported but every commit @@ -1007,11 +1083,16 @@ static void export_marks(char *file) error("Unable to write marks file %s.", file); } -static void import_marks(char *input_file) +static void import_marks(char *input_file, int check_exists) { char line[512]; - FILE *f = xfopen(input_file, "r"); + FILE *f; + struct stat sb; + if (check_exists && stat(input_file, &sb)) + return; + + f = xfopen(input_file, "r"); while (fgets(line, sizeof(line), f)) { uint32_t mark; char *line_end, *mark_end; @@ -1070,12 +1151,45 @@ static void handle_deletes(void) } } +static char *anonymize_seed(void *data) +{ + return xstrdup(data); +} + +static int parse_opt_anonymize_map(const struct option *opt, + const char *arg, int unset) +{ + struct hashmap *map = opt->value; + const char *delim, *value; + size_t keylen; + + BUG_ON_OPT_NEG(unset); + + delim = strchr(arg, ':'); + if (delim) { + keylen = delim - arg; + value = delim + 1; + } else { + keylen = strlen(arg); + value = arg; + } + + if (!keylen || !*value) + return error(_("--anonymize-map token cannot be empty")); + + anonymize_str(map, anonymize_seed, arg, keylen, (void *)value); + + return 0; +} + int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct object_array commits = OBJECT_ARRAY_INIT; struct commit *commit; - char *export_filename = NULL, *import_filename = NULL; + char *export_filename = NULL, + *import_filename = NULL, + *import_filename_if_exists = NULL; uint32_t lastimportid; struct string_list refspecs_list = STRING_LIST_INIT_NODUP; struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP; @@ -1088,10 +1202,17 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, N_("mode"), N_("select handling of tags that tag filtered objects"), parse_opt_tag_of_filtered_mode), + OPT_CALLBACK(0, "reencode", &reencode_mode, N_("mode"), + N_("select handling of commit messages in an alternate encoding"), + parse_opt_reencode_mode), OPT_STRING(0, "export-marks", &export_filename, N_("file"), N_("Dump marks to this file")), OPT_STRING(0, "import-marks", &import_filename, N_("file"), N_("Import marks from this file")), + OPT_STRING(0, "import-marks-if-exists", + &import_filename_if_exists, + N_("file"), + N_("Import marks from this file if it exists")), OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger, N_("Fake a tagger when tags lack one")), OPT_BOOL(0, "full-tree", &full_tree, @@ -1102,10 +1223,15 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), N_("Apply refspec to exported refs")), OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")), + OPT_CALLBACK_F(0, "anonymize-map", &anonymized_seeds, N_("from:to"), + N_("convert <from> to <to> in anonymized output"), + PARSE_OPT_NONEG, parse_opt_anonymize_map), OPT_BOOL(0, "reference-excluded-parents", &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")), OPT_BOOL(0, "show-original-ids", &show_original_ids, N_("Show original object ids of blobs/commits")), + OPT_BOOL(0, "mark-tags", &mark_tags, + N_("Label tags with mark ids")), OPT_END() }; @@ -1127,6 +1253,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (argc > 1) usage_with_options (fast_export_usage, options); + if (anonymized_seeds.cmpfn && !anonymize) + die(_("--anonymize-map without --anonymize does not make sense")); + if (refspecs_list.nr) { int i; @@ -1139,8 +1268,12 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (use_done_feature) printf("feature done\n"); + if (import_filename && import_filename_if_exists) + die(_("Cannot pass both --import-marks and --import-marks-if-exists")); if (import_filename) - import_marks(import_filename); + import_marks(import_filename, 0); + else if (import_filename_if_exists) + import_marks(import_filename_if_exists, 1); lastimportid = last_idnum; if (import_filename && revs.prune_data.nr) |