Merge branch 'jc/maint-template-permbits' into maint
authorJunio C Hamano <gitster@pobox.com>
Fri, 19 Sep 2008 02:53:01 +0000 (19:53 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 19 Sep 2008 02:53:01 +0000 (19:53 -0700)
* jc/maint-template-permbits:
  Fix permission bits on sources checked out with an overtight umask

56 files changed:
Documentation/RelNotes-1.6.0.2.txt
Documentation/git-web--browse.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/merge-config.txt
GIT-VERSION-GEN
Makefile
builtin-archive.c
builtin-checkout-index.c
builtin-checkout.c
builtin-commit-tree.c
builtin-diff-files.c
builtin-diff.c
builtin-fetch-pack.c
builtin-fetch.c
builtin-http-fetch.c
builtin-init-db.c
builtin-pack-objects.c
builtin-read-tree.c
builtin-rev-list.c
builtin-rm.c
builtin-send-pack.c
builtin-tar-tree.c
builtin-unpack-objects.c
builtin-update-index.c
combine-diff.c
compat/fnmatch/fnmatch.c [moved from compat/fnmatch.c with 100% similarity]
compat/fnmatch/fnmatch.h [moved from compat/fnmatch.h with 100% similarity]
compat/regex/regex.c [moved from compat/regex.c with 100% similarity]
compat/regex/regex.h [moved from compat/regex.h with 100% similarity]
csum-file.c
diff.c
fast-import.c
git-svn.perl
gitweb/gitweb.perl
grep.c
grep.h
http.c
index-pack.c
pack-write.c
pack.h
perl/Git.pm
revision.c
sha1_file.c
t/lib-git-svn.sh
t/t0024-crlf-archive.sh [new file with mode: 0644]
t/t4018-diff-funcname.sh
t/t4019-diff-wserror.sh
t/t7002-grep.sh
t/t7201-co.sh
t/t7501-commit.sh
t/t9113-git-svn-dcommit-new-file.sh
t/t9118-git-svn-funky-branch-names.sh
t/t9126-git-svn-follow-deleted-readded-directory.sh [new file with mode: 0755]
t/t9126/follow-deleted-readded.dump [new file with mode: 0644]
t/t9700/test.pl

index 6c5446b..7a9646f 100644 (file)
@@ -17,6 +17,10 @@ Fixes since v1.6.0.1
 * Many commands did not use the correct working tree location when used
   with GIT_WORK_TREE environment settings.
 
+* Some systems needs to use compatibility fnmach and regex libraries
+  independent from each other; the compat/ area has been reorganized to
+  allow this.
+
 
 * "git apply --unidiff-zero" incorrectly applied a -U0 patch that inserts
   a new line before the second line.
@@ -24,9 +28,15 @@ Fixes since v1.6.0.1
 * "git blame -c" did not exactly work like "git annotate" when range
   boundaries are involved.
 
+* "git checkout file" when file is still unmerged checked out contents from
+  a random high order stage, which was confusing.
+
 * "git clone $there $here/" with extra trailing slashes after explicit
   local directory name $here did not work as expected.
 
+* "git diff" on tracked contents with CRLF line endings did not drive "less"
+  intelligently when showing added or removed lines.
+
 * "git diff --dirstat -M" did not add changes in subdirectories up
   correctly for renamed paths.
 
@@ -42,18 +52,29 @@ Fixes since v1.6.0.1
 
 * "git gui" translation updates and i18n fixes.
 
+* "git index-pack" is more careful against disk corruption while completing
+  a thin pack.
+
 * "git log -i --grep=pattern" did not ignore case; neither "git log -E
   --grep=pattern" triggered extended regexp.
 
 * "git log --pretty="%ad" --date=short" did not use short format when
   showing the timestamp.
 
+* "git log --author=author" match incorrectly matched with the
+  timestamp part of "author " line in commit objects.
+
+* "git log -F --author=author" did not work at all.
+
 * Build procedure for "git shell" that used stub versions of some
   functions and globals was not understood by linkers on some platforms.
 
 * "git stash" was fooled by a stat-dirty but otherwise unmodified paths
   and refused to work until the user refreshed the index.
 
+* "git svn" was broken on Perl before 5.8 with recent fixes to reduce
+  use of temporary files.
+
 * "git verify-pack -v" did not work correctly when given more than one
   packfile.
 
@@ -61,7 +82,6 @@ Also contains many documentation updates.
 
 --
 exec >/var/tmp/1
-O=v1.6.0.1-61-g1eff26c
+O=v1.6.0.1-78-g3632cfc
 echo O=$(git describe maint)
 git shortlog --no-merges $O..maint
-
index 36afad8..7f7a45b 100644 (file)
@@ -77,7 +77,7 @@ the URLs passed as arguments.
 Note about konqueror
 --------------------
 
-When 'konqueror' is specified by the a command line option or a
+When 'konqueror' is specified by a command line option or a
 configuration variable, we launch 'kfmclient' to try to open the HTML
 man page on an already opened konqueror in a new tab if possible.
 
index 363a785..df420ae 100644 (file)
@@ -43,9 +43,11 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.6.0/git.html[documentation for release 1.6.0]
+* link:v1.6.0.2/git.html[documentation for release 1.6.0.2]
 
 * release notes for
+  link:RelNotes-1.6.0.2.txt[1.6.0.2],
+  link:RelNotes-1.6.0.1.txt[1.6.0.1],
   link:RelNotes-1.6.0.txt[1.6.0].
 
 * link:v1.5.6.5/git.html[documentation for release 1.5.6.5]
index 49a167f..8962768 100644 (file)
@@ -270,27 +270,27 @@ See linkgit:git[1] for details.
 Defining a custom hunk-header
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Each group of changes (called "hunk") in the textual diff output
+Each group of changes (called "hunk") in the textual diff output
 is prefixed with a line of the form:
 
        @@ -k,l +n,m @@ TEXT
 
-The text is called 'hunk header', and by default a line that
-begins with an alphabet, an underscore or a dollar sign is used,
-which matches what GNU 'diff -p' output uses.  This default
-selection however is not suited for some contents, and you can
-use customized pattern to make a selection.
+This is called a 'hunk header'.  The "TEXT" portion is by default a line
+that begins with an alphabet, an underscore or a dollar sign; this
+matches what GNU 'diff -p' output uses.  This default selection however
+is not suited for some contents, and you can use a customized pattern
+to make a selection.
 
-First in .gitattributes, you would assign the `diff` attribute
+First, in .gitattributes, you would assign the `diff` attribute
 for paths.
 
 ------------------------
 *.tex  diff=tex
 ------------------------
 
-Then, you would define "diff.tex.funcname" configuration to
+Then, you would define "diff.tex.funcname" configuration to
 specify a regular expression that matches a line that you would
-want to appear as the hunk header, like this:
+want to appear as the hunk header "TEXT", like this:
 
 ------------------------
 [diff "tex"]
@@ -311,7 +311,7 @@ patterns are available:
 
 - `bibtex` suitable for files with BibTeX coded references.
 
-- `java` suitable for source code in the Java lanugage.
+- `java` suitable for source code in the Java language.
 
 - `pascal` suitable for source code in the Pascal/Delphi language.
 
index 00277e0..c735788 100644 (file)
@@ -1,5 +1,5 @@
 merge.stat::
-       Whether to print the diffstat between ORIG_HEAD and merge result
+       Whether to print the diffstat between ORIG_HEAD and the merge result
        at the end of the merge.  True by default.
 
 merge.log::
index 156dc13..6c7465c 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.6.0.GIT
+DEF_VER=v1.6.0.2.GIT
 
 LF='
 '
index 672ea74..8d81095 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -626,6 +626,8 @@ ifeq ($(uname_S),Darwin)
        endif
        NO_STRLCPY = YesPlease
        NO_MEMMEM = YesPlease
+       COMPAT_CFLAGS += -Icompat/regex
+       COMPAT_OBJS += compat/regex/regex.o
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@ -675,6 +677,8 @@ ifeq ($(uname_S),FreeBSD)
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
        DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
+       COMPAT_CFLAGS += -Icompat/regex
+       COMPAT_OBJS += compat/regex/regex.o
 endif
 ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
@@ -700,6 +704,8 @@ ifeq ($(uname_S),AIX)
        INTERNAL_QSORT = UnfortunatelyYes
        NEEDS_LIBICONV=YesPlease
        BASIC_CFLAGS += -D_LARGE_FILES
+       COMPAT_CFLAGS += -Icompat/regex
+       COMPAT_OBJS += compat/regex/regex.o
 endif
 ifeq ($(uname_S),GNU)
        # GNU/Hurd
@@ -750,10 +756,10 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_SVN_TESTS = YesPlease
        NO_PERL_MAKEMAKER = YesPlease
        NO_POSIX_ONLY_PROGRAMS = YesPlease
-       COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat
+       COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
        COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
-       COMPAT_OBJS += compat/mingw.o compat/fnmatch.o compat/regex.o compat/winansi.o
+       COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/regex/regex.o compat/winansi.o
        EXTLIBS += -lws2_32
        X = .exe
        gitexecdir = ../libexec/git-core
index 5ceec43..432ce2a 100644 (file)
@@ -111,6 +111,8 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
 {
        const char *remote = NULL;
 
+       git_config(git_default_config, NULL);
+
        remote = extract_remote_arg(&argc, argv);
        if (remote)
                return run_remote_archiver(remote, argc, argv);
index 90f8523..55b7aaf 100644 (file)
@@ -5,26 +5,26 @@
  *
  * Careful: order of argument flags does matter. For example,
  *
- *     git-checkout-index -a -f file.c
+ *     git checkout-index -a -f file.c
  *
  * Will first check out all files listed in the cache (but not
  * overwrite any old ones), and then force-checkout "file.c" a
  * second time (ie that one _will_ overwrite any old contents
  * with the same filename).
  *
- * Also, just doing "git-checkout-index" does nothing. You probably
- * meant "git-checkout-index -a". And if you want to force it, you
- * want "git-checkout-index -f -a".
+ * Also, just doing "git checkout-index" does nothing. You probably
+ * meant "git checkout-index -a". And if you want to force it, you
+ * want "git checkout-index -f -a".
  *
  * Intuitiveness is not the goal here. Repeatability is. The
  * reason for the "no arguments means no work" thing is that
  * from scripts you are supposed to be able to do things like
  *
- *     find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
+ *     find . -name '*.h' -print0 | xargs -0 git checkout-index -f --
  *
  * or:
  *
- *     find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+ *     find . -name '*.h' -print0 | git checkout-index -f -z --stdin
  *
  * which will force all existing *.h files to be replaced with
  * their cached copies. If an empty command line implied "all",
@@ -107,7 +107,7 @@ static int checkout_file(const char *name, int prefix_length)
        }
 
        if (!state.quiet) {
-               fprintf(stderr, "git-checkout-index: %s ", name);
+               fprintf(stderr, "git checkout-index: %s ", name);
                if (!has_same_name)
                        fprintf(stderr, "is not in the cache");
                else if (checkout_stage)
index f6f8f08..8e77767 100644 (file)
@@ -76,6 +76,15 @@ static int read_tree_some(struct tree *tree, const char **pathspec)
        return 0;
 }
 
+static int skip_same_name(struct cache_entry *ce, int pos)
+{
+       while (++pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name))
+               ; /* skip */
+       return pos;
+}
+
+
 static int checkout_paths(struct tree *source_tree, const char **pathspec)
 {
        int pos;
@@ -107,6 +116,20 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
        if (report_path_error(ps_matched, pathspec, 0))
                return 1;
 
+       /* Any unmerged paths? */
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               if (pathspec_match(pathspec, NULL, ce->name, 0)) {
+                       if (!ce_stage(ce))
+                               continue;
+                       errs = 1;
+                       error("path '%s' is unmerged", ce->name);
+                       pos = skip_same_name(ce, pos) - 1;
+               }
+       }
+       if (errs)
+               return 1;
+
        /* Now we are committed to check them out */
        memset(&state, 0, sizeof(state));
        state.force = 1;
