{"thread":{"id":"329","subject":"[PATCH] GIT: Create tar archives of tree on the fly","startedAt":"2005-04-26T14:42:23Z","lastAt":"2005-04-30T01:22:58Z","messageCount":4,"participants":["Rene Scharfe","Linus Torvalds"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"1738","messageId":"20050426144222.GA12035@lsrfire.ath.cx","threadId":"329","inReplyTo":null,"subject":"[PATCH] GIT: Create tar archives of tree on the fly","fromName":"Rene Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2005-04-26T14:42:23Z","receivedAt":"2005-04-26T14:42:23Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"This patch introduces tar-tree, a tool to generate tar archives out of\ngit repositories.  Basically I took ls-tree and cat-file and melted them\ntogether.  That means tar-tree doesn't create any temporary files, it\njust streams out the archive as it goes.\n\nThis could be useful for the web interface(s) to provide a downloadable\ntarball for any commit or tree object.  For bigger repositories like the\nLinux kernel caching the resulting files might be a good idea,\nthough. :-P\n\nOn my machine it's also a bit faster than directly tarring up the\nchecked out files.  I only ran a few basic checks to make sure the\nperformance is in the same ballpark, YMMV.\n\nExample usage:\n\n  $ tar-tree a2755a80f40e5794ddc20e00f781af9d6320fafb linux-2.6.12-rc3 |\n        bzip2 -9 > linux-2.6.12-rc3.tar.bz2\n\ntar-tree accepts tree IDs and commit IDs.  In the former case all files\nwithin the archive get the current time set as mtime.  Given a commit ID\ntar-tree tries to figure out the commit date and sets mtime of all files\nto that instead.\n\nCurrently the size of a file within the created archive is limited to\n2^33-1.  This could be fixed easily within the archive format (with a\nPax extended header), but size is unsigned long throughout GIT, so this\nwould need to be fixed first.  OTOH I think putting 4GB+ files into a\nGIT archive is insane anyway. :]\n\nPath names are limited to 500 characters at the moment.  This can be\nstretched if the need should arise.\n\nPatch is against d1df5743809614241883ecad51876607cf432034.\n\nSigned-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>\n\ndiff -Nur a/Makefile b/Makefile\n--- a/Makefile\t2005-04-26 03:26:45.000000000 +0200\n+++ b/Makefile\t2005-04-26 08:09:03.000000000 +0200\n@@ -18,7 +18,7 @@\n \tcat-file fsck-cache checkout-cache diff-tree rev-tree show-files \\\n \tcheck-files ls-tree merge-base merge-cache unpack-file git-export \\\n \tdiff-cache convert-cache http-pull rpush rpull rev-list git-mktag \\\n-\tdiff-tree-helper\n+\tdiff-tree-helper tar-tree\n \n all: $(PROG)\n \ndiff -Nur a/tar-tree.c b/tar-tree.c\n--- a/tar-tree.c\t1970-01-01 01:00:00.000000000 +0100\n+++ b/tar-tree.c\t2005-04-26 08:23:05.000000000 +0200\n@@ -0,0 +1,328 @@\n+#include <time.h>\n+#include \"cache.h\"\n+\n+static const char *tar_tree_usage = \"tar-tree <key> [basedir]\";\n+static const char *basedir;\n+static time_t archive_time;\n+\n+struct path_prefix {\n+\tstruct path_prefix *prev;\n+\tconst char *name;\n+};\n+\n+static unsigned long write_out(void *buf, unsigned long size)\n+{\n+\twhile (size > 0) {\n+\t\tlong ret = write(1, buf, size);\n+\t\tif (ret < 0) {\n+\t\t\tif (errno == EAGAIN)\n+\t\t\t\tcontinue;\n+\t\t\t/* Ignore epipe */\n+\t\t\tif (errno == EPIPE)\n+\t\t\t\tbreak;\n+\t\t\tdie(\"tar-tree: %s\", strerror(errno));\n+\t\t} else if (!ret) {\n+\t\t\tdie(\"tar-tree: disk full?\");\n+\t\t}\n+\t\tsize -= ret;\n+\t\tbuf += ret;\n+\t}\n+\treturn size;\n+}\n+\n+static unsigned long write_block(void *buf, unsigned long size)\n+{\n+\tunsigned long ret = write_out(buf, size);\n+\tif (!ret) {\n+\t\tunsigned long slack = 512 - size % 512;\n+\t\tif (slack % 512) {\n+\t\t\tchar padding[511];\n+\t\t\tmemset(padding, 0, slack);\n+\t\t\tret = write_out(padding, slack);\n+\t\t}\n+\t}\n+\treturn ret;\n+}\n+\n+static void append_string(char **p, const char *s)\n+{\n+\tunsigned int len = strlen(s);\n+\tmemcpy(*p, s, len);\n+\t*p += len;\n+}\n+\n+static void append_char(char **p, char c)\n+{\n+\t**p = c;\n+\t*p += 1;\n+}\n+\n+static void append_long(char **p, long n)\n+{\n+\tint len = sprintf(*p, \"%ld\", n);\n+\t*p += len;\n+}\n+\n+static void append_path_prefix(char **buffer, struct path_prefix *prefix)\n+{\n+\tif (!prefix)\n+\t\treturn;\n+\tappend_path_prefix(buffer, prefix->prev);\n+\tappend_string(buffer, prefix->name);\n+\tappend_char(buffer, '/');\n+}\n+\n+static unsigned int path_prefix_len(struct path_prefix *prefix)\n+{\n+\tif (!prefix)\n+\t\treturn 0;\n+\treturn path_prefix_len(prefix->prev) + strlen(prefix->name) + 1;\n+}\n+\n+static void append_path(char **p, int is_dir, const char *basepath,\n+\t\t\tstruct path_prefix *prefix, const char *path)\n+{\n+\tif (basepath) {\n+\t\tappend_string(p, basepath);\n+\t\tappend_char(p, '/');\n+\t}\n+\tappend_path_prefix(p, prefix);\n+\tappend_string(p, path);\n+\tif (is_dir)\n+\t\tappend_char(p, '/');\n+}\n+\n+static unsigned int path_len(int is_dir, const char *basepath,\n+\t\t\t     struct path_prefix *prefix, const char *path)\n+{\n+\tunsigned int len = 0;\n+\tif (basepath)\n+\t\tlen += strlen(basepath) + 1;\n+\tlen += path_prefix_len(prefix) + strlen(path);\n+\tif (is_dir)\n+\t\tlen++;\n+\treturn len;\n+}\n+\n+static void append_extended_header_prefix(char **p, const char *keyword,\n+\t\t\t\t\t  int valuelen)\n+{\n+\tint reclen = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;\n+\tif (reclen > 9)\n+\t\treclen++;\n+\tif (reclen > 99)\n+\t\treclen++;\n+\tif (reclen > 512)\n+\t\tdie(\"tar-tree: extended header too big, wtf?\");\n+\tappend_long(p, reclen);\n+\tappend_char(p, ' ');\n+\tappend_string(p, keyword);\n+\tappend_char(p, '=');\n+}\n+\n+static long write_header(const char *, const char *, struct path_prefix *,\n+\t\t\t const char *, unsigned int, unsigned long);\n+\n+static long write_extended_header(const char *headerfilename, int is_dir,\n+\t\t\t\t  const char *basepath,\n+\t\t\t\t  struct path_prefix *prefix,\n+\t\t\t\t  const char *path, unsigned int namelen)\n+{\n+\tchar records[512], *p;\n+\tunsigned long ret;\n+\n+\tmemset(records, 0, sizeof(records));\n+\tp = records;\n+\tappend_extended_header_prefix(&p, \"path\", namelen);\n+\tappend_path(&p, is_dir, basepath, prefix, path);\n+\tappend_char(&p, '\\n');\n+\tret = write_header(NULL, NULL, NULL, headerfilename, 0100600,\n+\t                   p - records);\n+\tif (!ret)\n+\t\tret = write_out(records, sizeof(records));\n+\treturn ret;\n+}\n+\n+static long write_header(const char *sha1, const char *basepath,\n+\t\t\t struct path_prefix *prefix, const char *path,\n+\t\t\t unsigned int mode, unsigned long size)\n+{\n+\tunsigned int namelen; \n+\tchar *p, header[512];\n+\tunsigned int checksum = 0;\n+\tint i;\n+\n+\tmemset(header, 0, sizeof(header));\n+\n+\tnamelen = path_len(S_ISDIR(mode), basepath, prefix, path);\n+\tif (namelen > 500) {\n+\t\tfprintf(stderr, \"tar-tree: name too log of object %s\\n\",\n+\t\t        sha1_to_hex(sha1));\n+\t\treturn size;\n+\t} else if (namelen > 100) {\n+\t\tunsigned long ret;\n+\t\tchar *sha1_hex = sha1_to_hex(sha1);\n+\t\tchar headerfilename[51];\n+\n+\t\tsprintf(header, \"%s.data\", sha1_hex);\n+\t\tsprintf(headerfilename, \"%s.paxheader\", sha1_hex);\n+\t\tret = write_extended_header(headerfilename, S_ISDIR(mode),\n+\t\t                            basepath, prefix, path, namelen);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t} else {\n+\t\tp = header;\n+\t\tappend_path(&p, S_ISDIR(mode), basepath, prefix, path);\n+\t}\n+\n+\tif (S_ISDIR(mode))\n+\t\tmode |= 0755;\t/* GIT doesn't store permissions of dirs */\n+\tsprintf(&header[100], \"%07o\", mode & 07777);\n+\n+\t/* XXX: should we provide more meaningful info here? */\n+\tsprintf(&header[108], \"%07o\", 0);\t/* uid */\n+\tsprintf(&header[116], \"%07o\", 0);\t/* gid */\n+\tstrncpy(&header[265], \"git\", 31);\t/* uname */\n+\tstrncpy(&header[297], \"git\", 31);\t/* gname */\n+\n+\tsprintf(&header[124], \"%011lo\", S_ISDIR(mode) ? 0 : size);\n+\tsprintf(&header[136], \"%011lo\", archive_time);\n+\n+\t/* typeflag */\n+\tif (!sha1)\n+\t\theader[156] = 'x';\t/* extended header */\n+\telse\n+\t\theader[156] = S_ISDIR(mode) ? '5' : '0';\n+\n+\tstrcpy(&header[257], \"ustar\");\n+\tstrcpy(&header[263], \"00\");\n+\n+\tprintf(&header[329], \"%07o\", 0);\t/* devmajor */\n+\tprintf(&header[337], \"%07o\", 0);\t/* devminor */\n+\n+\tmemset(&header[148], ' ', 8);\n+\tfor (i = 0; i < sizeof(header); i++)\n+\t\tchecksum += header[i];\n+\tsprintf(&header[148], \"%07o\", checksum & 0x1fffff);\n+\n+\treturn write_out(header, sizeof(header));\n+}\n+\n+static unsigned long write_trailer(void)\n+{\n+\tchar block[1024];\n+\tmemset(block, 0, sizeof(block));\n+\treturn write_out(block, sizeof(block));\n+}\n+\n+static void traverse_tree(void *buffer, unsigned long size,\n+\t\t\t  struct path_prefix *prefix)\n+{\n+\tstruct path_prefix this_prefix;\n+\tthis_prefix.prev = prefix;\n+\n+\twhile (size) {\n+\t\tint namelen = strlen(buffer)+1;\n+\t\tvoid *eltbuf;\n+\t\tchar elttype[20];\n+\t\tunsigned long eltsize;\n+\t\tunsigned char *sha1 = buffer + namelen;\n+\t\tchar *path = strchr(buffer, ' ') + 1;\n+\t\tunsigned int mode;\n+\n+\t\tif (size < namelen + 20 || sscanf(buffer, \"%o\", &mode) != 1)\n+\t\t\tdie(\"corrupt 'tree' file\");\n+\t\tbuffer = sha1 + 20;\n+\t\tsize -= namelen + 20;\n+\n+\t\teltbuf = read_sha1_file(sha1, elttype, &eltsize);\n+\t\tif (!eltbuf) {\n+\t\t\terror(\"cannot read %s\", sha1_to_hex(sha1));\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (write_header(sha1, basedir, prefix, path, mode, eltsize))\n+\t\t\texit(0);\n+\t\tif (!strcmp(elttype, \"tree\")) {\n+\t\t\tthis_prefix.name = path;\n+\t\t\ttraverse_tree(eltbuf, eltsize, &this_prefix);\n+\t\t} else if (!strcmp(elttype, \"blob\")) {\n+\t\t\tif (write_block(eltbuf, eltsize))\n+\t\t\t\texit(0);\n+\t\t}\n+\t\tfree(eltbuf);\n+\t}\n+}\n+\n+time_t commit_time(const unsigned char *sha1)\n+{\n+\tchar type[20];\n+\tvoid *buffer;\n+\tunsigned long size;\n+\ttime_t result = 0;\n+\n+\tbuffer = read_sha1_file(sha1, type, &size);\n+\tif (buffer) {\n+\t\tchar *p = buffer;\n+\t\twhile (size > 0) {\n+\t\t\tchar *endp = memchr(p, '\\n', size);\n+\t\t\tif (!endp)\n+\t\t\t\tbreak;\n+\t\t\t*endp = '\\0';\n+\t\t\tif (endp - p > 10 && !memcmp(p, \"committer \", 10)) {\n+\t\t\t\tchar *nump = strrchr(p, ' ');\n+\t\t\t\tif (!nump)\n+\t\t\t\t\tbreak;\n+\t\t\t\t*nump = '\\0';\n+\t\t\t\tnump = strrchr(p, ' ');\n+\t\t\t\tif (!nump)\n+\t\t\t\t\tbreak;\n+\t\t\t\tresult = strtoul(nump, &endp, 10);\n+\t\t\t\tif (*endp != '\\0')\n+\t\t\t\t\tresult = 0;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tsize -= endp - p - 1;\n+\t\t\tp = endp + 1;\n+\t\t}\n+\t}\n+\tfree(buffer);\n+\treturn result;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tunsigned char sha1[20];\n+\tvoid *buffer;\n+\tunsigned long size;\n+\tunsigned char tree_sha1[20];\n+\n+\tswitch (argc) {\n+\tcase 3:\n+\t\tbasedir = argv[2];\n+\t\t/* FALLTHROUGH */\n+\tcase 2:\n+\t\tif (get_sha1_hex(argv[1], sha1) < 0)\n+\t\t\tusage(tar_tree_usage);\n+\t\tbreak;\n+\tdefault:\n+\t\tusage(tar_tree_usage);\n+\t}\n+\n+\tsha1_file_directory = getenv(DB_ENVIRONMENT);\n+\tif (!sha1_file_directory)\n+\t\tsha1_file_directory = DEFAULT_DB_ENVIRONMENT;\n+\n+\tbuffer = read_tree_with_tree_or_commit_sha1(sha1, &size, tree_sha1);\n+\tif (!buffer)\n+\t\tdie(\"unable to read sha1 file\");\n+\tif (memcmp(sha1, tree_sha1, 20))\t/* is sha1 a commit object? */\n+\t\tarchive_time = commit_time(sha1);\n+\tif (!archive_time)\n+\t\tarchive_time = time(NULL);\n+\tif (basedir)\n+\t\twrite_header(\"0\", NULL, NULL, basedir, 040755, 0);\n+\ttraverse_tree(buffer, size, NULL);\n+\tfree(buffer);\n+\twrite_trailer();\n+\treturn 0;\n+}\n"},{"id":"2209","messageId":"Pine.LNX.4.58.0504291522250.18901@ppc970.osdl.org","threadId":"329","inReplyTo":"20050426144222.GA12035@lsrfire.ath.cx","subject":"Re: [PATCH] GIT: Create tar archives of tree on the fly","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2005-04-29T22:26:14Z","receivedAt":"2005-04-29T22:26:14Z","isPatch":true,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\n\nHaving just done the git-0.7.tar.gz file with git-tar-tree, I started \nwondering if there is some nice way to encode the commit version that got \ntarred up into the tar archive itself.\n\nThere are various obvious ways, like creating a fake zero-sized file \ncalled <base>/.git-version-<commit-id>, and maybe that's the right thing \nto do. But maybe the tar archive format (and no, I don't even want to know \ndetails) has some nice way to hide off a keyname even _without_ having to \ncreate a file.\n\nWould people like to have such a file for later? Obviously there would be \na need to suppress it with a command line flag if you don't want it (or \nhave a cmd line flag to enable it in the first place), what do people \nthink? Rene?\n\n\t\tLinus\n"},{"id":"2218","messageId":"4272CD8E.8040706@lsrfire.ath.cx","threadId":"329","inReplyTo":"Pine.LNX.4.58.0504291522250.18901@ppc970.osdl.org","subject":"Re: [PATCH] GIT: Create tar archives of tree on the fly","fromName":"Rene Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2005-04-30T00:13:02Z","receivedAt":"2005-04-30T00:13:02Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Linus Torvalds schrieb:\n> \n> Having just done the git-0.7.tar.gz file with git-tar-tree, I started\n>  wondering if there is some nice way to encode the commit version\n> that got tarred up into the tar archive itself.\n\nThe pax archive format allows for comments; you can store the commit ID\nin a (archive-)global comment.  Archivers are supposed to ignore it and\nGNU tar at least does so.  You can extract the ID with\n\n   $ dd bs=1 skip=523 count=41 2>/dev/null < TARFILE\n\nbecause it would always end up at that position at the start of the archive.\n\nRene\n"},{"id":"2226","messageId":"20050430012258.GA13930@lsrfire.ath.cx","threadId":"329","inReplyTo":"Pine.LNX.4.58.0504291522250.18901@ppc970.osdl.org","subject":"Re: [PATCH] GIT: Create tar archives of tree on the fly","fromName":"Rene Scharfe","fromEmail":"rene.scharfe@lsrfire.ath.cx","sentAt":"2005-04-30T01:22:58Z","receivedAt":"2005-04-30T01:22:58Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"On Fri, Apr 29, 2005 at 03:26:14PM -0700, Linus Torvalds wrote:\n> \n> \n> Having just done the git-0.7.tar.gz file with git-tar-tree, I started \n> wondering if there is some nice way to encode the commit version that got \n> tarred up into the tar archive itself.\n\n... and here is the patch that changes git-tar-tree to add the commit ID\nas a comment in a global pax header to the tar file.  Archivers ignore\nthis field.  A little sample program is included to read the ID from a\npreviously prepared archive.\n\nSigned-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>\n\nWrite commit ID to global extended pax header at the beginning of the tar\nfile, if possible.  get-tar-commit-id.c is an example program to get the\nID back out of such a tar archive.\n\n---\ncommit 716d21c45ba1c329fb88febf4704a4ab629a3933\ntree 72f4d42eac2cd9099a663c16cb8201f90a8ff9c9\nparent 0fc65a4572625405ff6dd9d8c16d835f2b1ebd49\nauthor Rene Scharfe <rene.scharfe@lsrfire.ath.cx> 1114812895 +0200\ncommitter Rene Scharfe <rene.scharfe@lsrfire.ath.cx> 1114812895 +0200\n\nIndex: get-tar-commit-id.c\n===================================================================\n--- /dev/null  (tree:c1546808797f6a3c4e6ae82069cee3dc316fbf24)\n+++ 72f4d42eac2cd9099a663c16cb8201f90a8ff9c9/get-tar-commit-id.c  (mode:100644 sha1:a1a17e53d29136df431d2a128292d7aefefaea41)\n@@ -0,0 +1,27 @@\n+#include <stdio.h>\n+#include <string.h>\n+#include <unistd.h>\n+\n+#define HEADERSIZE\t1024\n+\n+int main(int argc, char **argv)\n+{\n+\tchar buffer[HEADERSIZE];\n+\tssize_t n;\n+\n+\tn = read(0, buffer, HEADERSIZE);\n+\tif (n < HEADERSIZE) {\n+\t\tfprintf(stderr, \"read error\\n\");\n+\t\treturn 3;\n+\t}\n+\tif (buffer[156] != 'g')\n+\t\treturn 1;\n+\tif (memcmp(&buffer[512], \"52 comment=\", 11))\n+\t\treturn 1;\n+\tn = write(1, &buffer[523], 41);\n+\tif (n < 41) {\n+\t\tfprintf(stderr, \"write error\\n\");\n+\t\treturn 2;\n+\t}\n+\treturn 0;\n+}\nIndex: tar-tree.c\n===================================================================\n--- c1546808797f6a3c4e6ae82069cee3dc316fbf24/tar-tree.c  (mode:100644 sha1:5cc7cfef6db1269d81589b82255537fb64ba02fa)\n+++ 72f4d42eac2cd9099a663c16cb8201f90a8ff9c9/tar-tree.c  (mode:100644 sha1:ea7ea91add54532c38dde9e343efe34d56805341)\n@@ -160,7 +160,7 @@\n \treturn len;\n }\n \n-static void write_header(const char *, const char *, struct path_prefix *,\n+static void write_header(const char *, char, const char *, struct path_prefix *,\n                          const char *, unsigned int, unsigned long);\n \n /* stores a pax extended header directly in the block buffer */\n@@ -169,7 +169,7 @@\n                                   struct path_prefix *prefix,\n                                   const char *path, unsigned int namelen)\n {\n-\tchar *records, *p;\n+\tchar *p;\n \tunsigned int size = 1 + 6 + namelen + 1;\n \tif (size > 9)\n \t\tsize++;\n@@ -177,12 +177,10 @@\n \t\tsize++;\n \tif (size > RECORDSIZE)\n \t\tdie(\"tar-tree: extended header too big, wtf?\");\n-\twrite_header(NULL, NULL, NULL, headerfilename, 0100600, size);\n-\n-\trecords = block + offset;\n-\tmemset(records, 0, RECORDSIZE);\n+\twrite_header(NULL, 'x', NULL, NULL, headerfilename, 0100600, size);\n+\tp = block + offset;\n+\tmemset(p, 0, RECORDSIZE);\n \toffset += RECORDSIZE;\n-\tp = records;\n \tappend_long(&p, size);\n \tappend_string(&p, \" path=\");\n \tappend_path(&p, is_dir, basepath, prefix, path);\n@@ -190,8 +188,22 @@\n \twrite_if_needed();\n }\n \n+static void write_global_extended_header(const char *sha1)\n+{\n+\tchar *p;\n+\twrite_header(NULL, 'g', NULL, NULL, \"pax_global_header\", 0, 52);\n+\tp = block + offset;\n+\tmemset(p, 0, RECORDSIZE);\n+\toffset += RECORDSIZE;\n+\tappend_long(&p, 52);\t/* 2 + 9 + 40 + 1 */\n+\tappend_string(&p, \" comment=\");\n+\tappend_string(&p, sha1_to_hex(sha1));\n+\tappend_char(&p, '\\n');\n+\twrite_if_needed();\n+}\n+\n /* stores a ustar header directly in the block buffer */\n-static void write_header(const char *sha1, const char *basepath,\n+static void write_header(const char *sha1, char typeflag, const char *basepath,\n                          struct path_prefix *prefix, const char *path,\n                          unsigned int mode, unsigned long size)\n {\n@@ -236,11 +248,7 @@\n \tsprintf(&header[124], \"%011lo\", S_ISDIR(mode) ? 0 : size);\n \tsprintf(&header[136], \"%011lo\", archive_time);\n \n-\t/* typeflag */\n-\tif (!sha1)\n-\t\theader[156] = 'x';\t/* extended header */\n-\telse\n-\t\theader[156] = S_ISDIR(mode) ? '5' : '0';\n+\theader[156] = typeflag;\n \n \tmemcpy(&header[257], \"ustar\", 6);\n \tmemcpy(&header[263], \"00\", 2);\n@@ -279,7 +287,8 @@\n \t\teltbuf = read_sha1_file(sha1, elttype, &eltsize);\n \t\tif (!eltbuf)\n \t\t\tdie(\"cannot read %s\", sha1_to_hex(sha1));\n-\t\twrite_header(sha1, basedir, prefix, path, mode, eltsize);\n+\t\twrite_header(sha1, S_ISDIR(mode) ? '5' : '0', basedir,\n+\t\t             prefix, path, mode, eltsize);\n \t\tif (!strcmp(elttype, \"tree\")) {\n \t\t\tthis_prefix.name = path;\n \t\t\ttraverse_tree(eltbuf, eltsize, &this_prefix);\n@@ -320,6 +329,7 @@\n int main(int argc, char **argv)\n {\n \tunsigned char sha1[20];\n+\tunsigned char commit_sha1[20];\n \tvoid *buffer;\n \tunsigned long size;\n \n@@ -339,8 +349,9 @@\n \tif (!sha1_file_directory)\n \t\tsha1_file_directory = DEFAULT_DB_ENVIRONMENT;\n \n-\tbuffer = read_object_with_reference(sha1, \"commit\", &size, NULL);\n+\tbuffer = read_object_with_reference(sha1, \"commit\", &size, commit_sha1);\n \tif (buffer) {\n+\t\twrite_global_extended_header(commit_sha1);\n \t\tarchive_time = commit_time(buffer, size);\n \t\tfree(buffer);\n \t}\n@@ -351,7 +362,7 @@\n \tif (!archive_time)\n \t\tarchive_time = time(NULL);\n \tif (basedir)\n-\t\twrite_header(\"0\", NULL, NULL, basedir, 040755, 0);\n+\t\twrite_header(\"0\", '5', NULL, NULL, basedir, 040755, 0);\n \ttraverse_tree(buffer, size, NULL);\n \tfree(buffer);\n \twrite_trailer();\n"}]}