Code

Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Mon, 12 Nov 2007 08:14:15 +0000 (00:14 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 12 Nov 2007 08:14:15 +0000 (00:14 -0800)
* maint:
  for-each-ref: fix off by one read.
  git-branch: remove mention of non-existent '-b' option
  git-svn: prevent dcommitting if the index is dirty.
  Fix memory leak in traverse_commit_list

1  2 
Documentation/git-branch.txt
builtin-for-each-ref.c
git-svn.perl

index 5e81aa4ee15af50bd47aa9b26ffd566328f3c35e,37cb8b83b2f7354fe03d99d22b2262cb6e85e42a..5ce905de862253aa2bd2ed619819306165e978f0
@@@ -85,7 -85,7 +85,7 @@@ OPTION
  -a::
        List both remote-tracking branches and local branches.
  
 --v::
 +-v, --verbose::
        Show sha1 and commit subject line for each head.
  
  --abbrev=<length>::
        '--track' were given.
  
  --no-track::
-       When -b is given and a branch is created off a remote branch,
+       When a branch is created off a remote branch,
        set up configuration so that git-pull will not retrieve data
        from the remote branch, ignoring the branch.autosetupmerge
        configuration variable.
diff --combined builtin-for-each-ref.c
index e909e66bedb168b7d10f6f0c3cd806d6ce139a2c,0327f403060f80648aeed5a00f5aa1f06f403325..bfde2e2bbeffaed68b369b25cfe1b5ef4b108e12
@@@ -7,7 -7,6 +7,7 @@@
  #include "tree.h"
  #include "blob.h"
  #include "quote.h"
 +#include "parse-options.h"
  
  /* Quoting styles */
  #define QUOTE_NONE 0
@@@ -88,6 -87,7 +88,6 @@@ static int used_atom_cnt, sort_atom_lim
  static int parse_atom(const char *atom, const char *ep)
  {
        const char *sp;
 -      char *n;
        int i, at;
  
        sp = atom;
        /* Is the atom a valid one? */
        for (i = 0; i < ARRAY_SIZE(valid_atom); i++) {
                int len = strlen(valid_atom[i].name);
 -              if (len == ep - sp && !memcmp(valid_atom[i].name, sp, len))
 +              /*
 +               * If the atom name has a colon, strip it and everything after
 +               * it off - it specifies the format for this entry, and
 +               * shouldn't be used for checking against the valid_atom
 +               * table.
 +               */
 +              const char *formatp = strchr(sp, ':');
 +              if (!formatp || ep < formatp)
 +                      formatp = ep;
 +              if (len == formatp - sp && !memcmp(valid_atom[i].name, sp, len))
                        break;
        }
  
                             (sizeof *used_atom) * used_atom_cnt);
        used_atom_type = xrealloc(used_atom_type,
                                  (sizeof(*used_atom_type) * used_atom_cnt));
 -      n = xmalloc(ep - atom + 1);
 -      memcpy(n, atom, ep - atom);
 -      n[ep-atom] = 0;
 -      used_atom[at] = n;
 +      used_atom[at] = xmemdupz(atom, ep - atom);
        used_atom_type[at] = valid_atom[i].cmp_type;
        return at;
  }
