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

[PATCH 2/2] git-archive --format=zip: add symlink support

From
Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
Date
Oct 6, 2006, 23:47 UTC
Message-ID
<4526EB17.1010309@lsrfire.ath.cx>
Add symlink support to ZIP file creation, and a few tests.

This implementation sets the "version made by" field (creator_version) to Unix for symlinks, only; regular files and directories are still marked as originating from FAT/VFAT/NTFS.

Also set "external file attributes" (attr2) to 0 for regular files and 16 for directories (FAT attribute), and to the file mode for symlinks.

We could always set the creator_version to Unix and include the mode, but then Info-ZIP unzip would set the mode of the extracted files to *exactly* the value stored in attr2. The FAT trick makes it apply the umask instead. Note: FAT has no executable bit, so this information is not stored in the ZIP file.

Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
---

I tested this change only using Info-ZIP unzip 5.52 on Linux. I wonder what PKZIP or WinZIP would do with symlink entries.

All calls of git-tar-tree in the test script should be converted to git-archive --format=tar, then the script can be renamed to t5000-archive.sh. Later.

 archive-zip.c       |   13 +++++++++----
 t/t5000-tar-tree.sh |   35 +++++++++++++++++++++++++++++++++++
 2 files changed, 44 insertions(+), 4 deletions(-)
diff --git a/archive-zip.c b/archive-zip.c
index ae74623..d8317f8 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -145,6 +145,7 @@ static int write_zip_entry(const unsigne
 {
 	struct zip_local_header header;
 	struct zip_dir_header dirent;
+	unsigned long attr2;
 	unsigned long compressed_size;
 	unsigned long uncompressed_size;
 	unsigned long crc;
@@ -172,12 +173,16 @@ static int write_zip_entry(const unsigne
 
 	if (S_ISDIR(mode)) {
 		method = 0;
+		attr2 = 16;
 		result = READ_TREE_RECURSIVE;
 		out = NULL;
 		uncompressed_size = 0;
 		compressed_size = 0;
-	} else if (S_ISREG(mode)) {
-		method = zlib_compression_level == 0 ? 0 : 8;
+	} else if (S_ISREG(mode) || S_ISLNK(mode)) {
+		method = 0;
+		attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0;
+		if (S_ISREG(mode) && zlib_compression_level != 0)
+			method = 8;
 		result = 0;
 		buffer = read_sha1_file(sha1, type, &size);
 		if (!buffer)
@@ -213,7 +218,7 @@ static int write_zip_entry(const unsigne
 	}
 
 	copy_le32(dirent.magic, 0x02014b50);
-	copy_le16(dirent.creator_version, 0);
+	copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0);
 	copy_le16(dirent.version, 10);
 	copy_le16(dirent.flags, 0);
 	copy_le16(dirent.compression_method, method);
@@ -227,7 +232,7 @@ static int write_zip_entry(const unsigne
 	copy_le16(dirent.comment_length, 0);
 	copy_le16(dirent.disk, 0);
 	copy_le16(dirent.attr1, 0);
-	copy_le32(dirent.attr2, 0);
+	copy_le32(dirent.attr2, attr2);
 	copy_le32(dirent.offset, zip_offset);
 	memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header));
 	zip_dir_offset += sizeof(struct zip_dir_header);
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 278eb66..cf08e92 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -26,6 +26,7 @@ commit id embedding:
 
 . ./test-lib.sh
 TAR=${TAR:-tar}
+UNZIP=${UNZIP:-unzip}
 
 test_expect_success \
     'populate workdir' \
@@ -95,4 +96,38 @@ test_expect_success \
     'validate file contents with prefix' \
     'diff -r a c/prefix/a'
 
+test_expect_success \
+    'git-archive --format=zip' \
+    'git-archive --format=zip HEAD >d.zip'
+
+test_expect_success \
+    'extract ZIP archive' \
+    '(mkdir d && cd d && $UNZIP ../d.zip)'
+
+test_expect_success \
+    'validate filenames' \
+    '(cd d/a && find .) | sort >d.lst &&
+     diff a.lst d.lst'
+
+test_expect_success \
+    'validate file contents' \
+    'diff -r a d/a'
+
+test_expect_success \
+    'git-archive --format=zip with prefix' \
+    'git-archive --format=zip --prefix=prefix/ HEAD >e.zip'
+
+test_expect_success \
+    'extract ZIP archive with prefix' \
+    '(mkdir e && cd e && $UNZIP ../e.zip)'
+
+test_expect_success \
+    'validate filenames with prefix' \
+    '(cd e/prefix/a && find .) | sort >e.lst &&
+     diff a.lst e.lst'
+
+test_expect_success \
+    'validate file contents with prefix' \
+    'diff -r a e/prefix/a'
+
 test_done
-- 
1.4.2.3.gf59615
Message 1 of 1 in “git-archive --format=zip: add symlink support”
  1. 2/2 git-archive --format=zip: add symlink supportRene Scharfe, Oct 6, 2006

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.