Imported Upstream version 2.3.3 upstream/2.3.3
authorDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 3 Mar 2021 06:14:54 +0000 (15:14 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 3 Mar 2021 06:14:54 +0000 (15:14 +0900)
37 files changed:
Documentation/RelNotes/2.3.3.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/git-apply.txt
Documentation/git-clean.txt
Documentation/git-remote.txt
Documentation/git-submodule.txt
Documentation/git.txt
Documentation/gitmodules.txt
GIT-VERSION-GEN
RelNotes
builtin/apply.c
builtin/grep.c
connect.c
ctype.c
daemon.c
diff.c
diffcore-rename.c
git-compat-util.h
kwset.c
kwset.h
t/diff-lib.sh
t/diff-lib/COPYING [new file with mode: 0644]
t/diff-lib/README [new file with mode: 0644]
t/t4003-diff-rename-1.sh
t/t4005-diff-rename-2.sh
t/t4007-rename-3.sh
t/t4008-diff-break-rewrite.sh
t/t4009-diff-rename-4.sh
t/t4010-diff-pathspec.sh
t/t4047-diff-dirstat.sh
t/t4058-diff-duplicates.sh [new file with mode: 0755]
t/t4122-apply-symlink-inside.sh
t/t4139-apply-escape.sh [new file with mode: 0755]
t/t5516-fetch-push.sh
t/t5570-git-daemon.sh
t/t9300-fast-import.sh
t/test-lib-functions.sh

diff --git a/Documentation/RelNotes/2.3.3.txt b/Documentation/RelNotes/2.3.3.txt
new file mode 100644 (file)
index 0000000..5ef1264
--- /dev/null
@@ -0,0 +1,39 @@
+Git v2.3.3 Release Notes
+========================
+
+Fixes since v2.3.2
+------------------
+
+ * A corrupt input to "git diff -M" used cause us to segfault.
+
+ * The borrowed code in kwset API did not follow our usual convention
+   to use "unsigned char" to store values that range from 0-255.
+
+ * Description given by "grep -h" for its --exclude-standard option
+   was phrased poorly.
+
+ * Documentaton for "git remote add" mentioned "--tags" and
+   "--no-tags" and it was not clear that fetch from the remote in
+   the future will use the default behaviour when neither is given
+   to override it.
+
+ * "git diff --shortstat --dirstat=changes" showed a dirstat based on
+   lines that was never asked by the end user in addition to the
+   dirstat that the user asked for.
+
+ * The interaction between "git submodule update" and the
+   submodule.*.update configuration was not clearly documented.
+
+ * "git apply" was not very careful about reading from, removing,
+   updating and creating paths outside the working tree (under
+   --index/--cached) or the current directory (when used as a
+   replacement for GNU patch).
+
+ * "git daemon" looked up the hostname even when "%CH" and "%IP"
+   interpolations are not requested, which was unnecessary.
+
+ * The "interpolated-path" option of "git daemon" inserted any string
+   client declared on the "host=" capability request without checking.
+   Sanitize and limit %H and %CH to a saner and a valid DNS name.
+
+Also contains typofixes, documentation updates and trivial code clean-ups.
index 9307c98..5fbe940 100644 (file)
@@ -2408,12 +2408,16 @@ status.submodulesummary::
 
 submodule.<name>.path::
 submodule.<name>.url::
+       The path within this project and URL for a submodule. These
+       variables are initially populated by 'git submodule init'. See
+       linkgit:git-submodule[1] and linkgit:gitmodules[5] for
+       details.
+
 submodule.<name>.update::
-       The path within this project, URL, and the updating strategy
-       for a submodule.  These variables are initially populated
-       by 'git submodule init'; edit them to override the
-       URL and other values found in the `.gitmodules` file.  See
-       linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
+       The default update procedure for a submodule. This variable
+       is populated by `git submodule init` from the
+       linkgit:gitmodules[5] file. See description of 'update'
+       command in linkgit:git-submodule[1].
 
 submodule.<name>.branch::
        The remote branch name for a submodule, used by `git submodule
index f605327..9489664 100644 (file)
@@ -16,7 +16,7 @@ SYNOPSIS
          [--ignore-space-change | --ignore-whitespace ]
          [--whitespace=(nowarn|warn|fix|error|error-all)]
          [--exclude=<path>] [--include=<path>] [--directory=<root>]
-         [--verbose] [<patch>...]
+         [--verbose] [--unsafe-paths] [<patch>...]
 
 DESCRIPTION
 -----------
@@ -229,6 +229,16 @@ For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh`
 can be applied to the file in the working tree `modules/git-gui/git-gui.sh` by
 running `git apply --directory=modules/git-gui`.
 
+--unsafe-paths::
+       By default, a patch that affects outside the working area
+       (either a Git controlled working tree, or the current working
+       directory when "git apply" is used as a replacement of GNU
+       patch) is rejected as a mistake (or a mischief).
++
+When `git apply` is used as a "better GNU patch", the user can pass
+the `--unsafe-paths` option to override this safety check.  This option
+has no effect when `--index` or `--cached` is in use.
+
 Configuration
 -------------
 
index 94b6d19..641681f 100644 (file)
@@ -34,8 +34,12 @@ OPTIONS
 -f::
 --force::
        If the Git configuration variable clean.requireForce is not set
-       to false, 'git clean' will refuse to run unless given -f, -n or
-       -i.
+       to false, 'git clean' will refuse to delete files or directories
+       unless given -f, -n or -i. Git will refuse to delete directories
+       with .git sub directory or file unless a second -f
+       is given. This affects also git submodules where the storage area
+       of the removed submodule under .git/modules/ is not removed until
+       -f is given twice.
 
 -i::
 --interactive::
index a77607b..4c6d6de 100644 (file)
@@ -58,6 +58,9 @@ remote repository.
 With `--no-tags` option, `git fetch <name>` does not import tags from
 the remote repository.
 +
+By default, only tags on fetched branches are imported
+(see linkgit:git-fetch[1]).
++
 With `-t <branch>` option, instead of the default glob
 refspec for the remote to track all branches under
 the `refs/remotes/<name>/` namespace, a refspec to track only `<branch>`
index 8e6af65..2c25916 100644 (file)
@@ -154,27 +154,51 @@ If `--force` is specified, the submodule's work tree will be removed even if
 it contains local modifications.
 
 update::
-       Update the registered submodules, i.e. clone missing submodules and
-       checkout the commit specified in the index of the containing repository.
-       This will make the submodules HEAD be detached unless `--rebase` or
-       `--merge` is specified or the key `submodule.$name.update` is set to
-       `rebase`, `merge` or `none`. `none` can be overridden by specifying
-       `--checkout`. Setting the key `submodule.$name.update` to `!command`
-       will cause `command` to be run. `command` can be any arbitrary shell
-       command that takes a single argument, namely the sha1 to update to.
 +
+--
+Update the registered submodules to match what the superproject
+expects by cloning missing submodules and updating the working tree of
+the submodules. The "updating" can be done in several ways depending
+on command line options and the value of `submodule.<name>.update`
+configuration variable. Supported update procedures are:
+
+       checkout;; the commit recorded in the superproject will be
+           checked out in the submodule on a detached HEAD. This is
+           done when `--checkout` option is given, or no option is
+           given, and `submodule.<name>.update` is unset, or if it is
+           set to 'checkout'.
++
+If `--force` is specified, the submodule will be checked out (using
+`git checkout --force` if appropriate), even if the commit specified
+in the index of the containing repository already matches the commit
+checked out in the submodule.
+
+       rebase;; the current branch of the submodule will be rebased
+           onto the commit recorded in the superproject. This is done
+           when `--rebase` option is given, or no option is given, and
+           `submodule.<name>.update` is set to 'rebase'.
+
+       merge;; the commit recorded in the superproject will be merged
+           into the current branch in the submodule. This is done
+           when `--merge` option is given, or no option is given, and
+           `submodule.<name>.update` is set to 'merge'.
+
+       custom command;; arbitrary shell command that takes a single
+           argument (the sha1 of the commit recorded in the
+           superproject) is executed. This is done when no option is
+           given, and `submodule.<name>.update` has the form of
+           '!command'.
+
+When no option is given and `submodule.<name>.update` is set to 'none',
+the submodule is not updated.
+
 If the submodule is not yet initialized, and you just want to use the
 setting as stored in .gitmodules, you can automatically initialize the
 submodule with the `--init` option.
-+
+
 If `--recursive` is specified, this command will recurse into the
 registered submodules, and update any nested submodules within.
-+
-If `--force` is specified, the submodule will be checked out (using
-`git checkout --force` if appropriate), even if the commit specified in the
-index of the containing repository already matches the commit checked out in
-the submodule.
-
+--
 summary::
        Show commit summary between the given commit (defaults to HEAD) and
        working tree/index. For a submodule in question, a series of commits
@@ -238,10 +262,12 @@ OPTIONS
        When running add, allow adding an otherwise ignored submodule path.
        When running deinit the submodule work trees will be removed even if
        they contain local changes.
-       When running update, throw away local changes in submodules when
-       switching to a different commit; and always run a checkout operation
-       in the submodule, even if the commit listed in the index of the
-       containing repository matches the commit checked out in the submodule.
+       When running update (only effective with the checkout procedure),
+       throw away local changes in submodules when switching to a
+       different commit; and always run a checkout operation in the
+       submodule, even if the commit listed in the index of the
+       containing repository matches the commit checked out in the
+       submodule.
 
 --cached::
        This option is only valid for status and summary commands.  These
@@ -302,7 +328,7 @@ the submodule itself.
        Checkout the commit recorded in the superproject on a detached HEAD
        in the submodule. This is the default behavior, the main use of
        this option is to override `submodule.$name.update` when set to
-       `merge`, `rebase` or `none`.
+       a value other than `checkout`.
        If the key `submodule.$name.update` is either not explicitly set or
        set to `checkout`, this option is implicit.
 
index 52c4d2a..af30620 100644 (file)
@@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v2.3.2/git.html[documentation for release 2.3.2]
+* link:v2.3.3/git.html[documentation for release 2.3.3]
 
 * release notes for
+  link:RelNotes/2.3.3.txt[2.3.3],
   link:RelNotes/2.3.2.txt[2.3.2],
   link:RelNotes/2.3.1.txt[2.3.1],
   link:RelNotes/2.3.0.txt[2.3].
index f6c0dfd..ac70eca 100644 (file)
@@ -38,18 +38,15 @@ submodule.<name>.url::
 In addition, there are a number of optional keys:
 
 submodule.<name>.update::
-       Defines what to do when the submodule is updated by the superproject.
-       If 'checkout' (the default), the new commit specified in the
-       superproject will be checked out in the submodule on a detached HEAD.
-       If 'rebase', the current branch of the submodule will be rebased onto
-       the commit specified in the superproject. If 'merge', the commit
-       specified in the superproject will be merged into the current branch
-       in the submodule.
-       If 'none', the submodule with name `$name` will not be updated
-       by default.
-
-       This config option is overridden if 'git submodule update' is given
-       the '--merge', '--rebase' or '--checkout' options.
+       Defines the default update procedure for the named submodule,
+       i.e. how the submodule is updated by "git submodule update"
+       command in the superproject. This is only used by `git
+       submodule init` to initialize the configuration variable of
+       the same name. Allowed values here are 'checkout', 'rebase',
+       'merge' or 'none'. See description of 'update' command in
+       linkgit:git-submodule[1] for their meaning. Note that the
+       '!command' form is intentionally ignored here for security
+       reasons.
 
 submodule.<name>.branch::
        A remote branch name for tracking updates in the upstream submodule.
index 5007865..f92fe17 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.3.2
+DEF_VER=v2.3.3
 
 LF='
 '
index 689db12..9b1c4ac 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.3.2.txt
\ No newline at end of file
+Documentation/RelNotes/2.3.3.txt
\ No newline at end of file
index c484b53..0ca687f 100644 (file)
@@ -51,6 +51,7 @@ static int apply_verbosely;
 static int allow_overlap;
 static int no_add;
 static int threeway;
+static int unsafe_paths;
 static const char *fake_ancestor;
 static int line_termination = '\n';
 static unsigned int p_context = UINT_MAX;
@@ -3221,7 +3222,7 @@ static int load_patch_target(struct strbuf *buf,
                             const char *name,
                             unsigned expected_mode)
 {
-       if (cached) {
+       if (cached || check_index) {
                if (read_file_or_gitlink(ce, buf))
                        return error(_("read of %s failed"), name);
        } else if (name) {
@@ -3230,6 +3231,8 @@ static int load_patch_target(struct strbuf *buf,
                                return read_file_or_gitlink(ce, buf);
                        else
                                return SUBMODULE_PATCH_WITHOUT_INDEX;
+               } else if (has_symlink_leading_path(name, strlen(name))) {
+                       return error(_("reading from '%s' beyond a symbolic link"), name);
                } else {
                        if (read_old_data(st, name, buf))
                                return error(_("read of %s failed"), name);
@@ -3570,6 +3573,121 @@ static int check_to_create(const char *new_name, int ok_if_exists)
 }
 
 /*
+ * We need to keep track of how symlinks in the preimage are
+ * manipulated by the patches.  A patch to add a/b/c where a/b
+ * is a symlink should not be allowed to affect the directory
+ * the symlink points at, but if the same patch removes a/b,
+ * it is perfectly fine, as the patch removes a/b to make room
+ * to create a directory a/b so that a/b/c can be created.
+ */
+static struct string_list symlink_changes;
+#define SYMLINK_GOES_AWAY 01
+#define SYMLINK_IN_RESULT 02
+
+static uintptr_t register_symlink_changes(const char *path, uintptr_t what)
+{
+       struct string_list_item *ent;
+
+       ent = string_list_lookup(&symlink_changes, path);
+       if (!ent) {
+               ent = string_list_insert(&symlink_changes, path);
+               ent->util = (void *)0;
+       }
+       ent->util = (void *)(what | ((uintptr_t)ent->util));
+       return (uintptr_t)ent->util;
+}
+
+static uintptr_t check_symlink_changes(const char *path)
+{
+       struct string_list_item *ent;
+
+       ent = string_list_lookup(&symlink_changes, path);
+       if (!ent)
+               return 0;
+       return (uintptr_t)ent->util;
+}
+
+static void prepare_symlink_changes(struct patch *patch)
+{
+       for ( ; patch; patch = patch->next) {
+               if ((patch->old_name && S_ISLNK(patch->old_mode)) &&
+                   (patch->is_rename || patch->is_delete))
+                       /* the symlink at patch->old_name is removed */
+                       register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY);
+
+               if (patch->new_name && S_ISLNK(patch->new_mode))
+                       /* the symlink at patch->new_name is created or remains */
+                       register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT);
+       }
+}
+
+static int path_is_beyond_symlink_1(struct strbuf *name)
+{
+       do {
+               unsigned int change;
+
+               while (--name->len && name->buf[name->len] != '/')
+                       ; /* scan backwards */
+               if (!name->len)
+                       break;
+               name->buf[name->len] = '\0';
+               change = check_symlink_changes(name->buf);
+               if (change & SYMLINK_IN_RESULT)
+                       return 1;
+               if (change & SYMLINK_GOES_AWAY)
+                       /*
+                        * This cannot be "return 0", because we may
+                        * see a new one created at a higher level.
+                        */
+                       continue;
+
+               /* otherwise, check the preimage */
+               if (check_index) {
+                       struct cache_entry *ce;
+
+                       ce = cache_file_exists(name->buf, name->len, ignore_case);
+                       if (ce && S_ISLNK(ce->ce_mode))
+                               return 1;
+               } else {
+                       struct stat st;
+                       if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode))
+                               return 1;
+               }
+       } while (1);
+       return 0;
+}
+
+static int path_is_beyond_symlink(const char *name_)
+{
+       int ret;
+       struct strbuf name = STRBUF_INIT;
+
+       assert(*name_ != '\0');
+       strbuf_addstr(&name, name_);
+       ret = path_is_beyond_symlink_1(&name);
+       strbuf_release(&name);
+
+       return ret;
+}
+
+static void die_on_unsafe_path(struct patch *patch)
+{
+       const char *old_name = NULL;
+       const char *new_name = NULL;
+       if (patch->is_delete)
+               old_name = patch->old_name;
+       else if (!patch->is_new && !patch->is_copy)
+               old_name = patch->old_name;
+       if (!patch->is_delete)
+               new_name = patch->new_name;
+
+       if (old_name && !verify_path(old_name))
+               die(_("invalid path '%s'"), old_name);
+       if (new_name && !verify_path(new_name))
+               die(_("invalid path '%s'"), new_name);
+}
+
+/*
  * Check and apply the patch in-core; leave the result in patch->result
  * for the caller to write it out to the final destination.
  */