@@@ -159,18 -153,17 +159,18 @@@ static const char *find_next(const cha
   * Make sure the format string is well formed, and parse out
   * the used atoms.
   */
 -static void verify_format(const char *format)
 +static int verify_format(const char *format)
  {
        const char *cp, *sp;
        for (cp = format; *cp && (sp = find_next(cp)); ) {
                const char *ep = strchr(sp, ')');
                if (!ep)
 -                      die("malformatted format string %s", sp);
 +                      return error("malformatted format string %s", sp);
                /* sp points at "%(" and ep points at the closing ")" */
                parse_atom(sp + 2, ep);
                cp = ep + 1;
        }
 +      return 0;
  }
  
  /*
@@@ -304,7 -297,7 +304,7 @@@ static const char *find_wholine(const c
                if (!eol)
                        return "";
                eol++;
-               if (eol[1] == '\n')
+               if (*eol == '\n')
                        return ""; /* end of header */
                buf = eol;
        }
  static const char *copy_line(const char *buf)
  {
        const char *eol = strchr(buf, '\n');
 -      char *line;
 -      int len;
        if (!eol)
                return "";
 -      len = eol - buf;
 -      line = xmalloc(len + 1);
 -      memcpy(line, buf, len);
 -      line[len] = 0;
 -      return line;
 +      return xmemdupz(buf, eol - buf);
  }
  
  static const char *copy_name(const char *buf)
  {
 -      const char *eol = strchr(buf, '\n');
 -      const char *eoname = strstr(buf, " <");
 -      char *line;
 -      int len;
 -      if (!(eoname && eol && eoname < eol))
 -              return "";
 -      len = eoname - buf;
 -      line = xmalloc(len + 1);
 -      memcpy(line, buf, len);
 -      line[len] = 0;
 -      return line;
 +      const char *cp;
 +      for (cp = buf; *cp && *cp != '\n'; cp++) {
 +              if (!strncmp(cp, " <", 2))
 +                      return xmemdupz(buf, cp - buf);
 +      }
 +      return "";
  }
  
  static const char *copy_email(const char *buf)
  {
        const char *email = strchr(buf, '<');
        const char *eoemail = strchr(email, '>');
 -      char *line;
 -      int len;
        if (!email || !eoemail)
                return "";
 -      eoemail++;
 -      len = eoemail - email;
 -      line = xmalloc(len + 1);
 -      memcpy(line, email, len);
 -      line[len] = 0;
 -      return line;
 +      return xmemdupz(email, eoemail + 1 - email);
  }
  
 -static void grab_date(const char *buf, struct atom_value *v)
 +static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
  {
        const char *eoemail = strstr(buf, "> ");
        char *zone;
        unsigned long timestamp;
        long tz;
 +      enum date_mode date_mode = DATE_NORMAL;
 +      const char *formatp;
 +
 +      /*
 +       * We got here because atomname ends in "date" or "date<something>";
 +       * it's not possible that <something> is not ":<format>" because
 +       * parse_atom() wouldn't have allowed it, so we can assume that no
 +       * ":" means no format is specified, and use the default.
 +       */
 +      formatp = strchr(atomname, ':');
 +      if (formatp != NULL) {
 +              formatp++;
 +              date_mode = parse_date_format(formatp);
 +      }
  
        if (!eoemail)
                goto bad;
        tz = strtol(zone, NULL, 10);
        if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
                goto bad;
 -      v->s = xstrdup(show_date(timestamp, tz, 0));
 +      v->s = xstrdup(show_date(timestamp, tz, date_mode));
        v->ul = timestamp;
        return;
   bad:
@@@ -394,7 -391,7 +394,7 @@@ static void grab_person(const char *who
                if (name[wholen] != 0 &&
                    strcmp(name + wholen, "name") &&
                    strcmp(name + wholen, "email") &&
 -                  strcmp(name + wholen, "date"))
 +                  prefixcmp(name + wholen, "date"))
                        continue;
                if (!wholine)
                        wholine = find_wholine(who, wholen, buf, sz);
                        v->s = copy_name(wholine);
                else if (!strcmp(name + wholen, "email"))
                        v->s = copy_email(wholine);
 -              else if (!strcmp(name + wholen, "date"))
 -                      grab_date(wholine, v);
 +              else if (!prefixcmp(name + wholen, "date"))
 +                      grab_date(wholine, v, name);
        }
  
        /* For a tag or a commit object, if "creator" or "creatordate" is
                if (deref)
                        name++;
  
 -              if (!strcmp(name, "creatordate"))
 -                      grab_date(wholine, v);
 +              if (!prefixcmp(name, "creatordate"))
 +                      grab_date(wholine, v, name);
                else if (!strcmp(name, "creator"))
                        v->s = copy_line(wholine);
        }
@@@ -802,76 -799,94 +802,76 @@@ static struct ref_sort *default_sort(vo
        return sort;
  }
  
 -int cmd_for_each_ref(int ac, const char **av, const char *prefix)
 +int opt_parse_sort(const struct option *opt, const char *arg, int unset)
 +{
 +      struct ref_sort **sort_tail = opt->value;
 +      struct ref_sort *s;
 +      int len;
 +
 +      if (!arg) /* should --no-sort void the list ? */
 +              return -1;
 +
 +      *sort_tail = s = xcalloc(1, sizeof(*s));
 +      sort_tail = &s->next;
 +
 +      if (*arg == '-') {
 +              s->reverse = 1;
 +              arg++;
 +      }
 +      len = strlen(arg);
 +      s->atom = parse_atom(arg, arg+len);
 +      return 0;
 +}
 +
 +static char const * const for_each_ref_usage[] = {
 +      "git-for-each-ref [options] [<pattern>]",
 +      NULL
 +};
 +
 +int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
  {
        int i, num_refs;
 -      const char *format = NULL;
 +      const char *format = "%(objectname) %(objecttype)\t%(refname)";
        struct ref_sort *sort = NULL, **sort_tail = &sort;
 -      int maxcount = 0;
 -      int quote_style = -1; /* unspecified yet */
 +      int maxcount = 0, quote_style;
 +      int quote_shell = 0, quote_perl = 0, quote_python = 0, quote_tcl = 0;
        struct refinfo **refs;
        struct grab_ref_cbdata cbdata;
  
 -      for (i = 1; i < ac; i++) {
 -              const char *arg = av[i];
 -              if (arg[0] != '-')
 -                      break;
 -              if (!strcmp(arg, "--")) {
 -                      i++;
 -                      break;
 -              }
 -              if (!prefixcmp(arg, "--format=")) {
 -                      if (format)
 -                              die("more than one --format?");
 -                      format = arg + 9;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) {
 -                      if (0 <= quote_style)
 -                              die("more than one quoting style?");
 -                      quote_style = QUOTE_SHELL;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) {
 -                      if (0 <= quote_style)
 -                              die("more than one quoting style?");
 -                      quote_style = QUOTE_PERL;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "--python") ) {
 -                      if (0 <= quote_style)
 -                              die("more than one quoting style?");
 -                      quote_style = QUOTE_PYTHON;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "--tcl") ) {
 -                      if (0 <= quote_style)
 -                              die("more than one quoting style?");
 -                      quote_style = QUOTE_TCL;
 -                      continue;
 -              }
 -              if (!prefixcmp(arg, "--count=")) {
 -                      if (maxcount)
 -                              die("more than one --count?");
 -                      maxcount = atoi(arg + 8);
 -                      if (maxcount <= 0)
 -                              die("The number %s did not parse", arg);
 -                      continue;
 -              }
 -              if (!prefixcmp(arg, "--sort=")) {
 -                      struct ref_sort *s = xcalloc(1, sizeof(*s));
 -                      int len;
 -
 -                      s->next = NULL;
 -                      *sort_tail = s;
 -                      sort_tail = &s->next;
 -
 -                      arg += 7;
 -                      if (*arg == '-') {
 -                              s->reverse = 1;
 -                              arg++;
 -                      }
 -                      len = strlen(arg);
 -                      sort->atom = parse_atom(arg, arg+len);
 -                      continue;
 -              }
 -              break;
 +      struct option opts[] = {
 +              OPT_BOOLEAN('s', "shell", &quote_shell, "quote placeholders suitably for shells"),
 +              OPT_BOOLEAN('p', "perl",  &quote_perl, "quote placeholders suitably for perl"),
 +              OPT_BOOLEAN( 0 , "python", &quote_python, "quote placeholders suitably for python"),
 +              OPT_BOOLEAN( 0 , "tcl",  &quote_tcl, "quote placeholders suitably for tcl"),
 +
 +              OPT_GROUP(""),
 +              OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
 +              OPT_STRING(  0 , "format", &format, "format", "format to use for the output"),
 +              OPT_CALLBACK(0 , "sort", sort_tail, "key",
 +                          "field name to sort on", &opt_parse_sort),
 +              OPT_END(),
 +      };
 +
 +      parse_options(argc, argv, opts, for_each_ref_usage, 0);
 +      if (maxcount < 0) {
 +              error("invalid --count argument: `%d'", maxcount);
 +              usage_with_options(for_each_ref_usage, opts);
        }
 -      if (quote_style < 0)
 -              quote_style = QUOTE_NONE;
 +      if (quote_shell + quote_perl + quote_python + quote_tcl > 1) {
 +              error("more than one quoting style ?");
 +              usage_with_options(for_each_ref_usage, opts);
 +      }
 +      if (verify_format(format))
 +              usage_with_options(for_each_ref_usage, opts);
  
 +      quote_style = QUOTE_SHELL * quote_shell + QUOTE_PERL * quote_perl +
 +              QUOTE_PYTHON * quote_python + QUOTE_TCL * quote_tcl;
        if (!sort)
                sort = default_sort();
        sort_atom_limit = used_atom_cnt;
 -      if (!format)
 -              format = "%(objectname) %(objecttype)\t%(refname)";
 -
 -      verify_format(format);
  
        memset(&cbdata, 0, sizeof(cbdata));
 -      cbdata.grab_pattern = av + i;
 +      cbdata.grab_pattern = argv;
        for_each_ref(grab_single_ref, &cbdata);
        refs = cbdata.grab_array;
        num_refs = cbdata.grab_cnt;
