summaryrefslogtreecommitdiff
path: root/builtin-fast-export.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin-fast-export.c')
-rw-r--r--[-rwxr-xr-x]builtin-fast-export.c315
1 files changed, 271 insertions, 44 deletions
diff --git a/builtin-fast-export.c b/builtin-fast-export.c
index e1c56303e5..b0a4029c94 100755..100644
--- a/builtin-fast-export.c
+++ b/builtin-fast-export.c
@@ -13,17 +13,20 @@
#include "log-tree.h"
#include "revision.h"
#include "decorate.h"
-#include "path-list.h"
+#include "string-list.h"
#include "utf8.h"
#include "parse-options.h"
static const char *fast_export_usage[] = {
- "git-fast-export [rev-list-opts]",
+ "git fast-export [rev-list-opts]",
NULL
};
static int progress;
-static enum { VERBATIM, WARN, STRIP, ABORT } signed_tag_mode = ABORT;
+static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT;
+static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT;
+static int fake_missing_tagger;
+static int no_data;
static int parse_opt_signed_tag_mode(const struct option *opt,
const char *arg, int unset)
@@ -41,6 +44,20 @@ static int parse_opt_signed_tag_mode(const struct option *opt,
return 0;
}
+static int parse_opt_tag_of_filtered_mode(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset || !strcmp(arg, "abort"))
+ tag_of_filtered_mode = ABORT;
+ else if (!strcmp(arg, "drop"))
+ tag_of_filtered_mode = DROP;
+ else if (!strcmp(arg, "rewrite"))
+ tag_of_filtered_mode = REWRITE;
+ else
+ return error("Unknown tag-of-filtered mode: %s", arg);
+ return 0;
+}
+
static struct decoration idnums;
static uint32_t last_idnum;
@@ -56,10 +73,24 @@ static int has_unshown_parent(struct commit *commit)
}
/* Since intptr_t is C99, we do not use it here */
-static void mark_object(struct object *object)
+static inline uint32_t *mark_to_ptr(uint32_t mark)
+{
+ return ((uint32_t *)NULL) + mark;
+}
+
+static inline uint32_t ptr_to_mark(void * mark)
+{
+ return (uint32_t *)mark - (uint32_t *)NULL;
+}
+
+static inline void mark_object(struct object *object, uint32_t mark)
{
- last_idnum++;
- add_decoration(&idnums, object, ((uint32_t *)NULL) + last_idnum);
+ add_decoration(&idnums, object, mark_to_ptr(mark));
+}
+
+static inline void mark_next_object(struct object *object)
+{
+ mark_object(object, ++last_idnum);
}
static int get_object_mark(struct object *object)
@@ -67,7 +98,7 @@ static int get_object_mark(struct object *object)
void *decoration = lookup_decoration(&idnums, object);
if (!decoration)
return 0;
- return (uint32_t *)decoration - (uint32_t *)NULL;
+ return ptr_to_mark(decoration);
}
static void show_progress(void)
@@ -86,6 +117,9 @@ static void handle_object(const unsigned char *sha1)
char *buf;
struct object *object;
+ if (no_data)
+ return;
+
if (is_null_sha1(sha1))
return;
@@ -100,11 +134,11 @@ static void handle_object(const unsigned char *sha1)
if (!buf)
die ("Could not read blob %s", sha1_to_hex(sha1));
- mark_object(object);
+ mark_next_object(object);
- printf("blob\nmark :%d\ndata %lu\n", last_idnum, size);
+ printf("blob\nmark :%"PRIu32"\ndata %lu\n", last_idnum, size);
if (size && fwrite(buf, size, 1, stdout) != 1)
- die ("Could not write blob %s", sha1_to_hex(sha1));
+ die_errno ("Could not write blob '%s'", sha1_to_hex(sha1));
printf("\n");
show_progress();
@@ -118,13 +152,46 @@ static void show_filemodify(struct diff_queue_struct *q,
{
int i;
for (i = 0; i < q->nr; i++) {
+ struct diff_filespec *ospec = q->queue[i]->one;
struct diff_filespec *spec = q->queue[i]->two;
- if (is_null_sha1(spec->sha1))
+
+ switch (q->queue[i]->status) {
+ case DIFF_STATUS_DELETED:
printf("D %s\n", spec->path);
- else {
- struct object *object = lookup_object(spec->sha1);
- printf("M %06o :%d %s\n", spec->mode,
- get_object_mark(object), spec->path);
+ break;
+
+ case DIFF_STATUS_COPIED:
+ case DIFF_STATUS_RENAMED:
+ printf("%c \"%s\" \"%s\"\n", q->queue[i]->status,
+ ospec->path, spec->path);
+
+ if (!hashcmp(ospec->sha1, spec->sha1) &&
+ ospec->mode == spec->mode)
+ break;
+ /* fallthrough */
+
+ case DIFF_STATUS_TYPE_CHANGED:
+ case DIFF_STATUS_MODIFIED:
+ case DIFF_STATUS_ADDED:
+ /*
+ * Links refer to objects in another repositories;
+ * output the SHA-1 verbatim.
+ */
+ if (no_data || S_ISGITLINK(spec->mode))
+ printf("M %06o %s %s\n", spec->mode,
+ sha1_to_hex(spec->sha1), spec->path);
+ else {
+ struct object *object = lookup_object(spec->sha1);
+ printf("M %06o :%d %s\n", spec->mode,
+ get_object_mark(object), spec->path);
+ }
+ break;
+
+ default:
+ die("Unexpected comparison status '%c' for %s, %s",
+ q->queue[i]->status,
+ ospec->path ? ospec->path : "none",
+ spec->path ? spec->path : "none");
}
}
}
@@ -173,7 +240,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
if (message)
message += 2;
- if (commit->parents) {
+ if (commit->parents &&
+ get_object_mark(&commit->parents->item->object) != 0) {
parse_commit(commit->parents->item);
diff_tree_sha1(commit->parents->item->tree->object.sha1,
commit->tree->object.sha1, "", &rev->diffopt);
@@ -182,13 +250,17 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
diff_root_tree_sha1(commit->tree->object.sha1,
"", &rev->diffopt);
+ /* Export the referenced blobs, and remember the marks. */
for (i = 0; i < diff_queued_diff.nr; i++)
- handle_object(diff_queued_diff.queue[i]->two->sha1);
+ if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
+ handle_object(diff_queued_diff.queue[i]->two->sha1);
- mark_object(&commit->object);
+ mark_next_object(&commit->object);
if (!is_encoding_utf8(encoding))
reencoded = reencode_string(message, "UTF-8", encoding);
- printf("commit %s\nmark :%d\n%.*s\n%.*s\ndata %u\n%s",
+ if (!commit->parents)
+ printf("reset %s\n", (const char*)commit->util);
+ printf("commit %s\nmark :%"PRIu32"\n%.*s\n%.*s\ndata %u\n%s",
(const char *)commit->util, last_idnum,
(int)(author_end - author), author,
(int)(committer_end - committer), committer,
@@ -204,14 +276,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
continue;
if (i == 0)
printf("from :%d\n", mark);
- else if (i == 1)
- printf("merge :%d", mark);
else
- printf(" :%d", mark);
+ printf("merge :%d\n", mark);
i++;
}
- if (i > 1)
- printf("\n");
log_tree_diff_flush(rev);
rev->diffopt.output_format = saved_output_format;
@@ -240,6 +308,23 @@ static void handle_tag(const char *name, struct tag *tag)
char *buf;
const char *tagger, *tagger_end, *message;
size_t message_size = 0;
+ struct object *tagged;
+ int tagged_mark;
+ struct commit *p;
+
+ /* Trees have no identifer in fast-export output, thus we have no way
+ * to output tags of trees, tags of tags of trees, etc. Simply omit
+ * such tags.
+ */
+ tagged = tag->tagged;
+ while (tagged->type == OBJ_TAG) {
+ tagged = ((struct tag *)tagged)->tagged;
+ }
+ if (tagged->type == OBJ_TREE) {
+ warning("Omitting tag %s,\nsince tags of trees (or tags of tags of trees, etc.) are not supported.",
+ sha1_to_hex(tag->object.sha1));
+ return;
+ }
buf = read_sha1_file(tag->object.sha1, &type, &size);
if (!buf)
@@ -250,10 +335,17 @@ static void handle_tag(const char *name, struct tag *tag)
message_size = strlen(message);
}
tagger = memmem(buf, message ? message - buf : size, "\ntagger ", 8);
- if (!tagger)
- die ("No tagger for tag %s", sha1_to_hex(tag->object.sha1));
- tagger++;
- tagger_end = strchrnul(tagger, '\n');
+ if (!tagger) {
+ if (fake_missing_tagger)
+ tagger = "tagger Unspecified Tagger "
+ "<unspecified-tagger> 0 +0000";
+ else
+ tagger = "";
+ tagger_end = tagger + strlen(tagger);
+ } else {
+ tagger++;
+ tagger_end = strchrnul(tagger, '\n');
+ }
/* handle signed tags */
if (message) {
@@ -277,16 +369,52 @@ static void handle_tag(const char *name, struct tag *tag)
}
}
+ /* handle tag->tagged having been filtered out due to paths specified */
+ tagged = tag->tagged;
+ tagged_mark = get_object_mark(tagged);
+ if (!tagged_mark) {
+ switch(tag_of_filtered_mode) {
+ case ABORT:
+ die ("Tag %s tags unexported object; use "
+ "--tag-of-filtered-object=<mode> to handle it.",
+ sha1_to_hex(tag->object.sha1));
+ case DROP:
+ /* Ignore this tag altogether */
+ return;
+ case REWRITE:
+ if (tagged->type != OBJ_COMMIT) {
+ die ("Tag %s tags unexported %s!",
+ sha1_to_hex(tag->object.sha1),
+ typename(tagged->type));
+ }
+ p = (struct commit *)tagged;
+ for (;;) {
+ if (p->parents && p->parents->next)
+ break;
+ if (p->object.flags & UNINTERESTING)
+ break;
+ if (!(p->object.flags & TREESAME))
+ break;
+ if (!p->parents)
+ die ("Can't find replacement commit for tag %s\n",
+ sha1_to_hex(tag->object.sha1));
+ p = p->parents->item;
+ }
+ tagged_mark = get_object_mark(&p->object);
+ }
+ }
+
if (!prefixcmp(name, "refs/tags/"))
name += 10;
- printf("tag %s\nfrom :%d\n%.*s\ndata %d\n%.*s\n",
- name, get_object_mark(tag->tagged),
+ printf("tag %s\nfrom :%d\n%.*s%sdata %d\n%.*s\n",
+ name, tagged_mark,
(int)(tagger_end - tagger), tagger,
+ tagger == tagger_end ? "" : "\n",
(int)message_size, (int)message_size, message ? message : "");
}
static void get_tags_and_duplicates(struct object_array *pending,
- struct path_list *extra_refs)
+ struct string_list *extra_refs)
{
struct tag *tag;
int i;
@@ -306,8 +434,11 @@ static void get_tags_and_duplicates(struct object_array *pending,
break;
case OBJ_TAG:
tag = (struct tag *)e->item;
+
+ /* handle nested tags */
while (tag && tag->object.type == OBJ_TAG) {
- path_list_insert(full_name, extra_refs)->util = tag;
+ parse_object(tag->object.sha1);
+ string_list_append(full_name, extra_refs)->util = tag;
tag = (struct tag *)tag->tagged;
}
if (!tag)
@@ -319,27 +450,33 @@ static void get_tags_and_duplicates(struct object_array *pending,
case OBJ_BLOB:
handle_object(tag->object.sha1);
continue;
+ default: /* OBJ_TAG (nested tags) is already handled */
+ warning("Tag points to object of unexpected type %s, skipping.",
+ typename(tag->object.type));
+ continue;
}
break;
default:
- die ("Unexpected object of type %s",
- typename(e->item->type));
+ warning("%s: Unexpected object of type %s, skipping.",
+ e->name,
+ typename(e->item->type));
+ continue;
}
if (commit->util)
/* more than one name for the same object */
- path_list_insert(full_name, extra_refs)->util = commit;
+ string_list_append(full_name, extra_refs)->util = commit;
else
commit->util = full_name;
}
}
-static void handle_tags_and_duplicates(struct path_list *extra_refs)
+static void handle_tags_and_duplicates(struct string_list *extra_refs)
{
struct commit *commit;
int i;
for (i = extra_refs->nr - 1; i >= 0; i--) {
- const char *name = extra_refs->items[i].path;
+ const char *name = extra_refs->items[i].string;
struct object *object = extra_refs->items[i].util;
switch (object->type) {
case OBJ_TAG:
@@ -356,30 +493,121 @@ static void handle_tags_and_duplicates(struct path_list *extra_refs)
}
}
+static void export_marks(char *file)
+{
+ unsigned int i;
+ uint32_t mark;
+ struct object_decoration *deco = idnums.hash;
+ FILE *f;
+ int e = 0;
+
+ f = fopen(file, "w");
+ if (!f)
+ error("Unable to open marks file %s for writing.", file);
+
+ for (i = 0; i < idnums.size; i++) {
+ if (deco->base && deco->base->type == 1) {
+ mark = ptr_to_mark(deco->decoration);
+ if (fprintf(f, ":%"PRIu32" %s\n", mark,
+ sha1_to_hex(deco->base->sha1)) < 0) {
+ e = 1;
+ break;
+ }
+ }
+ deco++;
+ }
+
+ e |= ferror(f);
+ e |= fclose(f);
+ if (e)
+ error("Unable to write marks file %s.", file);
+}
+
+static void import_marks(char *input_file)
+{
+ char line[512];
+ FILE *f = fopen(input_file, "r");
+ if (!f)
+ die_errno("cannot read '%s'", input_file);
+
+ while (fgets(line, sizeof(line), f)) {
+ uint32_t mark;
+ char *line_end, *mark_end;
+ unsigned char sha1[20];
+ struct object *object;
+
+ line_end = strchr(line, '\n');
+ if (line[0] != ':' || !line_end)
+ die("corrupt mark line: %s", line);
+ *line_end = '\0';
+
+ mark = strtoumax(line + 1, &mark_end, 10);
+ if (!mark || mark_end == line + 1
+ || *mark_end != ' ' || get_sha1(mark_end + 1, sha1))
+ die("corrupt mark line: %s", line);
+
+ object = parse_object(sha1);
+ if (!object)
+ die ("Could not read blob %s", sha1_to_hex(sha1));
+
+ if (object->flags & SHOWN)
+ error("Object %s already has a mark", sha1);
+
+ mark_object(object, mark);
+ if (last_idnum < mark)
+ last_idnum = mark;
+
+ object->flags |= SHOWN;
+ }
+ fclose(f);
+}
+
int cmd_fast_export(int argc, const char **argv, const char *prefix)
{
struct rev_info revs;
struct object_array commits = { 0, 0, NULL };
- struct path_list extra_refs = { NULL, 0, 0, 0 };
+ struct string_list extra_refs = { NULL, 0, 0, 0 };
struct commit *commit;
+ char *export_filename = NULL, *import_filename = NULL;
struct option options[] = {
OPT_INTEGER(0, "progress", &progress,
"show progress after <n> objects"),
OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode",
"select handling of signed tags",
parse_opt_signed_tag_mode),
+ OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, "mode",
+ "select handling of tags that tag filtered objects",
+ parse_opt_tag_of_filtered_mode),
+ OPT_STRING(0, "export-marks", &export_filename, "FILE",
+ "Dump marks to this file"),
+ OPT_STRING(0, "import-marks", &import_filename, "FILE",
+ "Import marks from this file"),
+ OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger,
+ "Fake a tagger when tags lack one"),
+ { OPTION_NEGBIT, 0, "data", &no_data, NULL,
+ "Skip output of blob data",
+ PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 },
OPT_END()
};
+ if (argc == 1)
+ usage_with_options (fast_export_usage, options);
+
/* we handle encodings */
- git_config(git_default_config);
+ git_config(git_default_config, NULL);
init_revisions(&revs, prefix);
+ revs.topo_order = 1;
+ revs.show_source = 1;
+ revs.rewrite_parents = 1;
argc = setup_revisions(argc, argv, &revs, NULL);
- argc = parse_options(argc, argv, options, fast_export_usage, 0);
+ argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0);
if (argc > 1)
usage_with_options (fast_export_usage, options);
+ if (import_filename)
+ import_marks(import_filename);
+
get_tags_and_duplicates(&revs.pending, &extra_refs);
if (prepare_revision_walk(&revs))
@@ -388,11 +616,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
while ((commit = get_revision(&revs))) {
if (has_unshown_parent(commit)) {
- struct commit_list *parent = commit->parents;
add_object_array(&commit->object, NULL, &commits);
- for (; parent; parent = parent->next)
- if (!parent->item->util)
- parent->item->util = commit->util;
}
else {
handle_commit(commit, &revs);
@@ -402,5 +626,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
handle_tags_and_duplicates(&extra_refs);
+ if (export_filename)
+ export_marks(export_filename);
+
return 0;
}