Merge branch 'maint' into bc/master-diff-hunk-header-fix
authorShawn O. Pearce <spearce@spearce.org>
Mon, 29 Sep 2008 17:52:34 +0000 (10:52 -0700)
committerShawn O. Pearce <spearce@spearce.org>
Mon, 29 Sep 2008 17:52:34 +0000 (10:52 -0700)
* maint: (41 commits)
Clarify commit error message for unmerged files
Use strchrnul() instead of strchr() plus manual workaround
Use remove_path from dir.c instead of own implementation
Add remove_path: a function to remove as much as possible of a path
git-submodule: Fix "Unable to checkout" for the initial 'update'
Clarify how the user can satisfy stash's 'dirty state' check.
Remove empty directories in recursive merge
Documentation: clarify the details of overriding LESS via core.pager
Update release notes for 1.6.0.3
checkout: Do not show local changes when in quiet mode
for-each-ref: Fix --format=%(subject) for log message without newlines
git-stash.sh: don't default to refs/stash if invalid ref supplied
maint: check return of split_cmdline to avoid bad config strings
builtin-prune.c: prune temporary packs in <object_dir>/pack directory
Do not perform cross-directory renames when creating packs
Use dashless git commands in setgitperms.perl
git-remote: do not use user input in a printf format string
make "git remote" report multiple URLs
Start draft release notes for 1.6.0.3
git-repack uses --no-repack-object, not --no-repack-delta.
...

Conflicts:
RelNotes

16 files changed:
1  2 
Documentation/config.txt
Documentation/gitattributes.txt
builtin-checkout.c
builtin-commit.c
builtin-log.c
builtin-merge-recursive.c
builtin-merge.c
contrib/completion/git-completion.bash
diff.c
dir.c
fast-import.c
git-submodule.sh
git-svn.perl
gitweb/gitweb.perl
read-cache.c
sha1_file.c
diff --combined Documentation/config.txt
index ed3285f89927e1d988527ae5b67a5bea357d30e0,87b028fbc1817c9c641e5fe03aab641e3bd5eb63..5dda70de96cb5f4627568ea4533800dc6a739d4d
@@@ -363,8 -363,17 +363,17 @@@ core.pager:
        variable.  Note that git sets the `LESS` environment
        variable to `FRSX` if it is unset when it runs the
        pager.  One can change these settings by setting the
-       `LESS` variable to some other value or by giving the
-       `core.pager` option a value such as "`less -+FRSX`".
+       `LESS` variable to some other value.  Alternately,
+       these settings can be overridden on a project or
+       global basis by setting the `core.pager` option.
+       Setting `core.pager` has no affect on the `LESS`
+       environment variable behaviour above, so if you want
+       to override git's default settings this way, you need
+       to be explicit.  For example, to disable the S option
+       in a backward compatible manner, set `core.pager`
+       to "`less -+$LESS -FRX`".  This will be passed to the
+       shell by git, which will translate the final command to
+       "`LESS=FRSX less -+FRSX -FRX`".
  
  core.whitespace::
        A comma separated list of common whitespace problems to
@@@ -572,10 -581,6 +581,10 @@@ diff.autorefreshindex:
        affects only 'git-diff' Porcelain, and not lower level
        'diff' commands, such as 'git-diff-files'.
  
 +diff.suppress-blank-empty::
 +      A boolean to inhibit the standard behavior of printing a space
 +      before each empty output line. Defaults to false.
 +
  diff.external::
        If this config variable is set, diff generation is not
        performed using the internal diff machinery, but using the
index 9a752570f3c2d5a9ae41f3711474e9ab86864964,53da9b4f6ba34b03768d77158c19d53e44681809..2ae771f2fb213a478cf8ed5273829c97ad1392a9
@@@ -270,27 -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.xfuncname" configuration to
+ Then, you would define "diff.tex.xfuncname" 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,16 -311,10 +311,16 @@@ patterns are available
  
  - `bibtex` suitable for files with BibTeX coded references.
  
 +- `html` suitable for HTML/XHTML documents.
 +
  - `java` suitable for source code in the Java language.
  
  - `pascal` suitable for source code in the Pascal/Delphi language.
  
 +- `php` suitable for source code in the PHP language.
 +
 +- `python` suitable for source code in the Python language.
 +
  - `ruby` suitable for source code in the Ruby language.
  
  - `tex` suitable for source code for LaTeX documents.
diff --combined builtin-checkout.c
index d986ac7abbd619c486a377e4433cf3fca687ea7a,c4fc2b2c562725018789dead3b86ccfbc4e2925f..4ca76a6f48e529862486f9577b640539dec10d24
@@@ -184,7 -184,7 +184,7 @@@ struct checkout_opts 
        int force;
        int writeout_error;
  
 -      char *new_branch;
 +      const char *new_branch;
        int new_branch_log;
        enum branch_track track;
  };
@@@ -269,6 -269,8 +269,8 @@@ static int merge_working_tree(struct ch
                }
  
                /* 2-way merge to the new branch */
+               topts.initial_checkout = (!active_nr &&
+                                         (old->commit == new->commit));
                topts.update = 1;
                topts.merge = 1;
                topts.gently = opts->merge;
            commit_locked_index(lock_file))
                die("unable to write new index file");
  
-       if (!opts->force)
+       if (!opts->force && !opts->quiet)
                show_local_changes(&new->commit->object);
  
        return 0;