@@ -114,7 +137,11 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
                if (pathspec_match(pathspec, NULL, ce->name, 0)) {
-                       errs |= checkout_entry(ce, &state, NULL);
+                       if (!ce_stage(ce)) {
+                               errs |= checkout_entry(ce, &state, NULL);
+                               continue;
+                       }
+                       pos = skip_same_name(ce, pos) - 1;
                }
        }
 
index 291c43c..9b84c48 100644 (file)
@@ -24,7 +24,7 @@ static void check_valid(unsigned char *sha1, enum object_type expect)
                    typename(expect));
 }
 
-static const char commit_tree_usage[] = "git-commit-tree <sha1> [-p <sha1>]* < changelog";
+static const char commit_tree_usage[] = "git commit-tree <sha1> [-p <sha1>]* < changelog";
 
 static void new_parent(struct commit *parent, struct commit_list **parents_p)
 {
index 9bf10bb..2b578c7 100644 (file)
@@ -50,7 +50,12 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
            3 < rev.max_count)
                usage(diff_files_usage);
 
-       if (rev.max_count == -1 &&
+       /*
+        * "diff-files --base -p" should not combine merges because it
+        * was not asked to.  "diff-files -c -p" should not densify
+        * (the user should ask with "diff-files --cc" explicitly).
+        */
+       if (rev.max_count == -1 && !rev.combine_merges &&
            (rev.diffopt.output_format & DIFF_FORMAT_PATCH))
                rev.combine_merges = rev.dense_combined_merges = 1;
 
index 037c303..d5fe775 100644 (file)
@@ -223,7 +223,13 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
                argv++; argc--;
        }
 