diff --combined git-svn.perl
index dd93e320a7c0d20e8eec1c171c9c96a32ced406b,4c779b6c6d1c53030c0ed984155c885a25604e57..1e244975abe1b41f10a784dc1e7869ee38efbfc3
@@@ -9,11 -9,6 +9,11 @@@ use vars qw/   $AUTHOR $VERSIO
  $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
  $VERSION = '@@GIT_VERSION@@';
  
 +# From which subdir have we been invoked?
 +my $cmd_dir_prefix = eval {
 +      command_oneline([qw/rev-parse --show-prefix/], STDERR => 0)
 +} || '';
 +
  my $git_dir_user_set = 1 if defined $ENV{GIT_DIR};
  $ENV{GIT_DIR} ||= '.git';
  $Git::SVN::default_repo_id = 'svn';
@@@ -24,12 -19,12 +24,12 @@@ $Git::SVN::Log::TZ = $ENV{TZ}
  $ENV{TZ} = 'UTC';
  $| = 1; # unbuffer STDOUT
  
 -sub fatal (@) { print STDERR @_; exit 1 }
 +sub fatal (@) { print STDERR "@_\n"; exit 1 }
  require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
  require SVN::Ra;
  require SVN::Delta;
  if ($SVN::Core::VERSION lt '1.1.0') {
 -      fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n";
 +      fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)";
  }
  push @Git::SVN::Ra::ISA, 'SVN::Ra';
  push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