@@@ -462,28 -464,13 +464,28 @@@ int cmd_checkout(int argc, const char *
  
        git_config(git_default_config, NULL);
  
 -      opts.track = git_branch_track;
 +      opts.track = BRANCH_TRACK_UNSPECIFIED;
  
        argc = parse_options(argc, argv, options, checkout_usage,
                             PARSE_OPT_KEEP_DASHDASH);
  
 -      if (!opts.new_branch && (opts.track != git_branch_track))
 -              die("git checkout: --track and --no-track require -b");
 +      /* --track without -b should DWIM */
 +      if (0 < opts.track && !opts.new_branch) {
 +              const char *argv0 = argv[0];
 +              if (!argc || !strcmp(argv0, "--"))
 +                      die ("--track needs a branch name");
 +              if (!prefixcmp(argv0, "refs/"))
 +                      argv0 += 5;
 +              if (!prefixcmp(argv0, "remotes/"))
 +                      argv0 += 8;
 +              argv0 = strchr(argv0, '/');
 +              if (!argv0 || !argv0[1])
 +                      die ("Missing branch name; try -b");
 +              opts.new_branch = argv0 + 1;
 +      }
 +
 +      if (opts.track == BRANCH_TRACK_UNSPECIFIED)
 +              opts.track = git_branch_track;
  
        if (opts.force && opts.merge)
                die("git checkout: -f and -m are incompatible");
diff --combined builtin-commit.c
index 8165bb3d31c6cdd92c83663c5dd081e564dcfc1e,e2a7e48b1ce97a74ef73a1feb53a9b9248cf8668..6b23143cd7e044927801608aa4f312245f6210f6
@@@ -320,7 -320,7 +320,7 @@@ static char *prepare_index(int argc, co
                die("unable to write new_index file");
  
        fd = hold_lock_file_for_update(&false_lock,
 -                                     git_path("next-index-%d", getpid()), 1);
 +                                     git_path("next-index-%"PRIuMAX, (uintmax_t) getpid()), 1);
  
        create_base_index();
        add_remove_files(&partial);
@@@ -639,7 -639,7 +639,7 @@@ static int prepare_to_commit(const cha
                active_cache_tree = cache_tree();
        if (cache_tree_update(active_cache_tree,
                              active_cache, active_nr, 0, 0) < 0) {
-               error("Error building trees");
+               error("Error building trees; the index is unmerged?");
                return 0;
        }
  
@@@ -710,31 -710,6 +710,31 @@@ static int message_is_empty(struct strb
        return 1;
  }
  
 +static const char *find_author_by_nickname(const char *name)
 +{
 +      struct rev_info revs;
 +      struct commit *commit;
 +      struct strbuf buf = STRBUF_INIT;
 +      const char *av[20];
 +      int ac = 0;
 +
 +      init_revisions(&revs, NULL);
 +      strbuf_addf(&buf, "--author=%s", name);
 +      av[++ac] = "--all";
 +      av[++ac] = "-i";
 +      av[++ac] = buf.buf;
 +      av[++ac] = NULL;
 +      setup_revisions(ac, av, &revs, NULL);
 +      prepare_revision_walk(&revs);
 +      commit = get_revision(&revs);
 +      if (commit) {
 +              strbuf_release(&buf);
 +              format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL);
 +              return strbuf_detach(&buf, NULL);
 +      }
 +      die("No existing author found with '%s'", name);
 +}
 +
  static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix)
        logfile = parse_options_fix_filename(prefix, logfile);
        template_file = parse_options_fix_filename(prefix, template_file);
  
 +      if (force_author && !strchr(force_author, '>'))
 +              force_author = find_author_by_nickname(force_author);
 +
        if (logfile || message.len || use_message)
                use_editor = 0;
        if (edit_flag)
diff --combined builtin-log.c
index 1d3c5cbf580f3989d9605ed68f80beb050e19d39,2efe5937346ee7c0a4d85e4087a4ea49a9806005..d4aaddc9dbe2d401216ebf51a7097a7fa0f08387
@@@ -217,11 -217,6 +217,11 @@@ static int cmd_log_walk(struct rev_inf
        if (rev->early_output)
                finish_early_output(rev);
  
 +      /*
 +       * For --check and --exit-code, the exit code is based on CHECK_FAILED
 +       * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to
 +       * retain that state information if replacing rev->diffopt in this loop
 +       */
        while ((commit = get_revision(rev)) != NULL) {
                log_tree_commit(rev, commit);
                if (!rev->reflog_info) {
                free_commit_list(commit->parents);
                commit->parents = NULL;
        }
 -      return 0;
 +      if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
 +          DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) {
 +              return 02;
 +      }
 +      return diff_result_code(&rev->diffopt, 0);
  }
  
  static int git_log_config(const char *var, const char *value, void *cb)
@@@ -844,7 -835,7 +844,7 @@@ int cmd_format_patch(int argc, const ch
                        committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
                        endpos = strchr(committer, '>');
                        if (!endpos)
-                               die("bogos committer info %s\n", committer);
+                               die("bogus committer info %s\n", committer);
                        add_signoff = xmemdupz(committer, endpos - committer + 1);
                }
                else if (!strcmp(argv[i], "--attach")) {
        if (argc > 1)
                die ("unrecognized argument: %s", argv[1]);
  
 -      if (!rev.diffopt.output_format)
 +      if (!rev.diffopt.output_format
 +              || rev.diffopt.output_format == DIFF_FORMAT_PATCH)
                rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH;
  
        if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff)
index dfb363ee2f2e5764264722171de17a9a57556dd8,b9738655adc66386e55eccafa9ab891bdcf30960..36aa7984590245a766307d8668b9f49090f45d42
@@@ -18,6 -18,7 +18,7 @@@
  #include "ll-merge.h"
  #include "interpolate.h"
  #include "attr.h"
+ #include "dir.h"
  #include "merge-recursive.h"
  
  static int subtree_merge;
@@@ -416,24 -417,6 +417,6 @@@ static int update_stages(const char *pa
        return 0;
  }
  
- static int remove_path(const char *name)
- {
-       int ret;
-       char *slash, *dirs;
-       ret = unlink(name);
-       if (ret)
-               return ret;
-       dirs = xstrdup(name);
-       while ((slash = strrchr(name, '/'))) {
-               *slash = '\0';
-               if (rmdir(name) != 0)
-                       break;
-       }
-       free(dirs);
-       return ret;
- }
  static int remove_file(int clean, const char *path, int no_wd)
  {
        int update_cache = index_only || clean;
                        return -1;
        }
        if (update_working_directory) {
-               unlink(path);
-               if (errno != ENOENT || errno != EISDIR)
+               if (remove_path(path))
                        return -1;
-               remove_path(path);
        }
        return 0;
  }