@@ -3656,6 +3774,22 @@ static int check_patch(struct patch *patch)
                }
        }
 
+       if (!unsafe_paths)
+               die_on_unsafe_path(patch);
+
+       /*
+        * An attempt to read from or delete a path that is beyond a
+        * symbolic link will be prevented by load_patch_target() that
+        * is called at the beginning of apply_data() so we do not
+        * have to worry about a patch marked with "is_delete" bit
+        * here.  We however need to make sure that the patch result
+        * is not deposited to a path that is beyond a symbolic link
+        * here.
+        */
+       if (!patch->is_delete && path_is_beyond_symlink(patch->new_name))
+               return error(_("affected file '%s' is beyond a symbolic link"),
+                            patch->new_name);
+
        if (apply_data(patch, &st, ce) < 0)
                return error(_("%s: patch does not apply"), name);
        patch->rejected = 0;
@@ -3666,6 +3800,7 @@ static int check_patch_list(struct patch *patch)
 {
        int err = 0;
 
+       prepare_symlink_changes(patch);
        prepare_fn_table(patch);
        while (patch) {
                if (apply_verbosely)
@@ -4404,6 +4539,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
                        N_("make sure the patch is applicable to the current index")),
                OPT_BOOL(0, "cached", &cached,
                        N_("apply a patch without touching the working tree")),
+               OPT_BOOL(0, "unsafe-paths", &unsafe_paths,
+                       N_("accept a patch that touches outside the working area")),
                OPT_BOOL(0, "apply", &force_apply,
                        N_("also apply the patch (use with --stat/--summary/--check)")),
                OPT_BOOL('3', "3way", &threeway,
@@ -4476,6 +4613,9 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
                        die(_("--cached outside a repository"));
                check_index = 1;
        }
+       if (check_index)
+               unsafe_paths = 0;
+
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
                int fd;
index 4063882..e77f7cf 100644 (file)
@@ -641,7 +641,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                OPT_BOOL(0, "untracked", &untracked,
                        N_("search in both tracked and untracked files")),
                OPT_SET_INT(0, "exclude-standard", &opt_exclude,
-                           N_("search also in ignored files"), 1),
+                           N_("ignore files specified via '.gitignore'"), 1),
                OPT_GROUP(""),
                OPT_BOOL('v', "invert-match", &opt.invert,
                        N_("show non-matching lines")),