-       if (revs->max_count == -1 &&
+       /*
+        * "diff --base" should not combine merges because it was not
+        * asked to.  "diff -c" should not densify (if the user wants
+        * dense one, --cc can be explicitly asked for, or just rely
+        * on the default).
+        */
+       if (revs->max_count == -1 && !revs->combine_merges &&
            (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
                revs->combine_merges = revs->dense_combined_merges = 1;
 
index 6b37281..85509f5 100644 (file)
@@ -750,7 +750,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        if (!ret && nr_heads) {
                /* If the heads to pull were given, we should have
                 * consumed all of them by matching the remote.
-                * Otherwise, 'git-fetch remote no-such-ref' would
+                * Otherwise, 'git fetch remote no-such-ref' would
                 * silently succeed without issuing an error.
                 */
                for (i = 0; i < nr_heads; i++)
index 7eec4a0..ee93d3a 100644 (file)
@@ -86,10 +86,10 @@ static void add_merge_config(struct ref **head,
                /*
                 * Not fetched to a tracking branch?  We need to fetch
                 * it anyway to allow this branch's "branch.$name.merge"
-                * to be honored by git-pull, but we do not have to
+                * to be honored by 'git pull', but we do not have to
                 * fail if branch.$name.merge is misconfigured to point
                 * at a nonexisting branch.  If we were indeed called by
-                * git-pull, it will notice the misconfiguration because
+                * 'git pull', it will notice the misconfiguration because
                 * there is no entry in the resulting FETCH_HEAD marked
                 * for merging.
                 */
@@ -396,7 +396,7 @@ static int store_updated_refs(const char *url, const char *remote_name,
  * The refs we are going to fetch are in to_fetch (nr_heads in
  * total).  If running
  *
- *  $ git-rev-list --objects to_fetch[0] to_fetch[1] ... --not --all
+ *  $ git rev-list --objects to_fetch[0] to_fetch[1] ... --not --all
  *
  * does not error out, that means everything reachable from the
  * refs we are going to fetch exists and is connected to some of
index 3a06248..03f34d7 100644 (file)
@@ -42,7 +42,7 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
                arg++;
        }
        if (argc < arg + 2 - commits_on_stdin) {
-               usage("git-http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
+               usage("git http-fetch [-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url");
                return 1;
        }
        if (commits_on_stdin) {
@@ -75,7 +75,7 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
                fprintf(stderr,
 "Some loose object were found to be corrupt, but they might be just\n"
 "a false '404 Not Found' error message sent with incorrect HTTP\n"
-"status code.  Suggest running git-fsck.\n");
+"status code.  Suggest running 'git fsck'.\n");
        }
 
        walker_free(walker);
index baf0d09..8140c12 100644 (file)
@@ -37,7 +37,7 @@ static void copy_templates_1(char *path, int baselen,
 
        /* Note: if ".git/hooks" file exists in the repository being
         * re-initialized, /etc/core-git/templates/hooks/update would
-        * cause git-init to fail here.  I think this is sane but
+        * cause "git init" to fail here.  I think this is sane but
         * it means that the set of templates we ship by default, along
         * with the way the namespace under .git/ is organized, should
         * be really carefully chosen.
index ef3befe..217fd49 100644 (file)
@@ -23,7 +23,7 @@
 #endif
 
 static const char pack_usage[] = "\
-git-pack-objects [{ -q | --progress | --all-progress }] \n\
+git pack-objects [{ -q | --progress | --all-progress }] \n\
        [--max-pack-size=N] [--local] [--incremental] \n\
        [--window=N] [--window-memory=N] [--depth=N] \n\
        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
@@ -410,25 +410,22 @@ static unsigned long write_object(struct sha1file *f,
        return hdrlen + datalen;
 }
 
-static off_t write_one(struct sha1file *f,
+static int write_one(struct sha1file *f,
                               struct object_entry *e,
-                              off_t offset)
+                              off_t *offset)
 {
        unsigned long size;
 
        /* offset is non zero if object is written already. */
        if (e->idx.offset || e->preferred_base)
-               return offset;
+               return 1;
 
        /* if we are deltified, write out base object first. */
-       if (e->delta) {
-               offset = write_one(f, e->delta, offset);
-               if (!offset)
-                       return 0;
-       }
+       if (e->delta && !write_one(f, e->delta, offset))
+               return 0;
 
-       e->idx.offset = offset;
-       size = write_object(f, e, offset);
+       e->idx.offset = *offset;
+       size = write_object(f, e, *offset);
        if (!size) {
                e->idx.offset = 0;
                return 0;
@@ -436,9 +433,10 @@ static off_t write_one(struct sha1file *f,
        written_list[nr_written++] = &e->idx;
 
        /* make sure off_t is sufficiently large not to wrap */
-       if (offset > offset + size)
+       if (*offset > *offset + size)
                die("pack too large for current definition of off_t");
-       return offset + size;
+       *offset += size;
+       return 1;
 }
 
 /* forward declaration for write_pack_file */
@@ -448,7 +446,7 @@ static void write_pack_file(void)
 {
        uint32_t i = 0, j;
        struct sha1file *f;
-       off_t offset, offset_one, last_obj_offset = 0;
+       off_t offset;
        struct pack_header hdr;
        uint32_t nr_remaining = nr_result;
        time_t last_mtime = 0;
@@ -480,11 +478,8 @@ static void write_pack_file(void)
                offset = sizeof(hdr);
                nr_written = 0;
                for (; i < nr_objects; i++) {
-                       last_obj_offset = offset;
-                       offset_one = write_one(f, objects + i, offset);
-                       if (!offset_one)
+                       if (!write_one(f, objects + i, &offset))
                                break;
-                       offset = offset_one;
                        display_progress(progress_state, written);
                }
 
@@ -497,8 +492,9 @@ static void write_pack_file(void)
                } else if (nr_written == nr_remaining) {
                        sha1close(f, sha1, CSUM_FSYNC);
                } else {
-                       int fd = sha1close(f, NULL, 0);
-                       fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written);
+                       int fd = sha1close(f, sha1, 0);
+                       fixup_pack_header_footer(fd, sha1, pack_tmp_name,
+                                                nr_written, sha1, offset);
                        close(fd);
                }
 
@@ -1876,7 +1872,7 @@ static void mark_in_pack_object(struct object *object, struct packed_git *p, str
 
 /*
  * Compare the objects in the offset order, in order to emulate the
- * "git-rev-list --objects" output that produced the pack originally.
+ * "git rev-list --objects" output that produced the pack originally.
  */
 static int ofscmp(const void *a_, const void *b_)
 {
index dddc304..ac219ac 100644 (file)
@@ -64,7 +64,7 @@ static void prime_cache_tree(void)
 
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git read-tree (<sha> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
 
 static struct lock_file lock_file;
 
index c023003..facaff2 100644 (file)
@@ -178,7 +178,7 @@ static void finish_object(struct object_array_entry *p)
 static void show_object(struct object_array_entry *p)
 {
        /* An object with name "foo\n0000000..." can be used to
-        * confuse downstream git-pack-objects very badly.
+        * confuse downstream "git pack-objects" very badly.
         */
        const char *ep = strchr(p->name, '\n');
 
index 6bd8211..fdac34f 100644 (file)
@@ -104,7 +104,7 @@ static int check_local_mod(unsigned char *head, int index_only)
                                     "from both the file and the HEAD\n"
                                     "(use -f to force removal)", name);
                else if (!index_only) {
-                       /* It's not dangerous to git-rm --cached a
+                       /* It's not dangerous to "git rm --cached" a
                         * file if the index matches the file or the
                         * HEAD, since it means the deleted content is
                         * still available somewhere.
index 7588d22..2af9f29 100644 (file)
@@ -43,7 +43,7 @@ static int pack_objects(int fd, struct ref *refs)
        po.out = fd;
        po.git_cmd = 1;
        if (start_command(&po))
-               die("git-pack-objects failed (%s)", strerror(errno));
+               die("git pack-objects failed (%s)", strerror(errno));
 
        /*
         * We feed the pack-objects we just spawned with revision
index cb7007e..0713bca 100644 (file)
@@ -9,26 +9,26 @@
 
 static const char tar_tree_usage[] =
 "git tar-tree [--remote=<repo>] <tree-ish> [basedir]\n"
-"*** Note that this command is now deprecated; use git-archive instead.";
+"*** Note that this command is now deprecated; use \"git archive\" instead.";
 
 int cmd_tar_tree(int argc, const char **argv, const char *prefix)
 {
        /*
-        * git-tar-tree is now a wrapper around git-archive --format=tar
+        * "git tar-tree" is now a wrapper around "git archive --format=tar"
         *
         * $0 --remote=<repo> arg... ==>
-        *      git-archive --format=tar --remote=<repo> arg...
+        *      git archive --format=tar --remote=<repo> arg...
         * $0 tree-ish ==>
-        *      git-archive --format=tar tree-ish
+        *      git archive --format=tar tree-ish
         * $0 tree-ish basedir ==>
-        *      git-archive --format-tar --prefix=basedir tree-ish
+        *      git archive --format-tar --prefix=basedir tree-ish
         */
        int i;
        const char **nargv = xcalloc(sizeof(*nargv), argc + 2);
        char *basedir_arg;
        int nargc = 0;
 
-       nargv[nargc++] = "git-archive";
+       nargv[nargc++] = "archive";
        nargv[nargc++] = "--format=tar";
 
        if (2 <= argc && !prefixcmp(argv[1], "--remote=")) {
@@ -53,8 +53,8 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix)
        nargv[nargc] = NULL;
 
        fprintf(stderr,
-               "*** git-tar-tree is now deprecated.\n"
-               "*** Running git-archive instead.\n***");
+               "*** \"git tar-tree\" is now deprecated.\n"
+               "*** Running \"git archive\" instead.\n***");
        for (i = 0; i < nargc; i++) {
                fputc(' ', stderr);
                sq_quote_print(stderr, nargv[i]);
index a891866..40b20f2 100644 (file)
@@ -13,7 +13,7 @@
 #include "fsck.h"
 
 static int dry_run, quiet, recover, has_errors, strict;
-static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] [--strict] < pack-file";
+static const char unpack_usage[] = "git unpack-objects [-n] [-q] [-r] [--strict] < pack-file";
 
 /* We always read in 4kB chunks. */
 static unsigned char buffer[4096];
index 5637d41..9d19c51 100644 (file)
@@ -14,7 +14,7 @@
  * Default to not allowing changes to the list of files. The
  * tool doesn't actually care, but this makes it harder to add
  * files to the revision control by mistake by doing something
- * like "git-update-index *" and suddenly having all the object
+ * like "git update-index *" and suddenly having all the object
  * files be revision controlled.
  */
 static int allow_add;
@@ -310,18 +310,18 @@ static void read_index_info(int line_termination)
                /* This reads lines formatted in one of three formats:
                 *
                 * (1) mode         SP sha1          TAB path
-                * The first format is what "git-apply --index-info"
+                * The first format is what "git apply --index-info"
                 * reports, and used to reconstruct a partial tree
                 * that is used for phony merge base tree when falling
                 * back on 3-way merge.
                 *
                 * (2) mode SP type SP sha1          TAB path
-                * The second format is to stuff git-ls-tree output
+                * The second format is to stuff "git ls-tree" output
                 * into the index file.
                 *
                 * (3) mode         SP sha1 SP stage TAB path
                 * This format is to put higher order stages into the
-                * index file and matches git-ls-files --stage output.
+                * index file and matches "git ls-files --stage" output.
                 */
                errno = 0;
                ul = strtoul(buf.buf, &ptr, 8);
index 4dfc330..aa9d79e 100644 (file)
@@ -500,6 +500,18 @@ static int hunk_comment_line(const char *bol)
        return (isalpha(ch) || ch == '_' || ch == '$');
 }
 
+static void show_line_to_eol(const char *line, int len, const char *reset)
+{
+       int saw_cr_at_eol = 0;
+       if (len < 0)
+               len = strlen(line);
+       saw_cr_at_eol = (len && line[len-1] == '\r');
+
+       printf("%.*s%s%s\n", len - saw_cr_at_eol, line,
+              reset,
+              saw_cr_at_eol ? "\r" : "");
+}
+
 static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                       int use_color)
 {
@@ -593,7 +605,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                                        else
                                                putchar(' ');
                                }
-                               printf("%s%s\n", ll->line, c_reset);
+                               show_line_to_eol(ll->line, -1, c_reset);
                                ll = ll->next;
                        }
                        if (cnt < lno)
@@ -617,7 +629,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                                        putchar(' ');
                                p_mask <<= 1;
                        }
-                       printf("%.*s%s\n", sl->len, sl->bol, c_reset);
+                       show_line_to_eol(sl->bol, sl->len, c_reset);
                }
        }
 }
similarity index 100%
rename from compat/fnmatch.c
rename to compat/fnmatch/fnmatch.c
similarity index 100%
rename from compat/fnmatch.h
rename to compat/fnmatch/fnmatch.h
similarity index 100%
rename from compat/regex.c
rename to compat/regex/regex.c
similarity index 100%
rename from compat/regex.h
rename to compat/regex/regex.h
index ace64f1..2838954 100644 (file)
@@ -42,11 +42,11 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags)
                sha1flush(f, offset);
                f->offset = 0;
        }