@@@ -128,19 -123,8 +128,19 @@@ my %cmd = 
        'set-tree' => [ \&cmd_set_tree,
                        "Set an SVN repository to a git tree-ish",
                        { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
 +      'create-ignore' => [ \&cmd_create_ignore,
 +                           'Create a .gitignore per svn:ignore',
 +                           { 'revision|r=i' => \$_revision
 +                           } ],
 +        'propget' => [ \&cmd_propget,
 +                     'Print the value of a property on a file or directory',
 +                     { 'revision|r=i' => \$_revision } ],
 +        'proplist' => [ \&cmd_proplist,
 +                     'List all properties of a file or directory',
 +                     { 'revision|r=i' => \$_revision } ],
        'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
 -                      { 'revision|r=i' => \$_revision } ],
 +                      { 'revision|r=i' => \$_revision
 +                      } ],
        'multi-fetch' => [ \&cmd_multi_fetch,
                           "Deprecated alias for $0 fetch --all",
                           { 'revision|r=s' => \$_revision, %fc_opts } ],
                          'non-recursive' => \$Git::SVN::Log::non_recursive,
                          'authors-file|A=s' => \$_authors,
                          'color' => \$Git::SVN::Log::color,
 -                        'pager=s' => \$Git::SVN::Log::pager,
 +                        'pager=s' => \$Git::SVN::Log::pager
                        } ],
        'find-rev' => [ \&cmd_find_rev, "Translate between SVN revision numbers and tree-ish",
 -                      { } ],
 +                      {} ],
        'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory",
                        { 'merge|m|M' => \$_merge,
                          'verbose|v' => \$_verbose,
@@@ -252,7 -236,7 +252,7 @@@ Usage: $0 <command> [options] [argument
                next if $cmd && $cmd ne $_;
                next if /^multi-/; # don't show deprecated commands
                print $fd '  ',pack('A17',$_),$cmd{$_}->[1],"\n";
 -              foreach (keys %{$cmd{$_}->[2]}) {
 +              foreach (sort keys %{$cmd{$_}->[2]}) {
                        # mixed-case options are for .git/config only
                        next if /[A-Z]/ && /^[a-z]+$/i;
                        # prints out arguments as they should be passed:
@@@ -372,7 -356,7 +372,7 @@@ sub cmd_set_tree 
                } elsif (scalar @tmp > 1) {
                        push @revs, reverse(command('rev-list',@tmp));
                } else {
 -                      fatal "Failed to rev-parse $c\n";
 +                      fatal "Failed to rev-parse $c";
                }
        }
        my $gs = Git::SVN->new;
                fatal "There are new revisions that were fetched ",
                      "and need to be merged (or acknowledged) ",
                      "before committing.\nlast rev: $r_last\n",
 -                    " current: $gs->{last_rev}\n";
 +                    " current: $gs->{last_rev}";
        }
        $gs->set_tree($_) foreach @revs;
        print "Done committing ",scalar @revs," revisions to SVN\n";
  
  sub cmd_dcommit {
        my $head = shift;
+       git_cmd_try { command_oneline(qw/diff-index --quiet HEAD/) }
+               'Cannot dcommit with a dirty index.  Commit your changes first'
+               . "or stash them with `git stash'.\n";
        $head ||= 'HEAD';
        my @refs;
        my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
                        (undef, $last_rev, undef) = cmt_metadata("$d~1");
                        unless (defined $last_rev) {
                                fatal "Unable to extract revision information ",
 -                                    "from commit $d~1\n";
 +                                    "from commit $d~1";
                        }
                }
                if ($_dry_run) {
@@@ -543,100 -530,7 +546,100 @@@ sub cmd_show_ignore 
        my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
        $gs ||= Git::SVN->new;
        my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
 -      $gs->traverse_ignore(\*STDOUT, $gs->{path}, $r);
 +      $gs->prop_walk($gs->{path}, $r, sub {
 +              my ($gs, $path, $props) = @_;
 +              print STDOUT "\n# $path\n";
 +              my $s = $props->{'svn:ignore'} or return;
 +              $s =~ s/[\r\n]+/\n/g;
 +              chomp $s;
 +              $s =~ s#^#$path#gm;
 +              print STDOUT "$s\n";
 +      });
 +}
 +
 +sub cmd_create_ignore {
 +      my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
 +      $gs ||= Git::SVN->new;
 +      my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
 +      $gs->prop_walk($gs->{path}, $r, sub {
 +              my ($gs, $path, $props) = @_;
 +              # $path is of the form /path/to/dir/
 +              my $ignore = '.' . $path . '.gitignore';
 +              my $s = $props->{'svn:ignore'} or return;
 +              open(GITIGNORE, '>', $ignore)
 +                or fatal("Failed to open `$ignore' for writing: $!");
 +              $s =~ s/[\r\n]+/\n/g;
 +              chomp $s;
 +              # Prefix all patterns so that the ignore doesn't apply
 +              # to sub-directories.
 +              $s =~ s#^#/#gm;
 +              print GITIGNORE "$s\n";
 +              close(GITIGNORE)
 +                or fatal("Failed to close `$ignore': $!");
 +              command_noisy('add', $ignore);
 +      });
 +}
 +
 +# get_svnprops(PATH)
 +# ------------------
 +# Helper for cmd_propget and cmd_proplist below.
 +sub get_svnprops {
 +      my $path = shift;
 +      my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
 +      $gs ||= Git::SVN->new;
 +
 +      # prefix THE PATH by the sub-directory from which the user
 +      # invoked us.
 +      $path = $cmd_dir_prefix . $path;
 +      fatal("No such file or directory: $path") unless -e $path;
 +      my $is_dir = -d $path ? 1 : 0;
 +      $path = $gs->{path} . '/' . $path;
 +
 +      # canonicalize the path (otherwise libsvn will abort or fail to
 +      # find the file)
 +      # File::Spec->canonpath doesn't collapse x/../y into y (for a
 +      # good reason), so let's do this manually.
 +      $path =~ s#/+#/#g;
 +      $path =~ s#/\.(?:/|$)#/#g;
 +      $path =~ s#/[^/]+/\.\.##g;
 +      $path =~ s#/$##g;
 +
 +      my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
 +      my $props;
 +      if ($is_dir) {
 +              (undef, undef, $props) = $gs->ra->get_dir($path, $r);
 +      }
 +      else {
 +              (undef, $props) = $gs->ra->get_file($path, $r, undef);
 +      }
 +      return $props;
 +}
 +
 +# cmd_propget (PROP, PATH)
 +# ------------------------
 +# Print the SVN property PROP for PATH.
 +sub cmd_propget {
 +      my ($prop, $path) = @_;
 +      $path = '.' if not defined $path;
 +      usage(1) if not defined $prop;
 +      my $props = get_svnprops($path);
 +      if (not defined $props->{$prop}) {
 +              fatal("`$path' does not have a `$prop' SVN property.");
 +      }
 +      print $props->{$prop} . "\n";
 +}
 +
 +# cmd_proplist (PATH)
 +# -------------------
 +# Print the list of SVN properties for PATH.
 +sub cmd_proplist {
 +      my $path = shift;
 +      $path = '.' if not defined $path;
 +      my $props = get_svnprops($path);
 +      print "Properties on '$path':\n";
 +      foreach (sort keys %{$props}) {
 +              print "  $_\n";
 +      }
  }
  
  sub cmd_multi_init {
@@@ -685,7 -579,7 +688,7 @@@ sub cmd_multi_fetch 
  sub cmd_commit_diff {
        my ($ta, $tb, $url) = @_;
        my $usage = "Usage: $0 commit-diff -r<revision> ".
 -                  "<tree-ish> <tree-ish> [<URL>]\n";
 +                  "<tree-ish> <tree-ish> [<URL>]";
        fatal($usage) if (!defined $ta || !defined $tb);
        my $svn_path;
        if (!defined $url) {
        if (defined $_message && defined $_file) {
                fatal("Both --message/-m and --file/-F specified ",
                      "for the commit message.\n",
 -                    "I have no idea what you mean\n");
 +                    "I have no idea what you mean");
        }
        if (defined $_file) {
                $_message = file_to_s($_file);
@@@ -766,7 -660,7 +769,7 @@@ sub complete_svn_url 
        if ($path !~ m#^[a-z\+]+://#) {
                if (!defined $url || $url !~ m#^[a-z\+]+://#) {
                        fatal("E: '$path' is not a complete URL ",
 -                            "and a separate URL is not specified\n");
 +                            "and a separate URL is not specified");
                }
                return ($url, $path);
        }
@@@ -787,7 -681,7 +790,7 @@@ sub complete_url_ls_init 
                $repo_path =~ s#^/+##;
                unless ($ra) {
                        fatal("E: '$repo_path' is not a complete URL ",
 -                            "and a separate URL is not specified\n");
 +                            "and a separate URL is not specified");
                }
        }
        my $url = $ra->{url};
@@@ -960,8 -854,7 +963,8 @@@ sub cmt_metadata 
  
  sub working_head_info {
        my ($head, $refs) = @_;
 -      my ($fh, $ctx) = command_output_pipe('log', '--no-color', $head);
 +      my @args = ('log', '--no-color', '--first-parent');
 +      my ($fh, $ctx) = command_output_pipe(@args, $head);
        my $hash;
        my %max;
        while (<$fh>) {
@@@ -1628,45 -1521,28 +1631,45 @@@ sub rel_path 
        $url;
  }
  
 -sub traverse_ignore {
 -      my ($self, $fh, $path, $r) = @_;
 -      $path =~ s#^/+##g;
 -      my $ra = $self->ra;
 -      my ($dirent, undef, $props) = $ra->get_dir($path, $r);
 +# prop_walk(PATH, REV, SUB)
 +# -------------------------
 +# Recursively traverse PATH at revision REV and invoke SUB for each
 +# directory that contains a SVN property.  SUB will be invoked as
 +# follows:  &SUB(gs, path, props);  where `gs' is this instance of
 +# Git::SVN, `path' the path to the directory where the properties
 +# `props' were found.  The `path' will be relative to point of checkout,
 +# that is, if url://repo/trunk is the current Git branch, and that
 +# directory contains a sub-directory `d', SUB will be invoked with `/d/'
 +# as `path' (note the trailing `/').
 +sub prop_walk {
 +      my ($self, $path, $rev, $sub) = @_;
 +
 +      my ($dirent, undef, $props) = $self->ra->get_dir($path, $rev);
 +      $path =~ s#^/*#/#g;
        my $p = $path;
 -      $p =~ s#^\Q$self->{path}\E(/|$)##;
 -      print $fh length $p ? "\n# $p\n" : "\n# /\n";
 -      if (my $s = $props->{'svn:ignore'}) {
 -              $s =~ s/[\r\n]+/\n/g;
 -              chomp $s;
 -              if (length $p == 0) {
 -                      $s =~ s#\n#\n/$p#g;
 -                      print $fh "/$s\n";
 -              } else {
 -                      $s =~ s#\n#\n/$p/#g;
 -                      print $fh "/$p/$s\n";
 -              }
 -      }
 +      # Strip the irrelevant part of the path.
 +      $p =~ s#^/+\Q$self->{path}\E(/|$)#/#;
 +      # Ensure the path is terminated by a `/'.
 +      $p =~ s#/*$#/#;
 +
 +      # The properties contain all the internal SVN stuff nobody
 +      # (usually) cares about.
 +      my $interesting_props = 0;
 +      foreach (keys %{$props}) {
 +              # If it doesn't start with `svn:', it must be a
 +              # user-defined property.
 +              ++$interesting_props and next if $_ !~ /^svn:/;
 +              # FIXME: Fragile, if SVN adds new public properties,
 +              # this needs to be updated.
 +              ++$interesting_props if /^svn:(?:ignore|keywords|executable
 +                                               |eol-style|mime-type
 +                                               |externals|needs-lock)$/x;
 +      }
 +      &$sub($self, $p, $props) if $interesting_props;
 +
        foreach (sort keys %$dirent) {
                next if $dirent->{$_}->{kind} != $SVN::Node::dir;
 -              $self->traverse_ignore($fh, "$path/$_", $r);
 +              $self->prop_walk($path . '/' . $_, $rev, $sub);
        }
  }
  
@@@ -1793,7 -1669,7 +1796,7 @@@ sub assert_index_clean 
                $x = command_oneline('write-tree');
                if ($y ne $x) {
                        ::fatal "trees ($treeish) $y != $x\n",
 -                              "Something is seriously wrong...\n";
 +                              "Something is seriously wrong...";
                }
        });
  }