index 2a5c400..d50f52a 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -669,10 +669,20 @@ struct child_process *git_connect(int fd[2], const char *url,
                printf("Diag: path=%s\n", path ? path : "NULL");
                conn = NULL;
        } else if (protocol == PROTO_GIT) {
+               /*
+                * Set up virtual host information based on where we will
+                * connect, unless the user has overridden us in
+                * the environment.
+                */
+               char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST");
+               if (target_host)
+                       target_host = xstrdup(target_host);
+               else
+                       target_host = xstrdup(hostandport);
+
                /* These underlying connection commands die() if they
                 * cannot connect.
                 */
-               char *target_host = xstrdup(hostandport);
                if (git_use_proxy(hostandport))
                        conn = git_proxy_connect(fd, hostandport);
                else
diff --git a/ctype.c b/ctype.c
index 0bfebb4..fc0225c 100644 (file)
--- a/ctype.c
+++ b/ctype.c
@@ -30,7 +30,7 @@ const unsigned char sane_ctype[256] = {
 };
 
 /* For case-insensitive kwset */
-const char tolower_trans_tbl[256] = {
+const unsigned char tolower_trans_tbl[256] = {
        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
index 54a03bd..c3edd96 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -61,6 +61,22 @@ static char *canon_hostname;
 static char *ip_address;
 static char *tcp_port;
 
+static int hostname_lookup_done;
+
+static void lookup_hostname(void);
+
+static const char *get_canon_hostname(void)
+{
+       lookup_hostname();
+       return canon_hostname;
+}
+
+static const char *get_ip_address(void)
+{
+       lookup_hostname();
+       return ip_address;
+}
+
 static void logreport(int priority, const char *err, va_list params)
 {
        if (log_syslog) {
@@ -106,6 +122,46 @@ static void NORETURN daemon_die(const char *err, va_list params)
        exit(1);
 }
 
+static void strbuf_addstr_or_null(struct strbuf *sb, const char *s)
+{
+       if (s)
+               strbuf_addstr(sb, s);
+}
+
+struct expand_path_context {
+       const char *directory;
+};
+
+static size_t expand_path(struct strbuf *sb, const char *placeholder, void *ctx)
+{
+       struct expand_path_context *context = ctx;
+
+       switch (placeholder[0]) {
+       case 'H':
+               strbuf_addstr_or_null(sb, hostname);
+               return 1;
+       case 'C':
+               if (placeholder[1] == 'H') {
+                       strbuf_addstr_or_null(sb, get_canon_hostname());
+                       return 2;
+               }
+               break;
+       case 'I':
+               if (placeholder[1] == 'P') {
+                       strbuf_addstr_or_null(sb, get_ip_address());
+                       return 2;
+               }
+               break;
+       case 'P':
+               strbuf_addstr_or_null(sb, tcp_port);
+               return 1;
+       case 'D':
+               strbuf_addstr(sb, context->directory);
+               return 1;
+       }
+       return 0;
+}
+
 static const char *path_ok(const char *directory)
 {
        static char rpath[PATH_MAX];
@@ -144,14 +200,10 @@ static const char *path_ok(const char *directory)
        }
        else if (interpolated_path && saw_extended_args) {
                struct strbuf expanded_path = STRBUF_INIT;
-               struct strbuf_expand_dict_entry dict[6];
-
-               dict[0].placeholder = "H"; dict[0].value = hostname;
-               dict[1].placeholder = "CH"; dict[1].value = canon_hostname;
-               dict[2].placeholder = "IP"; dict[2].value = ip_address;
-               dict[3].placeholder = "P"; dict[3].value = tcp_port;
-               dict[4].placeholder = "D"; dict[4].value = directory;
-               dict[5].placeholder = NULL; dict[5].value = NULL;
+               struct expand_path_context context;
+
+               context.directory = directory;
+
                if (*dir != '/') {
                        /* Allow only absolute */
                        logerror("'%s': Non-absolute path denied (interpolated-path active)", dir);
@@ -159,7 +211,7 @@ static const char *path_ok(const char *directory)
                }
 
                strbuf_expand(&expanded_path, interpolated_path,
-                               strbuf_expand_dict_cb, &dict);
+                             expand_path, &context);
                strlcpy(interp_path, expanded_path.buf, PATH_MAX);
                strbuf_release(&expanded_path);
                loginfo("Interpolated dir '%s'", interp_path);
@@ -254,8 +306,8 @@ static int run_access_hook(struct daemon_service *service, const char *dir, cons
        *arg++ = service->name;
        *arg++ = path;
        *arg++ = STRARG(hostname);
-       *arg++ = STRARG(canon_hostname);
-       *arg++ = STRARG(ip_address);
+       *arg++ = STRARG(get_canon_hostname());
+       *arg++ = STRARG(get_ip_address());
        *arg++ = STRARG(tcp_port);
        *arg = NULL;
 #undef STRARG
@@ -485,6 +537,45 @@ static void parse_host_and_port(char *hostport, char **host,
 }
 
 /*
+ * Sanitize a string from the client so that it's OK to be inserted into a
+ * filesystem path. Specifically, we disallow slashes, runs of "..", and
+ * trailing and leading dots, which means that the client cannot escape
+ * our base path via ".." traversal.
+ */
+static void sanitize_client_strbuf(struct strbuf *out, const char *in)
+{
+       for (; *in; in++) {
+               if (*in == '/')
+                       continue;
+               if (*in == '.' && (!out->len || out->buf[out->len - 1] == '.'))
+                       continue;
+               strbuf_addch(out, *in);
+       }
+
+       while (out->len && out->buf[out->len - 1] == '.')
+               strbuf_setlen(out, out->len - 1);
+}
+
+static char *sanitize_client(const char *in)
+{
+       struct strbuf out = STRBUF_INIT;
+       sanitize_client_strbuf(&out, in);
+       return strbuf_detach(&out, NULL);
+}
+
+/*
+ * Like sanitize_client, but we also perform any canonicalization
+ * to make life easier on the admin.
+ */
+static char *canonicalize_client(const char *in)
+{
+       struct strbuf out = STRBUF_INIT;
+       sanitize_client_strbuf(&out, in);
+       strbuf_tolower(&out);
+       return strbuf_detach(&out, NULL);
+}
+
+/*
  * Read the host as supplied by the client connection.
  */
 static void parse_host_arg(char *extra_args, int buflen)
@@ -505,10 +596,11 @@ static void parse_host_arg(char *extra_args, int buflen)
                                parse_host_and_port(val, &host, &port);
                                if (port) {
                                        free(tcp_port);
-                                       tcp_port = xstrdup(port);
+                                       tcp_port = sanitize_client(port);
                                }
                                free(hostname);
-                               hostname = xstrdup_tolower(host);
+                               hostname = canonicalize_client(host);
+                               hostname_lookup_done = 0;
                        }
 
                        /* On to the next one */
@@ -517,11 +609,14 @@ static void parse_host_arg(char *extra_args, int buflen)
                if (extra_args < end && *extra_args)
                        die("Invalid request");
        }
+}
 
-       /*
-        * Locate canonical hostname and its IP address.
-        */
-       if (hostname) {
+/*
+ * Locate canonical hostname and its IP address.
+ */
+static void lookup_hostname(void)
+{
+       if (!hostname_lookup_done && hostname) {
 #ifndef NO_IPV6
                struct addrinfo hints;
                struct addrinfo *ai;
@@ -541,8 +636,9 @@ static void parse_host_arg(char *extra_args, int buflen)
                        ip_address = xstrdup(addrbuf);
 
                        free(canon_hostname);
-                       canon_hostname = xstrdup(ai->ai_canonname ?
-                                                ai->ai_canonname : ip_address);
+                       canon_hostname = ai->ai_canonname ?
+                               sanitize_client(ai->ai_canonname) :
+                               xstrdup(ip_address);
 
                        freeaddrinfo(ai);
                }
@@ -564,11 +660,12 @@ static void parse_host_arg(char *extra_args, int buflen)
                                  addrbuf, sizeof(addrbuf));
 
                        free(canon_hostname);
-                       canon_hostname = xstrdup(hent->h_name);
+                       canon_hostname = sanitize_client(hent->h_name);
                        free(ip_address);
                        ip_address = xstrdup(addrbuf);
                }
 #endif
+               hostname_lookup_done = 1;
        }
 }
 