@@@ -729,13 -710,13 +710,13 @@@ static void conflict_rename_rename(stru
        const char *dst_name2 = ren2_dst;
        if (string_list_has_string(&current_directory_set, ren1_dst)) {
                dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
 -              output(1, "%s is a directory in %s added as %s instead",
 +              output(1, "%s is a directory in %s adding as %s instead",
                       ren1_dst, branch2, dst_name1);
                remove_file(0, ren1_dst, 0);
        }
        if (string_list_has_string(&current_directory_set, ren2_dst)) {
                dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
 -              output(1, "%s is a directory in %s added as %s instead",
 +              output(1, "%s is a directory in %s adding as %s instead",
                       ren2_dst, branch1, dst_name2);
                remove_file(0, ren2_dst, 0);
        }
@@@ -760,7 -741,7 +741,7 @@@ static void conflict_rename_dir(struct 
                                const char *branch1)
  {
        char *new_path = unique_path(ren1->pair->two->path, branch1);
 -      output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path);
 +      output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path);
        remove_file(0, ren1->pair->two->path, 0);
        update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
        free(new_path);
@@@ -773,7 -754,7 +754,7 @@@ static void conflict_rename_rename_2(st
  {
        char *new_path1 = unique_path(ren1->pair->two->path, branch1);
        char *new_path2 = unique_path(ren2->pair->two->path, branch2);
 -      output(1, "Renamed %s to %s and %s to %s instead",
 +      output(1, "Renaming %s to %s and %s to %s instead",
               ren1->pair->one->path, new_path1,
               ren2->pair->one->path, new_path2);
        remove_file(0, ren1->pair->two->path, 0);
@@@ -887,10 -868,10 +868,10 @@@ static int process_renames(struct strin
                                                 branch1,
                                                 branch2);
                                if (mfi.merge || !mfi.clean)
 -                                      output(1, "Renamed %s->%s", src, ren1_dst);
 +                                      output(1, "Renaming %s->%s", src, ren1_dst);
  
                                if (mfi.merge)
 -                                      output(2, "Auto-merged %s", ren1_dst);
 +                                      output(2, "Auto-merging %s", ren1_dst);
  
                                if (!mfi.clean) {
                                        output(1, "CONFLICT (content): merge conflict in %s",
  
                        if (string_list_has_string(&current_directory_set, ren1_dst)) {
                                clean_merge = 0;
 -                              output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s "
 +                              output(1, "CONFLICT (rename/directory): Rename %s->%s in %s "
                                       " directory %s added in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren1_dst, branch2);
                                conflict_rename_dir(ren1, branch1);
                        } else if (sha_eq(src_other.sha1, null_sha1)) {
                                clean_merge = 0;
 -                              output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s "
 +                              output(1, "CONFLICT (rename/delete): Rename %s->%s in %s "
                                       "and deleted in %s",
                                       ren1_src, ren1_dst, branch1,
                                       branch2);
                                const char *new_path;
                                clean_merge = 0;
                                try_merge = 1;
 -                              output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. "
 +                              output(1, "CONFLICT (rename/add): Rename %s->%s in %s. "
                                       "%s added in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren1_dst, branch2);
                                new_path = unique_path(ren1_dst, branch2);
 -                              output(1, "Added as %s instead", new_path);
 +                              output(1, "Adding as %s instead", new_path);
                                update_file(0, dst_other.sha1, dst_other.mode, new_path);
                        } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) {
                                ren2 = item->util;
                                clean_merge = 0;
                                ren2->processed = 1;
 -                              output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. "
 -                                     "Renamed %s->%s in %s",
 +                              output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. "
 +                                     "Rename %s->%s in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren2->pair->one->path, ren2->pair->two->path, branch2);
                                conflict_rename_rename_2(ren1, branch1, ren2, branch2);
                                        output(3, "Skipped %s (merged same as existing)", ren1_dst);
                                else {
                                        if (mfi.merge || !mfi.clean)
 -                                              output(1, "Renamed %s => %s", ren1_src, ren1_dst);
 +                                              output(1, "Renaming %s => %s", ren1_src, ren1_dst);
                                        if (mfi.merge)
 -                                              output(2, "Auto-merged %s", ren1_dst);
 +                                              output(2, "Auto-merging %s", ren1_dst);
                                        if (!mfi.clean) {
                                                output(1, "CONFLICT (rename/modify): Merge conflict in %s",
                                                       ren1_dst);
@@@ -1039,7 -1020,7 +1020,7 @@@ static int process_entry(const char *pa
                        /* Deleted in both or deleted in one and
                         * unchanged in the other */
                        if (a_sha)
 -                              output(2, "Removed %s", path);
 +                              output(2, "Removing %s", path);
                        /* do not touch working file if it did not exist */
                        remove_file(1, path, !a_sha);
                } else {
                        const char *new_path = unique_path(path, add_branch);
                        clean_merge = 0;
                        output(1, "CONFLICT (%s): There is a directory with name %s in %s. "
 -                             "Added %s as %s",
 +                             "Adding %s as %s",
                               conf, path, other_branch, path, new_path);
                        remove_file(0, path, 0);
                        update_file(0, sha, mode, new_path);
                } else {
 -                      output(2, "Added %s", path);
 +                      output(2, "Adding %s", path);
                        update_file(1, sha, mode, path);
                }
        } else if (a_sha && b_sha) {
                        reason = "add/add";
                        o_sha = (unsigned char *)null_sha1;
                }
 -              output(2, "Auto-merged %s", path);
 +              output(2, "Auto-merging %s", path);
                o.path = a.path = b.path = (char *)path;
                hashcpy(o.sha1, o_sha);
                o.mode = o_mode;
diff --combined builtin-merge.c
index 9ad9791068c9330f28413ac67315246989c8d96d,dcaf3681dc4433a0a258010f2b0a06fc26dc2212..1094c5fd58bdd3a91ee8c9e76ebcadf72d66f726
@@@ -22,7 -22,6 +22,7 @@@
  #include "log-tree.h"
  #include "color.h"
  #include "rerere.h"
 +#include "help.h"
  
  #define DEFAULT_TWOHEAD (1<<0)
  #define DEFAULT_OCTOPUS (1<<1)
@@@ -78,9 -77,7 +78,9 @@@ static int option_parse_message(const s
  static struct strategy *get_strategy(const char *name)
  {
        int i;
 -      struct strbuf err;
 +      struct strategy *ret;
 +      static struct cmdnames main_cmds, other_cmds;
 +      static int loaded;
  
        if (!name)
                return NULL;
                if (!strcmp(name, all_strategy[i].name))
                        return &all_strategy[i];
  
 -      strbuf_init(&err, 0);
 -      for (i = 0; i < ARRAY_SIZE(all_strategy); i++)
 -              strbuf_addf(&err, " %s", all_strategy[i].name);
 -      fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
 -      fprintf(stderr, "Available strategies are:%s.\n", err.buf);
 -      exit(1);
 +      if (!loaded) {
 +              struct cmdnames not_strategies;
 +              loaded = 1;
 +
 +              memset(&not_strategies, 0, sizeof(struct cmdnames));
 +              load_command_list("git-merge-", &main_cmds, &other_cmds);
 +              for (i = 0; i < main_cmds.cnt; i++) {
 +                      int j, found = 0;
 +                      struct cmdname *ent = main_cmds.names[i];
 +                      for (j = 0; j < ARRAY_SIZE(all_strategy); j++)
 +                              if (!strncmp(ent->name, all_strategy[j].name, ent->len)
 +                                              && !all_strategy[j].name[ent->len])
 +                                      found = 1;
 +                      if (!found)
 +                              add_cmdname(&not_strategies, ent->name, ent->len);
 +                      exclude_cmds(&main_cmds, &not_strategies);
 +              }
 +      }
 +      if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
 +              fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
 +              fprintf(stderr, "Available strategies are:");
 +              for (i = 0; i < main_cmds.cnt; i++)
 +                      fprintf(stderr, " %s", main_cmds.names[i]->name);
 +              fprintf(stderr, ".\n");
 +              if (other_cmds.cnt) {
 +                      fprintf(stderr, "Available custom strategies are:");
 +                      for (i = 0; i < other_cmds.cnt; i++)
 +                              fprintf(stderr, " %s", other_cmds.names[i]->name);
 +                      fprintf(stderr, ".\n");
 +              }
 +              exit(1);
 +      }
 +
 +      ret = xmalloc(sizeof(struct strategy));
 +      memset(ret, 0, sizeof(struct strategy));
 +      ret->name = xstrdup(name);
 +      return ret;
  }
  
  static void append_strategy(struct strategy *s)
@@@ -476,6 -442,8 +476,8 @@@ static int git_merge_config(const char 
  
                buf = xstrdup(v);
                argc = split_cmdline(buf, &argv);
+               if (argc < 0)
+                       die("Bad branch.%s.mergeoptions string", branch);
                argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
                memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
                argc++;
@@@ -868,11 -836,6 +870,11 @@@ int cmd_merge(int argc, const char **ar
                if (argc != 1)
                        die("Can merge only exactly one commit into "
                                "empty head");
 +              if (squash)
 +                      die("Squash commit into empty head not supported yet");
 +              if (!allow_fast_forward)
 +                      die("Non-fast-forward commit does not make sense into "
 +                          "an empty head");
                remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
                if (!remote_head)
                        die("%s - not something we can merge", argv[0]);
index d3fb6ae5073c2b298eee37d566abf6a7cf834377,3bc45f6b47837489f36e34b7af43f2be52838272..2d8d1c3d76e6d78fa234aa7cc60a80fefca34e26
@@@ -750,7 -750,7 +750,7 @@@ _git_commit (
        --*)
                __gitcomp "
                        --all --author= --signoff --verify --no-verify
-                       --edit --amend --include --only
+                       --edit --amend --include --only --interactive
                        "
                return
        esac
@@@ -1493,7 -1493,7 +1493,7 @@@ _git_submodule (
  {
        __git_has_doubledash && return
  
 -      local subcommands="add status init update"
 +      local subcommands="add status init update summary foreach sync"
        if [ -z "$(__git_find_subcommand "$subcommands")" ]; then
                local cur="${COMP_WORDS[COMP_CWORD]}"
                case "$cur" in
diff --combined diff.c
index 05dd8f0b566df29ff1dbe1e03db8b50f309d2329,781fa15ac13ce11f8b77b95665659cfcbcab5836..a3793f65e7872020e1a3c58dc99695f99504ab3f
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -20,7 -20,6 +20,7 @@@
  
  static int diff_detect_rename_default;
  static int diff_rename_limit_default = 200;
 +static int diff_suppress_blank_empty;
  int diff_use_color_default = -1;
  static const char *external_diff_cmd_cfg;
  int diff_auto_refresh_index = 1;
@@@ -182,12 -181,6 +182,12 @@@ int git_diff_basic_config(const char *v
                return 0;
        }
  
 +      /* like GNU diff's --suppress-blank-empty option  */
 +      if (!strcmp(var, "diff.suppress-blank-empty")) {
 +              diff_suppress_blank_empty = git_config_bool(var, value);
 +              return 0;
 +      }
 +
        if (!prefixcmp(var, "diff.")) {
                const char *ep = strrchr(var, '.');
                if (ep != var + 4) {
@@@ -387,6 -380,7 +387,6 @@@ static void diff_words_append(char *lin
  }
  
  struct diff_words_data {
 -      struct xdiff_emit_state xm;
        struct diff_words_buffer minus, plus;
        FILE *file;
  };
@@@ -476,8 -470,11 +476,8 @@@ static void diff_words_show(struct diff
  
        xpp.flags = XDF_NEED_MINIMAL;
        xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc;
 -      ecb.outf = xdiff_outf;
 -      ecb.priv = diff_words;
 -      diff_words->xm.consume = fn_out_diff_words_aux;
 -      xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
 -
 +      xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
 +                    &xpp, &xecfg, &ecb);
        free(minus.ptr);
        free(plus.ptr);
        diff_words->minus.text.size = diff_words->plus.text.size = 0;
  typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
  
  struct emit_callback {
 -      struct xdiff_emit_state xm;
        int nparents, color_diff;
        unsigned ws_rule;
        sane_truncate_fn truncate;
@@@ -600,12 -598,6 +600,12 @@@ static void fn_out_consume(void *priv, 
                ecbdata->label_path[0] = ecbdata->label_path[1] = NULL;
        }
  
 +      if (diff_suppress_blank_empty
 +          && len == 2 && line[0] == ' ' && line[1] == '\n') {
 +              line[0] = '\n';
 +              len = 1;
 +      }
 +
        /* This is not really necessary for now because
         * this codepath only deals with two-way diffs.
         */
@@@ -734,6 -726,8 +734,6 @@@ static char *pprint_rename(const char *
  }
  
  struct diffstat_t {
 -      struct xdiff_emit_state xm;
 -
        int nr;
        int alloc;
        struct diffstat_file {
@@@ -1163,6 -1157,7 +1163,6 @@@ static void free_diffstat_info(struct d
  }
  
  struct checkdiff_t {
 -      struct xdiff_emit_state xm;
        const char *filename;
        int lineno;
        struct diff_options *o;
@@@ -1404,9 -1399,6 +1404,9 @@@ static const struct funcname_pattern_en
  }
  
  static const struct funcname_pattern_entry builtin_funcname_pattern[] = {
 +      { "bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
 +        REG_EXTENDED },
 +      { "html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", REG_EXTENDED },
        { "java",
          "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n"
          "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$",
        { "pascal",
          "^((procedure|function|constructor|destructor|interface|"
                "implementation|initialization|finalization)[ \t]*.*)$"
 -        "|"
 +        "\n"
          "^(.*=[ \t]*(class|record).*)$",
          REG_EXTENDED },
 +      { "php", "^[\t ]*((function|class).*)", REG_EXTENDED },
 +      { "python", "^[ \t]*((class|def)[ \t].*)$", REG_EXTENDED },
 +      { "ruby", "^[ \t]*((class|module|def)[ \t].*)$",
 +        REG_EXTENDED },
        { "bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$",
          REG_EXTENDED },
        { "tex",
          "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$",
          REG_EXTENDED },
 -      { "ruby", "^[ \t]*((class|module|def)[ \t].*)$",
 -        REG_EXTENDED },
  };
  
  static const struct funcname_pattern_entry *diff_funcname_pattern(struct diff_filespec *one)
@@@ -1558,13 -1548,15 +1558,13 @@@ static void builtin_diff(const char *na
                        xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
                else if (!prefixcmp(diffopts, "-u"))
                        xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
 -              ecb.outf = xdiff_outf;
 -              ecb.priv = &ecbdata;
 -              ecbdata.xm.consume = fn_out_consume;
                if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) {
                        ecbdata.diff_words =
                                xcalloc(1, sizeof(struct diff_words_data));
                        ecbdata.diff_words->file = o->file;
                }
 -              xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
 +              xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
 +                            &xpp, &xecfg, &ecb);
                if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS))
                        free_diff_words_data(&ecbdata);
        }
@@@ -1615,8 -1607,9 +1615,8 @@@ static void builtin_diffstat(const cha
  
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
 -              ecb.outf = xdiff_outf;
 -              ecb.priv = diffstat;
 -              xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
 +              xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
 +                            &xpp, &xecfg, &ecb);
        }
  
   free_and_return:
@@@ -1637,6 -1630,7 +1637,6 @@@ static void builtin_checkdiff(const cha
                return;
  
        memset(&data, 0, sizeof(data));
 -      data.xm.consume = checkdiff_consume;
        data.filename = name_b ? name_b : name_a;
        data.lineno = 0;
        data.o = o;
                memset(&xecfg, 0, sizeof(xecfg));
                xecfg.ctxlen = 1; /* at least one context line */
                xpp.flags = XDF_NEED_MINIMAL;
 -              ecb.outf = xdiff_outf;
 -              ecb.priv = &data;
 -              xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
 +              xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
 +                            &xpp, &xecfg, &ecb);
  
                if ((data.ws_rule & WS_TRAILING_SPACE) &&
                    data.trailing_blanks_start) {
@@@ -2417,13 -2412,6 +2417,6 @@@ int diff_setup_done(struct diff_option
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
  
-       /*
-        * If we postprocess in diffcore, we cannot simply return
-        * upon the first hit.  We need to run diff as usual.
-        */
-       if (options->pickaxe || options->filter)
-               DIFF_OPT_CLR(options, QUIET);
        return 0;
  }
  
@@@ -3054,6 -3042,7 +3047,6 @@@ static void diff_summary(FILE *file, st
  }
  
  struct patch_id_t {
 -      struct xdiff_emit_state xm;
        SHA_CTX *ctx;
        int patchlen;
  };
@@@ -3098,6 -3087,7 +3091,6 @@@ static int diff_get_patch_id(struct dif
        SHA1_Init(&ctx);
        memset(&data, 0, sizeof(struct patch_id_t));
        data.ctx = &ctx;
 -      data.xm.consume = patch_id_consume;
  
        for (i = 0; i < q->nr; i++) {
                xpparam_t xpp;
                xpp.flags = XDF_NEED_MINIMAL;
                xecfg.ctxlen = 3;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
 -              ecb.outf = xdiff_outf;
 -              ecb.priv = &data;
 -              xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
 +              xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
 +                            &xpp, &xecfg, &ecb);
        }
  
        SHA1_Final(sha1, &ctx);
@@@ -3240,6 -3231,7 +3233,6 @@@ void diff_flush(struct diff_options *op
                struct diffstat_t diffstat;
  
                memset(&diffstat, 0, sizeof(struct diffstat_t));
 -              diffstat.xm.consume = diffstat_consume;
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
                        if (check_pair_status(p))
@@@ -3411,10 -3403,7 +3404,7 @@@ static void diffcore_skip_stat_unmatch(
  
  void diffcore_std(struct diff_options *options)
  {
-       if (DIFF_OPT_TST(options, QUIET))
-               return;
-       if (options->skip_stat_unmatch && !DIFF_OPT_TST(options, FIND_COPIES_HARDER))
+       if (options->skip_stat_unmatch)
                diffcore_skip_stat_unmatch(options);
        if (options->break_opt != -1)
                diffcore_break(options->break_opt);
diff --combined dir.c
index acf1001c4f9dd51587e3b07e52d4101ff3f1cb66,cfaa28ff23acb462aa0cfd54a405316320ec3bc8..1c3c5015249e6ac0ed0461b49f6581aebc8701a3
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -52,6 -52,11 +52,6 @@@ int common_prefix(const char **pathspec
        return prefix;
  }
  
 -static inline int special_char(unsigned char c1)
 -{
 -      return !c1 || c1 == '*' || c1 == '[' || c1 == '?' || c1 == '\\';
 -}
 -
  /*
   * Does 'match' matches the given name?
   * A match is found if
@@@ -75,7 -80,7 +75,7 @@@ static int match_one(const char *match
        for (;;) {
                unsigned char c1 = *match;
                unsigned char c2 = *name;
 -              if (special_char(c1))
 +              if (isspecial(c1))
                        break;
                if (c1 != c2)
                        return 0;
@@@ -675,12 -680,17 +675,12 @@@ static int cmp_name(const void *p1, con
   */
  static int simple_length(const char *match)
  {
 -      const char special[256] = {
 -              [0] = 1, ['?'] = 1,
 -              ['\\'] = 1, ['*'] = 1,
 -              ['['] = 1
 -      };
        int len = -1;
  
        for (;;) {
                unsigned char c = *match++;
                len++;
 -              if (special[c])
 +              if (isspecial(c))
                        return len;
        }
  }
@@@ -717,12 -727,8 +717,12 @@@ static void free_simplify(struct path_s
  
  int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
  {
 -      struct path_simplify *simplify = create_simplify(pathspec);
 +      struct path_simplify *simplify;
 +
 +      if (has_symlink_leading_path(strlen(path), path))
 +              return dir->nr;
  
 +      simplify = create_simplify(pathspec);
        read_directory_recursive(dir, path, base, baselen, 0, simplify);
        free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
@@@ -831,3 -837,23 +831,23 @@@ void setup_standard_excludes(struct dir
        if (excludes_file && !access(excludes_file, R_OK))
                add_excludes_from_file(dir, excludes_file);
  }
+ int remove_path(const char *name)
+ {
+       char *slash;
+       if (unlink(name) && errno != ENOENT)
+               return -1;
+       slash = strrchr(name, '/');
+       if (slash) {
+               char *dirs = xstrdup(name);
+               slash = dirs + (slash - name);
+               do {
+                       *slash = '\0';
+               } while (rmdir(dirs) && (slash = strrchr(dirs, '/')));
+               free(dirs);
+       }
+       return 0;
+ }
diff --combined fast-import.c
index ccdf2e57b0cf1846996bcda8e89d7d8575a031f6,5473cd4d626c1260cf1a192ab05cc63c37a8fbcb..ab6689a64d69bdb741f662df2038ae1daae99837
@@@ -376,7 -376,7 +376,7 @@@ static void dump_marks_helper(FILE *, u
  
  static void write_crash_report(const char *err)
  {
 -      char *loc = git_path("fast_import_crash_%d", getpid());
 +      char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid());
        FILE *rpt = fopen(loc, "w");
        struct branch *b;
        unsigned long lu;
        fprintf(stderr, "fast-import: dumping crash report to %s\n", loc);
  
        fprintf(rpt, "fast-import crash report:\n");
 -      fprintf(rpt, "    fast-import process: %d\n", getpid());
 -      fprintf(rpt, "    parent process     : %d\n", getppid());
 +      fprintf(rpt, "    fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid());
 +      fprintf(rpt, "    parent process     : %"PRIuMAX"\n", (uintmax_t) getppid());
        fprintf(rpt, "    at %s\n", show_date(time(NULL), 0, DATE_LOCAL));
        fputc('\n', rpt);
  
@@@ -816,7 -816,7 +816,7 @@@ static void start_packfile(void
        int pack_fd;
  
        snprintf(tmpfile, sizeof(tmpfile),
-               "%s/tmp_pack_XXXXXX", get_object_directory());
+               "%s/pack/tmp_pack_XXXXXX", get_object_directory());
        pack_fd = xmkstemp(tmpfile);
        p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
        strcpy(p->pack_name, tmpfile);
@@@ -878,7 -878,7 +878,7 @@@ static char *create_index(void
        }
  
        snprintf(tmpfile, sizeof(tmpfile),
-               "%s/tmp_idx_XXXXXX", get_object_directory());
+               "%s/pack/tmp_idx_XXXXXX", get_object_directory());
        idx_fd = xmkstemp(tmpfile);
        f = sha1fd(idx_fd, tmpfile);
        sha1write(f, array, 256 * sizeof(int));
diff --combined git-submodule.sh
index 1c39b593a628cc94d24d227cc5d92e00ac5e71ed,5888735e4f2dac852f43c31195f430a1a6d23446..c3d0d24bc74e90ed4c40e055bff2f04fc4318d33
@@@ -6,10 -6,9 +6,10 @@@
  
  USAGE="[--quiet] [--cached] \
  [add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
 -[--] [<path>...]"
 +[--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]"
  OPTIONS_SPEC=
  . git-sh-setup
 +. git-parse-remote
  require_work_tree
  
  command=
@@@ -31,11 -30,12 +31,11 @@@ say(
  # Resolve relative url by appending to parent's url
  resolve_relative_url ()
  {
 -      branch="$(git symbolic-ref HEAD 2>/dev/null)"
 -      remote="$(git config branch.${branch#refs/heads/}.remote)"
 -      remote="${remote:-origin}"
 +      remote=$(get_default_remote)
        remoteurl=$(git config "remote.$remote.url") ||
                die "remote ($remote) does not have a url defined in .git/config"
        url="$1"
 +      remoteurl=${remoteurl%/}
        while test -n "$url"
        do
                case "$url" in
                        break;;
                esac
        done
 -      echo "$remoteurl/$url"
 +      echo "$remoteurl/${url%/}"
 +}
 +
 +#
 +# Get submodule info for registered submodules
 +# $@ = path to limit submodule list
 +#
 +module_list()
 +{
 +      git ls-files --stage -- "$@" | grep '^160000 '
  }
  
  #
@@@ -194,7 -185,7 +194,7 @@@ cmd_add(
        else
  
                module_clone "$path" "$realrepo" || exit
-               (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
+               (unset GIT_DIR; cd "$path" && git checkout -f -q ${branch:+-b "$branch" "origin/$branch"}) ||
                die "Unable to checkout submodule '$path'"
        fi
  
        die "Failed to register submodule '$path'"
  }
  
 +#
 +# Execute an arbitrary command sequence in each checked out
 +# submodule
 +#
 +# $@ = command to execute
 +#
 +cmd_foreach()
 +{
 +      module_list |
 +      while read mode sha1 stage path
 +      do
 +              if test -e "$path"/.git
 +              then
 +                      say "Entering '$path'"
 +                      (cd "$path" && eval "$@") ||
 +                      die "Stopping at '$path'; script returned non-zero status."
 +              fi
 +      done
 +}
 +
  #
  # Register submodules in .git/config
  #
@@@ -255,7 -226,7 +255,7 @@@ cmd_init(
                shift
        done
  
 -      git ls-files --stage -- "$@" | grep '^160000 ' |
 +      module_list "$@" |
        while read mode sha1 stage path
        do
                # Skip already registered paths
@@@ -313,7 -284,7 +313,7 @@@ cmd_update(
                esac
        done
  
 -      git ls-files --stage -- "$@" | grep '^160000 ' |
 +      module_list "$@" |
        while read mode sha1 stage path
        do
                name=$(module_name "$path") || exit
  
                if test "$subsha1" != "$sha1"
                then
+                       force=
+                       if test -z "$subsha1"
+                       then
+                               force="-f"
+                       fi
                        (unset GIT_DIR; cd "$path" && git-fetch &&
-                               git-checkout -q "$sha1") ||
+                               git-checkout $force -q "$sha1") ||
                        die "Unable to checkout '$sha1' in submodule path '$path'"
  
                        say "Submodule path '$path': checked out '$sha1'"
@@@ -578,7 -554,7 +583,7 @@@ cmd_status(
                shift
        done
  
 -      git ls-files --stage -- "$@" | grep '^160000 ' |
 +      module_list "$@" |
        while read mode sha1 stage path
        do
                name=$(module_name "$path") || exit
                fi
        done
  }
 +#
 +# Sync remote urls for submodules
 +# This makes the value for remote.$remote.url match the value
 +# specified in .gitmodules.
 +#
 +cmd_sync()
 +{
 +      while test $# -ne 0
 +      do
 +              case "$1" in
 +              -q|--quiet)
 +                      quiet=1
 +                      shift
 +                      ;;
 +              --)
 +                      shift
 +                      break
 +                      ;;
 +              -*)
 +                      usage
 +                      ;;
 +              *)
 +                      break
 +                      ;;
 +              esac
 +      done
 +      cd_to_toplevel
 +      module_list "$@" |
 +      while read mode sha1 stage path
 +      do
 +              name=$(module_name "$path")
 +              url=$(git config -f .gitmodules --get submodule."$name".url)
 +              if test -e "$path"/.git
 +              then
 +              (
 +                      unset GIT_DIR
 +                      cd "$path"
 +                      remote=$(get_default_remote)
 +                      say "Synchronizing submodule url for '$name'"
 +                      git config remote."$remote".url "$url"
 +              )
 +              fi
 +      done
 +}
  
  # This loop parses the command line arguments to find the
  # subcommand name to dispatch.  Parsing of the subcommand specific
  while test $# != 0 && test -z "$command"
  do
        case "$1" in
 -      add | init | update | status | summary)
 +      add | foreach | init | update | status | summary | sync)
                command=$1
                ;;
        -q|--quiet)
diff --combined git-svn.perl
index 2fe7bd3c4621bacd2f880686108d9d8829d145d4,7c7fc39483e2713674a8cf3526651e34bdb9e9f7..af8279acafd8a0e1701f3547da3011c585051e6d
@@@ -421,15 -421,15 +421,15 @@@ sub cmd_dcommit 
        $head ||= 'HEAD';
        my @refs;
        my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
 +      unless ($gs) {
 +              die "Unable to determine upstream SVN information from ",
 +                  "$head history.\nPerhaps the repository is empty.";
 +      }
        $url = defined $_commit_url ? $_commit_url : $gs->full_url;
        my $last_rev = $_revision if defined $_revision;
        if ($url) {
                print "Committing to $url ...\n";
        }
 -      unless ($gs) {
 -              die "Unable to determine upstream SVN information from ",
 -                  "$head history.\nPerhaps the repository is empty.";
 -      }
        my ($linear_refs, $parents) = linearize_history($gs, \@refs);
        if ($_no_rebase && scalar(@$linear_refs) > 1) {
                warn "Attempting to commit more than one change while ",
@@@ -803,28 -803,8 +803,28 @@@ sub cmd_commit_diff 
        }
  }
  
 +sub escape_uri_only {
 +      my ($uri) = @_;
 +      my @tmp;
 +      foreach (split m{/}, $uri) {
 +              s/([^\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg;
 +              push @tmp, $_;
 +      }
 +      join('/', @tmp);
 +}
 +
 +sub escape_url {
 +      my ($url) = @_;
 +      if ($url =~ m#^([^:]+)://([^/]*)(.*)$#) {
 +              my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
 +              $url = "$scheme://$domain$uri";
 +      }
 +      $url;
 +}
 +
  sub cmd_info {
        my $path = canonicalize_path(defined($_[0]) ? $_[0] : ".");
 +      my $fullpath = canonicalize_path($cmd_dir_prefix . $path);
        if (exists $_[1]) {
                die "Too many arguments specified\n";
        }
        my ($file_type, $diff_status) = find_file_type_and_diff_status($path);
  
        if (!$file_type && !$diff_status) {
 -              print STDERR "$path:  (Not a versioned resource)\n\n";
 -              return;
 +              print STDERR "svn: '$path' is not under version control\n";
 +              exit 1;
        }
  
        my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
        # canonicalize_path() will return "" to make libsvn 1.5.x happy,
        $path = "." if $path eq "";
  
 -      my $full_url = $url . ($path eq "." ? "" : "/$path");
 +      my $full_url = $url . ($fullpath eq "" ? "" : "/$fullpath");
  
        if ($_url) {
 -              print $full_url, "\n";
 +              print escape_url($full_url), "\n";
                return;
        }
  
        my $result = "Path: $path\n";
        $result .= "Name: " . basename($path) . "\n" if $file_type ne "dir";
 -      $result .= "URL: " . $full_url . "\n";
 +      $result .= "URL: " . escape_url($full_url) . "\n";
  
        eval {
                my $repos_root = $gs->repos_root;
                Git::SVN::remove_username($repos_root);
 -              $result .= "Repository Root: $repos_root\n";
 +              $result .= "Repository Root: " . escape_url($repos_root) . "\n";
        };
        if ($@) {
                $result .= "Repository Root: (offline)\n";
        }
  
        my ($lc_author, $lc_rev, $lc_date_utc);
 -      my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $path);
 +      my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $fullpath);
        my $log = command_output_pipe(@args);
        my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
        while (<$log>) {
@@@ -3400,12 -3380,11 +3400,12 @@@ sub generate_diff 
        while (<$diff_fh>) {
                chomp $_; # this gets rid of the trailing "\0"
                if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
 -                                      $::sha1\s($::sha1)\s
 +                                      ($::sha1)\s($::sha1)\s
                                        ([MTCRAD])\d*$/xo) {
                        push @mods, {   mode_a => $1, mode_b => $2,
 -                                      sha1_b => $3, chg => $4 };
 -                      if ($4 =~ /^(?:C|R)$/) {
 +                                      sha1_a => $3, sha1_b => $4,
 +                                      chg => $5 };
 +                      if ($5 =~ /^(?:C|R)$/) {
                                $state = 'file_a';
                        } else {
                                $state = 'file_b';
@@@ -3657,7 -3636,6 +3657,7 @@@ sub R 
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                $self->url_path($m->{file_a}), $self->{r});
        print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
 +      $self->apply_autoprops($file, $fbat);
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
  
@@@ -3684,52 -3662,33 +3684,52 @@@ sub change_file_prop 
        $self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
  }
  
 -sub chg_file {
 -      my ($self, $fbat, $m) = @_;
 -      if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
 -              $self->change_file_prop($fbat,'svn:executable','*');
 -      } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
 -              $self->change_file_prop($fbat,'svn:executable',undef);
 -      }
 -      my $fh = Git::temp_acquire('git_blob');
 -      if ($m->{mode_b} =~ /^120/) {
 +sub _chg_file_get_blob ($$$$) {
 +      my ($self, $fbat, $m, $which) = @_;
 +      my $fh = Git::temp_acquire("git_blob_$which");
 +      if ($m->{"mode_$which"} =~ /^120/) {
                print $fh 'link ' or croak $!;
                $self->change_file_prop($fbat,'svn:special','*');
 -      } elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
 +      } elsif ($m->{mode_a} =~ /^120/ && $m->{"mode_$which"} !~ /^120/) {
                $self->change_file_prop($fbat,'svn:special',undef);
        }
 -      my $size = $::_repository->cat_blob($m->{sha1_b}, $fh);
 -      croak "Failed to read object $m->{sha1_b}" if ($size < 0);
 +      my $blob = $m->{"sha1_$which"};
 +      return ($fh,) if ($blob =~ /^0{40}$/);
 +      my $size = $::_repository->cat_blob($blob, $fh);
 +      croak "Failed to read object $blob" if ($size < 0);
        $fh->flush == 0 or croak $!;
        seek $fh, 0, 0 or croak $!;
  
        my $exp = ::md5sum($fh);
        seek $fh, 0, 0 or croak $!;
 +      return ($fh, $exp);
 +}
  
 +sub chg_file {
 +      my ($self, $fbat, $m) = @_;
 +      if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
 +              $self->change_file_prop($fbat,'svn:executable','*');
 +      } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
 +              $self->change_file_prop($fbat,'svn:executable',undef);
 +      }
 +      my ($fh_a, $exp_a) = _chg_file_get_blob $self, $fbat, $m, 'a';
 +      my ($fh_b, $exp_b) = _chg_file_get_blob $self, $fbat, $m, 'b';
        my $pool = SVN::Pool->new;
 -      my $atd = $self->apply_textdelta($fbat, undef, $pool);
 -      my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool);
 -      die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
 -      Git::temp_release($fh, 1);
 +      my $atd = $self->apply_textdelta($fbat, $exp_a, $pool);
 +      if (-s $fh_a) {
 +              my $txstream = SVN::TxDelta::new ($fh_a, $fh_b, $pool);
 +              my $res = SVN::TxDelta::send_txstream($txstream, @$atd, $pool);
 +              if (defined $res) {
 +                      die "Unexpected result from send_txstream: $res\n",
 +                          "(SVN::Core::VERSION: $SVN::Core::VERSION)\n";
 +              }
 +      } else {
 +              my $got = SVN::TxDelta::send_stream($fh_b, @$atd, $pool);
 +              die "Checksum mismatch\nexpected: $exp_b\ngot: $got\n"
 +                  if ($got ne $exp_b);
 +      }
 +      Git::temp_release($fh_b, 1);
 +      Git::temp_release($fh_a, 1);
        $pool->clear;
  }
  
@@@ -4010,21 -3969,21 +4010,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);
diff --combined gitweb/gitweb.perl
index 29e21564c8e7b2dd711af2e96e03b811b2c1a268,269f1125d9442cb4e0eaecb9069af1d32b4d947b..da474d082c4422ea2def6a7090ac79ed39eb9d47
@@@ -1090,23 -1090,13 +1090,23 @@@ sub format_log_line_html 
  }
  
  # format marker of refs pointing to given object
 +
 +# the destination action is chosen based on object type and current context:
 +# - for annotated tags, we choose the tag view unless it's the current view
 +#   already, in which case we go to shortlog view
 +# - for other refs, we keep the current view if we're in history, shortlog or
 +#   log view, and select shortlog otherwise
  sub format_ref_marker {
        my ($refs, $id) = @_;
        my $markers = '';
  
        if (defined $refs->{$id}) {
                foreach my $ref (@{$refs->{$id}}) {
 +                      # this code exploits the fact that non-lightweight tags are the
 +                      # only indirect objects, and that they are the only objects for which
 +                      # we want to use tag instead of shortlog as action
                        my ($type, $name) = qw();
 +                      my $indirect = ($ref =~ s/\^\{\}$//);
                        # e.g. tags/v2.6.11 or heads/next
                        if ($ref =~ m!^(.*?)s?/(.*)$!) {
                                $type = $1;
                                $name = $ref;
                        }
  
 -                      $markers .= " <span class=\"$type\" title=\"$ref\">" .
 -                                  esc_html($name) . "</span>";
 +                      my $class = $type;
 +                      $class .= " indirect" if $indirect;
 +
 +                      my $dest_action = "shortlog";
 +
 +                      if ($indirect) {
 +                              $dest_action = "tag" unless $action eq "tag";
 +                      } elsif ($action =~ /^(history|(short)?log)$/) {
 +                              $dest_action = $action;
 +                      }
 +
 +                      my $dest = "";
 +                      $dest .= "refs/" unless $ref =~ m!^refs/!;
 +                      $dest .= $ref;
 +
 +                      my $link = $cgi->a({
 +                              -href => href(
 +                                      action=>$dest_action,
 +                                      hash=>$dest
 +                              )}, $name);
 +
 +                      $markers .= " <span class=\"$class\" title=\"$ref\">" .
 +                              $link . "</span>";
                }
        }
  
@@@ -1949,7 -1918,7 +1949,7 @@@ sub git_get_references 
  
        while (my $line = <$fd>) {
                chomp $line;
 -              if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
 +              if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
                        if (defined $refs{$1}) {
                                push @{$refs{$1}}, $2;
                        } else {
@@@ -2123,7 -2092,7 +2123,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 --combined read-cache.c
index 5150c1e14b7384bd443bffb17da2887394237c61,8f96fd17226e26c4b8a1497aace5b9fffb664bbe..5b1b3ad03b6d7294ca06b5b1053799958ad831c7
@@@ -8,11 -8,6 +8,11 @@@
  #include "cache-tree.h"
  #include "refs.h"
  #include "dir.h"
 +#include "tree.h"
 +#include "commit.h"
 +#include "diff.h"
 +#include "diffcore.h"
 +#include "revision.h"
  
  /* Index extensions.
   *
@@@ -1123,10 -1118,6 +1123,10 @@@ static void convert_from_disk(struct on
        ce->ce_size  = ntohl(ondisk->size);
        /* On-disk flags are just 16 bits */
        ce->ce_flags = ntohs(ondisk->flags);
 +
 +      /* For future extension: we do not understand this entry yet */
 +      if (ce->ce_flags & CE_EXTENDED)
 +              die("Unknown index entry format");
        hashcpy(ce->sha1, ondisk->sha1);
  
        len = ce->ce_flags & CE_NAMEMASK;
@@@ -1253,6 -1244,7 +1253,7 @@@ int discard_index(struct index_state *i
        istate->cache_nr = 0;
        istate->cache_changed = 0;
        istate->timestamp = 0;
+       istate->name_hash_initialized = 0;
        free_hash(&istate->name_hash);
        cache_tree_free(&(istate->cache_tree));
        free(istate->alloc);
@@@ -1488,59 -1480,3 +1489,59 @@@ int read_index_unmerged(struct index_st
        istate->cache_nr = dst - istate->cache;
        return !!last;
  }
 +
 +struct update_callback_data
 +{
 +      int flags;
 +      int add_errors;
 +};
 +
 +static void update_callback(struct diff_queue_struct *q,
 +                          struct diff_options *opt, void *cbdata)
 +{
 +      int i;
 +      struct update_callback_data *data = cbdata;
 +
 +      for (i = 0; i < q->nr; i++) {
 +              struct diff_filepair *p = q->queue[i];
 +              const char *path = p->one->path;
 +              switch (p->status) {
 +              default:
 +                      die("unexpected diff status %c", p->status);
 +              case DIFF_STATUS_UNMERGED:
 +              case DIFF_STATUS_MODIFIED:
 +              case DIFF_STATUS_TYPE_CHANGED:
 +                      if (add_file_to_index(&the_index, path, data->flags)) {
 +                              if (!(data->flags & ADD_CACHE_IGNORE_ERRORS))
 +                                      die("updating files failed");
 +                              data->add_errors++;
 +                      }
 +                      break;
 +              case DIFF_STATUS_DELETED:
 +                      if (data->flags & ADD_CACHE_IGNORE_REMOVAL)
 +                              break;
 +                      if (!(data->flags & ADD_CACHE_PRETEND))
 +                              remove_file_from_index(&the_index, path);
 +                      if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE))
 +                              printf("remove '%s'\n", path);
 +                      break;
 +              }
 +      }
 +}
 +
 +int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
 +{
 +      struct update_callback_data data;
 +      struct rev_info rev;
 +      init_revisions(&rev, prefix);
 +      setup_revisions(0, NULL, &rev, NULL);
 +      rev.prune_data = pathspec;
 +      rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
 +      rev.diffopt.format_callback = update_callback;
 +      data.flags = flags;
 +      data.add_errors = 0;
 +      rev.diffopt.format_callback_data = &data;
 +      run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
 +      return !!data.add_errors;
 +}
 +
diff --combined sha1_file.c
index 9ee1ed16ad2df8847bd5008a5d61c807c145357a,e2cb342a32f31be2b9ffc1867fbfd671fe63cef1..aec81bbae7628574e997fc3a6c9f5ae3dda2476d
@@@ -2136,7 -2136,9 +2136,9 @@@ static void write_sha1_file_prepare(con
   */
  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,
@@@ -2361,22 -2363,51 +2363,22 @@@ int has_sha1_file(const unsigned char *
        return has_loose_object(sha1);
  }
  
 -int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
 +static int index_mem(unsigned char *sha1, void *buf, size_t size,
 +                   int write_object, enum object_type type, const char *path)
  {
 -      struct strbuf buf;
 -      int ret;
 -
 -      strbuf_init(&buf, 0);
 -      if (strbuf_read(&buf, fd, 4096) < 0) {
 -              strbuf_release(&buf);
 -              return -1;
 -      }
 -
 -      if (!type)
 -              type = blob_type;
 -      if (write_object)
 -              ret = write_sha1_file(buf.buf, buf.len, type, sha1);
 -      else
 -              ret = hash_sha1_file(buf.buf, buf.len, type, sha1);
 -      strbuf_release(&buf);
 -
 -      return ret;
 -}
 -
 -int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
 -           enum object_type type, const char *path)
 -{
 -      size_t size = xsize_t(st->st_size);
 -      void *buf = NULL;
        int ret, re_allocated = 0;
  
 -      if (size)
 -              buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
 -      close(fd);
 -
        if (!type)
                type = OBJ_BLOB;
  
        /*
         * Convert blobs to git internal format
         */
 -      if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
 +      if ((type == OBJ_BLOB) && path) {
                struct strbuf nbuf;
                strbuf_init(&nbuf, 0);
                if (convert_to_git(path, buf, size, &nbuf,
                                   write_object ? safe_crlf : 0)) {
 -                      munmap(buf, size);
                        buf = strbuf_detach(&nbuf, &size);
                        re_allocated = 1;
                }
                ret = write_sha1_file(buf, size, typename(type), sha1);
        else
                ret = hash_sha1_file(buf, size, typename(type), sha1);
 -      if (re_allocated) {
 +      if (re_allocated)
                free(buf);
 -              return ret;
 -      }
 -      if (size)
 +      return ret;
 +}
 +
 +int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
 +           enum object_type type, const char *path)
 +{
 +      int ret;
 +      size_t size = xsize_t(st->st_size);
 +
 +      if (!S_ISREG(st->st_mode)) {
 +              struct strbuf sbuf;
 +              strbuf_init(&sbuf, 0);
 +              if (strbuf_read(&sbuf, fd, 4096) >= 0)
 +                      ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object,
 +                                      type, path);
 +              else
 +                      ret = -1;
 +              strbuf_release(&sbuf);
 +      } else if (size) {
 +              void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
 +              ret = index_mem(sha1, buf, size, write_object, type, path);
                munmap(buf, size);
 +      } else
 +              ret = index_mem(sha1, NULL, size, write_object, type, path);
 +      close(fd);
        return ret;
  }