From 7b95089c0f59a25bb1c506b6962eb64412c585eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=1B=2Cbi=1B=28B=20Scharfe?= <rene.scharfe@lsrfire.ath.cx>
Date: Mon, 3 Sep 2007 20:06:36 +0200
Subject: [PATCH 01/18] Export format_commit_message()

Drop the parameter "msg" of format_commit_message() (as it can be
inferred from the parameter "commit"), add a parameter "template"
in order to avoid accessing the static variable user_format
directly and export the result.

Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 commit.c | 9 +++++----
 commit.h | 1 +
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/commit.c b/commit.c
index dc5a0643f3..99f65cee0e 100644
--- a/commit.c
+++ b/commit.c
@@ -787,8 +787,8 @@ static void fill_person(struct interp *table, const char *msg, int len)
 	interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
 }
 
-static long format_commit_message(const struct commit *commit,
-		const char *msg, char **buf_p, unsigned long *space_p)
+long format_commit_message(const struct commit *commit, const void *format,
+                           char **buf_p, unsigned long *space_p)
 {
 	struct interp table[] = {
 		{ "%H" },	/* commit hash */
@@ -843,6 +843,7 @@ static long format_commit_message(const struct commit *commit,
 	char parents[1024];
 	int i;
 	enum { HEADER, SUBJECT, BODY } state;
+	const char *msg = commit->buffer;
 
 	if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
 		die("invalid interp table!");
@@ -924,7 +925,7 @@ static long format_commit_message(const struct commit *commit,
 		char *buf = *buf_p;
 		unsigned long space = *space_p;
 
-		space = interpolate(buf, space, user_format,
+		space = interpolate(buf, space, format,
 				    table, ARRAY_SIZE(table));
 		if (!space)
 			break;
@@ -1165,7 +1166,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
 	char *buf;
 
 	if (fmt == CMIT_FMT_USERFORMAT)
-		return format_commit_message(commit, msg, buf_p, space_p);
+		return format_commit_message(commit, user_format, buf_p, space_p);
 
 	encoding = (git_log_output_encoding
 		    ? git_log_output_encoding
diff --git a/commit.h b/commit.h
index 467872eeca..a8d76616d2 100644
--- a/commit.h
+++ b/commit.h
@@ -61,6 +61,7 @@ enum cmit_fmt {
 };
 
 extern enum cmit_fmt get_commit_format(const char *arg);
+extern long format_commit_message(const struct commit *commit, const void *template, char **buf_p, unsigned long *space_p);
 extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char **buf_p, unsigned long *space_p, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
 
 /** Removes the first commit from a list sorted by date, and adds all

From 8460b2fcd45668d91567c36a22ea4f1b14ba133d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= <rene.scharfe@lsrfire.ath.cx>
Date: Mon, 3 Sep 2007 20:07:01 +0200
Subject: [PATCH 02/18] archive: specfile support (--pretty=format: in archive
 files)

Add support for a new attribute, specfile.  Files marked as being
specfiles are expanded by git-archive when they are written to an
archive.  It has no effect on worktree files.  The same placeholders
as those for the option --pretty=format: of git-log et al. can be
used.

The attribute is useful for creating auto-updating specfiles.  It is
limited by the underlying function format_commit_message(), though.
E.g. currently there is no placeholder for git-describe like output,
and expanded specfiles can't contain NUL bytes.  That can be fixed
in format_commit_message() later and will then benefit users of
git-log, too.

Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/gitattributes.txt | 14 +++++++++
 archive-tar.c                   |  5 ++-
 archive-zip.c                   |  5 ++-
 archive.h                       |  3 ++
 builtin-archive.c               | 55 ++++++++++++++++++++++++++++++++-
 t/t5000-tar-tree.sh             | 19 ++++++++++++
 6 files changed, 98 insertions(+), 3 deletions(-)

diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 46f9d591aa..47a621b733 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -421,6 +421,20 @@ frotz	unspecified
 ----------------------------------------------------------------
 
 
+Creating an archive
+~~~~~~~~~~~~~~~~~~~
+
+`specfile`
+^^^^^^^^^^
+
+If the attribute `specfile` is set for a file then git will expand
+several placeholders when adding this file to an archive.  The
+expansion depends on the availability of a commit ID, i.e. if
+gitlink:git-archive[1] has been given a tree instead of a commit or a
+tag then no replacement will be done.  The placeholders are the same
+as those for the option `--pretty=format:` of gitlink:git-log[1].
+
+
 GIT
 ---
 Part of the gitlink:git[7] suite
diff --git a/archive-tar.c b/archive-tar.c
index 66fe3e375b..c0d95dab0d 100644
--- a/archive-tar.c
+++ b/archive-tar.c
@@ -17,6 +17,7 @@ static unsigned long offset;
 static time_t archive_time;
 static int tar_umask = 002;
 static int verbose;
+static const struct commit *commit;
 
 /* writes out the whole block, but only if it is full */
 static void write_if_needed(void)
@@ -285,7 +286,8 @@ static int write_tar_entry(const unsigned char *sha1,
 		buffer = NULL;
 		size = 0;
 	} else {
-		buffer = convert_sha1_file(path.buf, sha1, mode, &type, &size);
+		buffer = sha1_file_to_archive(path.buf, sha1, mode, &type,
+		                              &size, commit);
 		if (!buffer)
 			die("cannot read %s", sha1_to_hex(sha1));
 	}
@@ -304,6 +306,7 @@ int write_tar_archive(struct archiver_args *args)
 
 	archive_time = args->time;
 	verbose = args->verbose;
+	commit = args->commit;
 
 	if (args->commit_sha1)
 		write_global_extended_header(args->commit_sha1);
diff --git a/archive-zip.c b/archive-zip.c
index 444e1623db..f63dff3834 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -12,6 +12,7 @@
 static int verbose;
 static int zip_date;
 static int zip_time;
+static const struct commit *commit;
 
 static unsigned char *zip_dir;
 static unsigned int zip_dir_size;
@@ -195,7 +196,8 @@ static int write_zip_entry(const unsigned char *sha1,
 		if (S_ISREG(mode) && zlib_compression_level != 0)
 			method = 8;
 		result = 0;
-		buffer = convert_sha1_file(path, sha1, mode, &type, &size);
+		buffer = sha1_file_to_archive(path, sha1, mode, &type, &size,
+		                              commit);
 		if (!buffer)
 			die("cannot read %s", sha1_to_hex(sha1));
 		crc = crc32(crc, buffer, size);
@@ -316,6 +318,7 @@ int write_zip_archive(struct archiver_args *args)
 	zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);
 	zip_dir_size = ZIP_DIRECTORY_MIN_SIZE;
 	verbose = args->verbose;
+	commit = args->commit;
 
 	if (args->base && plen > 0 && args->base[plen - 1] == '/') {
 		char *base = xstrdup(args->base);
diff --git a/archive.h b/archive.h
index 6838dc788f..5791e657e9 100644
--- a/archive.h
+++ b/archive.h
@@ -8,6 +8,7 @@ struct archiver_args {
 	const char *base;
 	struct tree *tree;
 	const unsigned char *commit_sha1;
+	const struct commit *commit;
 	time_t time;
 	const char **pathspec;
 	unsigned int verbose : 1;
@@ -42,4 +43,6 @@ extern int write_tar_archive(struct archiver_args *);
 extern int write_zip_archive(struct archiver_args *);
 extern void *parse_extra_zip_args(int argc, const char **argv);
 
+extern void *sha1_file_to_archive(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size, const struct commit *commit);
+
 #endif	/* ARCHIVE_H */
diff --git a/builtin-archive.c b/builtin-archive.c
index 187491bc17..faccce302a 100644
--- a/builtin-archive.c
+++ b/builtin-archive.c
@@ -10,6 +10,7 @@
 #include "exec_cmd.h"
 #include "pkt-line.h"
 #include "sideband.h"
+#include "attr.h"
 
 static const char archive_usage[] = \
 "git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]";
@@ -80,6 +81,57 @@ static int run_remote_archiver(const char *remote, int argc,
 	return !!rv;
 }
 
+static void *convert_to_archive(const char *path,
+                                const void *src, unsigned long *sizep,
+                                const struct commit *commit)
+{
+	static struct git_attr *attr_specfile;
+	struct git_attr_check check[1];
+	char *interpolated = NULL;
+	unsigned long allocated = 0;
+
+	if (!commit)
+		return NULL;
+
+        if (!attr_specfile)
+                attr_specfile = git_attr("specfile", 8);
+
+	check[0].attr = attr_specfile;
+	if (git_checkattr(path, ARRAY_SIZE(check), check))
+		return NULL;
+	if (!ATTR_TRUE(check[0].value))
+		return NULL;
+
+	*sizep = format_commit_message(commit, src, &interpolated, &allocated);
+
+	return interpolated;
+}
+
+void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
+                           unsigned int mode, enum object_type *type,
+                           unsigned long *size,
+                           const struct commit *commit)
+{
+	void *buffer, *converted;
+
+	buffer = read_sha1_file(sha1, type, size);
+	if (buffer && S_ISREG(mode)) {
+		converted = convert_to_working_tree(path, buffer, size);
+		if (converted) {
+			free(buffer);
+			buffer = converted;
+		}
+
+		converted = convert_to_archive(path, buffer, size, commit);
+		if (converted) {
+			free(buffer);
+			buffer = converted;
+		}
+	}
+
+	return buffer;
+}
+
 static int init_archiver(const char *name, struct archiver *ar)
 {
 	int rv = -1, i;
@@ -109,7 +161,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,
 	const unsigned char *commit_sha1;
 	time_t archive_time;
 	struct tree *tree;
-	struct commit *commit;
+	const struct commit *commit;
 	unsigned char sha1[20];
 
 	if (get_sha1(name, sha1))
@@ -142,6 +194,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,
 	}
 	ar_args->tree = tree;
 	ar_args->commit_sha1 = commit_sha1;
+	ar_args->commit = commit;
 	ar_args->time = archive_time;
 }
 
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 1a4c53a031..3d5d01be78 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -28,12 +28,15 @@ commit id embedding:
 TAR=${TAR:-tar}
 UNZIP=${UNZIP:-unzip}
 
+SPECFILEFORMAT=%H%n
+
 test_expect_success \
     'populate workdir' \
     'mkdir a b c &&
      echo simple textfile >a/a &&
      mkdir a/bin &&
      cp /bin/sh a/bin &&
+     printf "%s" "$SPECFILEFORMAT" >a/specfile &&
      ln -s a a/l1 &&
      (p=long_path_to_a_file && cd a &&
       for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&
@@ -104,6 +107,22 @@ test_expect_success \
     'validate file contents with prefix' \
     'diff -r a c/prefix/a'
 
+test_expect_success \
+    'create an archive with a specfile' \
+    'echo specfile specfile >a/.gitattributes &&
+     git archive HEAD >f.tar &&
+     rm a/.gitattributes'
+
+test_expect_success \
+    'extract specfile' \
+    '(mkdir f && cd f && $TAR xf -) <f.tar'
+
+test_expect_success \
+     'validate specfile contents' \
+     'git log --max-count=1 "--pretty=format:$SPECFILEFORMAT" HEAD \
+      >f/a/specfile.expected &&
+      diff f/a/specfile.expected f/a/specfile'
+
 test_expect_success \
     'git archive --format=zip' \
     'git archive --format=zip HEAD >d.zip'

From 89b4256cfbb8d878cc4cd1104ac4865ba1f2a58e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= <rene.scharfe@lsrfire.ath.cx>
Date: Mon, 3 Sep 2007 20:08:01 +0200
Subject: [PATCH 03/18] Remove unused function convert_sha1_file()

convert_sha1_file() became unused by the previous patch -- remove it.

Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 cache.h   |  1 -
 convert.c | 15 ---------------
 2 files changed, 16 deletions(-)

diff --git a/cache.h b/cache.h
index 70abbd59bf..493983cbae 100644
--- a/cache.h
+++ b/cache.h
@@ -592,7 +592,6 @@ extern void trace_argv_printf(const char **argv, int count, const char *format,
 /* convert.c */
 extern char *convert_to_git(const char *path, const char *src, unsigned long *sizep);
 extern char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep);
-extern void *convert_sha1_file(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size);
 
 /* diff.c */
 extern int diff_auto_refresh_index;
diff --git a/convert.c b/convert.c
index 21908b1039..d77c8eb8b2 100644
--- a/convert.c
+++ b/convert.c
@@ -687,18 +687,3 @@ char *convert_to_working_tree(const char *path, const char *src, unsigned long *
 
 	return buf;
 }
-
-void *convert_sha1_file(const char *path, const unsigned char *sha1,
-                        unsigned int mode, enum object_type *type,
-                        unsigned long *size)
-{
-	void *buffer = read_sha1_file(sha1, type, size);
-	if (S_ISREG(mode) && buffer) {
-		void *converted = convert_to_working_tree(path, buffer, size);
-		if (converted) {
-			free(buffer);
-			buffer = converted;
-		}
-	}
-	return buffer;
-}

From b21b9f1de313acb5550c070911ae58c735cdb451 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= <rene.scharfe@lsrfire.ath.cx>
Date: Fri, 7 Sep 2007 00:32:54 +0200
Subject: [PATCH 04/18] add memmem()

memmem() is a nice GNU extension for searching a length limited string
in another one.

This compat version is based on the version found in glibc 2.2 (GPL 2);
I only removed the optimization of checking the first char by hand, and
generally tried to keep the code simple.  We can add it back if memcmp
shows up high in a profile, but for now I prefer to keep it (almost
trivially) simple.

Since I don't really know which platforms beside those with a glibc
have their own memmem(), I used a heuristic: if NO_STRCASESTR is set,
then NO_MEMMEM is set, too.

Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Makefile          | 11 +++++++++++
 compat/memmem.c   | 29 +++++++++++++++++++++++++++++
 git-compat-util.h |  6 ++++++
 3 files changed, 46 insertions(+)
 create mode 100644 compat/memmem.c

diff --git a/Makefile b/Makefile
index 51af531c9a..bae073fe37 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,8 @@ all::
 #
 # Define NO_STRCASESTR if you don't have strcasestr.
 #
+# Define NO_MEMMEM if you don't have memmem.
+#
 # Define NO_STRLCPY if you don't have strlcpy.
 #
 # Define NO_STRTOUMAX if you don't have strtoumax in the C library.
@@ -402,6 +404,7 @@ ifeq ($(uname_S),SunOS)
 	NEEDS_NSL = YesPlease
 	SHELL_PATH = /bin/bash
 	NO_STRCASESTR = YesPlease
+	NO_MEMMEM = YesPlease
 	NO_HSTRERROR = YesPlease
 	ifeq ($(uname_R),5.8)
 		NEEDS_LIBICONV = YesPlease
@@ -424,6 +427,7 @@ ifeq ($(uname_O),Cygwin)
 	NO_D_TYPE_IN_DIRENT = YesPlease
 	NO_D_INO_IN_DIRENT = YesPlease
 	NO_STRCASESTR = YesPlease
+	NO_MEMMEM = YesPlease
 	NO_SYMLINK_HEAD = YesPlease
 	NEEDS_LIBICONV = YesPlease
 	NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
@@ -442,6 +446,7 @@ ifeq ($(uname_S),FreeBSD)
 endif
 ifeq ($(uname_S),OpenBSD)
 	NO_STRCASESTR = YesPlease
+	NO_MEMMEM = YesPlease
 	NEEDS_LIBICONV = YesPlease
 	BASIC_CFLAGS += -I/usr/local/include
 	BASIC_LDFLAGS += -L/usr/local/lib
@@ -456,6 +461,7 @@ ifeq ($(uname_S),NetBSD)
 endif
 ifeq ($(uname_S),AIX)
 	NO_STRCASESTR=YesPlease
+	NO_MEMMEM = YesPlease
 	NO_STRLCPY = YesPlease
 	NEEDS_LIBICONV=YesPlease
 endif
@@ -467,6 +473,7 @@ ifeq ($(uname_S),IRIX64)
 	NO_IPV6=YesPlease
 	NO_SETENV=YesPlease
 	NO_STRCASESTR=YesPlease
+	NO_MEMMEM = YesPlease
 	NO_STRLCPY = YesPlease
 	NO_SOCKADDR_STORAGE=YesPlease
 	SHELL_PATH=/usr/gnu/bin/bash
@@ -661,6 +668,10 @@ ifdef NO_HSTRERROR
 	COMPAT_CFLAGS += -DNO_HSTRERROR
 	COMPAT_OBJS += compat/hstrerror.o
 endif
+ifdef NO_MEMMEM
+	COMPAT_CFLAGS += -DNO_MEMMEM
+	COMPAT_OBJS += compat/memmem.o
+endif
 
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
diff --git a/compat/memmem.c b/compat/memmem.c
new file mode 100644
index 0000000000..cd0d877364
--- /dev/null
+++ b/compat/memmem.c
@@ -0,0 +1,29 @@
+#include "../git-compat-util.h"
+
+void *gitmemmem(const void *haystack, size_t haystack_len,
+                const void *needle, size_t needle_len)
+{
+	const char *begin = haystack;
+	const char *last_possible = begin + haystack_len - needle_len;
+
+	/*
+	 * The first occurrence of the empty string is deemed to occur at
+	 * the beginning of the string.
+	 */
+	if (needle_len == 0)
+		return (void *)begin;
+
+	/*
+	 * Sanity check, otherwise the loop might search through the whole
+	 * memory.
+	 */
+	if (haystack_len < needle_len)
+		return NULL;
+
+	for (; begin <= last_possible; begin++) {
+		if (!memcmp(begin, needle, needle_len))
+			return (void *)begin;
+	}
+
+	return NULL;
+}
diff --git a/git-compat-util.h b/git-compat-util.h
index ca0a597a28..1bfbdeb94f 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -172,6 +172,12 @@ extern uintmax_t gitstrtoumax(const char *, char **, int);
 extern const char *githstrerror(int herror);
 #endif
 
+#ifdef NO_MEMMEM
+#define memmem gitmemmem
+void *gitmemmem(const void *haystack, size_t haystacklen,
+                const void *needle, size_t needlelen);
+#endif
+
 extern void release_pack_memory(size_t, int);
 
 static inline char* xstrdup(const char *str)

From df4a394f91d7d107c2a57e6c1df3638517cab54f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= <rene.scharfe@lsrfire.ath.cx>
Date: Fri, 7 Sep 2007 00:34:06 +0200
Subject: [PATCH 05/18] archive: specfile syntax change: "$Format:%PLCHLDR$"
 instead of just "%PLCHLDR" (take 2)

As suggested by Johannes, --pretty=format: placeholders in specfiles
need to be wrapped in $Format:...$ now.  This syntax change restricts
the expansion of placeholders and makes it easier to use with files
that contain non-placeholder percent signs.

Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/gitattributes.txt |  5 +++-
 builtin-archive.c               | 52 +++++++++++++++++++++++++++++----
 t/t5000-tar-tree.sh             |  4 +--
 3 files changed, 53 insertions(+), 8 deletions(-)

diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 47a621b733..37b3be8b72 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -432,7 +432,10 @@ several placeholders when adding this file to an archive.  The
 expansion depends on the availability of a commit ID, i.e. if
 gitlink:git-archive[1] has been given a tree instead of a commit or a
 tag then no replacement will be done.  The placeholders are the same
-as those for the option `--pretty=format:` of gitlink:git-log[1].
+as those for the option `--pretty=format:` of gitlink:git-log[1],
+except that they need to be wrapped like this: `$Format:PLACEHOLDERS$`
+in the file.  E.g. the string `$Format:%H$` will be replaced by the
+commit hash.
 
 
 GIT
diff --git a/builtin-archive.c b/builtin-archive.c
index faccce302a..65bf9cbec1 100644
--- a/builtin-archive.c
+++ b/builtin-archive.c
@@ -81,14 +81,58 @@ static int run_remote_archiver(const char *remote, int argc,
 	return !!rv;
 }
 
+static void *format_specfile(const struct commit *commit, const char *format,
+                             unsigned long *sizep)
+{
+	unsigned long len = *sizep, result_len = 0;
+	const char *a = format;
+	char *result = NULL;
+
+	for (;;) {
+		const char *b, *c;
+		char *fmt, *formatted = NULL;
+		unsigned long a_len, fmt_len, formatted_len, allocated = 0;
+
+		b = memmem(a, len, "$Format:", 8);
+		if (!b || a + len < b + 9)
+			break;
+		c = memchr(b + 8, '$', len - 8);
+		if (!c)
+			break;
+
+		a_len = b - a;
+		fmt_len = c - b - 8;
+		fmt = xmalloc(fmt_len + 1);
+		memcpy(fmt, b + 8, fmt_len);
+		fmt[fmt_len] = '\0';
+
+		formatted_len = format_commit_message(commit, fmt, &formatted,
+		                                      &allocated);
+		result = xrealloc(result, result_len + a_len + formatted_len);
+		memcpy(result + result_len, a, a_len);
+		memcpy(result + result_len + a_len, formatted, formatted_len);
+		result_len += a_len + formatted_len;
+		len -= c + 1 - a;
+		a = c + 1;
+	}
+
+	if (result && len) {
+		result = xrealloc(result, result_len + len);
+		memcpy(result + result_len, a, len);
+		result_len += len;
+	}
+
+	*sizep = result_len;
+
+	return result;
+}
+
 static void *convert_to_archive(const char *path,
                                 const void *src, unsigned long *sizep,
                                 const struct commit *commit)
 {
 	static struct git_attr *attr_specfile;
 	struct git_attr_check check[1];
-	char *interpolated = NULL;
-	unsigned long allocated = 0;
 
 	if (!commit)
 		return NULL;
@@ -102,9 +146,7 @@ static void *convert_to_archive(const char *path,
 	if (!ATTR_TRUE(check[0].value))
 		return NULL;
 
-	*sizep = format_commit_message(commit, src, &interpolated, &allocated);
-
-	return interpolated;
+	return format_specfile(commit, src, sizep);
 }
 
 void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 3d5d01be78..6e89e07272 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -36,7 +36,7 @@ test_expect_success \
      echo simple textfile >a/a &&
      mkdir a/bin &&
      cp /bin/sh a/bin &&
-     printf "%s" "$SPECFILEFORMAT" >a/specfile &&
+     printf "A\$Format:%s\$O" "$SPECFILEFORMAT" >a/specfile &&
      ln -s a a/l1 &&
      (p=long_path_to_a_file && cd a &&
       for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&
@@ -119,7 +119,7 @@ test_expect_success \
 
 test_expect_success \
      'validate specfile contents' \
-     'git log --max-count=1 "--pretty=format:$SPECFILEFORMAT" HEAD \
+     'git log --max-count=1 "--pretty=format:A${SPECFILEFORMAT}O" HEAD \
       >f/a/specfile.expected &&
       diff f/a/specfile.expected f/a/specfile'
 

From 38c9c9b798a0df875968ae49d699298131dfa24d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= <rene.scharfe@lsrfire.ath.cx>
Date: Thu, 6 Sep 2007 18:51:11 +0200
Subject: [PATCH 06/18] archive: rename attribute specfile to export-subst

As suggested by Junio and Johannes, change the name of the former
attribute specfile to export-subst to indicate its function rather
than purpose and to make clear that it is not applied to working tree
files.

Signed-off-by: Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/gitattributes.txt |  6 +++---
 builtin-archive.c               | 14 +++++++-------
 t/t5000-tar-tree.sh             | 18 +++++++++---------
 3 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 37b3be8b72..d0e951ee6f 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -424,10 +424,10 @@ frotz	unspecified
 Creating an archive
 ~~~~~~~~~~~~~~~~~~~
 
-`specfile`
-^^^^^^^^^^
+`export-subst`
+^^^^^^^^^^^^^^
 
-If the attribute `specfile` is set for a file then git will expand
+If the attribute `export-subst` is set for a file then git will expand
 several placeholders when adding this file to an archive.  The
 expansion depends on the availability of a commit ID, i.e. if
 gitlink:git-archive[1] has been given a tree instead of a commit or a
diff --git a/builtin-archive.c b/builtin-archive.c
index 65bf9cbec1..e221f115f9 100644
--- a/builtin-archive.c
+++ b/builtin-archive.c
@@ -81,8 +81,8 @@ static int run_remote_archiver(const char *remote, int argc,
 	return !!rv;
 }
 
-static void *format_specfile(const struct commit *commit, const char *format,
-                             unsigned long *sizep)
+static void *format_subst(const struct commit *commit, const char *format,
+                          unsigned long *sizep)
 {
 	unsigned long len = *sizep, result_len = 0;
 	const char *a = format;
@@ -131,22 +131,22 @@ static void *convert_to_archive(const char *path,
                                 const void *src, unsigned long *sizep,
                                 const struct commit *commit)
 {
-	static struct git_attr *attr_specfile;
+	static struct git_attr *attr_export_subst;
 	struct git_attr_check check[1];
 
 	if (!commit)
 		return NULL;
 
-        if (!attr_specfile)
-                attr_specfile = git_attr("specfile", 8);
+        if (!attr_export_subst)
+                attr_export_subst = git_attr("export-subst", 12);
 
-	check[0].attr = attr_specfile;
+	check[0].attr = attr_export_subst;
 	if (git_checkattr(path, ARRAY_SIZE(check), check))
 		return NULL;
 	if (!ATTR_TRUE(check[0].value))
 		return NULL;
 
-	return format_specfile(commit, src, sizep);
+	return format_subst(commit, src, sizep);
 }
 
 void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 6e89e07272..42e28ab758 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -28,7 +28,7 @@ commit id embedding:
 TAR=${TAR:-tar}
 UNZIP=${UNZIP:-unzip}
 
-SPECFILEFORMAT=%H%n
+SUBSTFORMAT=%H%n
 
 test_expect_success \
     'populate workdir' \
@@ -36,7 +36,7 @@ test_expect_success \
      echo simple textfile >a/a &&
      mkdir a/bin &&
      cp /bin/sh a/bin &&
-     printf "A\$Format:%s\$O" "$SPECFILEFORMAT" >a/specfile &&
+     printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile &&
      ln -s a a/l1 &&
      (p=long_path_to_a_file && cd a &&
       for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&
@@ -108,20 +108,20 @@ test_expect_success \
     'diff -r a c/prefix/a'
 
 test_expect_success \
-    'create an archive with a specfile' \
-    'echo specfile specfile >a/.gitattributes &&
+    'create an archive with a substfile' \
+    'echo substfile export-subst >a/.gitattributes &&
      git archive HEAD >f.tar &&
      rm a/.gitattributes'
 
 test_expect_success \
-    'extract specfile' \
+    'extract substfile' \
     '(mkdir f && cd f && $TAR xf -) <f.tar'
 
 test_expect_success \
-     'validate specfile contents' \
-     'git log --max-count=1 "--pretty=format:A${SPECFILEFORMAT}O" HEAD \
-      >f/a/specfile.expected &&
-      diff f/a/specfile.expected f/a/specfile'
+     'validate substfile contents' \
+     'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
+      >f/a/substfile.expected &&
+      diff f/a/substfile.expected f/a/substfile'
 
 test_expect_success \
     'git archive --format=zip' \

From 451e593181c554b06f1ce292b4233d396a355753 Mon Sep 17 00:00:00 2001
From: Mike Ralphson <mike@abacus.co.uk>
Date: Fri, 7 Sep 2007 17:43:37 +0100
Subject: [PATCH 07/18] Documentation / grammer nit

If we're counting, a smaller number is 'fewer' not 'less'

Signed-off-by: Mike Ralphson <mike@abacus.co.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-clone.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 227f092e26..253f4f03c5 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -68,7 +68,7 @@ OPTIONS
 	automatically setup .git/objects/info/alternates to
 	obtain objects from the reference repository.  Using
 	an already existing repository as an alternate will
-	require less objects to be copied from the repository
+	require fewer objects to be copied from the repository
 	being cloned, reducing network and local storage costs.
 
 --quiet::

From 059f446d57d51fbacdace3fbadf2414916c201dd Mon Sep 17 00:00:00 2001
From: "J. Bruce Fields" <bfields@citi.umich.edu>
Date: Fri, 7 Sep 2007 10:20:50 -0400
Subject: [PATCH 08/18] git-rebase: support --whitespace=<option>

Pass --whitespace=<option> to git-apply.  Since git-apply and git-am
expect this, I'm always surprised when I try to give it to git-rebase
and it doesn't work.

Signed-off-by: J. Bruce Fields <bfields@citi.umich.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-rebase.txt | 9 +++++++--
 git-rebase.sh                | 5 ++++-
 2 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 61b1810dba..0858fa8a63 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -8,8 +8,9 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] [-C<n>]
-	[-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
+	[-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
+	[--onto <newbase>] <upstream> [<branch>]
 'git-rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -209,6 +210,10 @@ OPTIONS
 	context exist they all must match.  By default no context is
 	ever ignored.
 
+--whitespace=<nowarn|warn|error|error-all|strip>::
+	This flag is passed to the `git-apply` program
+	(see gitlink:git-apply[1]) that applies the patch.
+
 -i, \--interactive::
 	Make a list of the commits which are about to be rebased.  Let the
 	user edit that list before rebasing.  This mode can also be used to
diff --git a/git-rebase.sh b/git-rebase.sh
index 3bd66b0a04..52c686fc8c 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -216,8 +216,11 @@ do
 	-v|--verbose)
 		verbose=t
 		;;
+	--whitespace=*)
+		git_am_opt="$git_am_opt $1"
+		;;
 	-C*)
-		git_am_opt=$1
+		git_am_opt="$git_am_opt $1"
 		shift
 		;;
 	-*)

From d05ec5a064e402475ab48617213f2a619a2fabc5 Mon Sep 17 00:00:00 2001
From: "J. Bruce Fields" <bfields@citi.umich.edu>
Date: Fri, 7 Sep 2007 10:20:51 -0400
Subject: [PATCH 09/18] git-rebase: fix -C option

The extra shift here causes failure to parse any commandline including
the -C option.

Signed-off-by: J. Bruce Fields <bfields@citi.umich.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 git-rebase.sh | 1 -
 1 file changed, 1 deletion(-)

diff --git a/git-rebase.sh b/git-rebase.sh
index 52c686fc8c..c9942f2400 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -221,7 +221,6 @@ do
 		;;
 	-C*)
 		git_am_opt="$git_am_opt $1"
-		shift
 		;;
 	-*)
 		usage

From ee834cf0c7de68557bc5c30552fce3e55f52e109 Mon Sep 17 00:00:00 2001
From: Michael Smith <msmith@cbnco.com>
Date: Fri, 7 Sep 2007 17:35:07 -0400
Subject: [PATCH 10/18] (cvs|svn)import: Ask git-tag to overwrite old tags.

If the tag was moved in CVS or SVN history, it will be moved in the
imported history as well. Tag history is not tracked.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 git-cvsimport.perl | 2 +-
 git-svnimport.perl | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/git-cvsimport.perl b/git-cvsimport.perl
index ba23eb8eeb..2954fb846e 100755
--- a/git-cvsimport.perl
+++ b/git-cvsimport.perl
@@ -779,7 +779,7 @@ sub commit {
 		$xtag =~ tr/_/\./ if ( $opt_u );
 		$xtag =~ s/[\/]/$opt_s/g;
 
-		system('git-tag', $xtag, $cid) == 0
+		system('git-tag', '-f', $xtag, $cid) == 0
 			or die "Cannot create tag $xtag: $!\n";
 
 		print "Created tag '$xtag' on '$branch'\n" if $opt_v;
diff --git a/git-svnimport.perl b/git-svnimport.perl
index 8c17fb5ae2..d3ad5b904f 100755
--- a/git-svnimport.perl
+++ b/git-svnimport.perl
@@ -873,7 +873,7 @@ sub commit {
 
 		$dest =~ tr/_/\./ if $opt_u;
 
-		system('git-tag', $dest, $cid) == 0
+		system('git-tag', '-f', $dest, $cid) == 0
 			or die "Cannot create tag $dest: $!\n";
 
 		print "Created tag '$dest' on '$branch'\n" if $opt_v;

From a51cdb0c0420ee3bef26bbd1a9aa75e1d464e5b7 Mon Sep 17 00:00:00 2001
From: Eric Wong <normalperson@yhbt.net>
Date: Fri, 7 Sep 2007 04:00:40 -0700
Subject: [PATCH 11/18] git-svn: fix "Malformed network data" with svn://
 servers

We have a workaround for the reparent function not working
correctly on the SVN native protocol servers.  This workaround
opens a new connection (SVN::Ra object) to the new
URL/directory.

Since libsvn appears limited to only supporting one connection
at a time, this workaround invalidates the Git::SVN::Ra object
that is $self inside gs_fetch_loop_common().  So we need to
restart that connection once all the fetching is done for each
loop iteration to be able to run get_log() successfully.

Signed-off-by: Eric Wong <normalperson@yhbt.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 git-svn.perl | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/git-svn.perl b/git-svn.perl
index d3c8cd0b8e..fbd4691bc5 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -3013,7 +3013,7 @@ package Git::SVN::Ra;
 use vars qw/@ISA $config_dir $_log_window_size/;
 use strict;
 use warnings;
-my ($can_do_switch, %ignored_err, $RA);
+my ($ra_invalid, $can_do_switch, %ignored_err, $RA);
 
 BEGIN {
 	# enforce temporary pool usage for some simple functions
@@ -3174,7 +3174,11 @@ sub gs_do_switch {
 			$self->{url} = $full_url;
 			$reparented = 1;
 		} else {
+			$_[0] = undef;
+			$self = undef;
+			$RA = undef;
 			$ra = Git::SVN::Ra->new($full_url);
+			$ra_invalid = 1;
 		}
 	}
 	$ra ||= $self;
@@ -3234,6 +3238,7 @@ sub gs_fetch_loop_common {
 	my $inc = $_log_window_size;
 	my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
 	my $longest_path = longest_common_path($gsv, $globs);
+	my $ra_url = $self->{url};
 	while (1) {
 		my %revs;
 		my $err;
@@ -3295,6 +3300,13 @@ sub gs_fetch_loop_common {
 				        "$g->{t}-maxRev";
 				Git::SVN::tmp_config($k, $r);
 			}
+			if ($ra_invalid) {
+				$_[0] = undef;
+				$self = undef;
+				$RA = undef;
+				$self = Git::SVN::Ra->new($ra_url);
+				$ra_invalid = undef;
+			}
 		}
 		# pre-fill the .rev_db since it'll eventually get filled in
 		# with '0' x40 if something new gets committed

From 3d59405ced89450fa443b64203a8bd9e34d4014c Mon Sep 17 00:00:00 2001
From: "Shawn O. Pearce" <spearce@spearce.org>
Date: Sun, 9 Sep 2007 01:09:17 -0400
Subject: [PATCH 12/18] Define NO_MEMMEM on Darwin as it lacks the function

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Makefile | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Makefile b/Makefile
index bae073fe37..c68b530d1e 100644
--- a/Makefile
+++ b/Makefile
@@ -398,6 +398,7 @@ ifeq ($(uname_S),Darwin)
 	NEEDS_LIBICONV = YesPlease
 	OLD_ICONV = UnfortunatelyYes
 	NO_STRLCPY = YesPlease
+	NO_MEMMEM = YesPlease
 endif
 ifeq ($(uname_S),SunOS)
 	NEEDS_SOCKET = YesPlease

From 5701115aa7cfe7edd57c2483085456a37e27a5ba Mon Sep 17 00:00:00 2001
From: Sven Verdoolaege <skimo@kotnet.org>
Date: Sat, 8 Sep 2007 12:30:22 +0200
Subject: [PATCH 13/18] git-diff: don't squelch the new SHA1 in submodule diffs

The code to squelch empty diffs introduced by commit
fb13227e089f22dc31a3b1624559153821056848 would inadvertently
populate filespec "two" of a submodule change using the uninitialized
(null) SHA1, thereby replacing the submodule SHA1 by 0{40} in the output.

This change teaches diffcore_skip_stat_unmatch to handle
submodule changes correctly.

Signed-off-by: Sven Verdoolaege <skimo@kotnet.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 diff.c                     | 21 +++++++++++++++++----
 t/t7400-submodule-basic.sh |  4 ++++
 2 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/diff.c b/diff.c
index 0d30d05263..1aca5df522 100644
--- a/diff.c
+++ b/diff.c
@@ -3144,6 +3144,22 @@ static void diffcore_apply_filter(const char *filter)
 	*q = outq;
 }
 
+/* Check whether two filespecs with the same mode and size are identical */
+static int diff_filespec_is_identical(struct diff_filespec *one,
+				      struct diff_filespec *two)
+{
+	if (S_ISGITLINK(one->mode)) {
+		diff_fill_sha1_info(one);
+		diff_fill_sha1_info(two);
+		return !hashcmp(one->sha1, two->sha1);
+	}
+	if (diff_populate_filespec(one, 0))
+		return 0;
+	if (diff_populate_filespec(two, 0))
+		return 0;
+	return !memcmp(one->data, two->data, one->size);
+}
+
 static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
 {
 	int i;
@@ -3175,10 +3191,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
 		    diff_populate_filespec(p->one, 1) ||
 		    diff_populate_filespec(p->two, 1) ||
 		    (p->one->size != p->two->size) ||
-
-		    diff_populate_filespec(p->one, 0) || /* (2) */
-		    diff_populate_filespec(p->two, 0) ||
-		    memcmp(p->one->data, p->two->data, p->one->size))
+		    !diff_filespec_is_identical(p->one, p->two)) /* (2) */
 			diff_q(&outq, p);
 		else {
 			/*
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index 9d142ed649..4fe3a41f07 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -152,6 +152,10 @@ test_expect_success 'the --cached sha1 should be rev1' '
 	git-submodule --cached status | grep "^+$rev1"
 '
 
+test_expect_success 'git diff should report the SHA1 of the new submodule commit' '
+	git-diff | grep "^+Subproject commit $rev2"
+'
+
 test_expect_success 'update should checkout rev1' '
 	git-submodule update &&
 	head=$(cd lib && git rev-parse HEAD) &&

From 7b02b85a66fee6b357e02f9e70dd0baa0fd24308 Mon Sep 17 00:00:00 2001
From: Eric Wong <normalperson@yhbt.net>
Date: Sat, 8 Sep 2007 16:33:08 -0700
Subject: [PATCH 14/18] git-svn: understand grafts when doing dcommit

Use the rev-list --parents functionality to read the parents
of the commit.  cat-file only shows the raw object with the
original parents and doesn't take into account grafts; so
we'll rely on rev-list machinery for the smarts here.

Signed-off-by: Eric Wong <normalperson@yhbt.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 git-svn.perl | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/git-svn.perl b/git-svn.perl
index fbd4691bc5..f8181609f9 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -841,14 +841,9 @@ sub working_head_info {
 
 sub read_commit_parents {
 	my ($parents, $c) = @_;
-	my ($fh, $ctx) = command_output_pipe(qw/cat-file commit/, $c);
-	while (<$fh>) {
-		chomp;
-		last if '';
-		/^parent ($sha1)/ or next;
-		push @{$parents->{$c}}, $1;
-	}
-	close $fh; # break the pipe
+	chomp(my $p = command_oneline(qw/rev-list --parents -1/, $c));
+	$p =~ s/^($c)\s*// or die "rev-list --parents -1 $c failed!\n";
+	@{$parents->{$c}} = split(/ /, $p);
 }
 
 sub linearize_history {

From aba91192ae39cd1a2f79e7ed91e966df3cfe10b7 Mon Sep 17 00:00:00 2001
From: Carlos Rica <jasampler@gmail.com>
Date: Sun, 9 Sep 2007 02:39:29 +0200
Subject: [PATCH 15/18] git-tag -s must fail if gpg cannot sign the tag.

Most of this patch code and message was written by Shawn O. Pearce.
I made some tests to know what the problem was, and then I changed
the code related with the SIGPIPE signal.

If the user has misconfigured `user.signingkey` in their .git/config
or just doesn't have any secret keys on their keyring and they ask
for a signed tag with `git tag -s` we better make sure the resulting
tag was actually signed by gpg.

Prior versions of builtin git-tag allowed this failure to slip
by without error as they were not checking the return value of
the finish_command() so they did not notice when gpg exited with
an error exit status.  They also did not fail if gpg produced an
empty output or if read_in_full received an error from the read
system call while trying to read the pipe back from gpg.

Finally, we did not actually honor any return value from the do_sign
function as it returns ssize_t but was being stored into an unsigned
long.  This caused the compiler to optimize out the die condition,
allowing git-tag to continue along and create the tag object.

However, when gpg gets a wrong username, it exits before any read was done
and then the writing process receives SIGPIPE and program is terminated.
By ignoring this signal, anyway, the function write_or_die gets EPIPE from
write_in_full and exits returning 0 to the system without a message.
Here we better call to write_in_full directly so we can fail
printing a message and return safely to the caller.

With these issues fixed `git-tag -s` will now fail to create the
tag and will report a non-zero exit status to its caller, thereby
allowing automated helper scripts to detect (and recover from)
failure if gpg is not working properly.

Proposed-by: Shawn O. Pearce <spearce@spearce.org>
Signed-off-by: Carlos Rica <jasampler@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin-tag.c  | 18 ++++++++++++++----
 t/t7004-tag.sh |  7 +++++++
 2 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/builtin-tag.c b/builtin-tag.c
index 348919cff8..3a9d2eea71 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -200,6 +200,10 @@ static ssize_t do_sign(char *buffer, size_t size, size_t max)
 			bracket[1] = '\0';
 	}
 
+	/* When the username signingkey is bad, program could be terminated
+	 * because gpg exits without reading and then write gets SIGPIPE. */
+	signal(SIGPIPE, SIG_IGN);
+
 	memset(&gpg, 0, sizeof(gpg));
 	gpg.argv = args;
 	gpg.in = -1;
@@ -212,12 +216,17 @@ static ssize_t do_sign(char *buffer, size_t size, size_t max)
 	if (start_command(&gpg))
 		return error("could not run gpg.");
 
-	write_or_die(gpg.in, buffer, size);
+	if (write_in_full(gpg.in, buffer, size) != size) {
+		close(gpg.in);
+		finish_command(&gpg);
+		return error("gpg did not accept the tag data");
+	}
 	close(gpg.in);
 	gpg.close_in = 0;
 	len = read_in_full(gpg.out, buffer + size, max - size);
 
-	finish_command(&gpg);
+	if (finish_command(&gpg) || !len || len < 0)
+		return error("gpg failed to sign the tag");
 
 	if (len == max - size)
 		return error("could not read the entire signature from gpg.");
@@ -310,9 +319,10 @@ static void create_tag(const unsigned char *object, const char *tag,
 	size += header_len;
 
 	if (sign) {
-		size = do_sign(buffer, size, max_size);
-		if (size < 0)
+		ssize_t r = do_sign(buffer, size, max_size);
+		if (r < 0)
 			die("unable to sign the tag");
+		size = r;
 	}
 
 	if (write_sha1_file(buffer, size, tag_type, result) < 0)
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 606d4f2a2c..0d07bc39c7 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -990,6 +990,13 @@ test_expect_success \
 	git diff expect actual
 '
 
+# try to sign with bad user.signingkey
+git config user.signingkey BobTheMouse
+test_expect_failure \
+	'git-tag -s fails if gpg is misconfigured' \
+	'git tag -s -m tail tag-gpg-failure'
+git config --unset user.signingkey
+
 # try to verify without gpg:
 
 rm -rf gpghome

From 05cc2ffc572f05e8aeec495a9ab9bc9609863491 Mon Sep 17 00:00:00 2001
From: Nicolas Pitre <nico@cam.org>
Date: Mon, 10 Sep 2007 00:15:29 -0400
Subject: [PATCH 16/18] fix doc for --compression argument to pack-objects

Remove obsolete details (core.legacyheaders is always true now).

Signed-off-by: Nicolas Pitre <nico@cam.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-pack-objects.txt | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index 6f17cff24a..f8a0be3511 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -155,12 +155,8 @@ base-name::
 	generated pack.  If not specified,  pack compression level is
 	determined first by pack.compression,  then by core.compression,
 	and defaults to -1,  the zlib default,  if neither is set.
-	Data copied from loose objects will be recompressed
-	if core.legacyheaders was true when they were created or if
-	the loose compression level (see core.loosecompression and
-	core.compression) is now a different value than the pack
-	compression level.  Add --no-reuse-object if you want to force
-	a uniform compression level on all data no matter the source.
+	Add \--no-reuse-object if you want to force a uniform compression
+	level on all data no matter the source.
 
 --delta-base-offset::
 	A packed archive can express base object of a delta as

From a4503a15af661ee865ed10102df15a6d3b43e60a Mon Sep 17 00:00:00 2001
From: "Shawn O. Pearce" <spearce@spearce.org>
Date: Sun, 9 Sep 2007 19:38:11 -0400
Subject: [PATCH 17/18] Make --no-thin the default in git-push to save server
 resources

1) pushes happen less often than fetches, so the bandwidth saving is
   much less visible in that case overall.

2) thin packs have to be complemented with missing delta bases to be
   valid, so many received thin packs will take more disk space.

3) the bother of repacking should be distributed amongst "clients"
   i.e. fetchers and pushers as much as possible, and not the server
   being fetched or pushed, to keep disk and CPU usage low on the
   server.

This is why a fetch should get thin packs but a push should not.

Both Nico and I have been assuming that --no-thin was the default
behavior of git-push ever since Nico introduced --fix-thin into the
index-pack process, which allowed fetch and receive-pack to avoid
exploding packfiles received during transfer.  This patch finally
makes it so.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin-push.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin-push.c b/builtin-push.c
index 2612f07f74..88c5024da7 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -9,7 +9,7 @@
 
 static const char push_usage[] = "git-push [--all] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]";
 
-static int all, force, thin = 1, verbose;
+static int all, force, thin, verbose;
 static const char *receivepack;
 
 static const char **refspec;

From 20fbfd869fe8faae0ce4573b60b540024db7385d Mon Sep 17 00:00:00 2001
From: Junio C Hamano <gitster@pobox.com>
Date: Mon, 10 Sep 2007 00:14:38 -0700
Subject: [PATCH 18/18] archive - leakfix for format_subst()

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin-archive.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/builtin-archive.c b/builtin-archive.c
index e221f115f9..a90c65ce54 100644
--- a/builtin-archive.c
+++ b/builtin-archive.c
@@ -108,6 +108,7 @@ static void *format_subst(const struct commit *commit, const char *format,
 
 		formatted_len = format_commit_message(commit, fmt, &formatted,
 		                                      &allocated);
+		free(fmt);
 		result = xrealloc(result, result_len + a_len + formatted_len);
 		memcpy(result + result_len, a, a_len);
 		memcpy(result + result_len + a_len, formatted, formatted_len);