+       SHA1_Final(f->buffer, &f->ctx);
+       if (result)
+               hashcpy(result, f->buffer);
        if (flags & (CSUM_CLOSE | CSUM_FSYNC)) {
                /* write checksum and close fd */
-               SHA1_Final(f->buffer, &f->ctx);
-               if (result)
-                       hashcpy(result, f->buffer);
                sha1flush(f, 20);
                if (flags & CSUM_FSYNC)
                        fsync_or_die(f->fd, f->name);
diff --git a/diff.c b/diff.c
index b3a7da7..5e01b2b 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -511,13 +511,20 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix)
 
 static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len)
 {
-       int has_trailing_newline = (len > 0 && line[len-1] == '\n');
+       int has_trailing_newline, has_trailing_carriage_return;
+
+       has_trailing_newline = (len > 0 && line[len-1] == '\n');
        if (has_trailing_newline)
                len--;
+       has_trailing_carriage_return = (len > 0 && line[len-1] == '\r');
+       if (has_trailing_carriage_return)
+               len--;
 
        fputs(set, file);
        fwrite(line, len, 1, file);
        fputs(reset, file);
+       if (has_trailing_carriage_return)
+               fputc('\r', file);
        if (has_trailing_newline)
                fputc('\n', file);
 }
index 7089e6f..d85b3a5 100644 (file)
@@ -951,7 +951,8 @@ static void end_packfile(void)
 
                close_pack_windows(pack_data);
                fixup_pack_header_footer(pack_data->pack_fd, pack_data->sha1,
-                                   pack_data->pack_name, object_count);
+                                   pack_data->pack_name, object_count,
+                                   NULL, 0);
                close(pack_data->pack_fd);
                idx_name = keep_pack(create_index());
 
index 7a1d26d..7c7fc39 100755 (executable)
@@ -3284,7 +3284,7 @@ sub close_file {
                                        my $out = syswrite($tmp_fh, $str, $res);
                                        defined($out) && $out == $res
                                                or croak("write ",
-                                                       $tmp_fh->filename,
+                                                       Git::temp_path($tmp_fh),
                                                        ": $!\n");
                                }
                                defined $res or croak $!;
@@ -3295,7 +3295,7 @@ sub close_file {
                }
 
                $hash = $::_repository->hash_and_insert_object(
-                               $fh->filename);
+                               Git::temp_path($fh));
                $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
 
                Git::temp_release($fb->{base}, 1);
@@ -3969,21 +3969,21 @@ sub gs_do_switch {
        my $old_url = $full_url;
        $full_url .= '/' . escape_uri_only($path) if length $path;
        my ($ra, $reparented);
-       if ($old_url ne $full_url) {
-               if ($old_url !~ m#^svn(\+ssh)?://#) {
-                       SVN::_Ra::svn_ra_reparent($self->{session}, $full_url,
-                                                 $pool);
-                       $self->{url} = $full_url;
-                       $reparented = 1;
-               } else {
-                       $_[0] = undef;
-                       $self = undef;
-                       $RA = undef;
-                       $ra = Git::SVN::Ra->new($full_url);
-                       $ra_invalid = 1;
-               }
+
+       if ($old_url =~ m#^svn(\+ssh)?://#) {
+               $_[0] = undef;
+               $self = undef;
+               $RA = undef;
+               $ra = Git::SVN::Ra->new($full_url);
+               $ra_invalid = 1;
+       } elsif ($old_url ne $full_url) {
+               SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool);
+               $self->{url} = $full_url;
+               $reparented = 1;
        }
+
        $ra ||= $self;
+       $url_b = escape_url($url_b);
        my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
        $reporter->set_path('', $rev_a, 0, @lock, $pool);
@@ -4383,7 +4383,7 @@ sub config_pager {
 
 sub run_pager {
        return unless -t *STDOUT && defined $pager;
-       pipe my $rfd, my $wfd or return;
+       pipe my ($rfd, $wfd) or return;
        defined(my $pid = fork) or ::fatal "Can't fork: $!";
        if (!$pid) {
                open STDOUT, '>&', $wfd or
index 90cd99b..269f112 100755 (executable)
@@ -2092,7 +2092,7 @@ sub parse_commit_text {
                        last;
                }
        }
-       if ($co{'title'} eq "") {
+       if (! defined $co{'title'} || $co{'title'} eq "") {
                $co{'title'} = $co{'title_short'} = '(no commit message)';
        }
        # remove added spaces
diff --git a/grep.c b/grep.c
index f67d671..7063511 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -2,6 +2,19 @@
 #include "grep.h"
 #include "xdiff-interface.h"
 
+void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field field, const char *pat)
+{
+       struct grep_pat *p = xcalloc(1, sizeof(*p));
+       p->pattern = pat;
+       p->origin = "header";
+       p->no = 0;
+       p->token = GREP_PATTERN_HEAD;
+       p->field = field;
+       *opt->pattern_tail = p;
+       opt->pattern_tail = &p->next;
+       p->next = NULL;
+}
+
 void append_grep_pattern(struct grep_opt *opt, const char *pat,
                         const char *origin, int no, enum grep_pat_token t)
 {
@@ -247,16 +260,53 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match)
        }
 }
 
+static int strip_timestamp(char *bol, char **eol_p)
+{
+       char *eol = *eol_p;
+       int ch;
+
+       while (bol < --eol) {
+               if (*eol != '>')
+                       continue;
+               *eol_p = ++eol;
+               ch = *eol;
+               *eol = '\0';
+               return ch;
+       }
+       return 0;
+}
+
+static struct {
+       const char *field;
+       size_t len;
+} header_field[] = {
+       { "author ", 7 },
+       { "committer ", 10 },
+};
+
 static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)
 {
        int hit = 0;
        int at_true_bol = 1;
+       int saved_ch = 0;
        regmatch_t pmatch[10];
 
        if ((p->token != GREP_PATTERN) &&
            ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))
                return 0;
 
+       if (p->token == GREP_PATTERN_HEAD) {
+               const char *field;
+               size_t len;
+               assert(p->field < ARRAY_SIZE(header_field));
+               field = header_field[p->field].field;
+               len = header_field[p->field].len;
+               if (strncmp(bol, field, len))
+                       return 0;
+               bol += len;
+               saved_ch = strip_timestamp(bol, &eol);
+       }
+
  again:
        if (!opt->fixed) {
                regex_t *exp = &p->regexp;
@@ -298,6 +348,8 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
                        goto again;
                }
        }
+       if (p->token == GREP_PATTERN_HEAD && saved_ch)
+               *eol = saved_ch;
        return hit;
 }
 
diff --git a/grep.h b/grep.h
index d252dd2..59b3f87 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -17,12 +17,18 @@ enum grep_context {
        GREP_CONTEXT_BODY,
 };
 
