git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[WIP PATCH] Add 'git fast-export', the sister of 'git fast-import'

From
Johannes Schindelin <johannes.schindelin@gmx.de>
Date
Nov 21, 2007, 03:40 UTC
Message-ID
<Pine.LNX.4.64.0711210336210.27959@racer.site>
[WIP: this does not handle tags yet, and it lacks a test script
 as well as documentation.]

This program dumps (parts of) a git repository in the format that fast-import understands.

For clarity's sake, it does not use the 'inline' method of specifying blobs in the commits, but builds the blobs before building the commits.B

---
	I am way too tired now to continue, but maybe someone else wants
	to pick up the ball.
	Oh, and it relies on "int" being castable to void * and vice 
	versa.  Is anybody aware of a platform where this can lead to
	problems?
	And yes, I will add a copyright when I woke up again.
 .gitignore            |    1 +
 Makefile              |    1 +
 builtin-fast-export.c |  202 +++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h             |    1 +
 git.c                 |    1 +
 5 files changed, 206 insertions(+), 0 deletions(-)
 create mode 100755 builtin-fast-export.c
diff --git a/.gitignore b/.gitignore
index 507e351..ef69e2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@ git-diff-files
 git-diff-index
 git-diff-tree
 git-describe
+git-fast-export
 git-fast-import
 git-fetch
 git-fetch--tool
diff --git a/Makefile b/Makefile
index dd3ba6f..8784eff 100644
--- a/Makefile
+++ b/Makefile
@@ -336,6 +336,7 @@ BUILTIN_OBJS = \
 	builtin-diff-files.o \
 	builtin-diff-index.o \
 	builtin-diff-tree.o \
+	builtin-fast-export.o \
 	builtin-fetch.o \
 	builtin-fetch-pack.o \
 	builtin-fetch--tool.o \
diff --git a/builtin-fast-export.c b/builtin-fast-export.c
new file mode 100755
index 0000000..2e8e3dd
--- /dev/null
+++ b/builtin-fast-export.c
@@ -0,0 +1,202 @@
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "object.h"
+#include "tag.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "log-tree.h"
+#include "revision.h"
+#include "decorate.h"
+#include "path-list.h"
+
+static struct decoration idnums;
+static uint32_t last_idnum;
+
+static const char *fast_export_usage = "git-fast-export [rev-list-opts]";
+
+static int has_unshown_parent(struct commit *commit)
+{
+	struct commit_list *parent;
+
+	for (parent = commit->parents; parent; parent = parent->next)
+		if (!(parent->item->object.flags & SHOWN))
+			return 1;
+	return 0;
+}
+
+static void handle_object(const unsigned char *sha1)
+{
+	unsigned long size;
+	enum object_type type;
+	char *buf;
+	struct object *object;
+
+	if (is_null_sha1(sha1))
+		return;
+
+	object = parse_object(sha1);
+	if (!object)
+		die ("Could not read blob %s", sha1_to_hex(sha1));
+
+	if (object->flags & SHOWN)
+		return;
+
+	buf = read_sha1_file(sha1, &type, &size);
+	if (!buf)
+		die ("Could not read blob %s", sha1_to_hex(sha1));
+
+	last_idnum++;
+	add_decoration(&idnums, object, (void *)last_idnum);
+
+	printf("blob\nmark :%d\ndata %lu\n", last_idnum, size);
+	if (fwrite(buf, size, 1, stdout) != 1)
+		die ("Could not write blob %s", sha1_to_hex(sha1));
+	printf("\n");
+
+	object->flags |= SHOWN;
+	free(buf);
+}
+
+static void show_filemodify(struct diff_queue_struct *q,
+		struct diff_options *options, void *data)
+{
+	int i;
+	for (i = 0; i < q->nr; i++) {
+		struct diff_filespec *spec = q->queue[i]->two;
+		if (is_null_sha1(spec->sha1))
+			printf("D %s\n", spec->path);
+		else {
+			struct object *object = lookup_object(spec->sha1);
+			printf("M 0%06o :%d %s\n", spec->mode,
+					(int)lookup_decoration(&idnums, object),
+					spec->path);
+		}
+	}
+}
+
+static void handle_commit(struct commit *commit, struct rev_info *rev)
+{
+	const char *author, *author_end, *committer, *committer_end, *message;
+	int saved_output_format = rev->diffopt.output_format;
+	int i;
+
+	rev->diffopt.output_format = DIFF_FORMAT_CALLBACK;
+
+	parse_commit(commit);
+	author = strstr(commit->buffer, "\nauthor ");
+	if (!author)
+		die ("Could not find author in commit %s",
+				sha1_to_hex(commit->object.sha1));
+	author++;
+	author_end = strchrnul(author, '\n');
+	committer = strstr(author_end, "\ncommitter ");
+	if (!committer)
+		die ("Could not find committer in commit %s",
+				sha1_to_hex(commit->object.sha1));
+	committer++;
+	committer_end = strchrnul(committer, '\n');
+	message = strstr(committer_end, "\n\n");
+	if (message)
+		message += 2;
+
+	if (commit->parents) {
+		parse_commit(commit->parents->item);
+		diff_tree_sha1(commit->parents->item->tree->object.sha1,
+				commit->tree->object.sha1, "", &rev->diffopt);
+	}
+	else
+		diff_root_tree_sha1(commit->tree->object.sha1,
+				"", &rev->diffopt);
+
+	for (i = 0; i < diff_queued_diff.nr; i++)
+		handle_object(diff_queued_diff.queue[i]->two->sha1);
+
+	last_idnum++;
+	add_decoration(&idnums, &commit->object, (void *)last_idnum);
+	printf("commit %s\nmark :%d\n%.*s\n%.*s\ndata %u\n%s",
+		(const char *)commit->util, last_idnum,
+		author_end - author, author,
+		committer_end - committer, committer,
+		message ? strlen(message) : 0, message ? message : "");
+
+	log_tree_diff_flush(rev);
+	rev->diffopt.output_format = saved_output_format;
+
+	printf("\n");
+}
+
+static void handle_tail(struct object_array *commits, struct rev_info *revs)
+{
+	struct commit *commit;
+	while (commits->nr) {
+		commit = (struct commit *)commits->objects[commits->nr - 1].item;
+		if (has_unshown_parent(commit))
+			return;
+		handle_commit(commit, revs);
+		commits->nr--;
+	}
+}
+
+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 commit *commit;
+	int i;
+
+	init_revisions(&revs, prefix);
+	argc = setup_revisions(argc, argv, &revs, NULL);
+	if (argc > 1)
+		usage (fast_export_usage);
+
+	for (i = 0; i < revs.pending.nr; i++) {
+		struct object_array_entry *e = revs.pending.objects + i;
+		unsigned char sha1[20];
+		char *full_name;
+
+		/* TODO: handle tags */
+		commit = (struct commit *)e->item;
+		if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1)
+			die ("Could not get a unique ref name for '%s'",
+				e->name);
+		if (commit->util)
+			/* more than one name for the same object */
+			path_list_insert(full_name, &extra_refs)->util = commit;
+		else
+			commit->util = full_name;
+	}
+
+	prepare_revision_walk(&revs);
+	revs.diffopt.format_callback = show_filemodify;
+	revs.diffopt.recursive = 1;
+	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);
+			handle_tail(&commits, &revs);
+		}
+	}
+
+	if (commits.nr)
+		die ("This should not happen: there were %d commits left!",
+			commits.nr);
+
+	for (i = 0; i < extra_refs.nr; i++) {
+		/* create refs pointing to already seen commits */
+		commit = extra_refs.items[i].util;
+		printf("reset %s\nfrom :%d\n\n",
+				extra_refs.items[i].path,
+				(int)lookup_decoration(&idnums,
+					&commit->object));
+	}
+
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index e76f5da..5723f9e 100644
--- a/builtin.h
+++ b/builtin.h
@@ -31,6 +31,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
+extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix);
diff --git a/git.c b/git.c
index 0b6825e..deda6dd 100644
--- a/git.c
+++ b/git.c
@@ -301,6 +301,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "diff-files", cmd_diff_files },
 		{ "diff-index", cmd_diff_index, RUN_SETUP },
 		{ "diff-tree", cmd_diff_tree, RUN_SETUP },