@@@ -2012,16 -1888,6 +2015,16 @@@ sub find_parent_branch 
                        $gs->ra->gs_do_switch($r0, $rev, $gs,
                                              $self->full_url, $ed)
                          or die "SVN connection failed somewhere...\n";
 +              } elsif ($self->ra->trees_match($new_url, $r0,
 +                                              $self->full_url, $rev)) {
 +                      print STDERR "Trees match:\n",
 +                                   "  $new_url\@$r0\n",
 +                                   "  ${\$self->full_url}\@$rev\n",
 +                                   "Following parent with no changes\n";
 +                      $self->tmp_index_do(sub {
 +                          command_noisy('read-tree', $parent);
 +                      });
 +                      $self->{last_commit} = $parent;
                } else {
                        print STDERR "Following parent with do_update\n";
                        $ed = SVN::Git::Fetcher->new($self);
@@@ -2219,7 -2085,7 +2222,7 @@@ sub set_tree 
        my ($self, $tree) = (shift, shift);
        my $log_entry = ::get_commit_entry($tree);
        unless ($self->{last_rev}) {
 -              fatal("Must have an existing revision to commit\n");
 +              fatal("Must have an existing revision to commit");
        }
        my %ed_opts = ( r => $self->{last_rev},
                        log => $log_entry->{log},
@@@ -2468,31 -2334,23 +2471,31 @@@ sub ssl_server_trust 
        my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
        $may_save = undef if $_no_auth_cache;
        print STDERR "Error validating server certificate for '$realm':\n";
 -      if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
 -              print STDERR " - The certificate is not issued by a trusted ",
 -                    "authority. Use the\n",
 -                    "   fingerprint to validate the certificate manually!\n";
 -      }
 -      if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
 -              print STDERR " - The certificate hostname does not match.\n";
 -      }
 -      if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
 -              print STDERR " - The certificate is not yet valid.\n";
 -      }
 -      if ($failures & $SVN::Auth::SSL::EXPIRED) {
 -              print STDERR " - The certificate has expired.\n";
 -      }
 -      if ($failures & $SVN::Auth::SSL::OTHER) {
 -              print STDERR " - The certificate has an unknown error.\n";
 -      }
 +      {
 +              no warnings 'once';
 +              # All variables SVN::Auth::SSL::* are used only once,
 +              # so we're shutting up Perl warnings about this.
 +              if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
 +                      print STDERR " - The certificate is not issued ",
 +                          "by a trusted authority. Use the\n",
 +                          "   fingerprint to validate ",
 +                          "the certificate manually!\n";
 +              }
 +              if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
 +                      print STDERR " - The certificate hostname ",
 +                          "does not match.\n";
 +              }
 +              if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
 +                      print STDERR " - The certificate is not yet valid.\n";
 +              }
 +              if ($failures & $SVN::Auth::SSL::EXPIRED) {
 +                      print STDERR " - The certificate has expired.\n";
 +              }
 +              if ($failures & $SVN::Auth::SSL::OTHER) {
 +                      print STDERR " - The certificate has ",
 +                          "an unknown error.\n";
 +              }
 +      } # no warnings 'once'
        printf STDERR
                "Certificate information:\n".
                " - Hostname: %s\n".