diff --git a/diff.c b/diff.c
index d1bd534..abc32c8 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -4541,7 +4541,7 @@ void diff_flush(struct diff_options *options)
                        show_stats(&diffstat, options);
                if (output_format & DIFF_FORMAT_SHORTSTAT)
                        show_shortstats(&diffstat, options);
-               if (output_format & DIFF_FORMAT_DIRSTAT)
+               if (output_format & DIFF_FORMAT_DIRSTAT && dirstat_by_line)
                        show_dirstat_by_line(&diffstat, options);
                free_diffstat_info(&diffstat);
                separator++;
index 4e132f1..af1fe08 100644 (file)
@@ -15,8 +15,7 @@ static struct diff_rename_dst {
 } *rename_dst;
 static int rename_dst_nr, rename_dst_alloc;
 
-static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
-                                                int insert_ok)
+static int find_rename_dst(struct diff_filespec *two)
 {
        int first, last;
 
@@ -27,16 +26,33 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
                struct diff_rename_dst *dst = &(rename_dst[next]);
                int cmp = strcmp(two->path, dst->two->path);
                if (!cmp)
-                       return dst;
+                       return next;
                if (cmp < 0) {
                        last = next;
                        continue;
                }
                first = next+1;
        }
-       /* not found */
-       if (!insert_ok)
-               return NULL;
+       return -first - 1;
+}
+
+static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two)
+{
+       int ofs = find_rename_dst(two);
+       return ofs < 0 ? NULL : &rename_dst[ofs];
+}
+
+/*
+ * Returns 0 on success, -1 if we found a duplicate.
+ */
+static int add_rename_dst(struct diff_filespec *two)
+{
+       int first = find_rename_dst(two);
+
+       if (first >= 0)
+               return -1;
+       first = -first - 1;
+
        /* insert to make it at "first" */
        ALLOC_GROW(rename_dst, rename_dst_nr + 1, rename_dst_alloc);
        rename_dst_nr++;
@@ -46,7 +62,7 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
        rename_dst[first].two = alloc_filespec(two->path);
        fill_filespec(rename_dst[first].two, two->sha1, two->sha1_valid, two->mode);
        rename_dst[first].pair = NULL;
-       return &(rename_dst[first]);
+       return 0;
 }
 
 /* Table of rename/copy src files */
@@ -450,8 +466,12 @@ void diffcore_rename(struct diff_options *options)
                        else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
                                 is_empty_blob_sha1(p->two->sha1))
                                continue;
-                       else
-                               locate_rename_dst(p->two, 1);
+                       else if (add_rename_dst(p->two) < 0) {
+                               warning("skipping rename detection, detected"
+                                       " duplicate destination '%s'",
+                                       p->two->path);
+                               goto cleanup;
+                       }
                }
                else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
                         is_empty_blob_sha1(p->one->sha1))
@@ -582,8 +602,7 @@ void diffcore_rename(struct diff_options *options)
                         * We would output this create record if it has
                         * not been turned into a rename/copy already.
                         */
-                       struct diff_rename_dst *dst =
-                               locate_rename_dst(p->two, 0);
+                       struct diff_rename_dst *dst = locate_rename_dst(p->two);
                        if (dst && dst->pair) {
                                diff_q(&outq, dst->pair);
                                pair_to_free = p;
@@ -613,8 +632,7 @@ void diffcore_rename(struct diff_options *options)
                         */
                        if (DIFF_PAIR_BROKEN(p)) {
                                /* broken delete */
-                               struct diff_rename_dst *dst =
-                                       locate_rename_dst(p->one, 0);
+                               struct diff_rename_dst *dst = locate_rename_dst(p->one);
                                if (dst && dst->pair)
                                        /* counterpart is now rename/copy */
                                        pair_to_free = p;
index 8b72e2a..35b3fe0 100644 (file)
@@ -688,7 +688,7 @@ static inline size_t xsize_t(off_t len)
 }
 
 /* in ctype.c, for kwset users */
-extern const char tolower_trans_tbl[256];
+extern const unsigned char tolower_trans_tbl[256];
 
 /* Sane ctype - no locale, and works with signed chars */
 #undef isascii
diff --git a/kwset.c b/kwset.c
index a0f49b3..e6236a0 100644 (file)
--- a/kwset.c
+++ b/kwset.c
@@ -80,13 +80,13 @@ struct kwset
   struct trie *next[NCHAR];    /* Table of children of the root. */
   char *target;                        /* Target string if there's only one. */
   int mind2;                   /* Used in Boyer-Moore search for one string. */
-  char const *trans;           /* Character translation table. */
+  unsigned char const *trans;  /* Character translation table. */
 };
 
 /* Allocate and initialize a keyword set object, returning an opaque
    pointer to it.  Return NULL if memory is not available. */
 kwset_t
-kwsalloc (char const *trans)
+kwsalloc (unsigned char const *trans)
 {
   struct kwset *kwset;
 
@@ -381,7 +381,7 @@ kwsprep (kwset_t kws)
   register struct kwset *kwset;
   register int i;
   register struct trie *curr;
-  register char const *trans;
+  register unsigned char const *trans;
   unsigned char delta[NCHAR];
 
   kwset = (struct kwset *) kws;
@@ -590,7 +590,7 @@ cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch)
   register int d;
   register char const *end, *qlim;
   register struct tree const *tree;