+		{ "fast-export", cmd_fast_export, RUN_SETUP },
 		{ "fetch", cmd_fetch, RUN_SETUP },
 		{ "fetch-pack", cmd_fetch_pack, RUN_SETUP },
 		{ "fetch--tool", cmd_fetch__tool, RUN_SETUP },
-- 
1.5.3.5.1751.gf6fb4
Next: Johannes Sixt
Message 1 of 27 in “Add 'git fast-export', the sister of 'git fast-import'”
  1. Add 'git fast-export', the sister of 'git fast-import'Johannes Schindelin, Nov 21, 2007
  2. Johannes SixtNov 21, 2007
  3. Shawn O. PearceNov 21, 2007
  4. Johannes SchindelinNov 21, 2007
  5. Andreas EricssonNov 21, 2007
  6. Johannes SchindelinNov 21, 2007
  7. Andreas EricssonNov 21, 2007
  8. Geert BoschNov 21, 2007
  9. Johannes SchindelinNov 21, 2007
  10. Han-Wen NienhuysNov 23, 2007
  11. Johannes SchindelinNov 23, 2007
  12. Han-Wen NienhuysNov 23, 2007
  13. Johannes SchindelinNov 23, 2007
  14. Shawn O. PearceNov 23, 2007
  15. Karl HasselströmNov 25, 2007
  16. Johannes SchindelinNov 26, 2007
  17. Karl HasselströmNov 27, 2007
  18. Johannes SchindelinNov 27, 2007
  19. Karl HasselströmNov 27, 2007
  20. Johannes SchindelinNov 27, 2007
  21. Johannes SchindelinNov 26, 2007
  22. Nguyen Thai Ngoc DuyNov 23, 2007
  23. Johannes SchindelinNov 23, 2007
  24. Shawn O. PearceNov 23, 2007
  25. Nguyen Thai Ngoc DuyNov 24, 2007
  26. Johannes SchindelinNov 27, 2007
  27. Nguyen Thai Ngoc DuyNov 27, 2007

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.