@@@ -2576,6 -2434,20 +2579,6 @@@ sub _read_password 
        $password;
  }
  
 -package main;
 -
 -{
 -      my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
 -                              $SVN::Node::dir.$SVN::Node::unknown.
 -                              $SVN::Node::none.$SVN::Node::file.
 -                              $SVN::Node::dir.$SVN::Node::unknown.
 -                              $SVN::Auth::SSL::CNMISMATCH.
 -                              $SVN::Auth::SSL::NOTYETVALID.
 -                              $SVN::Auth::SSL::EXPIRED.
 -                              $SVN::Auth::SSL::UNKNOWNCA.
 -                              $SVN::Auth::SSL::OTHER;
 -}
 -
  package SVN::Git::Fetcher;
  use vars qw/@ISA/;
  use strict;
@@@ -2992,21 -2864,16 +2995,21 @@@ sub open_or_add_dir 
        if (!defined $t) {
                die "$full_path not known in r$self->{r} or we have a bug!\n";
        }
 -      if ($t == $SVN::Node::none) {
 -              return $self->add_directory($full_path, $baton,
 -                                              undef, -1, $self->{pool});
 -      } elsif ($t == $SVN::Node::dir) {
 -              return $self->open_directory($full_path, $baton,
 -                                              $self->{r}, $self->{pool});
 -      }
 -      print STDERR "$full_path already exists in repository at ",
 -              "r$self->{r} and it is not a directory (",
 -              ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n";
 +      {
 +              no warnings 'once';
 +              # SVN::Node::none and SVN::Node::file are used only once,
 +              # so we're shutting up Perl's warnings about them.
 +              if ($t == $SVN::Node::none) {
 +                      return $self->add_directory($full_path, $baton,
 +                          undef, -1, $self->{pool});
 +              } elsif ($t == $SVN::Node::dir) {
 +                      return $self->open_directory($full_path, $baton,
 +                          $self->{r}, $self->{pool});
 +              } # no warnings 'once'
 +              print STDERR "$full_path already exists in repository at ",
 +                  "r$self->{r} and it is not a directory (",
 +                  ($t == $SVN::Node::file ? 'file' : 'unknown'),"/$t)\n";
 +      } # no warnings 'once'
        exit 1;
  }
  
