{"thread":{"id":"5400","subject":"[PATCH] Add git-zip-tree","startedAt":"2006-08-26T21:19:21Z","lastAt":"2006-08-26T21:19:21Z","messageCount":1,"participants":["Rene Scharfe"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"25958","messageId":"44F0BAD9.40408@lsrfire.ath.cx","threadId":"5400","inReplyTo":null,"subject":"[PATCH] Add git-zip-tree","fromName":"Rene Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2006-08-26T21:19:21Z","receivedAt":"2006-08-26T21:19:21Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"In the Windows world ZIP files are better supported than tar files.\nWindows even includes built-in support for ZIP files nowadays.\n\ngit-zip-tree is similar to git-tar-tree; it creates ZIP files out of\ngit trees.  It stores the commit ID (if available) in a ZIP file comment\nwhich can be extracted by unzip.\n\nThere's still quite some room for improvement: this initial version\nsupports no symlinks, calls write() way too often (three times per file)\nand there is no unit test.\n\nSigned-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>\n\n Documentation/git-zip-tree.txt |   67 +++++++\n Makefile                       |    3\n builtin-zip-tree.c             |  352 +++++++++++++++++++++++++++++++++++++++++\n builtin.h                      |    1\n git.c                          |    1\n 5 files changed, 423 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-zip-tree.txt b/Documentation/git-zip-tree.txt\nnew file mode 100644\nindex 0000000..2e9d981\n--- /dev/null\n+++ b/Documentation/git-zip-tree.txt\n@@ -0,0 +1,67 @@\n+git-zip-tree(1)\n+===============\n+\n+NAME\n+----\n+git-zip-tree - Creates a ZIP archive of the files in the named tree\n+\n+\n+SYNOPSIS\n+--------\n+'git-zip-tree' [-0|...|-9] <tree-ish> [ <base> ]\n+\n+DESCRIPTION\n+-----------\n+Creates a ZIP archive containing the tree structure for the named tree.\n+When <base> is specified it is added as a leading path to the files in the\n+generated ZIP archive.\n+\n+git-zip-tree behaves differently when given a tree ID versus when given\n+a commit ID or tag ID.  In the first case the current time is used as\n+modification time of each file in the archive.  In the latter case the\n+commit time as recorded in the referenced commit object is used instead.\n+Additionally the commit ID is stored as an archive comment.\n+\n+Currently git-zip-tree can handle only files and directories, symbolic\n+links are not supported.\n+\n+OPTIONS\n+-------\n+\n+-0::\n+\tStore the files instead of deflating them.\n+\n+-9::\n+\tHighest and slowest compression level.  You can specify any\n+\tnumber from 1 to 9 to adjust compression speed and ratio.\n+\n+<tree-ish>::\n+\tThe tree or commit to produce ZIP archive for.  If it is\n+\tthe object name of a commit object.\n+\n+<base>::\n+\tLeading path to the files in the resulting ZIP archive.\n+\n+EXAMPLES\n+--------\n+git zip-tree v1.4.0 git-1.4.0 >git-1.4.0.zip::\n+\n+\tCreate a ZIP file for v1.4.0 release.\n+\n+git zip-tree HEAD:Documentation/ git-docs >docs.zip::\n+\n+\tPut everything in the current head's Documentation/ directory\n+\tinto 'docs.zip', with the prefix 'git-docs/'.\n+\n+Author\n+------\n+Written by Rene Scharfe.\n+\n+Documentation\n+--------------\n+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.\n+\n+GIT\n+---\n+Part of the gitlink:git[7] suite\n+\ndiff --git a/Makefile b/Makefile\nindex b15b420..d9741f9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -292,7 +292,8 @@ BUILTIN_OBJS = \\\n \tbuiltin-update-ref.o \\\n \tbuiltin-upload-tar.o \\\n \tbuiltin-verify-pack.o \\\n-\tbuiltin-write-tree.o\n+\tbuiltin-write-tree.o \\\n+\tbuiltin-zip-tree.o\n \n GITLIBS = $(LIB_FILE) $(XDIFF_LIB)\n LIBS = $(GITLIBS) -lz\ndiff --git a/builtin-zip-tree.c b/builtin-zip-tree.c\nnew file mode 100644\nindex 0000000..8dfe421\n--- /dev/null\n+++ b/builtin-zip-tree.c\n@@ -0,0 +1,352 @@\n+/*\n+ * Copyright (c) 2006 Rene Scharfe\n+ */\n+#include <time.h>\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"blob.h\"\n+#include \"tree.h\"\n+#include \"quote.h\"\n+#include \"builtin.h\"\n+\n+static const char zip_tree_usage[] =\n+\"git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]\";\n+\n+static int zip_date;\n+static int zip_time;\n+\n+static unsigned char *zip_dir;\n+static unsigned int zip_dir_size;\n+\n+static unsigned int zip_offset;\n+static unsigned int zip_dir_offset;\n+static unsigned int zip_dir_entries;\n+\n+#define ZIP_DIRECTORY_MIN_SIZE\t(1024 * 1024)\n+\n+struct zip_local_header {\n+\tunsigned char magic[4];\n+\tunsigned char version[2];\n+\tunsigned char flags[2];\n+\tunsigned char compression_method[2];\n+\tunsigned char mtime[2];\n+\tunsigned char mdate[2];\n+\tunsigned char crc32[4];\n+\tunsigned char compressed_size[4];\n+\tunsigned char size[4];\n+\tunsigned char filename_length[2];\n+\tunsigned char extra_length[2];\n+};\n+\n+struct zip_dir_header {\n+\tunsigned char magic[4];\n+\tunsigned char creator_version[2];\n+\tunsigned char version[2];\n+\tunsigned char flags[2];\n+\tunsigned char compression_method[2];\n+\tunsigned char mtime[2];\n+\tunsigned char mdate[2];\n+\tunsigned char crc32[4];\n+\tunsigned char compressed_size[4];\n+\tunsigned char size[4];\n+\tunsigned char filename_length[2];\n+\tunsigned char extra_length[2];\n+\tunsigned char comment_length[2];\n+\tunsigned char disk[2];\n+\tunsigned char attr1[2];\n+\tunsigned char attr2[4];\n+\tunsigned char offset[4];\n+};\n+\n+struct zip_dir_trailer {\n+\tunsigned char magic[4];\n+\tunsigned char disk[2];\n+\tunsigned char directory_start_disk[2];\n+\tunsigned char entries_on_this_disk[2];\n+\tunsigned char entries[2];\n+\tunsigned char size[4];\n+\tunsigned char offset[4];\n+\tunsigned char comment_length[2];\n+};\n+\n+static void copy_le16(unsigned char *dest, unsigned int n)\n+{\n+\tdest[0] = 0xff & n;\n+\tdest[1] = 0xff & (n >> 010);\n+}\n+\n+static void copy_le32(unsigned char *dest, unsigned int n)\n+{\n+\tdest[0] = 0xff & n;\n+\tdest[1] = 0xff & (n >> 010);\n+\tdest[2] = 0xff & (n >> 020);\n+\tdest[3] = 0xff & (n >> 030);\n+}\n+\n+static void *zlib_deflate(void *data, unsigned long size,\n+                          unsigned long *compressed_size)\n+{\n+\tz_stream stream;\n+\tunsigned long maxsize;\n+\tvoid *buffer;\n+\tint result;\n+\n+\tmemset(&stream, 0, sizeof(stream));\n+\tdeflateInit(&stream, zlib_compression_level);\n+\tmaxsize = deflateBound(&stream, size);\n+\tbuffer = xmalloc(maxsize);\n+\n+\tstream.next_in = data;\n+\tstream.avail_in = size;\n+\tstream.next_out = buffer;\n+\tstream.avail_out = maxsize;\n+\n+\tdo {\n+\t\tresult = deflate(&stream, Z_FINISH);\n+\t} while (result == Z_OK);\n+\n+\tif (result != Z_STREAM_END) {\n+\t\tfree(buffer);\n+\t\treturn NULL;\n+\t}\n+\n+\tdeflateEnd(&stream);\n+\t*compressed_size = stream.total_out;\n+\n+\treturn buffer;\n+}\n+\n+static char *construct_path(const char *base, int baselen,\n+                            const char *filename, int isdir, int *pathlen)\n+{\n+\tint filenamelen = strlen(filename);\n+\tint len = baselen + filenamelen;\n+\tchar *path, *p;\n+\n+\tif (isdir)\n+\t\tlen++;\n+\tp = path = xmalloc(len + 1);\n+\n+\tmemcpy(p, base, baselen);\n+\tp += baselen;\n+\tmemcpy(p, filename, filenamelen);\n+\tp += filenamelen;\n+\tif (isdir)\n+\t\t*p++ = '/';\n+\t*p = '\\0';\n+\n+\t*pathlen = len;\n+\n+\treturn path;\n+}\n+\n+static int write_zip_entry(const unsigned char *sha1,\n+                           const char *base, int baselen,\n+                           const char *filename, unsigned mode, int stage)\n+{\n+\tstruct zip_local_header header;\n+\tstruct zip_dir_header dirent;\n+\tunsigned long compressed_size;\n+\tunsigned long uncompressed_size;\n+\tunsigned long crc;\n+\tunsigned long direntsize;\n+\tunsigned long size;\n+\tint method;\n+\tint result = -1;\n+\tint pathlen;\n+\tunsigned char *out;\n+\tchar *path;\n+\tchar type[20];\n+\tvoid *buffer = NULL;\n+\tvoid *deflated = NULL;\n+\n+\tcrc = crc32(0, Z_NULL, 0);\n+\n+\tpath = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen);\n+\tif (pathlen > 0xffff) {\n+\t\terror(\"path too long (%d chars, SHA1: %s): %s\", pathlen,\n+\t\t      sha1_to_hex(sha1), path);\n+\t\tgoto out;\n+\t}\n+\n+\tif (S_ISDIR(mode)) {\n+\t\tmethod = 0;\n+\t\tresult = READ_TREE_RECURSIVE;\n+\t\tout = NULL;\n+\t\tuncompressed_size = 0;\n+\t\tcompressed_size = 0;\n+\t} else if (S_ISREG(mode)) {\n+\t\tmethod = zlib_compression_level == 0 ? 0 : 8;\n+\t\tresult = 0;\n+\t\tbuffer = read_sha1_file(sha1, type, &size);\n+\t\tif (!buffer)\n+\t\t\tdie(\"cannot read %s\", sha1_to_hex(sha1));\n+\t\tcrc = crc32(crc, buffer, size);\n+\t\tout = buffer;\n+\t\tuncompressed_size = size;\n+\t\tcompressed_size = size;\n+\t} else {\n+\t\terror(\"unsupported file mode: 0%o (SHA1: %s)\", mode,\n+\t\t      sha1_to_hex(sha1));\n+\t\tgoto out;\n+\t}\n+\n+\tif (method == 8) {\n+\t\tdeflated = zlib_deflate(buffer, size, &compressed_size);\n+\t\tif (deflated && compressed_size - 6 < size) {\n+\t\t\t/* ZLIB --> raw compressed data (see RFC 1950) */\n+\t\t\tout = deflated + 2;\t/* CMF and FLG ... */\n+\t\t\tcompressed_size -= 6;\t/* ... and ADLER32 */\n+\t\t} else {\n+\t\t\tmethod = 0;\n+\t\t\tcompressed_size = size;\n+\t\t}\n+\t}\n+\n+\t/* make sure we have enough free space in the dictionary */\n+\tdirentsize = sizeof(struct zip_dir_header) + pathlen;\n+\twhile (zip_dir_size < zip_dir_offset + direntsize) {\n+\t\tzip_dir_size += ZIP_DIRECTORY_MIN_SIZE;\n+\t\tzip_dir = xrealloc(zip_dir, zip_dir_size);\n+\t}\n+\n+\tcopy_le32(dirent.magic, 0x02014b50);\n+\tcopy_le16(dirent.creator_version, 0);\n+\tcopy_le16(dirent.version, 20);\n+\tcopy_le16(dirent.flags, 0);\n+\tcopy_le16(dirent.compression_method, method);\n+\tcopy_le16(dirent.mtime, zip_time);\n+\tcopy_le16(dirent.mdate, zip_date);\n+\tcopy_le32(dirent.crc32, crc);\n+\tcopy_le32(dirent.compressed_size, compressed_size);\n+\tcopy_le32(dirent.size, uncompressed_size);\n+\tcopy_le16(dirent.filename_length, pathlen);\n+\tcopy_le16(dirent.extra_length, 0);\n+\tcopy_le16(dirent.comment_length, 0);\n+\tcopy_le16(dirent.disk, 0);\n+\tcopy_le16(dirent.attr1, 0);\n+\tcopy_le32(dirent.attr2, 0);\n+\tcopy_le32(dirent.offset, zip_offset);\n+\tmemcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header));\n+\tzip_dir_offset += sizeof(struct zip_dir_header);\n+\tmemcpy(zip_dir + zip_dir_offset, path, pathlen);\n+\tzip_dir_offset += pathlen;\n+\tzip_dir_entries++;\n+\n+\tcopy_le32(header.magic, 0x04034b50);\n+\tcopy_le16(header.version, 20);\n+\tcopy_le16(header.flags, 0);\n+\tcopy_le16(header.compression_method, method);\n+\tcopy_le16(header.mtime, zip_time);\n+\tcopy_le16(header.mdate, zip_date);\n+\tcopy_le32(header.crc32, crc);\n+\tcopy_le32(header.compressed_size, compressed_size);\n+\tcopy_le32(header.size, uncompressed_size);\n+\tcopy_le16(header.filename_length, pathlen);\n+\tcopy_le16(header.extra_length, 0);\n+\twrite_or_die(1, &header, sizeof(struct zip_local_header));\n+\tzip_offset += sizeof(struct zip_local_header);\n+\twrite_or_die(1, path, pathlen);\n+\tzip_offset += pathlen;\n+\tif (compressed_size > 0) {\n+\t\twrite_or_die(1, out, compressed_size);\n+\t\tzip_offset += compressed_size;\n+\t}\n+\n+out:\n+\tfree(buffer);\n+\tfree(deflated);\n+\tfree(path);\n+\n+\treturn result;\n+}\n+\n+static void write_zip_trailer(const unsigned char *sha1)\n+{\n+\tstruct zip_dir_trailer trailer;\n+\n+\tcopy_le32(trailer.magic, 0x06054b50);\n+\tcopy_le16(trailer.disk, 0);\n+\tcopy_le16(trailer.directory_start_disk, 0);\n+\tcopy_le16(trailer.entries_on_this_disk, zip_dir_entries);\n+\tcopy_le16(trailer.entries, zip_dir_entries);\n+\tcopy_le32(trailer.size, zip_dir_offset);\n+\tcopy_le32(trailer.offset, zip_offset);\n+\tcopy_le16(trailer.comment_length, sha1 ? 40 : 0);\n+\n+\twrite_or_die(1, zip_dir, zip_dir_offset);\n+\twrite_or_die(1, &trailer, sizeof(struct zip_dir_trailer));\n+\tif (sha1)\n+\t\twrite_or_die(1, sha1_to_hex(sha1), 40);\n+}\n+\n+static void dos_time(time_t *time, int *dos_date, int *dos_time)\n+{\n+\tstruct tm *t = localtime(time);\n+\n+\t*dos_date = t->tm_mday + (t->tm_mon + 1) * 32 +\n+\t            (t->tm_year + 1900 - 1980) * 512;\n+\t*dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048;\n+}\n+\n+int cmd_zip_tree(int argc, const char **argv, const char *prefix)\n+{\n+\tunsigned char sha1[20];\n+\tstruct tree *tree;\n+\tstruct commit *commit;\n+\ttime_t archive_time;\n+\tchar *base;\n+\tint baselen;\n+\n+\tgit_config(git_default_config);\n+\n+\tif (argc > 1 && argv[1][0] == '-') {\n+\t\tif (isdigit(argv[1][1]) && argv[1][2] == '\\0') {\n+\t\t\tzlib_compression_level = argv[1][1] - '0';\n+\t\t\targc--;\n+\t\t\targv++;\n+\t\t}\n+\t}\n+\n+\tswitch (argc) {\n+\tcase 3:\n+\t\tbase = strdup(argv[2]);\n+\t\tbaselen = strlen(base);\n+\t\tbreak;\n+\tcase 2:\n+\t\tbase = strdup(\"\");\n+\t\tbaselen = 0;\n+\t\tbreak;\n+\tdefault:\n+\t\tusage(zip_tree_usage);\n+\t}\n+\n+\tif (get_sha1(argv[1], sha1))\n+\t\tdie(\"Not a valid object name %s\", argv[1]);\n+\n+\tcommit = lookup_commit_reference_gently(sha1, 1);\n+\tarchive_time = commit ? commit->date : time(NULL);\n+\tdos_time(&archive_time, &zip_date, &zip_time);\n+\n+\tzip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);\n+\tzip_dir_size = ZIP_DIRECTORY_MIN_SIZE;\n+\n+\ttree = parse_tree_indirect(sha1);\n+\tif (!tree)\n+\t\tdie(\"not a tree object\");\n+\n+\tif (baselen > 0) {\n+\t\twrite_zip_entry(tree->object.sha1, \"\", 0, base, 040777, 0);\n+\t\tbase = xrealloc(base, baselen + 1);\n+\t\tbase[baselen] = '/';\n+\t\tbaselen++;\n+\t\tbase[baselen] = '\\0';\n+\t}\n+\tread_tree_recursive(tree, base, baselen, 0, NULL, write_zip_entry);\n+\twrite_zip_trailer(commit ? commit->object.sha1 : NULL);\n+\n+\tfree(zip_dir);\n+\tfree(base);\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex ade58c4..25431d7 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -52,6 +52,7 @@ extern int cmd_show(int argc, const char\n extern int cmd_stripspace(int argc, const char **argv, const char *prefix);\n extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);\n extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);\n+extern int cmd_zip_tree(int argc, const char **argv, const char *prefix);\n extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);\n extern int cmd_update_index(int argc, const char **argv, const char *prefix);\n extern int cmd_update_ref(int argc, const char **argv, const char *prefix);\ndiff --git a/git.c b/git.c\nindex a01d195..ded89fb 100644\n--- a/git.c\n+++ b/git.c\n@@ -263,6 +263,7 @@ static void handle_internal_command(int \n \t\t{ \"stripspace\", cmd_stripspace },\n \t\t{ \"symbolic-ref\", cmd_symbolic_ref, RUN_SETUP },\n \t\t{ \"tar-tree\", cmd_tar_tree, RUN_SETUP },\n+\t\t{ \"zip-tree\", cmd_zip_tree, RUN_SETUP },\n \t\t{ \"unpack-objects\", cmd_unpack_objects, RUN_SETUP },\n \t\t{ \"update-index\", cmd_update_index, RUN_SETUP },\n \t\t{ \"update-ref\", cmd_update_ref, RUN_SETUP },\n"}]}