+enum grep_header_field {
+       GREP_HEADER_AUTHOR = 0,
+       GREP_HEADER_COMMITTER,
+};
+
 struct grep_pat {
        struct grep_pat *next;
        const char *origin;
        int no;
        enum grep_pat_token token;
        const char *pattern;
+       enum grep_header_field field;
        regex_t regexp;
 };
 
@@ -74,6 +80,7 @@ struct grep_opt {
 };
 
 extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
+extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
 extern void compile_grep_patterns(struct grep_opt *opt);
 extern void free_grep_patterns(struct grep_opt *opt);
 extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
diff --git a/http.c b/http.c
index 1108ab4..a97fdf5 100644 (file)
--- a/http.c
+++ b/http.c
@@ -165,7 +165,16 @@ static CURL* get_curl_handle(void)
 {
        CURL* result = curl_easy_init();
 
-       curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
+       if (!curl_ssl_verify) {
+               curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0);
+               curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 0);
+       } else {
+               /* Verify authenticity of the peer's certificate */
+               curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 1);
+               /* The name in the cert must match whom we tried to connect */
+               curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2);
+       }
+
 #if LIBCURL_VERSION_NUM >= 0x070907
        curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
 #endif
index 728af7d..a6e91fe 100644 (file)
@@ -654,7 +654,7 @@ static void parse_pack_objects(unsigned char *sha1)
        }
 }
 
-static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_crc)
+static int write_compressed(struct sha1file *f, void *in, unsigned int size)
 {
        z_stream stream;
        unsigned long maxsize;
@@ -674,13 +674,12 @@ static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_c
        deflateEnd(&stream);
 
        size = stream.total_out;
-       write_or_die(fd, out, size);
-       *obj_crc = crc32(*obj_crc, out, size);
+       sha1write(f, out, size);
        free(out);
        return size;
 }
 
-static struct object_entry *append_obj_to_pack(
+static struct object_entry *append_obj_to_pack(struct sha1file *f,
                               const unsigned char *sha1, void *buf,
                               unsigned long size, enum object_type type)
 {
@@ -696,15 +695,15 @@ static struct object_entry *append_obj_to_pack(
                s >>= 7;
        }
        header[n++] = c;
-       write_or_die(output_fd, header, n);
-       obj[0].idx.crc32 = crc32(0, Z_NULL, 0);
-       obj[0].idx.crc32 = crc32(obj[0].idx.crc32, header, n);
+       crc32_begin(f);
+       sha1write(f, header, n);
        obj[0].size = size;
        obj[0].hdr_size = n;
        obj[0].type = type;
        obj[0].real_type = type;
        obj[1].idx.offset = obj[0].idx.offset + n;
-       obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32);
+       obj[1].idx.offset += write_compressed(f, buf, size);
+       obj[0].idx.crc32 = crc32_end(f);
        hashcpy(obj->idx.sha1, sha1);
        return obj;
 }
@@ -716,7 +715,7 @@ static int delta_pos_compare(const void *_a, const void *_b)
        return a->obj_no - b->obj_no;
 }
 
-static void fix_unresolved_deltas(int nr_unresolved)
+static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved)
 {
        struct delta_entry **sorted_by_pos;
        int i, n = 0;
@@ -754,8 +753,8 @@ static void fix_unresolved_deltas(int nr_unresolved)
                if (check_sha1_signature(d->base.sha1, base_obj.data,
                                base_obj.size, typename(type)))
                        die("local object %s is corrupt", sha1_to_hex(d->base.sha1));
-               base_obj.obj = append_obj_to_pack(d->base.sha1, base_obj.data,
-                       base_obj.size, type);
+               base_obj.obj = append_obj_to_pack(f, d->base.sha1,
+                                       base_obj.data, base_obj.size, type);
                link_base_data(NULL, &base_obj);
 
                find_delta_children(&d->base, &first, &last);
@@ -875,7 +874,7 @@ int main(int argc, char **argv)
        const char *keep_name = NULL, *keep_msg = NULL;
        char *index_name_buf = NULL, *keep_name_buf = NULL;
        struct pack_idx_entry **idx_objects;
-       unsigned char sha1[20];
+       unsigned char pack_sha1[20];
        int nongit = 0;
 
        setup_git_directory_gently(&nongit);
@@ -962,13 +961,15 @@ int main(int argc, char **argv)
        parse_pack_header();
        objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
        deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
-       parse_pack_objects(sha1);
+       parse_pack_objects(pack_sha1);
        if (nr_deltas == nr_resolved_deltas) {
                stop_progress(&progress);
                /* Flush remaining pack final 20-byte SHA1. */
                flush();
        } else {
                if (fix_thin_pack) {
+                       struct sha1file *f;
+                       unsigned char read_sha1[20], tail_sha1[20];
                        char msg[48];
                        int nr_unresolved = nr_deltas - nr_resolved_deltas;
                        int nr_objects_initial = nr_objects;
@@ -977,12 +978,19 @@ int main(int argc, char **argv)
                        objects = xrealloc(objects,
                                           (nr_objects + nr_unresolved + 1)
                                           * sizeof(*objects));
-                       fix_unresolved_deltas(nr_unresolved);
+                       f = sha1fd(output_fd, curr_pack);
+                       fix_unresolved_deltas(f, nr_unresolved);
                        sprintf(msg, "completed with %d local objects",
                                nr_objects - nr_objects_initial);
                        stop_progress_msg(&progress, msg);
-                       fixup_pack_header_footer(output_fd, sha1,
-                                                curr_pack, nr_objects);
+                       sha1close(f, tail_sha1, 0);
+                       hashcpy(read_sha1, pack_sha1);
+                       fixup_pack_header_footer(output_fd, pack_sha1,
+                                                curr_pack, nr_objects,
+                                                read_sha1, consumed_bytes-20);
+                       if (hashcmp(read_sha1, tail_sha1) != 0)
+                               die("Unexpected tail checksum for %s "
+                                   "(disk corruption?)", curr_pack);
                }
                if (nr_deltas != nr_resolved_deltas)
                        die("pack has %d unresolved deltas",
@@ -995,13 +1003,13 @@ int main(int argc, char **argv)
        idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
        for (i = 0; i < nr_objects; i++)
                idx_objects[i] = &objects[i].idx;
-       curr_index = write_idx_file(index_name, idx_objects, nr_objects, sha1);
+       curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
        free(idx_objects);
 
        final(pack_name, curr_pack,
                index_name, curr_index,
                keep_name, keep_msg,
-               sha1);
+               pack_sha1);
        free(objects);
        free(index_name_buf);
        free(keep_name_buf);
index ddcfd37..939ed56 100644 (file)
@@ -144,41 +144,93 @@ char *write_idx_file(char *index_name, struct pack_idx_entry **objects,
        return index_name;
 }
 
+/*
+ * Update pack header with object_count and compute new SHA1 for pack data
+ * associated to pack_fd, and write that SHA1 at the end.  That new SHA1
+ * is also returned in new_pack_sha1.
+ *
+ * If partial_pack_sha1 is non null, then the SHA1 of the existing pack
+ * (without the header update) is computed and validated against the
+ * one provided in partial_pack_sha1.  The validation is performed at
+ * partial_pack_offset bytes in the pack file.  The SHA1 of the remaining
+ * data (i.e. from partial_pack_offset to the end) is then computed and
+ * returned in partial_pack_sha1.
+ *
+ * Note that new_pack_sha1 is updated last, so both new_pack_sha1 and
+ * partial_pack_sha1 can refer to the same buffer if the caller is not
+ * interested in the resulting SHA1 of pack data above partial_pack_offset.
+ */
 void fixup_pack_header_footer(int pack_fd,
-                        unsigned char *pack_file_sha1,
+                        unsigned char *new_pack_sha1,
                         const char *pack_name,
-                        uint32_t object_count)
+                        uint32_t object_count,
+                        unsigned char *partial_pack_sha1,
+                        off_t partial_pack_offset)
 {
-       static const int buf_sz = 128 * 1024;
-       SHA_CTX c;
+       int aligned_sz, buf_sz = 8 * 1024;
+       SHA_CTX old_sha1_ctx, new_sha1_ctx;
        struct pack_header hdr;
        char *buf;
 
+       SHA1_Init(&old_sha1_ctx);
+       SHA1_Init(&new_sha1_ctx);
+
        if (lseek(pack_fd, 0, SEEK_SET) != 0)
-               die("Failed seeking to start: %s", strerror(errno));
+               die("Failed seeking to start of %s: %s", pack_name, strerror(errno));
        if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
                die("Unable to reread header of %s: %s", pack_name, strerror(errno));
        if (lseek(pack_fd, 0, SEEK_SET) != 0)
-               die("Failed seeking to start: %s", strerror(errno));
+               die("Failed seeking to start of %s: %s", pack_name, strerror(errno));
+       SHA1_Update(&old_sha1_ctx, &hdr, sizeof(hdr));
        hdr.hdr_entries = htonl(object_count);
+       SHA1_Update(&new_sha1_ctx, &hdr, sizeof(hdr));
        write_or_die(pack_fd, &hdr, sizeof(hdr));
-
-       SHA1_Init(&c);
-       SHA1_Update(&c, &hdr, sizeof(hdr));
+       partial_pack_offset -= sizeof(hdr);
 
        buf = xmalloc(buf_sz);
+       aligned_sz = buf_sz - sizeof(hdr);
        for (;;) {
-               ssize_t n = xread(pack_fd, buf, buf_sz);
+               ssize_t m, n;
+               m = (partial_pack_sha1 && partial_pack_offset < aligned_sz) ?
+                       partial_pack_offset : aligned_sz;
+               n = xread(pack_fd, buf, m);
                if (!n)
                        break;
                if (n < 0)
                        die("Failed to checksum %s: %s", pack_name, strerror(errno));
-               SHA1_Update(&c, buf, n);
+               SHA1_Update(&new_sha1_ctx, buf, n);
+
+               aligned_sz -= n;
+               if (!aligned_sz)
+                       aligned_sz = buf_sz;
+
+               if (!partial_pack_sha1)
+                       continue;
+
+               SHA1_Update(&old_sha1_ctx, buf, n);
+               partial_pack_offset -= n;
+               if (partial_pack_offset == 0) {
+                       unsigned char sha1[20];
+                       SHA1_Final(sha1, &old_sha1_ctx);
+                       if (hashcmp(sha1, partial_pack_sha1) != 0)
+                               die("Unexpected checksum for %s "
+                                   "(disk corruption?)", pack_name);
+                       /*
+                        * Now let's compute the SHA1 of the remainder of the
+                        * pack, which also means making partial_pack_offset
+                        * big enough not to matter anymore.
+                        */
+                       SHA1_Init(&old_sha1_ctx);
+                       partial_pack_offset = ~partial_pack_offset;
+                       partial_pack_offset -= MSB(partial_pack_offset, 1);
+               }
        }
        free(buf);
 
-       SHA1_Final(pack_file_sha1, &c);
-       write_or_die(pack_fd, pack_file_sha1, 20);
+       if (partial_pack_sha1)
+               SHA1_Final(partial_pack_sha1, &old_sha1_ctx);
+       SHA1_Final(new_pack_sha1, &new_sha1_ctx);
+       write_or_die(pack_fd, new_pack_sha1, 20);
        fsync_or_die(pack_fd, pack_name);
 }
 
diff --git a/pack.h b/pack.h
index 76e6aa2..a883334 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -58,7 +58,7 @@ struct pack_idx_entry {
 extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
 extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
 extern int verify_pack(struct packed_git *);
-extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t);
+extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
 extern char *index_pack_lockfile(int fd);
 
 #define PH_ERROR_EOF           (-1)
index 102e6a4..6aab712 100644 (file)
@@ -58,7 +58,7 @@ require Exporter;
                 command_bidi_pipe command_close_bidi_pipe
                 version exec_path hash_object git_cmd_try
                 remote_refs
-                temp_acquire temp_release temp_reset);
+                temp_acquire temp_release temp_reset temp_path);
 
 
 =head1 DESCRIPTION