-  register char const *trans;
+  register unsigned char const *trans;
 
   accept = NULL;
 
diff --git a/kwset.h b/kwset.h
index a21b2ea..61a134f 100644 (file)
--- a/kwset.h
+++ b/kwset.h
@@ -39,7 +39,7 @@ typedef struct kwset_t* kwset_t;
    if enough memory cannot be obtained.  The argument if non-NULL
    specifies a table of character translations to be applied to all
    pattern and search text. */
-extern kwset_t kwsalloc(char const *);
+extern kwset_t kwsalloc(unsigned char const *);
 
 /* Incrementally extend the keyword set to include the given string.
    Return NULL for success, or an error message.  Remember an index
index 75a35fc..c211dc4 100644 (file)
@@ -1,6 +1,6 @@
 :
 
-sanitize_diff_raw='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*  / X X \1#       /'
+sanitize_diff_raw='/^:/s/ '"\($_x40\)"' '"\($_x40\)"' \([A-Z]\)[0-9]*  / \1 \2 \3#     /'
 compare_diff_raw () {
     # When heuristics are improved, the score numbers would change.
     # Ignore them while comparing.
diff --git a/t/diff-lib/COPYING b/t/diff-lib/COPYING
new file mode 100644 (file)
index 0000000..6ff87c4
--- /dev/null
@@ -0,0 +1,361 @@
+
+ Note that the only valid version of the GPL as far as this project
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+
+ HOWEVER, in order to allow a migration to GPLv3 if that seems like
+ a good idea, I also ask that people involved with the project make
+ their preferences known. In particular, if you trust me to make that
+ decision, you might note so in your copyright message, ie something
+ like
+
+       This file is licensed under the GPL v2, or a later version
+       at the discretion of Linus.
+
+  might avoid issues. But we can also just decide to synchronize and
+  contact all copyright holders on record if/when the occasion arises.
+
+                       Linus Torvalds
+
+----------------------------------------
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/t/diff-lib/README b/t/diff-lib/README
new file mode 100644 (file)
index 0000000..548142c
--- /dev/null
@@ -0,0 +1,46 @@
+////////////////////////////////////////////////////////////////
+
+       GIT - the stupid content tracker
+
+////////////////////////////////////////////////////////////////
+
+"git" can mean anything, depending on your mood.
+
+ - random three-letter combination that is pronounceable, and not
+   actually used by any common UNIX command.  The fact that it is a
+   mispronunciation of "get" may or may not be relevant.
+ - stupid. contemptible and despicable. simple. Take your pick from the
+   dictionary of slang.
+ - "global information tracker": you're in a good mood, and it actually
+   works for you. Angels sing, and a light suddenly fills the room.
+ - "goddamn idiotic truckload of sh*t": when it breaks
+
+Git is a fast, scalable, distributed revision control system with an
+unusually rich command set that provides both high-level operations
+and full access to internals.
+
+Git is an Open Source project covered by the GNU General Public License.
+It was originally written by Linus Torvalds with help of a group of
+hackers around the net. It is currently maintained by Junio C Hamano.
+
+Please read the file INSTALL for installation instructions.
+See Documentation/tutorial.txt to get started, then see
+Documentation/everyday.txt for a useful minimum set of commands,
+and "man git-commandname" for documentation of each command.
+CVS users may also want to read Documentation/cvs-migration.txt.
+
+Many Git online resources are accessible from http://git.or.cz/
+including full documentation and Git related tools.
+
+The user discussion and development of Git take place on the Git
+mailing list -- everyone is welcome to post bug reports, feature
+requests, comments and patches to git@vger.kernel.org. To subscribe
+to the list, send an email with just "subscribe git" in the body to
+majordomo@vger.kernel.org. The mailing list archives are available at
+http://marc.theaimsgroup.com/?l=git and other archival sites.
+
+The messages titled "A note from the maintainer", "What's in
+git.git (stable)" and "What's cooking in git.git (topics)" and
+the discussion following them on the mailing list give a good
+reference for project status, development direction and
+remaining tasks.
index bfa8835..df2accb 100755 (executable)
@@ -11,7 +11,7 @@ test_description='More rename detection
 
 test_expect_success \
     'prepare reference tree' \
-    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING &&
      echo frotz >rezrov &&
     git update-index --add COPYING rezrov &&
     tree=$(git write-tree) &&
@@ -99,7 +99,7 @@ test_expect_success \
 
 test_expect_success \
     'prepare work tree once again' \
-    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING &&
      git update-index --add --remove COPYING COPYING.1'
 
 # tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
index 7d2c6e1..135addb 100755 (executable)
@@ -11,7 +11,7 @@ test_description='Same rename detection as t4003 but testing diff-raw.
 
 test_expect_success \
     'prepare reference tree' \
-    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING &&
      echo frotz >rezrov &&
     git update-index --add COPYING rezrov &&
     tree=$(git write-tree) &&
@@ -71,7 +71,7 @@ test_expect_success \
 
 test_expect_success \
     'prepare work tree once again' \
-    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING &&
      git update-index --add --remove COPYING COPYING.1'
 
 git diff-index -C --find-copies-harder $tree >current
index 11502b7..dae327f 100755 (executable)
@@ -11,7 +11,7 @@ test_description='Rename interaction with pathspec.
 
 test_expect_success 'prepare reference tree' '
        mkdir path0 path1 &&
-       cp "$TEST_DIRECTORY"/../COPYING path0/COPYING &&
+       cp "$TEST_DIRECTORY"/diff-lib/COPYING path0/COPYING &&
        git update-index --add path0/COPYING &&
        tree=$(git write-tree) &&
        echo $tree
index 8920464..9dd1bc5 100755 (executable)
@@ -10,179 +10,145 @@ We have two very different files, file0 and file1, registered in a tree.
 We update file1 so drastically that it is more similar to file0, and
 then remove file0.  With -B, changes to file1 should be broken into
 separate delete and create, resulting in removal of file0, removal of
-original file1 and creation of completely rewritten file1.
+original file1 and creation of completely rewritten file1.  The latter
+two are then merged back into a single "complete rewrite".
 
 Further, with -B and -M together, these three modifications should
 turn into rename-edit of file0 into file1.
 
 Starting from the same two files in the tree, we swap file0 and file1.
-With -B, this should be detected as two complete rewrites, resulting in
-four changes in total.
+With -B, this should be detected as two complete rewrites.
 
 Further, with -B and -M together, these should turn into two renames.
 '
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash
 
-test_expect_success \
-    setup \
-    'cat "$TEST_DIRECTORY"/../README >file0 &&
-     cat "$TEST_DIRECTORY"/../COPYING >file1 &&
-    git update-index --add file0 file1 &&
-    tree=$(git write-tree) &&
-    echo "$tree"'
-
-test_expect_success \
-    'change file1 with copy-edit of file0 and remove file0' \
-    'sed -e "s/git/GIT/" file0 >file1 &&
-     rm -f file0 &&
-    git update-index --remove file0 file1'
-
-test_expect_success \
-    'run diff with -B' \
-    'git diff-index -B --cached "$tree" >current'
-
-cat >expected <<\EOF
-:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D     file0
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 11e331465a89c394dc25c780de230043750c1ec8 M100  file1
-EOF
-
-test_expect_success \
-    'validate result of -B (#1)' \
-    'compare_diff_raw expected current'
-
-test_expect_success \
-    'run diff with -B and -M' \
-    'git diff-index -B -M "$tree" >current'
-
-cat >expected <<\EOF
-:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c R100  file0   file1
-EOF
-
-test_expect_success \
-    'validate result of -B -M (#2)' \
-    'compare_diff_raw expected current'
-
-test_expect_success \
-    'swap file0 and file1' \
-    'rm -f file0 file1 &&
-     git read-tree -m $tree &&
-     git checkout-index -f -u -a &&
-     mv file0 tmp &&
-     mv file1 file0 &&
-     mv tmp file1 &&
-     git update-index file0 file1'
-
-test_expect_success \
-    'run diff with -B' \
-    'git diff-index -B "$tree" >current'
-
-cat >expected <<\EOF
-:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100  file0
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
-EOF
-
-test_expect_success \
-    'validate result of -B (#3)' \
-    'compare_diff_raw expected current'
-
-test_expect_success \
-    'run diff with -B and -M' \
-    'git diff-index -B -M "$tree" >current'
-
-cat >expected <<\EOF
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100  file1   file0
-:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R100  file0   file1
-EOF
-
-test_expect_success \
-    'validate result of -B -M (#4)' \
-    'compare_diff_raw expected current'
-
-test_expect_success \
-    'make file0 into something completely different' \
-    'rm -f file0 &&
-     test_ln_s_add frotz file0 &&
-     git update-index file1'
-
-test_expect_success \
-    'run diff with -B' \
-    'git diff-index -B "$tree" >current'
-
-cat >expected <<\EOF
-:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
-EOF
-
-test_expect_success \
-    'validate result of -B (#5)' \
-    'compare_diff_raw expected current'
-
-test_expect_success \
-    'run diff with -B -M' \
-    'git diff-index -B -M "$tree" >current'
-
-# file0 changed from regular to symlink.  file1 is very close to the preimage of file0.
-# the change does not make file0 disappear, so file1 is denoted as a copy of file0
-cat >expected <<\EOF
-:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 C     file0   file1
-EOF
-
-test_expect_success \
-    'validate result of -B -M (#6)' \
-    'compare_diff_raw expected current'
-
-test_expect_success \
-    'run diff with -M' \
-    'git diff-index -M "$tree" >current'
-
-# This should not mistake file0 as the copy source of new file1
-# due to type differences.
-cat >expected <<\EOF
-:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M     file1
-EOF
-
-test_expect_success \
-    'validate result of -M (#7)' \
-    'compare_diff_raw expected current'
-
-test_expect_success \
-    'file1 edited to look like file0 and file0 rename-edited to file2' \
-    'rm -f file0 file1 &&
-     git read-tree -m $tree &&
-     git checkout-index -f -u -a &&
-     sed -e "s/git/GIT/" file0 >file1 &&
-     sed -e "s/git/GET/" file0 >file2 &&
-     rm -f file0 &&
-     git update-index --add --remove file0 file1 file2'
-
-test_expect_success \
-    'run diff with -B' \
-    'git diff-index -B "$tree" >current'
-
-cat >expected <<\EOF
-:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D     file0
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 08bb2fb671deff4c03a4d4a0a1315dff98d5732c M100  file1
-:000000 100644 0000000000000000000000000000000000000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 A     file2
-EOF
-
-test_expect_success \
-    'validate result of -B (#8)' \
-    'compare_diff_raw expected current'
-
-test_expect_success \
-    'run diff with -B -C' \
-    'git diff-index -B -C "$tree" >current'
-
-cat >expected <<\EOF
-:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095  file0   file1
-:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 59f832e5c8b3f7e486be15ad0cd3e95ba9af8998 R095  file0   file2
-EOF
-
-test_expect_success \
-    'validate result of -B -M (#9)' \
-    'compare_diff_raw expected current'
+test_expect_success setup '
+       cat "$TEST_DIRECTORY"/diff-lib/README >file0 &&
+       cat "$TEST_DIRECTORY"/diff-lib/COPYING >file1 &&
+       git update-index --add file0 file1 &&
+       git tag reference $(git write-tree)
+'
+
+test_expect_success 'change file1 with copy-edit of file0 and remove file0' '
+       sed -e "s/git/GIT/" file0 >file1 &&
+       rm -f file0 &&
+       git update-index --remove file0 file1
+'
+
+test_expect_success 'run diff with -B (#1)' '
+       git diff-index -B --cached reference >current &&
+       cat >expect <<-\EOF &&
+       :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D      file0
+       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100   file1
+       EOF
+       compare_diff_raw expect current
+'
+
+test_expect_success 'run diff with -B and -M (#2)' '
+       git diff-index -B -M reference >current &&
+       cat >expect <<-\EOF &&
+       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec R100   file0   file1
+       EOF
+       compare_diff_raw expect current
+'
+
+test_expect_success 'swap file0 and file1' '
+       rm -f file0 file1 &&
+       git read-tree -m reference &&
+       git checkout-index -f -u -a &&
+       mv file0 tmp &&
+       mv file1 file0 &&
+       mv tmp file1 &&
+       git update-index file0 file1
+'
+
+test_expect_success 'run diff with -B (#3)' '
+       git diff-index -B reference >current &&
+       cat >expect <<-\EOF &&
+       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100   file0
+       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100   file1
+       EOF
+       compare_diff_raw expect current
+'
+
+test_expect_success 'run diff with -B and -M (#4)' '
+       git diff-index -B -M reference >current &&
+       cat >expect <<-\EOF &&
+       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100   file1   file0
+       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 R100   file0   file1
+       EOF
+       compare_diff_raw expect current
+'
+
+test_expect_success 'make file0 into something completely different' '
+       rm -f file0 &&
+       test_ln_s_add frotz file0 &&
+       git update-index file1
+'
+
+test_expect_success 'run diff with -B (#5)' '
+       git diff-index -B reference >current &&
+       cat >expect <<-\EOF &&
+       :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T      file0
+       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100   file1
+       EOF
+       compare_diff_raw expect current
+'
+
+test_expect_success 'run diff with -B -M (#6)' '
+       git diff-index -B -M reference >current &&
+
+       # file0 changed from regular to symlink.  file1 is the same as the preimage
+       # of file0.  Because the change does not make file0 disappear, file1 is
+       # denoted as a copy of file0
+       cat >expect <<-\EOF &&
+       :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T      file0
+       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 C      file0   file1
+       EOF
+       compare_diff_raw expect current
+'
+
+test_expect_success 'run diff with -M (#7)' '
+       git diff-index -M reference >current &&
+
+       # This should not mistake file0 as the copy source of new file1
+       # due to type differences.
+       cat >expect <<-\EOF &&
+       :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T      file0
+       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M      file1
+       EOF
+       compare_diff_raw expect current
+'
+
+test_expect_success 'file1 edited to look like file0 and file0 rename-edited to file2' '
+       rm -f file0 file1 &&
+       git read-tree -m reference &&
+       git checkout-index -f -u -a &&
+       sed -e "s/git/GIT/" file0 >file1 &&
+       sed -e "s/git/GET/" file0 >file2 &&
+       rm -f file0 &&
+       git update-index --add --remove file0 file1 file2
+'
+
+test_expect_success 'run diff with -B (#8)' '
+       git diff-index -B reference >current &&
+       cat >expect <<-\EOF &&
+       :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D      file0
+       :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100   file1
+       :000000 100644 0000000000000000000000000000000000000000 69a939f651686f56322566e2fd76715947a24162 A      file2
+       EOF
+       compare_diff_raw expect current
+'
+
+test_expect_success 'run diff with -B -C (#9)' '
+       git diff-index -B -C reference >current &&
+       cat >expect <<-\EOF &&
+       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec C095   file0   file1
+       :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 69a939f651686f56322566e2fd76715947a24162 R095   file0   file2
+       EOF
+       compare_diff_raw expect current
+'
 
 test_done
index 57c094f..3641fd8 100755 (executable)
@@ -11,7 +11,7 @@ test_description='Same rename detection as t4003 but testing diff-raw -z.
 
 test_expect_success \
     'prepare reference tree' \
-    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING &&
      echo frotz >rezrov &&
     git update-index --add COPYING rezrov &&
     tree=$(git write-tree) &&
@@ -78,7 +78,7 @@ test_expect_success \
 
 test_expect_success \
     'prepare work tree once again' \
-    'cat "$TEST_DIRECTORY"/../COPYING >COPYING &&
+    'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING &&
      git update-index --add --remove COPYING COPYING.1'
 
 git diff-index -z -C --find-copies-harder $tree >current
index bf07841..43c488b 100755 (executable)
@@ -56,7 +56,7 @@ test_expect_success \
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
-:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M     file0
+:100644 100644 8e4020bb5a8d8c873b25de15933e75cc0fc275df dca6b92303befc93086aa025d90a5facd7eb2812 M     file0
 EOF
 test_expect_success \
     'limit to file0 should show file0' \
index ed7e093..0d50dce 100755 (executable)
@@ -973,4 +973,18 @@ test_expect_success 'diff.dirstat=future_param,0,lines should warn, but still wo
        test_i18ngrep -q "diff\\.dirstat" actual_error
 '
 
+test_expect_success '--shortstat --dirstat should output only one dirstat' '
+       git diff --shortstat --dirstat=changes HEAD^..HEAD >out &&
+       grep " dst/copy/changed/$" out >actual_diff_shortstat_dirstat_changes &&
+       test_line_count = 1 actual_diff_shortstat_dirstat_changes &&
+
+       git diff --shortstat --dirstat=lines HEAD^..HEAD >out &&
+       grep " dst/copy/changed/$" out >actual_diff_shortstat_dirstat_lines &&
+       test_line_count = 1 actual_diff_shortstat_dirstat_lines &&
+
+       git diff --shortstat --dirstat=files HEAD^..HEAD >out &&
+       grep " dst/copy/changed/$" out >actual_diff_shortstat_dirstat_files &&
+       test_line_count = 1 actual_diff_shortstat_dirstat_files
+'
+
 test_done
diff --git a/t/t4058-diff-duplicates.sh b/t/t4058-diff-duplicates.sh
new file mode 100755 (executable)
index 0000000..0a23242
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description='test tree diff when trees have duplicate entries'
+. ./test-lib.sh
+
+# make_tree_entry <mode> <mode> <sha1>
+#
+# We have to rely on perl here because not all printfs understand
+# hex escapes (only octal), and xxd is not portable.
+make_tree_entry () {
+       printf '%s %s\0' "$1" "$2" &&
+       perl -e 'print chr(hex($_)) for ($ARGV[0] =~ /../g)' "$3"
+}
+
+# Like git-mktree, but without all of the pesky sanity checking.
+# Arguments come in groups of three, each group specifying a single
+# tree entry (see make_tree_entry above).
+make_tree () {
+       while test $# -gt 2; do
+               make_tree_entry "$1" "$2" "$3"
+               shift; shift; shift
+       done |
+       git hash-object -w -t tree --stdin
+}
+
+# this is kind of a convoluted setup, but matches
+# a real-world case. Each tree contains four entries
+# for the given path, one with one sha1, and three with
+# the other. The first tree has them split across
+# two subtrees (which are themselves duplicate entries in
+# the root tree), and the second has them all in a single subtree.
+test_expect_success 'create trees with duplicate entries' '
+       blob_one=$(echo one | git hash-object -w --stdin) &&
+       blob_two=$(echo two | git hash-object -w --stdin) &&
+       inner_one_a=$(make_tree \
+               100644 inner $blob_one
+       ) &&
+       inner_one_b=$(make_tree \
+               100644 inner $blob_two \
+               100644 inner $blob_two \
+               100644 inner $blob_two
+       ) &&
+       outer_one=$(make_tree \
+               040000 outer $inner_one_a \
+               040000 outer $inner_one_b
+       ) &&
+       inner_two=$(make_tree \
+               100644 inner $blob_one \
+               100644 inner $blob_two \
+               100644 inner $blob_two \
+               100644 inner $blob_two
+       ) &&
+       outer_two=$(make_tree \
+               040000 outer $inner_two
+       ) &&
+       git tag one $outer_one &&
+       git tag two $outer_two
+'
+
+test_expect_success 'diff-tree between trees' '
+       {
+               printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" &&
+               printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" &&
+               printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" &&
+               printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" &&
+               printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" &&
+               printf ":100644 000000 $blob_two $_z40 D\touter/inner\n"
+       } >expect &&
+       git diff-tree -r --no-abbrev one two >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'diff-tree with renames' '
+       # same expectation as above, since we disable rename detection
+       git diff-tree -M -r --no-abbrev one two >actual &&
+       test_cmp expect actual
+'
+
+test_done
index 70b3a06..1779c0a 100755 (executable)
@@ -52,4 +52,110 @@ test_expect_success 'check result' '
 
 '
 
+test_expect_success SYMLINKS 'do not read from beyond symbolic link' '
+       git reset --hard &&
+       mkdir -p arch/x86_64/dir &&
+       >arch/x86_64/dir/file &&
+       git add arch/x86_64/dir/file &&
+       echo line >arch/x86_64/dir/file &&
+       git diff >patch &&
+       git reset --hard &&
+
+       mkdir arch/i386/dir &&
+       >arch/i386/dir/file &&
+       ln -s ../i386/dir arch/x86_64/dir &&
+
+       test_must_fail git apply patch &&
+       test_must_fail git apply --cached patch &&
+       test_must_fail git apply --index patch
+
+'
+
+test_expect_success SYMLINKS 'do not follow symbolic link (setup)' '
+
+       rm -rf arch/i386/dir arch/x86_64/dir &&
+       git reset --hard &&
+       ln -s ../i386/dir arch/x86_64/dir &&
+       git add arch/x86_64/dir &&
+       git diff HEAD >add_symlink.patch &&
+       git reset --hard &&
+
+       mkdir arch/x86_64/dir &&
+       >arch/x86_64/dir/file &&
+       git add arch/x86_64/dir/file &&
+       git diff HEAD >add_file.patch &&
+       git diff -R HEAD >del_file.patch &&
+       git reset --hard &&
+       rm -fr arch/x86_64/dir &&
+
+       cat add_symlink.patch add_file.patch >patch &&
+       cat add_symlink.patch del_file.patch >tricky_del &&
+
+       mkdir arch/i386/dir
+'
+
+test_expect_success SYMLINKS 'do not follow symbolic link (same input)' '
+
+       # same input creates a confusing symbolic link
+       test_must_fail git apply patch 2>error-wt &&
+       test_i18ngrep "beyond a symbolic link" error-wt &&
+       test_path_is_missing arch/x86_64/dir &&
+       test_path_is_missing arch/i386/dir/file &&
+
+       test_must_fail git apply --index patch 2>error-ix &&
+       test_i18ngrep "beyond a symbolic link" error-ix &&
+       test_path_is_missing arch/x86_64/dir &&
+       test_path_is_missing arch/i386/dir/file &&
+       test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+       test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+       test_must_fail git apply --cached patch 2>error-ct &&
+       test_i18ngrep "beyond a symbolic link" error-ct &&
+       test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+       test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+       >arch/i386/dir/file &&
+       git add arch/i386/dir/file &&
+
+       test_must_fail git apply tricky_del &&
+       test_path_is_file arch/i386/dir/file &&
+
+       test_must_fail git apply --index tricky_del &&
+       test_path_is_file arch/i386/dir/file &&
+       test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+       git ls-files --error-unmatch arch/i386/dir &&
+
+       test_must_fail git apply --cached tricky_del &&
+       test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+       git ls-files --error-unmatch arch/i386/dir
+'
+
+test_expect_success SYMLINKS 'do not follow symbolic link (existing)' '
+
+       # existing symbolic link
+       git reset --hard &&
+       ln -s ../i386/dir arch/x86_64/dir &&
+       git add arch/x86_64/dir &&
+
+       test_must_fail git apply add_file.patch 2>error-wt-add &&
+       test_i18ngrep "beyond a symbolic link" error-wt-add &&
+       test_path_is_missing arch/i386/dir/file &&
+
+       mkdir arch/i386/dir &&
+       >arch/i386/dir/file &&
+       test_must_fail git apply del_file.patch 2>error-wt-del &&
+       test_i18ngrep "beyond a symbolic link" error-wt-del &&
+       test_path_is_file arch/i386/dir/file &&
+       rm arch/i386/dir/file &&
+
+       test_must_fail git apply --index add_file.patch 2>error-ix-add &&
+       test_i18ngrep "beyond a symbolic link" error-ix-add &&
+       test_path_is_missing arch/i386/dir/file &&
+       test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+       test_must_fail git apply --cached add_file.patch 2>error-ct-file &&
+       test_i18ngrep "beyond a symbolic link" error-ct-file &&
+       test_must_fail git ls-files --error-unmatch arch/i386/dir
+'
+
 test_done
diff --git a/t/t4139-apply-escape.sh b/t/t4139-apply-escape.sh
new file mode 100755 (executable)
index 0000000..45b5660
--- /dev/null
@@ -0,0 +1,141 @@
+#!/bin/sh
+
+test_description='paths written by git-apply cannot escape the working tree'
+. ./test-lib.sh
+
+# tests will try to write to ../foo, and we do not
+# want them to escape the trash directory when they
+# fail
+test_expect_success 'bump git repo one level down' '
+       mkdir inside &&
+       mv .git inside/ &&
+       cd inside
+'
+
+# $1 = name of file
+# $2 = current path to file (if different)
+mkpatch_add () {
+       rm -f "${2:-$1}" &&
+       cat <<-EOF
+       diff --git a/$1 b/$1
+       new file mode 100644
+       index 0000000..53c74cd
+       --- /dev/null
+       +++ b/$1
+       @@ -0,0 +1 @@
+       +evil
+       EOF
+}
+
+mkpatch_del () {
+       echo evil >"${2:-$1}" &&
+       cat <<-EOF
+       diff --git a/$1 b/$1
+       deleted file mode 100644
+       index 53c74cd..0000000
+       --- a/$1
+       +++ /dev/null
+       @@ -1 +0,0 @@
+       -evil
+       EOF
+}
+
+# $1 = name of file
+# $2 = content of symlink
+mkpatch_symlink () {
+       rm -f "$1" &&
+       cat <<-EOF
+       diff --git a/$1 b/$1
+       new file mode 120000
+       index 0000000..$(printf "%s" "$2" | git hash-object --stdin)
+       --- /dev/null
+       +++ b/$1
+       @@ -0,0 +1 @@
+       +$2
+       \ No newline at end of file
+       EOF
+}
+
+test_expect_success 'cannot create file containing ..' '
+       mkpatch_add ../foo >patch &&
+       test_must_fail git apply patch &&
+       test_path_is_missing ../foo
+'
+
+test_expect_success 'can create file containing .. with --unsafe-paths' '
+       mkpatch_add ../foo >patch &&
+       git apply --unsafe-paths patch &&
+       test_path_is_file ../foo
+'
+
+test_expect_success  'cannot create file containing .. (index)' '
+       mkpatch_add ../foo >patch &&
+       test_must_fail git apply --index patch &&
+       test_path_is_missing ../foo
+'
+
+test_expect_success  'cannot create file containing .. with --unsafe-paths (index)' '
+       mkpatch_add ../foo >patch &&
+       test_must_fail git apply --index --unsafe-paths patch &&
+       test_path_is_missing ../foo
+'
+
+test_expect_success 'cannot delete file containing ..' '
+       mkpatch_del ../foo >patch &&
+       test_must_fail git apply patch &&
+       test_path_is_file ../foo
+'
+
+test_expect_success 'can delete file containing .. with --unsafe-paths' '
+       mkpatch_del ../foo >patch &&
+       git apply --unsafe-paths patch &&
+       test_path_is_missing ../foo
+'
+
+test_expect_success 'cannot delete file containing .. (index)' '
+       mkpatch_del ../foo >patch &&
+       test_must_fail git apply --index patch &&
+       test_path_is_file ../foo
+'
+
+test_expect_success SYMLINKS 'symlink escape via ..' '
+       {
+               mkpatch_symlink tmp .. &&
+               mkpatch_add tmp/foo ../foo
+       } >patch &&
+       test_must_fail git apply patch &&
+       test_path_is_missing tmp &&
+       test_path_is_missing ../foo
+'
+
+test_expect_success SYMLINKS 'symlink escape via .. (index)' '
+       {
+               mkpatch_symlink tmp .. &&
+               mkpatch_add tmp/foo ../foo
+       } >patch &&
+       test_must_fail git apply --index patch &&
+       test_path_is_missing tmp &&
+       test_path_is_missing ../foo
+'
+
+test_expect_success SYMLINKS 'symlink escape via absolute path' '
+       {
+               mkpatch_symlink tmp "$(pwd)" &&
+               mkpatch_add tmp/foo ../foo
+       } >patch &&
+       test_must_fail git apply patch &&
+       test_path_is_missing tmp &&
+       test_path_is_missing ../foo
+'
+
+test_expect_success SYMLINKS 'symlink escape via absolute path (index)' '
+       {
+               mkpatch_symlink tmp "$(pwd)" &&
+               mkpatch_add tmp/foo ../foo
+       } >patch &&
+       test_must_fail git apply --index patch &&
+       test_path_is_missing tmp &&
+       test_path_is_missing ../foo
+'
+
+test_done
index 85c7fec..594d7a6 100755 (executable)
@@ -238,7 +238,7 @@ test_expect_success 'push with pushInsteadOf' '
 test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' '
        mk_empty testrepo &&
        test_config "url.trash2/.pushInsteadOf" testrepo/ &&
-       test_config "url.trash3/.pusnInsteadOf" trash/wrong &&
+       test_config "url.trash3/.pushInsteadOf" trash/wrong &&
        test_config remote.r.url trash/wrong &&
        test_config remote.r.pushurl "testrepo/" &&
        git push r refs/heads/master:refs/remotes/origin/master &&
index 6b16379..b7e2832 100755 (executable)
@@ -142,4 +142,31 @@ test_expect_success 'read access denied' "test_remote_error -x 'no such reposito
 test_expect_success 'not exported'       "test_remote_error -n 'repository not exported' fetch repo.git       "
 
 stop_git_daemon
+start_git_daemon --interpolated-path="$GIT_DAEMON_DOCUMENT_ROOT_PATH/%H%D"
+
+test_expect_success 'access repo via interpolated hostname' '
+       repo="$GIT_DAEMON_DOCUMENT_ROOT_PATH/localhost/interp.git" &&
+       git init --bare "$repo" &&
+       git push "$repo" HEAD &&
+       >"$repo"/git-daemon-export-ok &&
+       rm -rf tmp.git &&
+       GIT_OVERRIDE_VIRTUAL_HOST=localhost \
+               git clone --bare "$GIT_DAEMON_URL/interp.git" tmp.git &&
+       rm -rf tmp.git &&
+       GIT_OVERRIDE_VIRTUAL_HOST=LOCALHOST \
+               git clone --bare "$GIT_DAEMON_URL/interp.git" tmp.git
+'
+
+test_expect_success 'hostname cannot break out of directory' '
+       rm -rf tmp.git &&
+       repo="$GIT_DAEMON_DOCUMENT_ROOT_PATH/../escape.git" &&
+       git init --bare "$repo" &&
+       git push "$repo" HEAD &&
+       >"$repo"/git-daemon-export-ok &&
+       test_must_fail \
+               env GIT_OVERRIDE_VIRTUAL_HOST=.. \
+               git clone --bare "$GIT_DAEMON_URL/escape.git" tmp.git
+'
+
+stop_git_daemon
 test_done
index 37c2d63..c538e0a 100755 (executable)
@@ -552,8 +552,8 @@ test_expect_success 'D: verify pack' '
 '
 
 cat >expect <<EOF
-:000000 100755 0000000000000000000000000000000000000000 35a59026a33beac1569b1c7f66f3090ce9c09afc A     newdir/exec.sh
-:000000 100644 0000000000000000000000000000000000000000 046d0371e9220107917db0d0e030628de8a1de9b A     newdir/interesting
+:000000 100755 0000000000000000000000000000000000000000 e74b7d465e52746be2b4bae983670711e6e66657 A     newdir/exec.sh
+:000000 100644 0000000000000000000000000000000000000000 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 A     newdir/interesting
 EOF
 git diff-tree -M -r branch^ branch >actual
 test_expect_success \
index 0d93e33..0698ce7 100644 (file)
@@ -745,7 +745,9 @@ test_ln_s_add () {
        else
                printf '%s' "$1" >"$2" &&
                ln_s_obj=$(git hash-object -w "$2") &&
-               git update-index --add --cacheinfo 120000 $ln_s_obj "$2"
+               git update-index --add --cacheinfo 120000 $ln_s_obj "$2" &&
+               # pick up stat info from the file
+               git update-index "$2"
        fi
 }