@@@ -3168,7 -3035,7 +3171,7 @@@ sub apply_diff 
                if (defined $o{$f}) {
                        $self->$f($m);
                } else {
 -                      fatal("Invalid change type: $f\n");
 +                      fatal("Invalid change type: $f");
                }
        }
        $self->rmdirs if $_rmdir;
@@@ -3201,57 -3068,30 +3204,57 @@@ BEGIN 
        }
  }
  
 +sub _auth_providers () {
 +      [
 +        SVN::Client::get_simple_provider(),
 +        SVN::Client::get_ssl_server_trust_file_provider(),
 +        SVN::Client::get_simple_prompt_provider(
 +          \&Git::SVN::Prompt::simple, 2),
 +        SVN::Client::get_ssl_client_cert_file_provider(),
 +        SVN::Client::get_ssl_client_cert_prompt_provider(
 +          \&Git::SVN::Prompt::ssl_client_cert, 2),
 +        SVN::Client::get_ssl_client_cert_pw_prompt_provider(
 +          \&Git::SVN::Prompt::ssl_client_cert_pw, 2),
 +        SVN::Client::get_username_provider(),
 +        SVN::Client::get_ssl_server_trust_prompt_provider(
 +          \&Git::SVN::Prompt::ssl_server_trust),
 +        SVN::Client::get_username_prompt_provider(
 +          \&Git::SVN::Prompt::username, 2)
 +      ]
 +}
 +
  sub new {
        my ($class, $url) = @_;
        $url =~ s!/+$!!;
        return $RA if ($RA && $RA->{url} eq $url);
  
        SVN::_Core::svn_config_ensure($config_dir, undef);
 -      my ($baton, $callbacks) = SVN::Core::auth_open_helper([
 -          SVN::Client::get_simple_provider(),
 -          SVN::Client::get_ssl_server_trust_file_provider(),
 -          SVN::Client::get_simple_prompt_provider(
 -            \&Git::SVN::Prompt::simple, 2),
 -          SVN::Client::get_ssl_client_cert_file_provider(),
 -          SVN::Client::get_ssl_client_cert_prompt_provider(
 -            \&Git::SVN::Prompt::ssl_client_cert, 2),
 -          SVN::Client::get_ssl_client_cert_pw_prompt_provider(
 -            \&Git::SVN::Prompt::ssl_client_cert_pw, 2),
 -          SVN::Client::get_username_provider(),
 -          SVN::Client::get_ssl_server_trust_prompt_provider(
 -            \&Git::SVN::Prompt::ssl_server_trust),
 -          SVN::Client::get_username_prompt_provider(
 -            \&Git::SVN::Prompt::username, 2),
 -        ]);
 +      my ($baton, $callbacks) = SVN::Core::auth_open_helper(_auth_providers);
        my $config = SVN::Core::config_get_config($config_dir);
        $RA = undef;
 +      my $dont_store_passwords = 1;
 +      my $conf_t = ${$config}{'config'};
 +      {
 +              no warnings 'once';
 +              # The usage of $SVN::_Core::SVN_CONFIG_* variables
 +              # produces warnings that variables are used only once.
 +              # I had not found the better way to shut them up, so
 +              # the warnings of type 'once' are disabled in this block.
 +              if (SVN::_Core::svn_config_get_bool($conf_t,
 +                  $SVN::_Core::SVN_CONFIG_SECTION_AUTH,
 +                  $SVN::_Core::SVN_CONFIG_OPTION_STORE_PASSWORDS,
 +                  1) == 0) {
 +                      SVN::_Core::svn_auth_set_parameter($baton,
 +                          $SVN::_Core::SVN_AUTH_PARAM_DONT_STORE_PASSWORDS,
 +                          bless (\$dont_store_passwords, "_p_void"));
 +              }
 +              if (SVN::_Core::svn_config_get_bool($conf_t,
 +                  $SVN::_Core::SVN_CONFIG_SECTION_AUTH,
 +                  $SVN::_Core::SVN_CONFIG_OPTION_STORE_AUTH_CREDS,
 +                  1) == 0) {
 +                      $Git::SVN::Prompt::_no_auth_cache = 1;
 +              }
 +      } # no warnings 'once'
        my $self = SVN::Ra->new(url => $url, auth => $baton,
                              config => $config,
                              pool => SVN::Pool->new,
@@@ -3313,24 -3153,6 +3316,24 @@@ sub get_log 
        $ret;
  }
  
 +sub trees_match {
 +      my ($self, $url1, $rev1, $url2, $rev2) = @_;
 +      my $ctx = SVN::Client->new(auth => _auth_providers);
 +      my $out = IO::File->new_tmpfile;
 +
 +      # older SVN (1.1.x) doesn't take $pool as the last parameter for
 +      # $ctx->diff(), so we'll create a default one
 +      my $pool = SVN::Pool->new_default_sub;
 +
 +      $ra_invalid = 1; # this will open a new SVN::Ra connection to $url1
 +      $ctx->diff([], $url1, $rev1, $url2, $rev2, 1, 1, 0, $out, $out);
 +      $out->flush;
 +      my $ret = (($out->stat)[7] == 0);
 +      close $out or croak $!;
 +
 +      $ret;
 +}
 +
  sub get_commit_editor {
        my ($self, $log, $cb, $pool) = @_;
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
@@@ -3799,15 -3621,15 +3802,15 @@@ sub config_pager 
  sub run_pager {
        return unless -t *STDOUT && defined $pager;
        pipe my $rfd, my $wfd or return;
 -      defined(my $pid = fork) or ::fatal "Can't fork: $!\n";
 +      defined(my $pid = fork) or ::fatal "Can't fork: $!";
        if (!$pid) {
                open STDOUT, '>&', $wfd or
 -                                   ::fatal "Can't redirect to stdout: $!\n";
 +                                   ::fatal "Can't redirect to stdout: $!";
                return;
        }
 -      open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!\n";
 +      open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!";
        $ENV{LESS} ||= 'FRSX';
 -      exec $pager or ::fatal "Can't run pager: $! ($pager)\n";
 +      exec $pager or ::fatal "Can't run pager: $! ($pager)";
  }
  
  sub tz_to_s_offset {
@@@ -3943,7 -3765,7 +3946,7 @@@ sub cmd_show_log 
                        $r_min = $r_max = $::_revision;
                } else {
                        ::fatal "-r$::_revision is not supported, use ",
 -                              "standard \'git log\' arguments instead\n";
 +                              "standard 'git log' arguments instead";
                }
        }