@@ -937,7 +937,7 @@ sub _close_cat_blob {
 
 { # %TEMP_* Lexical Context
 
-my (%TEMP_LOCKS, %TEMP_FILES);
+my (%TEMP_FILEMAP, %TEMP_FILES);
 
 =item temp_acquire ( NAME )
 
@@ -965,7 +965,7 @@ sub temp_acquire {
 
        my $temp_fd = _temp_cache($name);
 
-       $TEMP_LOCKS{$temp_fd} = 1;
+       $TEMP_FILES{$temp_fd}{locked} = 1;
        $temp_fd;
 }
 
@@ -991,16 +991,16 @@ the same string.
 sub temp_release {
        my ($self, $temp_fd, $trunc) = _maybe_self(@_);
 
-       if (ref($temp_fd) ne 'File::Temp') {
+       if (exists $TEMP_FILEMAP{$temp_fd}) {
                $temp_fd = $TEMP_FILES{$temp_fd};
        }
-       unless ($TEMP_LOCKS{$temp_fd}) {
+       unless ($TEMP_FILES{$temp_fd}{locked}) {
                carp "Attempt to release temp file '",
                        $temp_fd, "' that has not been locked";
        }
        temp_reset($temp_fd) if $trunc and $temp_fd->opened;
 
-       $TEMP_LOCKS{$temp_fd} = 0;
+       $TEMP_FILES{$temp_fd}{locked} = 0;
        undef;
 }
 
@@ -1009,9 +1009,9 @@ sub _temp_cache {
 
        _verify_require();
 
-       my $temp_fd = \$TEMP_FILES{$name};
+       my $temp_fd = \$TEMP_FILEMAP{$name};
        if (defined $$temp_fd and $$temp_fd->opened) {
-               if ($TEMP_LOCKS{$$temp_fd}) {
+               if ($TEMP_FILES{$$temp_fd}{locked}) {
                        throw Error::Simple("Temp file with moniker '",
                                $name, "' already in use");
                }
@@ -1021,12 +1021,13 @@ sub _temp_cache {
                        carp "Temp file '", $name,
                                "' was closed. Opening replacement.";
                }
-               $$temp_fd = File::Temp->new(
-                       TEMPLATE => 'Git_XXXXXX',
-                       DIR => File::Spec->tmpdir
+               my $fname;
+               ($$temp_fd, $fname) = File::Temp->tempfile(
+                       'Git_XXXXXX', UNLINK => 1
                        ) or throw Error::Simple("couldn't open new temp file");
                $$temp_fd->autoflush;
                binmode $$temp_fd;
+               $TEMP_FILES{$$temp_fd}{fname} = $fname;
        }
        $$temp_fd;
 }
@@ -1053,8 +1054,25 @@ sub temp_reset {
                or throw Error::Simple("expected file position to be reset");
 }
 
+=item temp_path ( NAME )
+
+=item temp_path ( FILEHANDLE )
+
+Returns the filename associated with the given tempfile.
+
+=cut
+
+sub temp_path {
+       my ($self, $temp_fd) = _maybe_self(@_);
+
+       if (exists $TEMP_FILEMAP{$temp_fd}) {
+               $temp_fd = $TEMP_FILEMAP{$temp_fd};
+       }
+       $TEMP_FILES{$temp_fd}{fname};
+}
+
 sub END {
-       unlink values %TEMP_FILES if %TEMP_FILES;
+       unlink values %TEMP_FILEMAP if %TEMP_FILEMAP;
 }
 
 } # %TEMP_* Lexical Context
index 36291b6..270294a 100644 (file)
@@ -953,22 +953,9 @@ static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token
        append_grep_pattern(&revs->grep_filter, ptn, "command line", 0, what);
 }
 
-static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)
+static void add_header_grep(struct rev_info *revs, enum grep_header_field field, const char *pattern)
 {
-       char *pat;
-       const char *prefix;
-       int patlen, fldlen;
-
-       fldlen = strlen(field);
-       patlen = strlen(pattern);
-       pat = xmalloc(patlen + fldlen + 10);
-       prefix = ".*";
-       if (*pattern == '^') {
-               prefix = "";
-               pattern++;
-       }
-       sprintf(pat, "^%s %s%s", field, prefix, pattern);
-       add_grep(revs, pat, GREP_PATTERN_HEAD);
+       append_header_grep_pattern(&revs->grep_filter, field, pattern);
 }
 
 static void add_message_grep(struct rev_info *revs, const char *pattern)
@@ -1154,9 +1141,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
         * Grepping the commit log
         */
        else if (!prefixcmp(arg, "--author=")) {
-               add_header_grep(revs, "author", arg+9);
+               add_header_grep(revs, GREP_HEADER_AUTHOR, arg+9);
        } else if (!prefixcmp(arg, "--committer=")) {
-               add_header_grep(revs, "committer", arg+12);
+               add_header_grep(revs, GREP_HEADER_COMMITTER, arg+12);
        } else if (!prefixcmp(arg, "--grep=")) {
                add_message_grep(revs, arg+7);
        } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
index 477d3fb..e2cb342 100644 (file)
@@ -2136,7 +2136,9 @@ static void write_sha1_file_prepare(const void *buf, unsigned long len,
  */
 int move_temp_to_file(const char *tmpfile, const char *filename)
 {
-       int ret = link(tmpfile, filename);
+       int ret = 0;
+       if (link(tmpfile, filename))
+               ret = errno;
 
        /*
         * Coda hack - coda doesn't like cross-directory links,
index a841df2..5b5f288 100644 (file)
@@ -135,3 +135,20 @@ close $wr or die $!;
 close $rd or die $!;
 EOF
 }
+
+require_svnserve () {
+    if test -z "$SVNSERVE_PORT"
+    then
+        say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)'
+        test_done
+        exit
+    fi
+}
+
+start_svnserve () {
+    svnserve --listen-port $SVNSERVE_PORT \
+             --root "$rawsvnrepo" \
+             --listen-once \
+             --listen-host 127.0.0.1 &
+}
+
diff --git a/t/t0024-crlf-archive.sh b/t/t0024-crlf-archive.sh
new file mode 100644 (file)
index 0000000..e533039
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='respect crlf in git archive'
+
+. ./test-lib.sh
+UNZIP=${UNZIP:-unzip}
+
+test_expect_success setup '
+
+       git config core.autocrlf true
+
+       printf "CRLF line ending\r\nAnd another\r\n" > sample &&
+       git add sample &&
+
+       test_tick &&
+       git commit -m Initial
+
+'
+
+test_expect_success 'tar archive' '
+
+       git archive --format=tar HEAD |
+       ( mkdir untarred && cd untarred && "$TAR" -xf - )
+
+       test_cmp sample untarred/sample
+
+'
+
+"$UNZIP" -v >/dev/null 2>&1
+if [ $? -eq 127 ]; then
+       echo "Skipping ZIP test, because unzip was not found"
+       test_done
+       exit
+fi
+
+test_expect_success 'zip archive' '
+
+       git archive --format=zip HEAD >test.zip &&
+
+       ( mkdir unzipped && cd unzipped && unzip ../test.zip ) &&
+
+       test_cmp sample unzipped/sample
+
+'
+
+test_done
index 833d6cb..18bcd97 100755 (executable)
@@ -57,4 +57,10 @@ test_expect_success 'last regexp must not be negated' '
        test_must_fail git diff --no-index Beer.java Beer-correct.java
 '
 
+test_expect_success 'alternation in pattern' '
+       git config diff.java.funcname "^[       ]*\\(\\(public\\|static\\).*\\)$"
+       git diff --no-index Beer.java Beer-correct.java |
+       grep "^@@.*@@ public static void main("
+'
+
 test_done
index 7eae1f4..84a1fe3 100755 (executable)
@@ -178,4 +178,16 @@ test_expect_success 'trailing empty lines (2)' '
 
 '
 
+test_expect_success 'do not color trailing cr in context' '
+       git config --unset core.whitespace
+       rm -f .gitattributes &&
+       echo AAAQ | tr Q "\015" >G &&
+       git add G &&
+       echo BBBQ | tr Q "\015" >>G
+       git diff --color G | tr "\015" Q >output &&
+       grep "BBB.*${blue_grep}Q" output &&
+       grep "AAA.*\[mQ" output
+
+'
+
 test_done
index c8b4f65..5e359cb 100755 (executable)
@@ -22,6 +22,7 @@ test_expect_success setup '
        mkdir t &&
        echo test >t/t &&
        git add file x y z t/t &&
+       test_tick &&
        git commit -m initial
 '
 
@@ -113,4 +114,54 @@ do
 
 done
 
+test_expect_success 'log grep setup' '
+       echo a >>file &&
+       test_tick &&
+       GIT_AUTHOR_NAME="With * Asterisk" \
+       GIT_AUTHOR_EMAIL="xyzzy@frotz.com" \
+       git commit -a -m "second" &&
+
+       echo a >>file &&
+       test_tick &&
+       git commit -a -m "third"
+
+'
+
+test_expect_success 'log grep (1)' '
+       git log --author=author --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (2)' '
+       git log --author=" * " -F --pretty=tformat:%s >actual &&
+       ( echo second ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (3)' '
+       git log --author="^A U" --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (4)' '
+       git log --author="frotz\.com>$" --pretty=tformat:%s >actual &&
+       ( echo second ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (5)' '
+       git log --author=Thor -F --grep=Thu --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (6)' '
+       git log --author=-0700  --pretty=tformat:%s >actual &&
+       >expect &&
+       test_cmp expect actual
+
+'
+
 test_done
index 2e2b3bf..fbec70d 100755 (executable)
@@ -337,4 +337,26 @@ test_expect_success \
     test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
     test_must_fail git checkout --track -b track'
 
+test_expect_success 'checkout an unmerged path should fail' '
+       rm -f .git/index &&
+       O=$(echo original | git hash-object -w --stdin) &&
+       A=$(echo ourside | git hash-object -w --stdin) &&
+       B=$(echo theirside | git hash-object -w --stdin) &&
+       (
+               echo "100644 $A 0       fild" &&
+               echo "100644 $O 1       file" &&
+               echo "100644 $A 2       file" &&
+               echo "100644 $B 3       file" &&
+               echo "100644 $A 0       filf"
+       ) | git update-index --index-info &&
+       echo "none of the above" >sample &&
+       cat sample >fild &&
+       cat sample >file &&
+       cat sample >filf &&
+       test_must_fail git checkout fild file filf &&
+       test_cmp sample fild &&
+       test_cmp sample filf &&
+       test_cmp sample file
+'
+
 test_done
index 469bff8..63bfc6d 100755 (executable)
@@ -141,7 +141,7 @@ EOF
 
 test_expect_success \
     'validate git rev-list output.' \
-    'diff current expected'
+    'test_cmp expected current'
 
 test_expect_success 'partial commit that involves removal (1)' '
 
@@ -151,7 +151,7 @@ test_expect_success 'partial commit that involves removal (1)' '
        git commit -m "Partial: add elif" elif &&
        git diff-tree --name-status HEAD^ HEAD >current &&
        echo "A elif" >expected &&
-       diff expected current
+       test_cmp expected current
 
 '
 
@@ -160,7 +160,7 @@ test_expect_success 'partial commit that involves removal (2)' '
        git commit -m "Partial: remove file" file &&
        git diff-tree --name-status HEAD^ HEAD >current &&
        echo "D file" >expected &&
-       diff expected current
+       test_cmp expected current
 
 '
 
@@ -171,7 +171,7 @@ test_expect_success 'partial commit that involves removal (3)' '
        git commit -m "Partial: modify elif" elif &&
        git diff-tree --name-status HEAD^ HEAD >current &&
        echo "M elif" >expected &&
-       diff expected current
+       test_cmp expected current
 
 '
 
@@ -187,7 +187,7 @@ test_expect_success 'amend commit to fix author' '
                expected &&
        git commit --amend --author="$author" &&
        git cat-file -p HEAD > current &&
-       diff expected current
+       test_cmp expected current
 
 '
 
@@ -256,7 +256,7 @@ test_expect_success 'amend commit to fix author' '
                expected &&
        git commit --amend --author="$author" &&
        git cat-file -p HEAD > current &&
-       diff expected current
+       test_cmp expected current
 
 '
 
index ae78e33..c2b24a4 100755 (executable)
@@ -12,19 +12,7 @@ test_description='git-svn dcommit new files over svn:// test'
 
 . ./lib-git-svn.sh
 
-if test -z "$SVNSERVE_PORT"
-then
-       say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)'
-       test_done
-       exit
-fi
-
-start_svnserve () {
-       svnserve --listen-port $SVNSERVE_PORT \
-                --root "$rawsvnrepo" \
-                --listen-once \
-                --listen-host 127.0.0.1 &
-}
+require_svnserve
 
 test_expect_success 'start tracking an empty repo' '
        svn mkdir -m "empty dir" "$svnrepo"/empty-dir &&
index 3281cbd..43ceb75 100755 (executable)
@@ -6,6 +6,10 @@
 test_description='git-svn funky branch names'
 . ./lib-git-svn.sh
 
+# Abo-Uebernahme (Bug #994)
+scary_uri='Abo-Uebernahme%20%28Bug%20%23994%29'
+scary_ref='Abo-Uebernahme%20(Bug%20#994)'
+
 test_expect_success 'setup svnrepo' '
        mkdir project project/trunk project/branches project/tags &&
        echo foo > project/trunk/foo &&
@@ -15,6 +19,8 @@ test_expect_success 'setup svnrepo' '
                        "$svnrepo/pr ject/branches/fun plugin" &&
        svn cp -m "more fun!" "$svnrepo/pr ject/branches/fun plugin" \
                              "$svnrepo/pr ject/branches/more fun plugin!" &&
+       svn cp -m "scary" "$svnrepo/pr ject/branches/fun plugin" \
+                     "$svnrepo/pr ject/branches/$scary_uri" &&
        start_httpd
        '
 
@@ -23,6 +29,7 @@ test_expect_success 'test clone with funky branch names' '
        cd project &&
                git rev-parse "refs/remotes/fun%20plugin" &&
                git rev-parse "refs/remotes/more%20fun%20plugin!" &&
+               git rev-parse "refs/remotes/$scary_ref" &&
        cd ..
        '
 
@@ -35,6 +42,15 @@ test_expect_success 'test dcommit to funky branch' "
        cd ..
        "
 
+test_expect_success 'test dcommit to scary branch' '
+       cd project &&
+       git reset --hard "refs/remotes/$scary_ref" &&
+       echo urls are scary >> foo &&
+       git commit -m "eep" -- foo &&
+       git svn dcommit &&
+       cd ..
+       '
+
 stop_httpd
 
 test_done
diff --git a/t/t9126-git-svn-follow-deleted-readded-directory.sh b/t/t9126-git-svn-follow-deleted-readded-directory.sh
new file mode 100755 (executable)
index 0000000..edec640
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# Copyright (c) 2008 Alec Berryman
+
+test_description='git svn fetch repository with deleted and readded directory'
+
+. ./lib-git-svn.sh
+
+# Don't run this by default; it opens up a port.
+require_svnserve
+
+test_expect_success 'load repository' '
+    svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9126/follow-deleted-readded.dump
+    '
+
+test_expect_success 'fetch repository' '
+    start_svnserve &&
+    git svn init svn://127.0.0.1:$SVNSERVE_PORT &&
+    git svn fetch
+    '
+
+test_done
diff --git a/t/t9126/follow-deleted-readded.dump b/t/t9126/follow-deleted-readded.dump
new file mode 100644 (file)
index 0000000..19da5d1
--- /dev/null
@@ -0,0 +1,201 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 1807dc6f-c693-4cda-9710-00e1be8c1f21
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2008-09-14T19:53:13.006748Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 12
+Create trunk
+K 10
+svn:author
+V 4
+alec
+K 8
+svn:date
+V 27
+2008-09-14T19:53:13.239689Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 2
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 20
+Create trunk/project
+K 10
+svn:author
+V 4
+alec
+K 8
+svn:date
+V 27
+2008-09-14T19:53:13.548860Z
+PROPS-END
+
+Node-path: trunk/project
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Revision-number: 3
+Prop-content-length: 111
+Content-length: 111
+
+K 7
+svn:log
+V 12
+add new file
+K 10
+svn:author
+V 4
+alec
+K 8
+svn:date
+V 27
+2008-09-14T19:53:15.433630Z
+PROPS-END
+
+Node-path: trunk/project/foo
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: d3b07384d113edec49eaa6238ad5ff00
+Content-length: 14
+
+PROPS-END
+foo
+
+
+Revision-number: 4
+Prop-content-length: 116
+Content-length: 116
+
+K 7
+svn:log
+V 17
+change foo to bar
+K 10
+svn:author
+V 4
+alec
+K 8
+svn:date
+V 27
+2008-09-14T19:53:17.339884Z
+PROPS-END
+
+Node-path: trunk/project/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 4
+Text-content-md5: c157a79031e1c40f85931829bc5fc552
+Content-length: 4
+
+bar
+
+
+Revision-number: 5
+Prop-content-length: 114
+Content-length: 114
+
+K 7
+svn:log
+V 15
+don't like that
+K 10
+svn:author
+V 4
+alec
+K 8
+svn:date
+V 27
+2008-09-14T19:53:19.335001Z
+PROPS-END
+
+Node-path: trunk/project
+Node-action: delete
+
+
+Revision-number: 6
+Prop-content-length: 110
+Content-length: 110
+
+K 7
+svn:log
+V 11
+reset trunk
+K 10
+svn:author
+V 4
+alec
+K 8
+svn:date
+V 27
+2008-09-14T19:53:19.845897Z
+PROPS-END
+
+Node-path: trunk/project
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 4
+Node-copyfrom-path: trunk/project
+
+
+Revision-number: 7
+Prop-content-length: 113
+Content-length: 113
+
+K 7
+svn:log
+V 14
+change to quux
+K 10
+svn:author
+V 4
+alec
+K 8
+svn:date
+V 27
+2008-09-14T19:53:21.367947Z
+PROPS-END
+
+Node-path: trunk/project/foo
+Node-kind: file
+Node-action: change
+Text-content-length: 5
+Text-content-md5: d3b07a382ec010c01889250fce66fb13
+Content-length: 5
+
+quux
+
+
index 4d23125..504f95a 100755 (executable)
@@ -9,7 +9,6 @@ use Test::More qw(no_plan);
 
 use Cwd;
 use File::Basename;
-use File::Temp;
 
 BEGIN { use_ok('Git') }
 
@@ -38,7 +37,7 @@ is($r->get_color("color.test.slot1", "red"), $ansi_green, "get_color");
 # Failure cases for config:
 # Save and restore STDERR; we will probably extract this into a
 # "dies_ok" method and possibly move the STDERR handling to Git.pm.
-open our $tmpstderr, ">&", STDERR or die "cannot save STDERR"; close STDERR;
+open our $tmpstderr, ">&STDERR" or die "cannot save STDERR"; close STDERR;
 eval { $r->config("test.dupstring") };
 ok($@, "config: duplicate entry in scalar context fails");
 eval { $r->config_bool("test.boolother") };
@@ -69,21 +68,25 @@ is($r->ident_person("Name", "email", "123 +0000"), "Name <email>",
 
 # objects and hashes
 ok(our $file1hash = $r->command_oneline('rev-parse', "HEAD:file1"), "(get file hash)");
-our $tmpfile = File::Temp->new;
-is($r->cat_blob($file1hash, $tmpfile), 15, "cat_blob: size");
+my $tmpfile = "file.tmp";
+open TEMPFILE, "+>$tmpfile" or die "Can't open $tmpfile: $!";
+is($r->cat_blob($file1hash, \*TEMPFILE), 15, "cat_blob: size");
 our $blobcontents;
-{ local $/; seek $tmpfile, 0, 0; $blobcontents = <$tmpfile>; }
+{ local $/; seek TEMPFILE, 0, 0; $blobcontents = <TEMPFILE>; }
 is($blobcontents, "changed file 1\n", "cat_blob: data");
-seek $tmpfile, 0, 0;
+close TEMPFILE or die "Failed writing to $tmpfile: $!";
 is(Git::hash_object("blob", $tmpfile), $file1hash, "hash_object: roundtrip");
-$tmpfile = File::Temp->new();
-print $tmpfile my $test_text = "test blob, to be inserted\n";
+open TEMPFILE, ">$tmpfile" or die "Can't open $tmpfile: $!";
+print TEMPFILE my $test_text = "test blob, to be inserted\n";
+close TEMPFILE or die "Failed writing to $tmpfile: $!";
 like(our $newhash = $r->hash_and_insert_object($tmpfile), qr/[0-9a-fA-F]{40}/,
      "hash_and_insert_object: returns hash");
-$tmpfile = File::Temp->new;
-is($r->cat_blob($newhash, $tmpfile), length $test_text, "cat_blob: roundtrip size");
-{ local $/; seek $tmpfile, 0, 0; $blobcontents = <$tmpfile>; }
+open TEMPFILE, "+>$tmpfile" or die "Can't open $tmpfile: $!";
+is($r->cat_blob($newhash, \*TEMPFILE), length $test_text, "cat_blob: roundtrip size");
+{ local $/; seek TEMPFILE, 0, 0; $blobcontents = <TEMPFILE>; }
 is($blobcontents, $test_text, "cat_blob: roundtrip data");
+close TEMPFILE;
+unlink $tmpfile;
 
 # paths
 is($r->repo_path, "./.git", "repo_path");