~ubuntu-branches/ubuntu/intrepid/git-core/intrepid-updates

« back to all changes in this revision

Viewing changes to git-svn.perl

  • Committer: Package Import Robot
  • Author(s): Gerrit Pape
  • Date: 2007-04-22 13:31:05 UTC
  • mto: This revision was merged to the branch mainline in revision 20.
  • Revision ID: package-import@ubuntu.com-20070422133105-xg8fnm18r2cxcbg1
Tags: upstream-1.5.1.2
ImportĀ upstreamĀ versionĀ 1.5.1.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
use warnings;
5
5
use strict;
6
6
use vars qw/    $AUTHOR $VERSION
7
 
                $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
8
 
                $GIT_SVN_INDEX $GIT_SVN
9
 
                $GIT_DIR $GIT_SVN_DIR $REVDB/;
 
7
                $sha1 $sha1_short $_revision
 
8
                $_q $_authors %users/;
10
9
$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
11
10
$VERSION = '@@GIT_VERSION@@';
12
11
 
13
 
use Cwd qw/abs_path/;
14
 
$GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
15
 
$ENV{GIT_DIR} = $GIT_DIR;
 
12
my $git_dir_user_set = 1 if defined $ENV{GIT_DIR};
 
13
$ENV{GIT_DIR} ||= '.git';
 
14
$Git::SVN::default_repo_id = 'svn';
 
15
$Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
 
16
$Git::SVN::Ra::_log_window_size = 100;
16
17
 
17
 
my $LC_ALL = $ENV{LC_ALL};
18
 
my $TZ = $ENV{TZ};
19
 
# make sure the svn binary gives consistent output between locales and TZs:
 
18
$Git::SVN::Log::TZ = $ENV{TZ};
20
19
$ENV{TZ} = 'UTC';
21
 
$ENV{LC_ALL} = 'C';
22
20
$| = 1; # unbuffer STDOUT
23
21
 
24
 
# If SVN:: library support is added, please make the dependencies
25
 
# optional and preserve the capability to use the command-line client.
26
 
# use eval { require SVN::... } to make it lazy load
27
 
# We don't use any modules not in the standard Perl distribution:
 
22
sub fatal (@) { print STDERR @_; exit 1 }
 
23
require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
 
24
require SVN::Ra;
 
25
require SVN::Delta;
 
26
if ($SVN::Core::VERSION lt '1.1.0') {
 
27
        fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n";
 
28
}
 
29
push @Git::SVN::Ra::ISA, 'SVN::Ra';
 
30
push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
 
31
push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
28
32
use Carp qw/croak/;
29
33
use IO::File qw//;
30
34
use File::Basename qw/dirname basename/;
31
35
use File::Path qw/mkpath/;
32
 
use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;
33
 
use File::Spec qw//;
34
 
use File::Copy qw/copy/;
35
 
use POSIX qw/strftime/;
 
36
use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
36
37
use IPC::Open3;
37
 
use Memoize;
38
 
memoize('revisions_eq');
39
 
memoize('cmt_metadata');
40
 
memoize('get_commit_time');
41
 
 
42
 
my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
43
 
 
44
 
sub nag_lib {
45
 
        print STDERR <<EOF;
46
 
! Please consider installing the SVN Perl libraries (version 1.1.0 or
47
 
! newer).  You will generally get better performance and fewer bugs,
48
 
! especially if you:
49
 
! 1) have a case-insensitive filesystem
50
 
! 2) replace symlinks with files (and vice-versa) in commits
51
 
 
52
 
EOF
 
38
use Git;
 
39
 
 
40
BEGIN {
 
41
        my $s;
 
42
        foreach (qw/command command_oneline command_noisy command_output_pipe
 
43
                    command_input_pipe command_close_pipe/) {
 
44
                $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ".
 
45
                      "*Git::SVN::Migration::$_ = ".
 
46
                      "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; ";
 
47
        }
 
48
        eval $s;
53
49
}
54
50
 
55
 
$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
56
 
libsvn_load();
57
 
nag_lib() unless $_use_lib;
58
 
 
59
 
my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
60
 
my $sha1 = qr/[a-f\d]{40}/;
61
 
my $sha1_short = qr/[a-f\d]{4,40}/;
62
 
my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
63
 
        $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote,
64
 
        $_repack, $_repack_nr, $_repack_flags, $_q,
65
 
        $_message, $_file, $_follow_parent, $_no_metadata,
66
 
        $_template, $_shared, $_no_default_regex, $_no_graft_copy,
67
 
        $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
68
 
        $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
69
 
        $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive);
70
 
my (@_branch_from, %tree_map, %users, %rusers, %equiv);
71
 
my ($_svn_co_url_revs, $_svn_pg_peg_revs);
72
 
my @repo_path_split_cache;
73
 
 
74
 
my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
75
 
                'branch|b=s' => \@_branch_from,
76
 
                'follow-parent|follow' => \$_follow_parent,
77
 
                'branch-all-refs|B' => \$_branch_all_refs,
 
51
my ($SVN);
 
52
 
 
53
$sha1 = qr/[a-f\d]{40}/;
 
54
$sha1_short = qr/[a-f\d]{4,40}/;
 
55
my ($_stdin, $_help, $_edit,
 
56
        $_message, $_file,
 
57
        $_template, $_shared,
 
58
        $_version, $_fetch_all,
 
59
        $_merge, $_strategy, $_dry_run, $_local,
 
60
        $_prefix, $_no_checkout, $_verbose);
 
61
$Git::SVN::_follow_parent = 1;
 
62
my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
 
63
                    'config-dir=s' => \$Git::SVN::Ra::config_dir,
 
64
                    'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache );
 
65
my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
78
66
                'authors-file|A=s' => \$_authors,
79
 
                'repack:i' => \$_repack,
80
 
                'no-metadata' => \$_no_metadata,
 
67
                'repack:i' => \$Git::SVN::_repack,
 
68
                'noMetadata' => \$Git::SVN::_no_metadata,
 
69
                'useSvmProps' => \$Git::SVN::_use_svm_props,
 
70
                'useSvnsyncProps' => \$Git::SVN::_use_svnsync_props,
 
71
                'log-window-size=i' => \$Git::SVN::Ra::_log_window_size,
 
72
                'no-checkout' => \$_no_checkout,
81
73
                'quiet|q' => \$_q,
82
 
                'ignore-nodate' => \$_ignore_nodate,
83
 
                'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
 
74
                'repack-flags|repack-args|repack-opts=s' =>
 
75
                   \$Git::SVN::_repack_flags,
 
76
                %remote_opts );
84
77
 
85
78
my ($_trunk, $_tags, $_branches);
86
 
my %multi_opts = ( 'trunk|T=s' => \$_trunk,
87
 
                'tags|t=s' => \$_tags,
88
 
                'branches|b=s' => \$_branches );
89
 
my %init_opts = ( 'template=s' => \$_template, 'shared' => \$_shared );
 
79
my %icv;
 
80
my %init_opts = ( 'template=s' => \$_template, 'shared:s' => \$_shared,
 
81
                  'trunk|T=s' => \$_trunk, 'tags|t=s' => \$_tags,
 
82
                  'branches|b=s' => \$_branches, 'prefix=s' => \$_prefix,
 
83
                  'no-metadata' => sub { $icv{noMetadata} = 1 },
 
84
                  'use-svm-props' => sub { $icv{useSvmProps} = 1 },
 
85
                  'use-svnsync-props' => sub { $icv{useSvnsyncProps} = 1 },
 
86
                  'rewrite-root=s' => sub { $icv{rewriteRoot} = $_[1] },
 
87
                  %remote_opts );
90
88
my %cmt_opts = ( 'edit|e' => \$_edit,
91
 
                'rmdir' => \$_rmdir,
92
 
                'find-copies-harder' => \$_find_copies_harder,
93
 
                'l=i' => \$_l,
94
 
                'copy-similarity|C=i'=> \$_cp_similarity
 
89
                'rmdir' => \$SVN::Git::Editor::_rmdir,
 
90
                'find-copies-harder' => \$SVN::Git::Editor::_find_copies_harder,
 
91
                'l=i' => \$SVN::Git::Editor::_rename_limit,
 
92
                'copy-similarity|C=i'=> \$SVN::Git::Editor::_cp_similarity
95
93
);
96
94
 
97
95
my %cmd = (
98
 
        fetch => [ \&fetch, "Download new revisions from SVN",
99
 
                        { 'revision|r=s' => \$_revision, %fc_opts } ],
100
 
        init => [ \&init, "Initialize a repo for tracking" .
 
96
        fetch => [ \&cmd_fetch, "Download new revisions from SVN",
 
97
                        { 'revision|r=s' => \$_revision,
 
98
                          'fetch-all|all' => \$_fetch_all,
 
99
                           %fc_opts } ],
 
100
        clone => [ \&cmd_clone, "Initialize and fetch revisions",
 
101
                        { 'revision|r=s' => \$_revision,
 
102
                           %fc_opts, %init_opts } ],
 
103
        init => [ \&cmd_init, "Initialize a repo for tracking" .
101
104
                          " (requires URL argument)",
102
105
                          \%init_opts ],
103
 
        commit => [ \&commit, "Commit git revisions to SVN",
104
 
                        {       'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
105
 
        'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
 
106
        'multi-init' => [ \&cmd_multi_init,
 
107
                          "Deprecated alias for ".
 
108
                          "'$0 init -T<trunk> -b<branches> -t<tags>'",
 
109
                          \%init_opts ],
 
110
        dcommit => [ \&cmd_dcommit,
 
111
                     'Commit several diffs to merge with upstream',
 
112
                        { 'merge|m|M' => \$_merge,
 
113
                          'strategy|s=s' => \$_strategy,
 
114
                          'verbose|v' => \$_verbose,
 
115
                          'dry-run|n' => \$_dry_run,
 
116
                          'fetch-all|all' => \$_fetch_all,
 
117
                        %cmt_opts, %fc_opts } ],
 
118
        'set-tree' => [ \&cmd_set_tree,
 
119
                        "Set an SVN repository to a git tree-ish",
 
120
                        { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
 
121
        'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
106
122
                        { 'revision|r=i' => \$_revision } ],
107
 
        rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
108
 
                        { 'no-ignore-externals' => \$_no_ignore_ext,
109
 
                          'copy-remote|remote=s' => \$_cp_remote,
110
 
                          'upgrade' => \$_upgrade } ],
111
 
        'graft-branches' => [ \&graft_branches,
112
 
                        'Detect merges/branches from already imported history',
113
 
                        { 'merge-rx|m' => \@_opt_m,
114
 
                          'branch|b=s' => \@_branch_from,
115
 
                          'branch-all-refs|B' => \$_branch_all_refs,
116
 
                          'no-default-regex' => \$_no_default_regex,
117
 
                          'no-graft-copy' => \$_no_graft_copy } ],
118
 
        'multi-init' => [ \&multi_init,
119
 
                        'Initialize multiple trees (like git-svnimport)',
120
 
                        { %multi_opts, %fc_opts } ],
121
 
        'multi-fetch' => [ \&multi_fetch,
122
 
                        'Fetch multiple trees (like git-svnimport)',
123
 
                        \%fc_opts ],
124
 
        'log' => [ \&show_log, 'Show commit logs',
125
 
                        { 'limit=i' => \$_limit,
 
123
        'multi-fetch' => [ \&cmd_multi_fetch,
 
124
                           "Deprecated alias for $0 fetch --all",
 
125
                           { 'revision|r=s' => \$_revision, %fc_opts } ],
 
126
        'migrate' => [ sub { },
 
127
                       # no-op, we automatically run this anyways,
 
128
                       'Migrate configuration/metadata/layout from
 
129
                        previous versions of git-svn',
 
130
                       { 'minimize' => \$Git::SVN::Migration::_minimize,
 
131
                         %remote_opts } ],
 
132
        'log' => [ \&Git::SVN::Log::cmd_show_log, 'Show commit logs',
 
133
                        { 'limit=i' => \$Git::SVN::Log::limit,
126
134
                          'revision|r=s' => \$_revision,
127
 
                          'verbose|v' => \$_verbose,
128
 
                          'incremental' => \$_incremental,
129
 
                          'oneline' => \$_oneline,
130
 
                          'show-commit' => \$_show_commit,
131
 
                          'non-recursive' => \$_non_recursive,
 
135
                          'verbose|v' => \$Git::SVN::Log::verbose,
 
136
                          'incremental' => \$Git::SVN::Log::incremental,
 
137
                          'oneline' => \$Git::SVN::Log::oneline,
 
138
                          'show-commit' => \$Git::SVN::Log::show_commit,
 
139
                          'non-recursive' => \$Git::SVN::Log::non_recursive,
132
140
                          'authors-file|A=s' => \$_authors,
 
141
                          'color' => \$Git::SVN::Log::color,
 
142
                          'pager=s' => \$Git::SVN::Log::pager,
133
143
                        } ],
134
 
        'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
 
144
        'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory",
 
145
                        { 'merge|m|M' => \$_merge,
 
146
                          'verbose|v' => \$_verbose,
 
147
                          'strategy|s=s' => \$_strategy,
 
148
                          'local|l' => \$_local,
 
149
                          'fetch-all|all' => \$_fetch_all,
 
150
                          %fc_opts } ],
 
151
        'commit-diff' => [ \&cmd_commit_diff,
 
152
                           'Commit a diff between two trees',
135
153
                        { 'message|m=s' => \$_message,
136
154
                          'file|F=s' => \$_file,
137
155
                          'revision|r=s' => \$_revision,
138
156
                        %cmt_opts } ],
139
 
        dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
140
 
                        { 'merge|m|M' => \$_merge,
141
 
                          'strategy|s=s' => \$_strategy,
142
 
                          'dry-run|n' => \$_dry_run,
143
 
                        %cmt_opts } ],
144
157
);
145
158
 
146
159
my $cmd;
155
168
my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
156
169
 
157
170
read_repo_config(\%opts);
158
 
my $rv = GetOptions(%opts, 'help|H|h' => \$_help,
159
 
                                'version|V' => \$_version,
160
 
                                'id|i=s' => \$GIT_SVN);
161
 
exit 1 if (!$rv && $cmd ne 'log');
 
171
Getopt::Long::Configure('pass_through') if ($cmd && $cmd eq 'log');
 
172
my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
 
173
                    'minimize-connections' => \$Git::SVN::Migration::_minimize,
 
174
                    'id|i=s' => \$Git::SVN::default_ref_id,
 
175
                    'svn-remote|remote|R=s' => sub {
 
176
                       $Git::SVN::no_reuse_existing = 1;
 
177
                       $Git::SVN::default_repo_id = $_[1] });
 
178
exit 1 if (!$rv && $cmd && $cmd ne 'log');
162
179
 
163
 
set_default_vals();
164
180
usage(0) if $_help;
165
181
version() if $_version;
166
182
usage(1) unless defined $cmd;
167
 
init_vars();
168
183
load_authors() if $_authors;
169
 
load_all_refs() if $_branch_all_refs;
170
 
svn_compat_check() unless $_use_lib;
171
 
migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/;
172
 
$cmd{$cmd}->[0]->(@ARGV);
 
184
 
 
185
# make sure we're always running
 
186
unless ($cmd =~ /(?:clone|init|multi-init)$/) {
 
187
        unless (-d $ENV{GIT_DIR}) {
 
188
                if ($git_dir_user_set) {
 
189
                        die "GIT_DIR=$ENV{GIT_DIR} explicitly set, ",
 
190
                            "but it is not a directory\n";
 
191
                }
 
192
                my $git_dir = delete $ENV{GIT_DIR};
 
193
                chomp(my $cdup = command_oneline(qw/rev-parse --show-cdup/));
 
194
                unless (length $cdup) {
 
195
                        die "Already at toplevel, but $git_dir ",
 
196
                            "not found '$cdup'\n";
 
197
                }
 
198
                chdir $cdup or die "Unable to chdir up to '$cdup'\n";
 
199
                unless (-d $git_dir) {
 
200
                        die "$git_dir still not found after going to ",
 
201
                            "'$cdup'\n";
 
202
                }
 
203
                $ENV{GIT_DIR} = $git_dir;
 
204
        }
 
205
}
 
206
unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) {
 
207
        Git::SVN::Migration::migration_check();
 
208
}
 
209
Git::SVN::init_vars();
 
210
eval {
 
211
        Git::SVN::verify_remotes_sanity();
 
212
        $cmd{$cmd}->[0]->(@ARGV);
 
213
};
 
214
fatal $@ if $@;
 
215
post_fetch_checkout();
173
216
exit 0;
174
217
 
175
218
####################### primary functions ######################
184
227
 
185
228
        foreach (sort keys %cmd) {
186
229
                next if $cmd && $cmd ne $_;
 
230
                next if /^multi-/; # don't show deprecated commands
187
231
                print $fd '  ',pack('A17',$_),$cmd{$_}->[1],"\n";
188
232
                foreach (keys %{$cmd{$_}->[2]}) {
 
233
                        # mixed-case options are for .git/config only
 
234
                        next if /[A-Z]/ && /^[a-z]+$/i;
189
235
                        # prints out arguments as they should be passed:
190
236
                        my $x = s#[:=]s$## ? '<arg>' : s#[:=]i$## ? '<num>' : '';
191
237
                        print $fd ' ' x 21, join(', ', map { length $_ > 1 ?
203
249
}
204
250
 
205
251
sub version {
206
 
        print "git-svn version $VERSION\n";
 
252
        print "git-svn version $VERSION (svn $SVN::Core::VERSION)\n";
207
253
        exit 0;
208
254
}
209
255
 
210
 
sub rebuild {
211
 
        if (quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0")) {
212
 
                copy_remote_ref();
213
 
        }
214
 
        $SVN_URL = shift or undef;
215
 
        my $newest_rev = 0;
216
 
        if ($_upgrade) {
217
 
                sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD");
218
 
        } else {
219
 
                check_upgrade_needed();
220
 
        }
221
 
 
222
 
        my $pid = open(my $rev_list,'-|');
223
 
        defined $pid or croak $!;
224
 
        if ($pid == 0) {
225
 
                exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!;
226
 
        }
227
 
        my $latest;
228
 
        while (<$rev_list>) {
229
 
                chomp;
230
 
                my $c = $_;
231
 
                croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
232
 
                my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
233
 
                next if (!@commit); # skip merges
234
 
                my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
235
 
                if (!defined $rev || !$uuid) {
236
 
                        croak "Unable to extract revision or UUID from ",
237
 
                                "$c, $commit[$#commit]\n";
238
 
                }
239
 
 
240
 
                # if we merged or otherwise started elsewhere, this is
241
 
                # how we break out of it
242
 
                next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
243
 
                next if (defined $SVN_URL && defined $url && ($url ne $SVN_URL));
244
 
 
245
 
                unless (defined $latest) {
246
 
                        if (!$SVN_URL && !$url) {
247
 
                                croak "SVN repository location required: $url\n";
248
 
                        }
249
 
                        $SVN_URL ||= $url;
250
 
                        $SVN_UUID ||= $uuid;
251
 
                        setup_git_svn();
252
 
                        $latest = $rev;
253
 
                }
254
 
                revdb_set($REVDB, $rev, $c);
255
 
                print "r$rev = $c\n";
256
 
                $newest_rev = $rev if ($rev > $newest_rev);
257
 
        }
258
 
        close $rev_list or croak $?;
259
 
 
260
 
        goto out if $_use_lib;
261
 
        if (!chdir $SVN_WC) {
262
 
                svn_cmd_checkout($SVN_URL, $latest, $SVN_WC);
263
 
                chdir $SVN_WC or croak $!;
264
 
        }
265
 
 
266
 
        $pid = fork;
267
 
        defined $pid or croak $!;
268
 
        if ($pid == 0) {
269
 
                my @svn_up = qw(svn up);
270
 
                push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
271
 
                sys(@svn_up,"-r$newest_rev");
272
 
                $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;
273
 
                index_changes();
274
 
                exec('git-write-tree') or croak $!;
275
 
        }
276
 
        waitpid $pid, 0;
277
 
        croak $? if $?;
278
 
out:
279
 
        if ($_upgrade) {
280
 
                print STDERR <<"";
281
 
Keeping deprecated refs/head/$GIT_SVN-HEAD for now.  Please remove it
282
 
when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
283
 
 
284
 
        }
285
 
}
286
 
 
287
 
sub init {
288
 
        my $url = shift or die "SVN repository location required " .
289
 
                                "as a command-line argument\n";
290
 
        $url =~ s!/+$!!; # strip trailing slash
291
 
 
292
 
        if (my $repo_path = shift) {
293
 
                unless (-d $repo_path) {
294
 
                        mkpath([$repo_path]);
295
 
                }
296
 
                $GIT_DIR = $ENV{GIT_DIR} = $repo_path . "/.git";
297
 
                init_vars();
298
 
        }
299
 
 
300
 
        $SVN_URL = $url;
301
 
        unless (-d $GIT_DIR) {
302
 
                my @init_db = ('git-init-db');
 
256
sub do_git_init_db {
 
257
        unless (-d $ENV{GIT_DIR}) {
 
258
                my @init_db = ('init');
303
259
                push @init_db, "--template=$_template" if defined $_template;
304
 
                push @init_db, "--shared" if defined $_shared;
305
 
                sys(@init_db);
306
 
        }
307
 
        setup_git_svn();
308
 
}
309
 
 
310
 
sub fetch {
311
 
        check_upgrade_needed();
312
 
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
313
 
        my $ret = $_use_lib ? fetch_lib(@_) : fetch_cmd(@_);
314
 
        if ($ret->{commit} && quiet_run(qw(git-rev-parse --verify
315
 
                                                refs/heads/master^0))) {
316
 
                sys(qw(git-update-ref refs/heads/master),$ret->{commit});
317
 
        }
318
 
        return $ret;
319
 
}
320
 
 
321
 
sub fetch_cmd {
322
 
        my (@parents) = @_;
323
 
        my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);
324
 
        unless ($_revision) {
325
 
                $_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD';
326
 
        }
327
 
        push @log_args, "-r$_revision";
328
 
        push @log_args, '--stop-on-copy' unless $_no_stop_copy;
329
 
 
330
 
        my $svn_log = svn_log_raw(@log_args);
331
 
 
332
 
        my $base = next_log_entry($svn_log) or croak "No base revision!\n";
333
 
        # don't need last_revision from grab_base_rev() because
334
 
        # user could've specified a different revision to skip (they
335
 
        # didn't want to import certain revisions into git for whatever
336
 
        # reason, so trust $base->{revision} instead.
337
 
        my (undef, $last_commit) = svn_grab_base_rev();
338
 
        unless (-d $SVN_WC) {
339
 
                svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC);
340
 
                chdir $SVN_WC or croak $!;
341
 
                read_uuid();
342
 
                $last_commit = git_commit($base, @parents);
343
 
                assert_tree($last_commit);
 
260
                if (defined $_shared) {
 
261
                        if ($_shared =~ /[a-z]/) {
 
262
                                push @init_db, "--shared=$_shared";
 
263
                        } else {
 
264
                                push @init_db, "--shared";
 
265
                        }
 
266
                }
 
267
                command_noisy(@init_db);
 
268
        }
 
269
        my $set;
 
270
        my $pfx = "svn-remote.$Git::SVN::default_repo_id";
 
271
        foreach my $i (keys %icv) {
 
272
                die "'$set' and '$i' cannot both be set\n" if $set;
 
273
                next unless defined $icv{$i};
 
274
                command_noisy('config', "$pfx.$i", $icv{$i});
 
275
                $set = $i;
 
276
        }
 
277
}
 
278
 
 
279
sub init_subdir {
 
280
        my $repo_path = shift or return;
 
281
        mkpath([$repo_path]) unless -d $repo_path;
 
282
        chdir $repo_path or die "Couldn't chdir to $repo_path: $!\n";
 
283
        $ENV{GIT_DIR} = '.git';
 
284
}
 
285
 
 
286
sub cmd_clone {
 
287
        my ($url, $path) = @_;
 
288
        if (!defined $path &&
 
289
            (defined $_trunk || defined $_branches || defined $_tags) &&
 
290
            $url !~ m#^[a-z\+]+://#) {
 
291
                $path = $url;
 
292
        }
 
293
        $path = basename($url) if !defined $path || !length $path;
 
294
        cmd_init($url, $path);
 
295
        Git::SVN::fetch_all($Git::SVN::default_repo_id);
 
296
}
 
297
 
 
298
sub cmd_init {
 
299
        if (defined $_trunk || defined $_branches || defined $_tags) {
 
300
                return cmd_multi_init(@_);
 
301
        }
 
302
        my $url = shift or die "SVN repository location required ",
 
303
                               "as a command-line argument\n";
 
304
        init_subdir(@_);
 
305
        do_git_init_db();
 
306
 
 
307
        Git::SVN->init($url);
 
308
}
 
309
 
 
310
sub cmd_fetch {
 
311
        if (grep /^\d+=./, @_) {
 
312
                die "'<rev>=<commit>' fetch arguments are ",
 
313
                    "no longer supported.\n";
 
314
        }
 
315
        my ($remote) = @_;
 
316
        if (@_ > 1) {
 
317
                die "Usage: $0 fetch [--all] [svn-remote]\n";
 
318
        }
 
319
        $remote ||= $Git::SVN::default_repo_id;
 
320
        if ($_fetch_all) {
 
321
                cmd_multi_fetch();
344
322
        } else {
345
 
                chdir $SVN_WC or croak $!;
346
 
                read_uuid();
347
 
                # looks like a user manually cp'd and svn switch'ed
348
 
                unless ($last_commit) {
349
 
                        sys(qw/svn revert -R ./);
350
 
                        assert_svn_wc_clean($base->{revision});
351
 
                        $last_commit = git_commit($base, @parents);
352
 
                        assert_tree($last_commit);
353
 
                }
354
 
        }
355
 
        my @svn_up = qw(svn up);
356
 
        push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
357
 
        my $last = $base;
358
 
        while (my $log_msg = next_log_entry($svn_log)) {
359
 
                if ($last->{revision} >= $log_msg->{revision}) {
360
 
                        croak "Out of order: last >= current: ",
361
 
                                "$last->{revision} >= $log_msg->{revision}\n";
362
 
                }
363
 
                # Revert is needed for cases like:
364
 
                # https://svn.musicpd.org/Jamming/trunk (r166:167), but
365
 
                # I can't seem to reproduce something like that on a test...
366
 
                sys(qw/svn revert -R ./);
367
 
                assert_svn_wc_clean($last->{revision});
368
 
                sys(@svn_up,"-r$log_msg->{revision}");
369
 
                $last_commit = git_commit($log_msg, $last_commit, @parents);
370
 
                $last = $log_msg;
371
 
        }
372
 
        close $svn_log->{fh};
373
 
        $last->{commit} = $last_commit;
374
 
        return $last;
375
 
}
376
 
 
377
 
sub fetch_lib {
378
 
        my (@parents) = @_;
379
 
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
380
 
        my $repo;
381
 
        ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
382
 
        $SVN_LOG ||= libsvn_connect($repo);
383
 
        $SVN ||= libsvn_connect($repo);
384
 
        my ($last_rev, $last_commit) = svn_grab_base_rev();
385
 
        my ($base, $head) = libsvn_parse_revision($last_rev);
386
 
        if ($base > $head) {
387
 
                return { revision => $last_rev, commit => $last_commit }
388
 
        }
389
 
        my $index = set_index($GIT_SVN_INDEX);
390
 
 
391
 
        # limit ourselves and also fork() since get_log won't release memory
392
 
        # after processing a revision and SVN stuff seems to leak
393
 
        my $inc = 1000;
394
 
        my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
395
 
        read_uuid();
396
 
        if (defined $last_commit) {
397
 
                unless (-e $GIT_SVN_INDEX) {
398
 
                        sys(qw/git-read-tree/, $last_commit);
399
 
                }
400
 
                chomp (my $x = `git-write-tree`);
401
 
                my ($y) = (`git-cat-file commit $last_commit`
402
 
                                                        =~ /^tree ($sha1)/m);
403
 
                if ($y ne $x) {
404
 
                        unlink $GIT_SVN_INDEX or croak $!;
405
 
                        sys(qw/git-read-tree/, $last_commit);
406
 
                }
407
 
                chomp ($x = `git-write-tree`);
408
 
                if ($y ne $x) {
409
 
                        print STDERR "trees ($last_commit) $y != $x\n",
410
 
                                 "Something is seriously wrong...\n";
411
 
                }
412
 
        }
413
 
        while (1) {
414
 
                # fork, because using SVN::Pool with get_log() still doesn't
415
 
                # seem to help enough to keep memory usage down.
416
 
                defined(my $pid = fork) or croak $!;
417
 
                if (!$pid) {
418
 
                        $SVN::Error::handler = \&libsvn_skip_unknown_revs;
419
 
 
420
 
                        # Yes I'm perfectly aware that the fourth argument
421
 
                        # below is the limit revisions number.  Unfortunately
422
 
                        # performance sucks with it enabled, so it's much
423
 
                        # faster to fetch revision ranges instead of relying
424
 
                        # on the limiter.
425
 
                        libsvn_get_log($SVN_LOG, '/'.$SVN_PATH,
426
 
                                        $min, $max, 0, 1, 1,
427
 
                                sub {
428
 
                                        my $log_msg;
429
 
                                        if ($last_commit) {
430
 
                                                $log_msg = libsvn_fetch(
431
 
                                                        $last_commit, @_);
432
 
                                                $last_commit = git_commit(
433
 
                                                        $log_msg,
434
 
                                                        $last_commit,
435
 
                                                        @parents);
436
 
                                        } else {
437
 
                                                $log_msg = libsvn_new_tree(@_);
438
 
                                                $last_commit = git_commit(
439
 
                                                        $log_msg, @parents);
440
 
                                        }
441
 
                                });
442
 
                        exit 0;
443
 
                }
444
 
                waitpid $pid, 0;
445
 
                croak $? if $?;
446
 
                ($last_rev, $last_commit) = svn_grab_base_rev();
447
 
                last if ($max >= $head);
448
 
                $min = $max + 1;
449
 
                $max += $inc;
450
 
                $max = $head if ($max > $head);
451
 
        }
452
 
        restore_index($index);
453
 
        return { revision => $last_rev, commit => $last_commit };
454
 
}
455
 
 
456
 
sub commit {
 
323
                Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes());
 
324
        }
 
325
}
 
326
 
 
327
sub cmd_set_tree {
457
328
        my (@commits) = @_;
458
 
        check_upgrade_needed();
459
329
        if ($_stdin || !@commits) {
460
330
                print "Reading from stdin...\n";
461
331
                @commits = ();
467
337
        }
468
338
        my @revs;
469
339
        foreach my $c (@commits) {
470
 
                chomp(my @tmp = safe_qx('git-rev-parse',$c));
 
340
                my @tmp = command('rev-parse',$c);
471
341
                if (scalar @tmp == 1) {
472
342
                        push @revs, $tmp[0];
473
343
                } elsif (scalar @tmp > 1) {
474
 
                        push @revs, reverse (safe_qx('git-rev-list',@tmp));
 
344
                        push @revs, reverse(command('rev-list',@tmp));
475
345
                } else {
476
 
                        die "Failed to rev-parse $c\n";
 
346
                        fatal "Failed to rev-parse $c\n";
477
347
                }
478
348
        }
479
 
        chomp @revs;
480
 
        $_use_lib ? commit_lib(@revs) : commit_cmd(@revs);
 
349
        my $gs = Git::SVN->new;
 
350
        my ($r_last, $cmt_last) = $gs->last_rev_commit;
 
351
        $gs->fetch;
 
352
        if (defined $gs->{last_rev} && $r_last != $gs->{last_rev}) {
 
353
                fatal "There are new revisions that were fetched ",
 
354
                      "and need to be merged (or acknowledged) ",
 
355
                      "before committing.\nlast rev: $r_last\n",
 
356
                      " current: $gs->{last_rev}\n";
 
357
        }
 
358
        $gs->set_tree($_) foreach @revs;
481
359
        print "Done committing ",scalar @revs," revisions to SVN\n";
482
360
}
483
361
 
484
 
sub commit_cmd {
485
 
        my (@revs) = @_;
486
 
 
487
 
        chdir $SVN_WC or croak "Unable to chdir $SVN_WC: $!\n";
488
 
        my $info = svn_info('.');
489
 
        my $fetched = fetch();
490
 
        if ($info->{Revision} != $fetched->{revision}) {
491
 
                print STDERR "There are new revisions that were fetched ",
492
 
                                "and need to be merged (or acknowledged) ",
493
 
                                "before committing.\n";
494
 
                exit 1;
495
 
        }
496
 
        $info = svn_info('.');
497
 
        read_uuid($info);
498
 
        my $last = $fetched;
499
 
        foreach my $c (@revs) {
500
 
                my $mods = svn_checkout_tree($last, $c);
501
 
                if (scalar @$mods == 0) {
502
 
                        print "Skipping, no changes detected\n";
503
 
                        next;
504
 
                }
505
 
                $last = svn_commit_tree($last, $c);
506
 
        }
507
 
}
508
 
 
509
 
sub commit_lib {
510
 
        my (@revs) = @_;
511
 
        my ($r_last, $cmt_last) = svn_grab_base_rev();
512
 
        defined $r_last or die "Must have an existing revision to commit\n";
513
 
        my $fetched = fetch();
514
 
        if ($r_last != $fetched->{revision}) {
515
 
                print STDERR "There are new revisions that were fetched ",
516
 
                                "and need to be merged (or acknowledged) ",
517
 
                                "before committing.\n",
518
 
                                "last rev: $r_last\n",
519
 
                                " current: $fetched->{revision}\n";
520
 
                exit 1;
521
 
        }
522
 
        read_uuid();
523
 
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
524
 
        my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
525
 
 
526
 
        my $repo;
527
 
        ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
528
 
        set_svn_commit_env();
529
 
        foreach my $c (@revs) {
530
 
                my $log_msg = get_commit_message($c, $commit_msg);
531
 
 
532
 
                # fork for each commit because there's a memory leak I
533
 
                # can't track down... (it's probably in the SVN code)
534
 
                defined(my $pid = open my $fh, '-|') or croak $!;
535
 
                if (!$pid) {
536
 
                        $SVN_LOG = libsvn_connect($repo);
537
 
                        $SVN = libsvn_connect($repo);
538
 
                        my $ed = SVN::Git::Editor->new(
539
 
                                        {       r => $r_last,
540
 
                                                ra => $SVN_LOG,
541
 
                                                c => $c,
542
 
                                                svn_path => $SVN_PATH
543
 
                                        },
544
 
                                        $SVN->get_commit_editor(
545
 
                                                $log_msg->{msg},
546
 
                                                sub {
547
 
                                                        libsvn_commit_cb(
548
 
                                                                @_, $c,
549
 
                                                                $log_msg->{msg},
550
 
                                                                $r_last,
551
 
                                                                $cmt_last)
552
 
                                                },
553
 
                                                @lock)
554
 
                                        );
555
 
                        my $mods = libsvn_checkout_tree($cmt_last, $c, $ed);
556
 
                        if (@$mods == 0) {
557
 
                                print "No changes\nr$r_last = $cmt_last\n";
558
 
                                $ed->abort_edit;
559
 
                        } else {
560
 
                                $ed->close_edit;
561
 
                        }
562
 
                        exit 0;
563
 
                }
564
 
                my ($r_new, $cmt_new, $no);
565
 
                while (<$fh>) {
566
 
                        print $_;
567
 
                        chomp;
568
 
                        if (/^r(\d+) = ($sha1)$/o) {
569
 
                                ($r_new, $cmt_new) = ($1, $2);
570
 
                        } elsif ($_ eq 'No changes') {
571
 
                                $no = 1;
572
 
                        }
573
 
                }
574
 
                close $fh or croak $?;
575
 
                if (! defined $r_new && ! defined $cmt_new) {
576
 
                        unless ($no) {
577
 
                                die "Failed to parse revision information\n";
578
 
                        }
579
 
                } else {
580
 
                        ($r_last, $cmt_last) = ($r_new, $cmt_new);
581
 
                }
582
 
        }
583
 
        $ENV{LC_ALL} = 'C';
584
 
        unlink $commit_msg;
585
 
}
586
 
 
587
 
sub dcommit {
588
 
        my $gs = "refs/remotes/$GIT_SVN";
589
 
        chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD"));
 
362
sub cmd_dcommit {
 
363
        my $head = shift;
 
364
        $head ||= 'HEAD';
 
365
        my @refs;
 
366
        my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
 
367
        unless ($gs) {
 
368
                die "Unable to determine upstream SVN information from ",
 
369
                    "$head history\n";
 
370
        }
 
371
        my $c = $refs[-1];
590
372
        my $last_rev;
591
 
        foreach my $d (reverse @refs) {
592
 
                if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) {
593
 
                        die "Commit $d\n",
594
 
                            "has no parent commit, and therefore ",
595
 
                            "nothing to diff against.\n",
596
 
                            "You should be working from a repository ",
597
 
                            "originally created by git-svn\n";
 
373
        foreach my $d (@refs) {
 
374
                if (!verify_ref("$d~1")) {
 
375
                        fatal "Commit $d\n",
 
376
                              "has no parent commit, and therefore ",
 
377
                              "nothing to diff against.\n",
 
378
                              "You should be working from a repository ",
 
379
                              "originally created by git-svn\n";
598
380
                }
599
381
                unless (defined $last_rev) {
600
382
                        (undef, $last_rev, undef) = cmt_metadata("$d~1");
601
383
                        unless (defined $last_rev) {
602
 
                                die "Unable to extract revision information ",
603
 
                                    "from commit $d~1\n";
 
384
                                fatal "Unable to extract revision information ",
 
385
                                      "from commit $d~1\n";
604
386
                        }
605
387
                }
606
388
                if ($_dry_run) {
607
389
                        print "diff-tree $d~1 $d\n";
608
390
                } else {
609
 
                        if (my $r = commit_diff("$d~1", $d, undef, $last_rev)) {
610
 
                                $last_rev = $r;
611
 
                        } # else: no changes, same $last_rev
 
391
                        my %ed_opts = ( r => $last_rev,
 
392
                                        log => get_commit_entry($d)->{log},
 
393
                                        ra => Git::SVN::Ra->new($url),
 
394
                                        tree_a => "$d~1",
 
395
                                        tree_b => $d,
 
396
                                        editor_cb => sub {
 
397
                                               print "Committed r$_[0]\n";
 
398
                                               $last_rev = $_[0]; },
 
399
                                        svn_path => '');
 
400
                        if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
 
401
                                print "No changes\n$d~1 == $d\n";
 
402
                        }
612
403
                }
613
404
        }
614
405
        return if $_dry_run;
615
 
        fetch();
616
 
        my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs);
 
406
        unless ($gs) {
 
407
                warn "Could not determine fetch information for $url\n",
 
408
                     "Will not attempt to fetch and rebase commits.\n",
 
409
                     "This probably means you have useSvmProps and should\n",
 
410
                     "now resync your SVN::Mirror repository.\n";
 
411
                return;
 
412
        }
 
413
        $_fetch_all ? $gs->fetch_all : $gs->fetch;
 
414
        # we always want to rebase against the current HEAD, not any
 
415
        # head that was passed to us
 
416
        my @diff = command('diff-tree', 'HEAD', $gs->refname, '--');
617
417
        my @finish;
618
418
        if (@diff) {
619
 
                @finish = qw/rebase/;
620
 
                push @finish, qw/--merge/ if $_merge;
621
 
                push @finish, "--strategy=$_strategy" if $_strategy;
622
 
                print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff;
 
419
                @finish = rebase_cmd();
 
420
                print STDERR "W: HEAD and ", $gs->refname, " differ, ",
 
421
                             "using @finish:\n", "@diff";
623
422
        } else {
624
 
                print "No changes between current HEAD and $gs\n",
625
 
                      "Hard resetting to the latest $gs\n";
 
423
                print "No changes between current HEAD and ",
 
424
                      $gs->refname, "\nResetting to the latest ",
 
425
                      $gs->refname, "\n";
626
426
                @finish = qw/reset --mixed/;
627
427
        }
628
 
        sys('git', @finish, $gs);
629
 
}
630
 
 
631
 
sub show_ignore {
632
 
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
633
 
        $_use_lib ? show_ignore_lib() : show_ignore_cmd();
634
 
}
635
 
 
636
 
sub show_ignore_cmd {
637
 
        require File::Find or die $!;
638
 
        if (defined $_revision) {
639
 
                die "-r/--revision option doesn't work unless the Perl SVN ",
640
 
                        "libraries are used\n";
641
 
        }
642
 
        chdir $SVN_WC or croak $!;
643
 
        my %ign;
644
 
        File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){
645
 
                s#^\./##;
646
 
                @{$ign{$_}} = svn_propget_base('svn:ignore', $_);
647
 
                }}, no_chdir=>1},'.');
648
 
 
649
 
        print "\n# /\n";
650
 
        foreach (@{$ign{'.'}}) { print '/',$_ if /\S/ }
651
 
        delete $ign{'.'};
652
 
        foreach my $i (sort keys %ign) {
653
 
                print "\n# ",$i,"\n";
654
 
                foreach (@{$ign{$i}}) { print '/',$i,'/',$_ if /\S/ }
655
 
        }
656
 
}
657
 
 
658
 
sub show_ignore_lib {
659
 
        my $repo;
660
 
        ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
661
 
        $SVN ||= libsvn_connect($repo);
662
 
        my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
663
 
        libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r);
664
 
}
665
 
 
666
 
sub graft_branches {
667
 
        my $gr_file = "$GIT_DIR/info/grafts";
668
 
        my ($grafts, $comments) = read_grafts($gr_file);
669
 
        my $gr_sha1;
670
 
 
671
 
        if (%$grafts) {
672
 
                # temporarily disable our grafts file to make this idempotent
673
 
                chomp($gr_sha1 = safe_qx(qw/git-hash-object -w/,$gr_file));
674
 
                rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
675
 
        }
676
 
 
677
 
        my $l_map = read_url_paths();
678
 
        my @re = map { qr/$_/is } @_opt_m if @_opt_m;
679
 
        unless ($_no_default_regex) {
680
 
                push @re, (qr/\b(?:merge|merging|merged)\s+with\s+([\w\.\-]+)/i,
681
 
                        qr/\b(?:merge|merging|merged)\s+([\w\.\-]+)/i,
682
 
                        qr/\b(?:from|of)\s+([\w\.\-]+)/i );
683
 
        }
684
 
        foreach my $u (keys %$l_map) {
685
 
                if (@re) {
686
 
                        foreach my $p (keys %{$l_map->{$u}}) {
687
 
                                graft_merge_msg($grafts,$l_map,$u,$p,@re);
688
 
                        }
689
 
                }
690
 
                unless ($_no_graft_copy) {
691
 
                        if ($_use_lib) {
692
 
                                graft_file_copy_lib($grafts,$l_map,$u);
693
 
                        } else {
694
 
                                graft_file_copy_cmd($grafts,$l_map,$u);
695
 
                        }
696
 
                }
697
 
        }
698
 
        graft_tree_joins($grafts);
699
 
 
700
 
        write_grafts($grafts, $comments, $gr_file);
701
 
        unlink "$gr_file~$gr_sha1" if $gr_sha1;
702
 
}
703
 
 
704
 
sub multi_init {
 
428
        command_noisy(@finish, $gs->refname);
 
429
}
 
430
 
 
431
sub cmd_rebase {
 
432
        command_noisy(qw/update-index --refresh/);
 
433
        my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
 
434
        unless ($gs) {
 
435
                die "Unable to determine upstream SVN information from ",
 
436
                    "working tree history\n";
 
437
        }
 
438
        if (command(qw/diff-index HEAD --/)) {
 
439
                print STDERR "Cannot rebase with uncommited changes:\n";
 
440
                command_noisy('status');
 
441
                exit 1;
 
442
        }
 
443
        unless ($_local) {
 
444
                $_fetch_all ? $gs->fetch_all : $gs->fetch;
 
445
        }
 
446
        command_noisy(rebase_cmd(), $gs->refname);
 
447
}
 
448
 
 
449
sub cmd_show_ignore {
 
450
        my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
 
451
        $gs ||= Git::SVN->new;
 
452
        my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
 
453
        $gs->traverse_ignore(\*STDOUT, $gs->{path}, $r);
 
454
}
 
455
 
 
456
sub cmd_multi_init {
705
457
        my $url = shift;
706
 
        $_trunk ||= 'trunk';
707
 
        $_trunk =~ s#/+$##;
708
 
        $url =~ s#/+$## if $url;
709
 
        if ($_trunk !~ m#^[a-z\+]+://#) {
710
 
                $_trunk = '/' . $_trunk if ($_trunk !~ m#^/#);
711
 
                unless ($url) {
712
 
                        print STDERR "E: '$_trunk' is not a complete URL ",
713
 
                                "and a separate URL is not specified\n";
714
 
                        exit 1;
715
 
                }
716
 
                $_trunk = $url . $_trunk;
717
 
        }
718
 
        my $ch_id;
719
 
        if ($GIT_SVN eq 'git-svn') {
720
 
                $ch_id = 1;
721
 
                $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
722
 
        }
723
 
        init_vars();
724
 
        unless (-d $GIT_SVN_DIR) {
725
 
                print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id;
726
 
                init($_trunk);
727
 
                sys('git-repo-config', 'svn.trunk', $_trunk);
728
 
        }
729
 
        complete_url_ls_init($url, $_branches, '--branches/-b', '');
730
 
        complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
731
 
}
732
 
 
733
 
sub multi_fetch {
734
 
        # try to do trunk first, since branches/tags
735
 
        # may be descended from it.
736
 
        if (-e "$GIT_DIR/svn/trunk/info/url") {
737
 
                fetch_child_id('trunk', @_);
738
 
        }
739
 
        rec_fetch('', "$GIT_DIR/svn", @_);
740
 
}
741
 
 
742
 
sub show_log {
743
 
        my (@args) = @_;
744
 
        my ($r_min, $r_max);
745
 
        my $r_last = -1; # prevent dupes
746
 
        rload_authors() if $_authors;
747
 
        if (defined $TZ) {
748
 
                $ENV{TZ} = $TZ;
749
 
        } else {
750
 
                delete $ENV{TZ};
751
 
        }
752
 
        if (defined $_revision) {
753
 
                if ($_revision =~ /^(\d+):(\d+)$/) {
754
 
                        ($r_min, $r_max) = ($1, $2);
755
 
                } elsif ($_revision =~ /^\d+$/) {
756
 
                        $r_min = $r_max = $_revision;
757
 
                } else {
758
 
                        print STDERR "-r$_revision is not supported, use ",
759
 
                                "standard \'git log\' arguments instead\n";
760
 
                        exit 1;
761
 
                }
762
 
        }
763
 
 
764
 
        my $pid = open(my $log,'-|');
765
 
        defined $pid or croak $!;
766
 
        if (!$pid) {
767
 
                exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!;
768
 
        }
769
 
        setup_pager();
770
 
        my (@k, $c, $d);
771
 
 
772
 
        while (<$log>) {
773
 
                if (/^commit ($sha1_short)/o) {
774
 
                        my $cmt = $1;
775
 
                        if ($c && cmt_showable($c) && $c->{r} != $r_last) {
776
 
                                $r_last = $c->{r};
777
 
                                process_commit($c, $r_min, $r_max, \@k) or
778
 
                                                                goto out;
779
 
                        }
780
 
                        $d = undef;
781
 
                        $c = { c => $cmt };
782
 
                } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) {
783
 
                        get_author_info($c, $1, $2, $3);
784
 
                } elsif (/^(?:tree|parent|committer) /) {
785
 
                        # ignore
786
 
                } elsif (/^:\d{6} \d{6} $sha1_short/o) {
787
 
                        push @{$c->{raw}}, $_;
788
 
                } elsif (/^[ACRMDT]\t/) {
789
 
                        # we could add $SVN_PATH here, but that requires
790
 
                        # remote access at the moment (repo_path_split)...
791
 
                        s#^([ACRMDT])\t#   $1 #;
792
 
                        push @{$c->{changed}}, $_;
793
 
                } elsif (/^diff /) {
794
 
                        $d = 1;
795
 
                        push @{$c->{diff}}, $_;
796
 
                } elsif ($d) {
797
 
                        push @{$c->{diff}}, $_;
798
 
                } elsif (/^    (git-svn-id:.+)$/) {
799
 
                        ($c->{url}, $c->{r}, undef) = extract_metadata($1);
800
 
                } elsif (s/^    //) {
801
 
                        push @{$c->{l}}, $_;
802
 
                }
803
 
        }
804
 
        if ($c && defined $c->{r} && $c->{r} != $r_last) {
805
 
                $r_last = $c->{r};
806
 
                process_commit($c, $r_min, $r_max, \@k);
807
 
        }
808
 
        if (@k) {
809
 
                my $swap = $r_max;
810
 
                $r_max = $r_min;
811
 
                $r_min = $swap;
812
 
                process_commit($_, $r_min, $r_max) foreach reverse @k;
813
 
        }
814
 
out:
815
 
        close $log;
816
 
        print '-' x72,"\n" unless $_incremental || $_oneline;
817
 
}
818
 
 
819
 
sub commit_diff_usage {
820
 
        print STDERR "Usage: $0 commit-diff <tree-ish> <tree-ish> [<URL>]\n";
821
 
        exit 1
822
 
}
823
 
 
824
 
sub commit_diff {
825
 
        if (!$_use_lib) {
826
 
                print STDERR "commit-diff must be used with SVN libraries\n";
827
 
                exit 1;
828
 
        }
829
 
        my $ta = shift or commit_diff_usage();
830
 
        my $tb = shift or commit_diff_usage();
831
 
        if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) {
832
 
                print STDERR "Needed URL or usable git-svn id command-line\n";
833
 
                commit_diff_usage();
834
 
        }
835
 
        my $r = shift;
836
 
        unless (defined $r) {
837
 
                if (defined $_revision) {
838
 
                        $r = $_revision
839
 
                } else {
840
 
                        die "-r|--revision is a required argument\n";
841
 
                }
 
458
        unless (defined $_trunk || defined $_branches || defined $_tags) {
 
459
                usage(1);
 
460
        }
 
461
        $_prefix = '' unless defined $_prefix;
 
462
        if (defined $url) {
 
463
                $url =~ s#/+$##;
 
464
                init_subdir(@_);
 
465
        }
 
466
        do_git_init_db();
 
467
        if (defined $_trunk) {
 
468
                my $trunk_ref = $_prefix . 'trunk';
 
469
                # try both old-style and new-style lookups:
 
470
                my $gs_trunk = eval { Git::SVN->new($trunk_ref) };
 
471
                unless ($gs_trunk) {
 
472
                        my ($trunk_url, $trunk_path) =
 
473
                                              complete_svn_url($url, $_trunk);
 
474
                        $gs_trunk = Git::SVN->init($trunk_url, $trunk_path,
 
475
                                                   undef, $trunk_ref);
 
476
                }
 
477
        }
 
478
        return unless defined $_branches || defined $_tags;
 
479
        my $ra = $url ? Git::SVN::Ra->new($url) : undef;
 
480
        complete_url_ls_init($ra, $_branches, '--branches/-b', $_prefix);
 
481
        complete_url_ls_init($ra, $_tags, '--tags/-t', $_prefix . 'tags/');
 
482
}
 
483
 
 
484
sub cmd_multi_fetch {
 
485
        my $remotes = Git::SVN::read_all_remotes();
 
486
        foreach my $repo_id (sort keys %$remotes) {
 
487
                if ($remotes->{$repo_id}->{url}) {
 
488
                        Git::SVN::fetch_all($repo_id, $remotes);
 
489
                }
 
490
        }
 
491
}
 
492
 
 
493
# this command is special because it requires no metadata
 
494
sub cmd_commit_diff {
 
495
        my ($ta, $tb, $url) = @_;
 
496
        my $usage = "Usage: $0 commit-diff -r<revision> ".
 
497
                    "<tree-ish> <tree-ish> [<URL>]\n";
 
498
        fatal($usage) if (!defined $ta || !defined $tb);
 
499
        my $svn_path;
 
500
        if (!defined $url) {
 
501
                my $gs = eval { Git::SVN->new };
 
502
                if (!$gs) {
 
503
                        fatal("Needed URL or usable git-svn --id in ",
 
504
                              "the command-line\n", $usage);
 
505
                }
 
506
                $url = $gs->{url};
 
507
                $svn_path = $gs->{path};
 
508
        }
 
509
        unless (defined $_revision) {
 
510
                fatal("-r|--revision is a required argument\n", $usage);
842
511
        }
843
512
        if (defined $_message && defined $_file) {
844
 
                print STDERR "Both --message/-m and --file/-F specified ",
845
 
                                "for the commit message.\n",
846
 
                                "I have no idea what you mean\n";
847
 
                exit 1;
 
513
                fatal("Both --message/-m and --file/-F specified ",
 
514
                      "for the commit message.\n",
 
515
                      "I have no idea what you mean\n");
848
516
        }
849
517
        if (defined $_file) {
850
518
                $_message = file_to_s($_file);
851
519
        } else {
852
 
                $_message ||= get_commit_message($tb,
853
 
                                        "$GIT_DIR/.svn-commit.tmp.$$")->{msg};
 
520
                $_message ||= get_commit_entry($tb)->{log};
854
521
        }
855
 
        my $repo;
856
 
        ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
857
 
        $SVN_LOG ||= libsvn_connect($repo);
858
 
        $SVN ||= libsvn_connect($repo);
 
522
        my $ra ||= Git::SVN::Ra->new($url);
 
523
        $svn_path ||= $ra->{svn_path};
 
524
        my $r = $_revision;
859
525
        if ($r eq 'HEAD') {
860
 
                $r = $SVN->get_latest_revnum;
 
526
                $r = $ra->get_latest_revnum;
861
527
        } elsif ($r !~ /^\d+$/) {
862
528
                die "revision argument: $r not understood by git-svn\n";
863
529
        }
864
 
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
865
 
        my $rev_committed;
866
 
        my $ed = SVN::Git::Editor->new({        r => $r,
867
 
                                                ra => $SVN_LOG, c => $tb,
868
 
                                                svn_path => $SVN_PATH
869
 
                                        },
870
 
                                $SVN->get_commit_editor($_message,
871
 
                                        sub {
872
 
                                                $rev_committed = $_[0];
873
 
                                                print "Committed $_[0]\n";
874
 
                                        }, @lock)
875
 
                                );
876
 
        my $mods = libsvn_checkout_tree($ta, $tb, $ed);
877
 
        if (@$mods == 0) {
 
530
        my %ed_opts = ( r => $r,
 
531
                        log => $_message,
 
532
                        ra => $ra,
 
533
                        tree_a => $ta,
 
534
                        tree_b => $tb,
 
535
                        editor_cb => sub { print "Committed r$_[0]\n" },
 
536
                        svn_path => $svn_path );
 
537
        if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
878
538
                print "No changes\n$ta == $tb\n";
879
 
                $ed->abort_edit;
880
 
        } else {
881
 
                $ed->close_edit;
882
539
        }
883
 
        $_message = $_file = undef;
884
 
        return $rev_committed;
885
540
}
886
541
 
887
542
########################### utility functions #########################
888
543
 
889
 
sub cmt_showable {
890
 
        my ($c) = @_;
891
 
        return 1 if defined $c->{r};
892
 
        if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
893
 
                                $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
894
 
                my @msg = safe_qx(qw/git-cat-file commit/, $c->{c});
895
 
                shift @msg while ($msg[0] ne "\n");
896
 
                shift @msg;
897
 
                @{$c->{l}} = grep !/^git-svn-id: /, @msg;
898
 
 
899
 
                (undef, $c->{r}, undef) = extract_metadata(
900
 
                                (grep(/^git-svn-id: /, @msg))[-1]);
901
 
        }
902
 
        return defined $c->{r};
903
 
}
904
 
 
905
 
sub git_svn_log_cmd {
906
 
        my ($r_min, $r_max) = @_;
907
 
        my @cmd = (qw/git-log --abbrev-commit --pretty=raw
908
 
                        --default/, "refs/remotes/$GIT_SVN");
909
 
        push @cmd, '-r' unless $_non_recursive;
910
 
        push @cmd, qw/--raw --name-status/ if $_verbose;
911
 
        return @cmd unless defined $r_max;
912
 
        if ($r_max == $r_min) {
913
 
                push @cmd, '--max-count=1';
914
 
                if (my $c = revdb_get($REVDB, $r_max)) {
915
 
                        push @cmd, $c;
916
 
                }
917
 
        } else {
918
 
                my ($c_min, $c_max);
919
 
                $c_max = revdb_get($REVDB, $r_max);
920
 
                $c_min = revdb_get($REVDB, $r_min);
921
 
                if (defined $c_min && defined $c_max) {
922
 
                        if ($r_max > $r_max) {
923
 
                                push @cmd, "$c_min..$c_max";
924
 
                        } else {
925
 
                                push @cmd, "$c_max..$c_min";
926
 
                        }
927
 
                } elsif ($r_max > $r_min) {
928
 
                        push @cmd, $c_max;
929
 
                } else {
930
 
                        push @cmd, $c_min;
931
 
                }
932
 
        }
933
 
        return @cmd;
934
 
}
935
 
 
936
 
sub fetch_child_id {
937
 
        my $id = shift;
938
 
        print "Fetching $id\n";
939
 
        my $ref = "$GIT_DIR/refs/remotes/$id";
940
 
        defined(my $pid = open my $fh, '-|') or croak $!;
941
 
        if (!$pid) {
942
 
                $_repack = undef;
943
 
                $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
944
 
                init_vars();
945
 
                fetch(@_);
946
 
                exit 0;
947
 
        }
948
 
        while (<$fh>) {
949
 
                print $_;
950
 
                check_repack() if (/^r\d+ = $sha1/);
951
 
        }
952
 
        close $fh or croak $?;
953
 
}
954
 
 
955
 
sub rec_fetch {
956
 
        my ($pfx, $p, @args) = @_;
957
 
        my @dir;
958
 
        foreach (sort <$p/*>) {
959
 
                if (-r "$_/info/url") {
960
 
                        $pfx .= '/' if $pfx && $pfx !~ m!/$!;
961
 
                        my $id = $pfx . basename $_;
962
 
                        next if $id eq 'trunk';
963
 
                        fetch_child_id($id, @args);
964
 
                } elsif (-d $_) {
965
 
                        push @dir, $_;
966
 
                }
967
 
        }
968
 
        foreach (@dir) {
969
 
                my $x = $_;
970
 
                $x =~ s!^\Q$GIT_DIR\E/svn/!!;
971
 
                rec_fetch($x, $_);
972
 
        }
 
544
sub rebase_cmd {
 
545
        my @cmd = qw/rebase/;
 
546
        push @cmd, '-v' if $_verbose;
 
547
        push @cmd, qw/--merge/ if $_merge;
 
548
        push @cmd, "--strategy=$_strategy" if $_strategy;
 
549
        @cmd;
 
550
}
 
551
 
 
552
sub post_fetch_checkout {
 
553
        return if $_no_checkout;
 
554
        my $gs = $Git::SVN::_head or return;
 
555
        return if verify_ref('refs/heads/master^0');
 
556
 
 
557
        my $valid_head = verify_ref('HEAD^0');
 
558
        command_noisy(qw(update-ref refs/heads/master), $gs->refname);
 
559
        return if ($valid_head || !verify_ref('HEAD^0'));
 
560
 
 
561
        return if $ENV{GIT_DIR} !~ m#^(?:.*/)?\.git$#;
 
562
        my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index";
 
563
        return if -f $index;
 
564
 
 
565
        chomp(my $bare = `git config --bool --get core.bare`);
 
566
        return if $bare eq 'true';
 
567
        return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true';
 
568
        command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
 
569
        print STDERR "Checked out HEAD:\n  ",
 
570
                     $gs->full_url, " r", $gs->last_rev, "\n";
 
571
}
 
572
 
 
573
sub complete_svn_url {
 
574
        my ($url, $path) = @_;
 
575
        $path =~ s#/+$##;
 
576
        if ($path !~ m#^[a-z\+]+://#) {
 
577
                if (!defined $url || $url !~ m#^[a-z\+]+://#) {
 
578
                        fatal("E: '$path' is not a complete URL ",
 
579
                              "and a separate URL is not specified\n");
 
580
                }
 
581
                return ($url, $path);
 
582
        }
 
583
        return ($path, '');
973
584
}
974
585
 
975
586
sub complete_url_ls_init {
976
 
        my ($url, $var, $switch, $pfx) = @_;
977
 
        unless ($var) {
 
587
        my ($ra, $repo_path, $switch, $pfx) = @_;
 
588
        unless ($repo_path) {
978
589
                print STDERR "W: $switch not specified\n";
979
590
                return;
980
591
        }
981
 
        $var =~ s#/+$##;
982
 
        if ($var !~ m#^[a-z\+]+://#) {
983
 
                $var = '/' . $var if ($var !~ m#^/#);
984
 
                unless ($url) {
985
 
                        print STDERR "E: '$var' is not a complete URL ",
986
 
                                "and a separate URL is not specified\n";
987
 
                        exit 1;
988
 
                }
989
 
                $var = $url . $var;
990
 
        }
991
 
        chomp(my @ls = $_use_lib ? libsvn_ls_fullurl($var)
992
 
                                : safe_qx(qw/svn ls --non-interactive/, $var));
993
 
        my $old = $GIT_SVN;
994
 
        defined(my $pid = fork) or croak $!;
995
 
        if (!$pid) {
996
 
                foreach my $u (map { "$var/$_" } (grep m!/$!, @ls)) {
997
 
                        $u =~ s#/+$##;
998
 
                        if ($u !~ m!\Q$var\E/(.+)$!) {
999
 
                                print STDERR "W: Unrecognized URL: $u\n";
1000
 
                                die "This should never happen\n";
1001
 
                        }
1002
 
                        # don't try to init already existing refs
1003
 
                        my $id = $pfx.$1;
1004
 
                        $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
1005
 
                        init_vars();
1006
 
                        unless (-d $GIT_SVN_DIR) {
1007
 
                                print "init $u => $id\n";
1008
 
                                init($u);
1009
 
                        }
1010
 
                }
1011
 
                exit 0;
1012
 
        }
1013
 
        waitpid $pid, 0;
1014
 
        croak $? if $?;
 
592
        $repo_path =~ s#/+$##;
 
593
        if ($repo_path =~ m#^[a-z\+]+://#) {
 
594
                $ra = Git::SVN::Ra->new($repo_path);
 
595
                $repo_path = '';
 
596
        } else {
 
597
                $repo_path =~ s#^/+##;
 
598
                unless ($ra) {
 
599
                        fatal("E: '$repo_path' is not a complete URL ",
 
600
                              "and a separate URL is not specified\n");
 
601
                }
 
602
        }
 
603
        my $url = $ra->{url};
 
604
        my $gs = Git::SVN->init($url, undef, undef, undef, 1);
 
605
        my $k = "svn-remote.$gs->{repo_id}.url";
 
606
        my $orig_url = eval { command_oneline(qw/config --get/, $k) };
 
607
        if ($orig_url && ($orig_url ne $gs->{url})) {
 
608
                die "$k already set: $orig_url\n",
 
609
                    "wanted to set to: $gs->{url}\n";
 
610
        }
 
611
        command_oneline('config', $k, $gs->{url}) unless $orig_url;
 
612
        my $remote_path = "$ra->{svn_path}/$repo_path/*";
 
613
        $remote_path =~ s#/+#/#g;
 
614
        $remote_path =~ s#^/##g;
1015
615
        my ($n) = ($switch =~ /^--(\w+)/);
1016
 
        sys('git-repo-config', "svn.$n", $var);
1017
 
}
1018
 
 
1019
 
sub common_prefix {
1020
 
        my $paths = shift;
1021
 
        my %common;
1022
 
        foreach (@$paths) {
1023
 
                my @tmp = split m#/#, $_;
1024
 
                my $p = '';
1025
 
                while (my $x = shift @tmp) {
1026
 
                        $p .= "/$x";
1027
 
                        $common{$p} ||= 0;
1028
 
                        $common{$p}++;
1029
 
                }
1030
 
        }
1031
 
        foreach (sort {length $b <=> length $a} keys %common) {
1032
 
                if ($common{$_} == @$paths) {
1033
 
                        return $_;
1034
 
                }
1035
 
        }
1036
 
        return '';
1037
 
}
1038
 
 
1039
 
# grafts set here are 'stronger' in that they're based on actual tree
1040
 
# matches, and won't be deleted from merge-base checking in write_grafts()
1041
 
sub graft_tree_joins {
1042
 
        my $grafts = shift;
1043
 
        map_tree_joins() if (@_branch_from && !%tree_map);
1044
 
        return unless %tree_map;
1045
 
 
1046
 
        git_svn_each(sub {
1047
 
                my $i = shift;
1048
 
                defined(my $pid = open my $fh, '-|') or croak $!;
1049
 
                if (!$pid) {
1050
 
                        exec qw/git-rev-list --pretty=raw/,
1051
 
                                        "refs/remotes/$i" or croak $!;
1052
 
                }
1053
 
                while (<$fh>) {
1054
 
                        next unless /^commit ($sha1)$/o;
1055
 
                        my $c = $1;
1056
 
                        my ($t) = (<$fh> =~ /^tree ($sha1)$/o);
1057
 
                        next unless $tree_map{$t};
1058
 
 
1059
 
                        my $l;
1060
 
                        do {
1061
 
                                $l = readline $fh;
1062
 
                        } until ($l =~ /^committer (?:.+) (\d+) ([\-\+]?\d+)$/);
1063
 
 
1064
 
                        my ($s, $tz) = ($1, $2);
1065
 
                        if ($tz =~ s/^\+//) {
1066
 
                                $s += tz_to_s_offset($tz);
1067
 
                        } elsif ($tz =~ s/^\-//) {
1068
 
                                $s -= tz_to_s_offset($tz);
1069
 
                        }
1070
 
 
1071
 
                        my ($url_a, $r_a, $uuid_a) = cmt_metadata($c);
1072
 
 
1073
 
                        foreach my $p (@{$tree_map{$t}}) {
1074
 
                                next if $p eq $c;
1075
 
                                my $mb = eval {
1076
 
                                        safe_qx('git-merge-base', $c, $p)
1077
 
                                };
1078
 
                                next unless ($@ || $?);
1079
 
                                if (defined $r_a) {
1080
 
                                        # see if SVN says it's a relative
1081
 
                                        my ($url_b, $r_b, $uuid_b) =
1082
 
                                                        cmt_metadata($p);
1083
 
                                        next if (defined $url_b &&
1084
 
                                                        defined $url_a &&
1085
 
                                                        ($url_a eq $url_b) &&
1086
 
                                                        ($uuid_a eq $uuid_b));
1087
 
                                        if ($uuid_a eq $uuid_b) {
1088
 
                                                if ($r_b < $r_a) {
1089
 
                                                        $grafts->{$c}->{$p} = 2;
1090
 
                                                        next;
1091
 
                                                } elsif ($r_b > $r_a) {
1092
 
                                                        $grafts->{$p}->{$c} = 2;
1093
 
                                                        next;
1094
 
                                                }
1095
 
                                        }
1096
 
                                }
1097
 
                                my $ct = get_commit_time($p);
1098
 
                                if ($ct < $s) {
1099
 
                                        $grafts->{$c}->{$p} = 2;
1100
 
                                } elsif ($ct > $s) {
1101
 
                                        $grafts->{$p}->{$c} = 2;
1102
 
                                }
1103
 
                                # what should we do when $ct == $s ?
1104
 
                        }
1105
 
                }
1106
 
                close $fh or croak $?;
1107
 
        });
1108
 
}
1109
 
 
1110
 
# this isn't funky-filename safe, but good enough for now...
1111
 
sub graft_file_copy_cmd {
1112
 
        my ($grafts, $l_map, $u) = @_;
1113
 
        my $paths = $l_map->{$u};
1114
 
        my $pfx = common_prefix([keys %$paths]);
1115
 
        $SVN_URL ||= $u.$pfx;
1116
 
        my $pid = open my $fh, '-|';
1117
 
        defined $pid or croak $!;
1118
 
        unless ($pid) {
1119
 
                my @exec = qw/svn log -v/;
1120
 
                push @exec, "-r$_revision" if defined $_revision;
1121
 
                exec @exec, $u.$pfx or croak $!;
1122
 
        }
1123
 
        my ($r, $mp) = (undef, undef);
1124
 
        while (<$fh>) {
1125
 
                chomp;
1126
 
                if (/^\-{72}$/) {
1127
 
                        $mp = $r = undef;
1128
 
                } elsif (/^r(\d+) \| /) {
1129
 
                        $r = $1 unless defined $r;
1130
 
                } elsif (/^Changed paths:/) {
1131
 
                        $mp = 1;
1132
 
                } elsif ($mp && m#^   [AR] /(\S.*?) \(from /(\S+?):(\d+)\)$#) {
1133
 
                        my ($p1, $p0, $r0) = ($1, $2, $3);
1134
 
                        my $c = find_graft_path_commit($paths, $p1, $r);
1135
 
                        next unless $c;
1136
 
                        find_graft_path_parents($grafts, $paths, $c, $p0, $r0);
1137
 
                }
1138
 
        }
1139
 
}
1140
 
 
1141
 
sub graft_file_copy_lib {
1142
 
        my ($grafts, $l_map, $u) = @_;
1143
 
        my $tree_paths = $l_map->{$u};
1144
 
        my $pfx = common_prefix([keys %$tree_paths]);
1145
 
        my ($repo, $path) = repo_path_split($u.$pfx);
1146
 
        $SVN_LOG ||= libsvn_connect($repo);
1147
 
        $SVN ||= libsvn_connect($repo);
1148
 
 
1149
 
        my ($base, $head) = libsvn_parse_revision();
1150
 
        my $inc = 1000;
1151
 
        my ($min, $max) = ($base, $head < $base+$inc ? $head : $base+$inc);
1152
 
        my $eh = $SVN::Error::handler;
1153
 
        $SVN::Error::handler = \&libsvn_skip_unknown_revs;
1154
 
        while (1) {
1155
 
                my $pool = SVN::Pool->new;
1156
 
                libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1,
1157
 
                        sub {
1158
 
                                libsvn_graft_file_copies($grafts, $tree_paths,
1159
 
                                                        $path, @_);
1160
 
                        }, $pool);
1161
 
                $pool->clear;
1162
 
                last if ($max >= $head);
1163
 
                $min = $max + 1;
1164
 
                $max += $inc;
1165
 
                $max = $head if ($max > $head);
1166
 
        }
1167
 
        $SVN::Error::handler = $eh;
1168
 
}
1169
 
 
1170
 
sub process_merge_msg_matches {
1171
 
        my ($grafts, $l_map, $u, $p, $c, @matches) = @_;
1172
 
        my (@strong, @weak);
1173
 
        foreach (@matches) {
1174
 
                # merging with ourselves is not interesting
1175
 
                next if $_ eq $p;
1176
 
                if ($l_map->{$u}->{$_}) {
1177
 
                        push @strong, $_;
1178
 
                } else {
1179
 
                        push @weak, $_;
1180
 
                }
1181
 
        }
1182
 
        foreach my $w (@weak) {
1183
 
                last if @strong;
1184
 
                # no exact match, use branch name as regexp.
1185
 
                my $re = qr/\Q$w\E/i;
1186
 
                foreach (keys %{$l_map->{$u}}) {
1187
 
                        if (/$re/) {
1188
 
                                push @strong, $l_map->{$u}->{$_};
1189
 
                                last;
1190
 
                        }
1191
 
                }
1192
 
                last if @strong;
1193
 
                $w = basename($w);
1194
 
                $re = qr/\Q$w\E/i;
1195
 
                foreach (keys %{$l_map->{$u}}) {
1196
 
                        if (/$re/) {
1197
 
                                push @strong, $l_map->{$u}->{$_};
1198
 
                                last;
1199
 
                        }
1200
 
                }
1201
 
        }
1202
 
        my ($rev) = ($c->{m} =~ /^git-svn-id:\s(?:\S+?)\@(\d+)
1203
 
                                        \s(?:[a-f\d\-]+)$/xsm);
1204
 
        unless (defined $rev) {
1205
 
                ($rev) = ($c->{m} =~/^git-svn-id:\s(\d+)
1206
 
                                        \@(?:[a-f\d\-]+)/xsm);
1207
 
                return unless defined $rev;
1208
 
        }
1209
 
        foreach my $m (@strong) {
1210
 
                my ($r0, $s0) = find_rev_before($rev, $m, 1);
1211
 
                $grafts->{$c->{c}}->{$s0} = 1 if defined $s0;
1212
 
        }
1213
 
}
1214
 
 
1215
 
sub graft_merge_msg {
1216
 
        my ($grafts, $l_map, $u, $p, @re) = @_;
1217
 
 
1218
 
        my $x = $l_map->{$u}->{$p};
1219
 
        my $rl = rev_list_raw($x);
1220
 
        while (my $c = next_rev_list_entry($rl)) {
1221
 
                foreach my $re (@re) {
1222
 
                        my (@br) = ($c->{m} =~ /$re/g);
1223
 
                        next unless @br;
1224
 
                        process_merge_msg_matches($grafts,$l_map,$u,$p,$c,@br);
1225
 
                }
1226
 
        }
1227
 
}
1228
 
 
1229
 
sub read_uuid {
1230
 
        return if $SVN_UUID;
1231
 
        if ($_use_lib) {
1232
 
                my $pool = SVN::Pool->new;
1233
 
                $SVN_UUID = $SVN->get_uuid($pool);
1234
 
                $pool->clear;
1235
 
        } else {
1236
 
                my $info = shift || svn_info('.');
1237
 
                $SVN_UUID = $info->{'Repository UUID'} or
1238
 
                                        croak "Repository UUID unreadable\n";
1239
 
        }
1240
 
}
1241
 
 
1242
 
sub quiet_run {
1243
 
        my $pid = fork;
1244
 
        defined $pid or croak $!;
1245
 
        if (!$pid) {
1246
 
                open my $null, '>', '/dev/null' or croak $!;
1247
 
                open STDERR, '>&', $null or croak $!;
1248
 
                open STDOUT, '>&', $null or croak $!;
1249
 
                exec @_ or croak $!;
1250
 
        }
1251
 
        waitpid $pid, 0;
1252
 
        return $?;
1253
 
}
1254
 
 
1255
 
sub repo_path_split {
1256
 
        my $full_url = shift;
1257
 
        $full_url =~ s#/+$##;
1258
 
 
1259
 
        foreach (@repo_path_split_cache) {
1260
 
                if ($full_url =~ s#$_##) {
1261
 
                        my $u = $1;
1262
 
                        $full_url =~ s#^/+##;
1263
 
                        return ($u, $full_url);
1264
 
                }
1265
 
        }
1266
 
 
1267
 
        if ($_use_lib) {
1268
 
                my $tmp = libsvn_connect($full_url);
1269
 
                my $url = $tmp->get_repos_root;
1270
 
                $full_url =~ s#^\Q$url\E/*##;
1271
 
                push @repo_path_split_cache, qr/^(\Q$url\E)/;
1272
 
                return ($url, $full_url);
1273
 
        } else {
1274
 
                my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i);
1275
 
                $path =~ s#^/+##;
1276
 
                my @paths = split(m#/+#, $path);
1277
 
                while (quiet_run(qw/svn ls --non-interactive/, $url)) {
1278
 
                        my $n = shift @paths || last;
1279
 
                        $url .= "/$n";
1280
 
                }
1281
 
                push @repo_path_split_cache, qr/^(\Q$url\E)/;
1282
 
                $path = join('/',@paths);
1283
 
                return ($url, $path);
1284
 
        }
1285
 
}
1286
 
 
1287
 
sub setup_git_svn {
1288
 
        defined $SVN_URL or croak "SVN repository location required\n";
1289
 
        unless (-d $GIT_DIR) {
1290
 
                croak "GIT_DIR=$GIT_DIR does not exist!\n";
1291
 
        }
1292
 
        mkpath([$GIT_SVN_DIR]);
1293
 
        mkpath(["$GIT_SVN_DIR/info"]);
1294
 
        open my $fh, '>>',$REVDB or croak $!;
1295
 
        close $fh;
1296
 
        s_to_file($SVN_URL,"$GIT_SVN_DIR/info/url");
1297
 
 
1298
 
}
1299
 
 
1300
 
sub assert_svn_wc_clean {
1301
 
        return if $_use_lib;
1302
 
        my ($svn_rev) = @_;
1303
 
        croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/);
1304
 
        my $lcr = svn_info('.')->{'Last Changed Rev'};
1305
 
        if ($svn_rev != $lcr) {
1306
 
                print STDERR "Checking for copy-tree ... ";
1307
 
                my @diff = grep(/^Index: /,(safe_qx(qw(svn diff),
1308
 
                                                "-r$lcr:$svn_rev")));
1309
 
                if (@diff) {
1310
 
                        croak "Nope!  Expected r$svn_rev, got r$lcr\n";
1311
 
                } else {
1312
 
                        print STDERR "OK!\n";
1313
 
                }
1314
 
        }
1315
 
        my @status = grep(!/^Performing status on external/,(`svn status`));
1316
 
        @status = grep(!/^\s*$/,@status);
1317
 
        @status = grep(!/^X/,@status) if $_no_ignore_ext;
1318
 
        if (scalar @status) {
1319
 
                print STDERR "Tree ($SVN_WC) is not clean:\n";
1320
 
                print STDERR $_ foreach @status;
1321
 
                croak;
1322
 
        }
 
616
        if (length $pfx && $pfx !~ m#/$#) {
 
617
                die "--prefix='$pfx' must have a trailing slash '/'\n";
 
618
        }
 
619
        command_noisy('config', "svn-remote.$gs->{repo_id}.$n",
 
620
                                "$remote_path:refs/remotes/$pfx*");
 
621
}
 
622
 
 
623
sub verify_ref {
 
624
        my ($ref) = @_;
 
625
        eval { command_oneline([ 'rev-parse', '--verify', $ref ],
 
626
                               { STDERR => 0 }); };
1323
627
}
1324
628
 
1325
629
sub get_tree_from_treeish {
1326
630
        my ($treeish) = @_;
1327
 
        croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
1328
 
        chomp(my $type = `git-cat-file -t $treeish`);
 
631
        # $treeish can be a symbolic ref, too:
 
632
        my $type = command_oneline(qw/cat-file -t/, $treeish);
1329
633
        my $expected;
1330
634
        while ($type eq 'tag') {
1331
 
                chomp(($treeish, $type) = `git-cat-file tag $treeish`);
 
635
                ($treeish, $type) = command(qw/cat-file tag/, $treeish);
1332
636
        }
1333
637
        if ($type eq 'commit') {
1334
 
                $expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];
1335
 
                ($expected) = ($expected =~ /^tree ($sha1)$/);
 
638
                $expected = (grep /^tree /, command(qw/cat-file commit/,
 
639
                                                    $treeish))[0];
 
640
                ($expected) = ($expected =~ /^tree ($sha1)$/o);
1336
641
                die "Unable to get tree from $treeish\n" unless $expected;
1337
642
        } elsif ($type eq 'tree') {
1338
643
                $expected = $treeish;
1342
647
        return $expected;
1343
648
}
1344
649
 
1345
 
sub assert_tree {
1346
 
        return if $_use_lib;
1347
 
        my ($treeish) = @_;
1348
 
        my $expected = get_tree_from_treeish($treeish);
1349
 
 
1350
 
        my $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';
1351
 
        if (-e $tmpindex) {
1352
 
                unlink $tmpindex or croak $!;
1353
 
        }
1354
 
        my $old_index = set_index($tmpindex);
1355
 
        index_changes(1);
1356
 
        chomp(my $tree = `git-write-tree`);
1357
 
        restore_index($old_index);
1358
 
        if ($tree ne $expected) {
1359
 
                croak "Tree mismatch, Got: $tree, Expected: $expected\n";
1360
 
        }
1361
 
        unlink $tmpindex;
1362
 
}
1363
 
 
1364
 
sub parse_diff_tree {
1365
 
        my $diff_fh = shift;
1366
 
        local $/ = "\0";
1367
 
        my $state = 'meta';
1368
 
        my @mods;
1369
 
        while (<$diff_fh>) {
1370
 
                chomp $_; # this gets rid of the trailing "\0"
1371
 
                if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
1372
 
                                        $sha1\s($sha1)\s([MTCRAD])\d*$/xo) {
1373
 
                        push @mods, {   mode_a => $1, mode_b => $2,
1374
 
                                        sha1_b => $3, chg => $4 };
1375
 
                        if ($4 =~ /^(?:C|R)$/) {
1376
 
                                $state = 'file_a';
1377
 
                        } else {
1378
 
                                $state = 'file_b';
1379
 
                        }
1380
 
                } elsif ($state eq 'file_a') {
1381
 
                        my $x = $mods[$#mods] or croak "Empty array\n";
1382
 
                        if ($x->{chg} !~ /^(?:C|R)$/) {
1383
 
                                croak "Error parsing $_, $x->{chg}\n";
1384
 
                        }
1385
 
                        $x->{file_a} = $_;
1386
 
                        $state = 'file_b';
1387
 
                } elsif ($state eq 'file_b') {
1388
 
                        my $x = $mods[$#mods] or croak "Empty array\n";
1389
 
                        if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
1390
 
                                croak "Error parsing $_, $x->{chg}\n";
1391
 
                        }
1392
 
                        if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
1393
 
                                croak "Error parsing $_, $x->{chg}\n";
1394
 
                        }
1395
 
                        $x->{file_b} = $_;
1396
 
                        $state = 'meta';
1397
 
                } else {
1398
 
                        croak "Error parsing $_\n";
1399
 
                }
1400
 
        }
1401
 
        close $diff_fh or croak $?;
1402
 
 
1403
 
        return \@mods;
1404
 
}
1405
 
 
1406
 
sub svn_check_prop_executable {
1407
 
        my $m = shift;
1408
 
        return if -l $m->{file_b};
1409
 
        if ($m->{mode_b} =~ /755$/) {
1410
 
                chmod((0755 &~ umask),$m->{file_b}) or croak $!;
1411
 
                if ($m->{mode_a} !~ /755$/) {
1412
 
                        sys(qw(svn propset svn:executable 1), $m->{file_b});
1413
 
                }
1414
 
                -x $m->{file_b} or croak "$m->{file_b} is not executable!\n";
1415
 
        } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
1416
 
                sys(qw(svn propdel svn:executable), $m->{file_b});
1417
 
                chmod((0644 &~ umask),$m->{file_b}) or croak $!;
1418
 
                -x $m->{file_b} and croak "$m->{file_b} is executable!\n";
1419
 
        }
1420
 
}
1421
 
 
1422
 
sub svn_ensure_parent_path {
1423
 
        my $dir_b = dirname(shift);
1424
 
        svn_ensure_parent_path($dir_b) if ($dir_b ne File::Spec->curdir);
1425
 
        mkpath([$dir_b]) unless (-d $dir_b);
1426
 
        sys(qw(svn add -N), $dir_b) unless (-d "$dir_b/.svn");
1427
 
}
1428
 
 
1429
 
sub precommit_check {
1430
 
        my $mods = shift;
1431
 
        my (%rm_file, %rmdir_check, %added_check);
1432
 
 
1433
 
        my %o = ( D => 0, R => 1, C => 2, A => 3, M => 3, T => 3 );
1434
 
        foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
1435
 
                if ($m->{chg} eq 'R') {
1436
 
                        if (-d $m->{file_b}) {
1437
 
                                err_dir_to_file("$m->{file_a} => $m->{file_b}");
1438
 
                        }
1439
 
                        # dir/$file => dir/file/$file
1440
 
                        my $dirname = dirname($m->{file_b});
1441
 
                        while ($dirname ne File::Spec->curdir) {
1442
 
                                if ($dirname ne $m->{file_a}) {
1443
 
                                        $dirname = dirname($dirname);
1444
 
                                        next;
1445
 
                                }
1446
 
                                err_file_to_dir("$m->{file_a} => $m->{file_b}");
1447
 
                        }
1448
 
                        # baz/zzz => baz (baz is a file)
1449
 
                        $dirname = dirname($m->{file_a});
1450
 
                        while ($dirname ne File::Spec->curdir) {
1451
 
                                if ($dirname ne $m->{file_b}) {
1452
 
                                        $dirname = dirname($dirname);
1453
 
                                        next;
1454
 
                                }
1455
 
                                err_dir_to_file("$m->{file_a} => $m->{file_b}");
1456
 
                        }
1457
 
                }
1458
 
                if ($m->{chg} =~ /^(D|R)$/) {
1459
 
                        my $t = $1 eq 'D' ? 'file_b' : 'file_a';
1460
 
                        $rm_file{ $m->{$t} } = 1;
1461
 
                        my $dirname = dirname( $m->{$t} );
1462
 
                        my $basename = basename( $m->{$t} );
1463
 
                        $rmdir_check{$dirname}->{$basename} = 1;
1464
 
                } elsif ($m->{chg} =~ /^(?:A|C)$/) {
1465
 
                        if (-d $m->{file_b}) {
1466
 
                                err_dir_to_file($m->{file_b});
1467
 
                        }
1468
 
                        my $dirname = dirname( $m->{file_b} );
1469
 
                        my $basename = basename( $m->{file_b} );
1470
 
                        $added_check{$dirname}->{$basename} = 1;
1471
 
                        while ($dirname ne File::Spec->curdir) {
1472
 
                                if ($rm_file{$dirname}) {
1473
 
                                        err_file_to_dir($m->{file_b});
1474
 
                                }
1475
 
                                $dirname = dirname $dirname;
1476
 
                        }
1477
 
                }
1478
 
        }
1479
 
        return (\%rmdir_check, \%added_check);
1480
 
 
1481
 
        sub err_dir_to_file {
1482
 
                my $file = shift;
1483
 
                print STDERR "Node change from directory to file ",
1484
 
                                "is not supported by Subversion: ",$file,"\n";
1485
 
                exit 1;
1486
 
        }
1487
 
        sub err_file_to_dir {
1488
 
                my $file = shift;
1489
 
                print STDERR "Node change from file to directory ",
1490
 
                                "is not supported by Subversion: ",$file,"\n";
1491
 
                exit 1;
1492
 
        }
1493
 
}
1494
 
 
1495
 
 
1496
 
sub get_diff {
1497
 
        my ($from, $treeish) = @_;
1498
 
        assert_tree($from);
1499
 
        print "diff-tree $from $treeish\n";
1500
 
        my $pid = open my $diff_fh, '-|';
1501
 
        defined $pid or croak $!;
1502
 
        if ($pid == 0) {
1503
 
                my @diff_tree = qw(git-diff-tree -z -r);
1504
 
                if ($_cp_similarity) {
1505
 
                        push @diff_tree, "-C$_cp_similarity";
1506
 
                } else {
1507
 
                        push @diff_tree, '-C';
1508
 
                }
1509
 
                push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
1510
 
                push @diff_tree, "-l$_l" if defined $_l;
1511
 
                exec(@diff_tree, $from, $treeish) or croak $!;
1512
 
        }
1513
 
        return parse_diff_tree($diff_fh);
1514
 
}
1515
 
 
1516
 
sub svn_checkout_tree {
1517
 
        my ($from, $treeish) = @_;
1518
 
        my $mods = get_diff($from->{commit}, $treeish);
1519
 
        return $mods unless (scalar @$mods);
1520
 
        my ($rm, $add) = precommit_check($mods);
1521
 
 
1522
 
        my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
1523
 
        foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
1524
 
                if ($m->{chg} eq 'C') {
1525
 
                        svn_ensure_parent_path( $m->{file_b} );
1526
 
                        sys(qw(svn cp),         $m->{file_a}, $m->{file_b});
1527
 
                        apply_mod_line_blob($m);
1528
 
                        svn_check_prop_executable($m);
1529
 
                } elsif ($m->{chg} eq 'D') {
1530
 
                        sys(qw(svn rm --force), $m->{file_b});
1531
 
                } elsif ($m->{chg} eq 'R') {
1532
 
                        svn_ensure_parent_path( $m->{file_b} );
1533
 
                        sys(qw(svn mv --force), $m->{file_a}, $m->{file_b});
1534
 
                        apply_mod_line_blob($m);
1535
 
                        svn_check_prop_executable($m);
1536
 
                } elsif ($m->{chg} eq 'M') {
1537
 
                        apply_mod_line_blob($m);
1538
 
                        svn_check_prop_executable($m);
1539
 
                } elsif ($m->{chg} eq 'T') {
1540
 
                        svn_check_prop_executable($m);
1541
 
                        apply_mod_line_blob($m);
1542
 
                        if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
1543
 
                                sys(qw(svn propdel svn:special), $m->{file_b});
1544
 
                        } else {
1545
 
                                sys(qw(svn propset svn:special *),$m->{file_b});
1546
 
                        }
1547
 
                } elsif ($m->{chg} eq 'A') {
1548
 
                        svn_ensure_parent_path( $m->{file_b} );
1549
 
                        apply_mod_line_blob($m);
1550
 
                        sys(qw(svn add), $m->{file_b});
1551
 
                        svn_check_prop_executable($m);
1552
 
                } else {
1553
 
                        croak "Invalid chg: $m->{chg}\n";
1554
 
                }
1555
 
        }
1556
 
 
1557
 
        assert_tree($treeish);
1558
 
        if ($_rmdir) { # remove empty directories
1559
 
                handle_rmdir($rm, $add);
1560
 
        }
1561
 
        assert_tree($treeish);
1562
 
        return $mods;
1563
 
}
1564
 
 
1565
 
sub libsvn_checkout_tree {
1566
 
        my ($from, $treeish, $ed) = @_;
1567
 
        my $mods = get_diff($from, $treeish);
1568
 
        return $mods unless (scalar @$mods);
1569
 
        my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
1570
 
        foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
1571
 
                my $f = $m->{chg};
1572
 
                if (defined $o{$f}) {
1573
 
                        $ed->$f($m, $_q);
1574
 
                } else {
1575
 
                        croak "Invalid change type: $f\n";
1576
 
                }
1577
 
        }
1578
 
        $ed->rmdirs($_q) if $_rmdir;
1579
 
        return $mods;
1580
 
}
1581
 
 
1582
 
# svn ls doesn't work with respect to the current working tree, but what's
1583
 
# in the repository.  There's not even an option for it... *sigh*
1584
 
# (added files don't show up and removed files remain in the ls listing)
1585
 
sub svn_ls_current {
1586
 
        my ($dir, $rm, $add) = @_;
1587
 
        chomp(my @ls = safe_qx('svn','ls',$dir));
1588
 
        my @ret = ();
1589
 
        foreach (@ls) {
1590
 
                s#/$##; # trailing slashes are evil
1591
 
                push @ret, $_ unless $rm->{$dir}->{$_};
1592
 
        }
1593
 
        if (exists $add->{$dir}) {
1594
 
                push @ret, keys %{$add->{$dir}};
1595
 
        }
1596
 
        return \@ret;
1597
 
}
1598
 
 
1599
 
sub handle_rmdir {
1600
 
        my ($rm, $add) = @_;
1601
 
 
1602
 
        foreach my $dir (sort {length $b <=> length $a} keys %$rm) {
1603
 
                my $ls = svn_ls_current($dir, $rm, $add);
1604
 
                next if (scalar @$ls);
1605
 
                sys(qw(svn rm --force),$dir);
1606
 
 
1607
 
                my $dn = dirname $dir;
1608
 
                $rm->{ $dn }->{ basename $dir } = 1;
1609
 
                $ls = svn_ls_current($dn, $rm, $add);
1610
 
                while (scalar @$ls == 0 && $dn ne File::Spec->curdir) {
1611
 
                        sys(qw(svn rm --force),$dn);
1612
 
                        $dir = basename $dn;
1613
 
                        $dn = dirname $dn;
1614
 
                        $rm->{ $dn }->{ $dir } = 1;
1615
 
                        $ls = svn_ls_current($dn, $rm, $add);
1616
 
                }
1617
 
        }
1618
 
}
1619
 
 
1620
 
sub get_commit_message {
1621
 
        my ($commit, $commit_msg) = (@_);
1622
 
        my %log_msg = ( msg => '' );
1623
 
        open my $msg, '>', $commit_msg or croak $!;
1624
 
 
1625
 
        chomp(my $type = `git-cat-file -t $commit`);
 
650
sub get_commit_entry {
 
651
        my ($treeish) = shift;
 
652
        my %log_entry = ( log => '', tree => get_tree_from_treeish($treeish) );
 
653
        my $commit_editmsg = "$ENV{GIT_DIR}/COMMIT_EDITMSG";
 
654
        my $commit_msg = "$ENV{GIT_DIR}/COMMIT_MSG";
 
655
        open my $log_fh, '>', $commit_editmsg or croak $!;
 
656
 
 
657
        my $type = command_oneline(qw/cat-file -t/, $treeish);
1626
658
        if ($type eq 'commit' || $type eq 'tag') {
1627
 
                my $pid = open my $msg_fh, '-|';
1628
 
                defined $pid or croak $!;
1629
 
 
1630
 
                if ($pid == 0) {
1631
 
                        exec('git-cat-file', $type, $commit) or croak $!;
1632
 
                }
 
659
                my ($msg_fh, $ctx) = command_output_pipe('cat-file',
 
660
                                                         $type, $treeish);
1633
661
                my $in_msg = 0;
1634
662
                while (<$msg_fh>) {
1635
663
                        if (!$in_msg) {
1636
664
                                $in_msg = 1 if (/^\s*$/);
1637
665
                        } elsif (/^git-svn-id: /) {
1638
 
                                # skip this, we regenerate the correct one
1639
 
                                # on re-fetch anyways
 
666
                                # skip this for now, we regenerate the
 
667
                                # correct one on re-fetch anyways
 
668
                                # TODO: set *:merge properties or like...
1640
669
                        } else {
1641
 
                                print $msg $_ or croak $!;
 
670
                                print $log_fh $_ or croak $!;
1642
671
                        }
1643
672
                }
1644
 
                close $msg_fh or croak $?;
 
673
                command_close_pipe($msg_fh, $ctx);
1645
674
        }
1646
 
        close $msg or croak $!;
 
675
        close $log_fh or croak $!;
1647
676
 
1648
677
        if ($_edit || ($type eq 'tree')) {
1649
678
                my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
1650
 
                system($editor, $commit_msg);
1651
 
        }
1652
 
 
1653
 
        # file_to_s removes all trailing newlines, so just use chomp() here:
1654
 
        open $msg, '<', $commit_msg or croak $!;
1655
 
        { local $/; chomp($log_msg{msg} = <$msg>); }
1656
 
        close $msg or croak $!;
1657
 
 
1658
 
        return \%log_msg;
1659
 
}
1660
 
 
1661
 
sub set_svn_commit_env {
1662
 
        if (defined $LC_ALL) {
1663
 
                $ENV{LC_ALL} = $LC_ALL;
1664
 
        } else {
1665
 
                delete $ENV{LC_ALL};
1666
 
        }
1667
 
}
1668
 
 
1669
 
sub svn_commit_tree {
1670
 
        my ($last, $commit) = @_;
1671
 
        my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
1672
 
        my $log_msg = get_commit_message($commit, $commit_msg);
1673
 
        my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/);
1674
 
        print "Committing $commit: $oneline\n";
1675
 
 
1676
 
        set_svn_commit_env();
1677
 
        my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
1678
 
        $ENV{LC_ALL} = 'C';
 
679
                # TODO: strip out spaces, comments, like git-commit.sh
 
680
                system($editor, $commit_editmsg);
 
681
        }
 
682
        rename $commit_editmsg, $commit_msg or croak $!;
 
683
        open $log_fh, '<', $commit_msg or croak $!;
 
684
        { local $/; chomp($log_entry{log} = <$log_fh>); }
 
685
        close $log_fh or croak $!;
1679
686
        unlink $commit_msg;
1680
 
        my ($committed) = ($ci_output[$#ci_output] =~ /(\d+)/);
1681
 
        if (!defined $committed) {
1682
 
                my $out = join("\n",@ci_output);
1683
 
                print STDERR "W: Trouble parsing \`svn commit' output:\n\n",
1684
 
                                $out, "\n\nAssuming English locale...";
1685
 
                ($committed) = ($out =~ /^Committed revision \d+\./sm);
1686
 
                defined $committed or die " FAILED!\n",
1687
 
                        "Commit output failed to parse committed revision!\n",
1688
 
                print STDERR " OK\n";
1689
 
        }
1690
 
 
1691
 
        my @svn_up = qw(svn up);
1692
 
        push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
1693
 
        if ($_optimize_commits && ($committed == ($last->{revision} + 1))) {
1694
 
                push @svn_up, "-r$committed";
1695
 
                sys(@svn_up);
1696
 
                my $info = svn_info('.');
1697
 
                my $date = $info->{'Last Changed Date'} or die "Missing date\n";
1698
 
                if ($info->{'Last Changed Rev'} != $committed) {
1699
 
                        croak "$info->{'Last Changed Rev'} != $committed\n"
1700
 
                }
1701
 
                my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
1702
 
                                        /(\d{4})\-(\d\d)\-(\d\d)\s
1703
 
                                         (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
1704
 
                                         or croak "Failed to parse date: $date\n";
1705
 
                $log_msg->{date} = "$tz $Y-$m-$d $H:$M:$S";
1706
 
                $log_msg->{author} = $info->{'Last Changed Author'};
1707
 
                $log_msg->{revision} = $committed;
1708
 
                $log_msg->{msg} .= "\n";
1709
 
                $log_msg->{parents} = [ $last->{commit} ];
1710
 
                $log_msg->{commit} = git_commit($log_msg, $commit);
1711
 
                return $log_msg;
1712
 
        }
1713
 
        # resync immediately
1714
 
        push @svn_up, "-r$last->{revision}";
1715
 
        sys(@svn_up);
1716
 
        return fetch("$committed=$commit");
1717
 
}
1718
 
 
1719
 
sub rev_list_raw {
1720
 
        my (@args) = @_;
1721
 
        my $pid = open my $fh, '-|';
1722
 
        defined $pid or croak $!;
1723
 
        if (!$pid) {
1724
 
                exec(qw/git-rev-list --pretty=raw/, @args) or croak $!;
1725
 
        }
1726
 
        return { fh => $fh, t => { } };
1727
 
}
1728
 
 
1729
 
sub next_rev_list_entry {
1730
 
        my $rl = shift;
1731
 
        my $fh = $rl->{fh};
1732
 
        my $x = $rl->{t};
1733
 
        while (<$fh>) {
1734
 
                if (/^commit ($sha1)$/o) {
1735
 
                        if ($x->{c}) {
1736
 
                                $rl->{t} = { c => $1 };
1737
 
                                return $x;
1738
 
                        } else {
1739
 
                                $x->{c} = $1;
1740
 
                        }
1741
 
                } elsif (/^parent ($sha1)$/o) {
1742
 
                        $x->{p}->{$1} = 1;
1743
 
                } elsif (s/^    //) {
1744
 
                        $x->{m} ||= '';
1745
 
                        $x->{m} .= $_;
1746
 
                }
1747
 
        }
1748
 
        return ($x != $rl->{t}) ? $x : undef;
1749
 
}
1750
 
 
1751
 
# read the entire log into a temporary file (which is removed ASAP)
1752
 
# and store the file handle + parser state
1753
 
sub svn_log_raw {
1754
 
        my (@log_args) = @_;
1755
 
        my $log_fh = IO::File->new_tmpfile or croak $!;
1756
 
        my $pid = fork;
1757
 
        defined $pid or croak $!;
1758
 
        if (!$pid) {
1759
 
                open STDOUT, '>&', $log_fh or croak $!;
1760
 
                exec (qw(svn log), @log_args) or croak $!
1761
 
        }
1762
 
        waitpid $pid, 0;
1763
 
        croak $? if $?;
1764
 
        seek $log_fh, 0, 0 or croak $!;
1765
 
        return { state => 'sep', fh => $log_fh };
1766
 
}
1767
 
 
1768
 
sub next_log_entry {
1769
 
        my $log = shift; # retval of svn_log_raw()
1770
 
        my $ret = undef;
1771
 
        my $fh = $log->{fh};
1772
 
 
1773
 
        while (<$fh>) {
1774
 
                chomp;
1775
 
                if (/^\-{72}$/) {
1776
 
                        if ($log->{state} eq 'msg') {
1777
 
                                if ($ret->{lines}) {
1778
 
                                        $ret->{msg} .= $_."\n";
1779
 
                                        unless(--$ret->{lines}) {
1780
 
                                                $log->{state} = 'sep';
1781
 
                                        }
1782
 
                                } else {
1783
 
                                        croak "Log parse error at: $_\n",
1784
 
                                                $ret->{revision},
1785
 
                                                "\n";
1786
 
                                }
1787
 
                                next;
1788
 
                        }
1789
 
                        if ($log->{state} ne 'sep') {
1790
 
                                croak "Log parse error at: $_\n",
1791
 
                                        "state: $log->{state}\n",
1792
 
                                        $ret->{revision},
1793
 
                                        "\n";
1794
 
                        }
1795
 
                        $log->{state} = 'rev';
1796
 
 
1797
 
                        # if we have an empty log message, put something there:
1798
 
                        if ($ret) {
1799
 
                                $ret->{msg} ||= "\n";
1800
 
                                delete $ret->{lines};
1801
 
                                return $ret;
1802
 
                        }
1803
 
                        next;
1804
 
                }
1805
 
                if ($log->{state} eq 'rev' && s/^r(\d+)\s*\|\s*//) {
1806
 
                        my $rev = $1;
1807
 
                        my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3);
1808
 
                        ($lines) = ($lines =~ /(\d+)/);
1809
 
                        $date = '1970-01-01 00:00:00 +0000'
1810
 
                                if ($_ignore_nodate && $date eq '(no date)');
1811
 
                        my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
1812
 
                                        /(\d{4})\-(\d\d)\-(\d\d)\s
1813
 
                                         (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
1814
 
                                         or croak "Failed to parse date: $date\n";
1815
 
                        $ret = {        revision => $rev,
1816
 
                                        date => "$tz $Y-$m-$d $H:$M:$S",
1817
 
                                        author => $author,
1818
 
                                        lines => $lines,
1819
 
                                        msg => '' };
1820
 
                        if (defined $_authors && ! defined $users{$author}) {
1821
 
                                die "Author: $author not defined in ",
1822
 
                                                "$_authors file\n";
1823
 
                        }
1824
 
                        $log->{state} = 'msg_start';
1825
 
                        next;
1826
 
                }
1827
 
                # skip the first blank line of the message:
1828
 
                if ($log->{state} eq 'msg_start' && /^$/) {
1829
 
                        $log->{state} = 'msg';
1830
 
                } elsif ($log->{state} eq 'msg') {
1831
 
                        if ($ret->{lines}) {
1832
 
                                $ret->{msg} .= $_."\n";
1833
 
                                unless (--$ret->{lines}) {
1834
 
                                        $log->{state} = 'sep';
1835
 
                                }
1836
 
                        } else {
1837
 
                                croak "Log parse error at: $_\n",
1838
 
                                        $ret->{revision},"\n";
1839
 
                        }
1840
 
                }
1841
 
        }
1842
 
        return $ret;
1843
 
}
1844
 
 
1845
 
sub svn_info {
1846
 
        my $url = shift || $SVN_URL;
1847
 
 
1848
 
        my $pid = open my $info_fh, '-|';
1849
 
        defined $pid or croak $!;
1850
 
 
1851
 
        if ($pid == 0) {
1852
 
                exec(qw(svn info),$url) or croak $!;
1853
 
        }
1854
 
 
1855
 
        my $ret = {};
1856
 
        # only single-lines seem to exist in svn info output
1857
 
        while (<$info_fh>) {
1858
 
                chomp $_;
1859
 
                if (m#^([^:]+)\s*:\s*(\S.*)$#) {
1860
 
                        $ret->{$1} = $2;
1861
 
                        push @{$ret->{-order}}, $1;
1862
 
                }
1863
 
        }
1864
 
        close $info_fh or croak $?;
1865
 
        return $ret;
1866
 
}
1867
 
 
1868
 
sub sys { system(@_) == 0 or croak $? }
1869
 
 
1870
 
sub do_update_index {
1871
 
        my ($z_cmd, $cmd, $no_text_base) = @_;
1872
 
 
1873
 
        my $z = open my $p, '-|';
1874
 
        defined $z or croak $!;
1875
 
        unless ($z) { exec @$z_cmd or croak $! }
1876
 
 
1877
 
        my $pid = open my $ui, '|-';
1878
 
        defined $pid or croak $!;
1879
 
        unless ($pid) {
1880
 
                exec('git-update-index',"--$cmd",'-z','--stdin') or croak $!;
1881
 
        }
1882
 
        local $/ = "\0";
1883
 
        while (my $x = <$p>) {
1884
 
                chomp $x;
1885
 
                if (!$no_text_base && lstat $x && ! -l _ &&
1886
 
                                svn_propget_base('svn:keywords', $x)) {
1887
 
                        my $mode = -x _ ? 0755 : 0644;
1888
 
                        my ($v,$d,$f) = File::Spec->splitpath($x);
1889
 
                        my $tb = File::Spec->catfile($d, '.svn', 'tmp',
1890
 
                                                'text-base',"$f.svn-base");
1891
 
                        $tb =~ s#^/##;
1892
 
                        unless (-f $tb) {
1893
 
                                $tb = File::Spec->catfile($d, '.svn',
1894
 
                                                'text-base',"$f.svn-base");
1895
 
                                $tb =~ s#^/##;
1896
 
                        }
1897
 
                        my @s = stat($x);
1898
 
                        unlink $x or croak $!;
1899
 
                        copy($tb, $x);
1900
 
                        chmod(($mode &~ umask), $x) or croak $!;
1901
 
                        utime $s[8], $s[9], $x;
1902
 
                }
1903
 
                print $ui $x,"\0";
1904
 
        }
1905
 
        close $ui or croak $?;
1906
 
}
1907
 
 
1908
 
sub index_changes {
1909
 
        return if $_use_lib;
1910
 
 
1911
 
        if (!-f "$GIT_SVN_DIR/info/exclude") {
1912
 
                open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!;
1913
 
                print $fd '.svn',"\n";
1914
 
                close $fd or croak $!;
1915
 
        }
1916
 
        my $no_text_base = shift;
1917
 
        do_update_index([qw/git-diff-files --name-only -z/],
1918
 
                        'remove',
1919
 
                        $no_text_base);
1920
 
        do_update_index([qw/git-ls-files -z --others/,
1921
 
                                "--exclude-from=$GIT_SVN_DIR/info/exclude"],
1922
 
                        'add',
1923
 
                        $no_text_base);
 
687
        \%log_entry;
1924
688
}
1925
689
 
1926
690
sub s_to_file {
1941
705
        return $ret;
1942
706
}
1943
707
 
1944
 
sub assert_revision_unknown {
1945
 
        my $r = shift;
1946
 
        if (my $c = revdb_get($REVDB, $r)) {
1947
 
                croak "$r = $c already exists! Why are we refetching it?";
1948
 
        }
1949
 
}
1950
 
 
1951
 
sub trees_eq {
1952
 
        my ($x, $y) = @_;
1953
 
        my @x = safe_qx('git-cat-file','commit',$x);
1954
 
        my @y = safe_qx('git-cat-file','commit',$y);
1955
 
        if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/
1956
 
                                || $y[0] !~ /^tree $sha1\n$/) {
1957
 
                print STDERR "Trees not equal: $y[0] != $x[0]\n";
1958
 
                return 0
1959
 
        }
1960
 
        return 1;
1961
 
}
1962
 
 
1963
 
sub git_commit {
1964
 
        my ($log_msg, @parents) = @_;
1965
 
        assert_revision_unknown($log_msg->{revision});
1966
 
        map_tree_joins() if (@_branch_from && !%tree_map);
1967
 
 
1968
 
        my (@tmp_parents, @exec_parents, %seen_parent);
1969
 
        if (my $lparents = $log_msg->{parents}) {
1970
 
                @tmp_parents = @$lparents
1971
 
        }
1972
 
        # commit parents can be conditionally bound to a particular
1973
 
        # svn revision via: "svn_revno=commit_sha1", filter them out here:
1974
 
        foreach my $p (@parents) {
1975
 
                next unless defined $p;
1976
 
                if ($p =~ /^(\d+)=($sha1_short)$/o) {
1977
 
                        if ($1 == $log_msg->{revision}) {
1978
 
                                push @tmp_parents, $2;
1979
 
                        }
1980
 
                } else {
1981
 
                        push @tmp_parents, $p if $p =~ /$sha1_short/o;
1982
 
                }
1983
 
        }
1984
 
        my $tree = $log_msg->{tree};
1985
 
        if (!defined $tree) {
1986
 
                my $index = set_index($GIT_SVN_INDEX);
1987
 
                index_changes();
1988
 
                chomp($tree = `git-write-tree`);
1989
 
                croak $? if $?;
1990
 
                restore_index($index);
1991
 
        }
1992
 
 
1993
 
        # just in case we clobber the existing ref, we still want that ref
1994
 
        # as our parent:
1995
 
        if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) {
1996
 
                push @tmp_parents, $cur;
1997
 
        }
1998
 
 
1999
 
        if (exists $tree_map{$tree}) {
2000
 
                foreach my $p (@{$tree_map{$tree}}) {
2001
 
                        my $skip;
2002
 
                        foreach (@tmp_parents) {
2003
 
                                # see if a common parent is found
2004
 
                                my $mb = eval {
2005
 
                                        safe_qx('git-merge-base', $_, $p)
2006
 
                                };
2007
 
                                next if ($@ || $?);
2008
 
                                $skip = 1;
2009
 
                                last;
2010
 
                        }
2011
 
                        next if $skip;
2012
 
                        my ($url_p, $r_p, $uuid_p) = cmt_metadata($p);
2013
 
                        next if (($SVN_UUID eq $uuid_p) &&
2014
 
                                                ($log_msg->{revision} > $r_p));
2015
 
                        next if (defined $url_p && defined $SVN_URL &&
2016
 
                                                ($SVN_UUID eq $uuid_p) &&
2017
 
                                                ($url_p eq $SVN_URL));
2018
 
                        push @tmp_parents, $p;
2019
 
                }
2020
 
        }
2021
 
        foreach (@tmp_parents) {
2022
 
                next if $seen_parent{$_};
2023
 
                $seen_parent{$_} = 1;
2024
 
                push @exec_parents, $_;
2025
 
                # MAXPARENT is defined to 16 in commit-tree.c:
2026
 
                last if @exec_parents > 16;
2027
 
        }
2028
 
 
2029
 
        set_commit_env($log_msg);
2030
 
        my @exec = ('git-commit-tree', $tree);
2031
 
        push @exec, '-p', $_  foreach @exec_parents;
2032
 
        defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
2033
 
                                                                or croak $!;
2034
 
        print $msg_fh $log_msg->{msg} or croak $!;
2035
 
        unless ($_no_metadata) {
2036
 
                print $msg_fh "\ngit-svn-id: $SVN_URL\@$log_msg->{revision}",
2037
 
                                        " $SVN_UUID\n" or croak $!;
2038
 
        }
2039
 
        $msg_fh->flush == 0 or croak $!;
2040
 
        close $msg_fh or croak $!;
2041
 
        chomp(my $commit = do { local $/; <$out_fh> });
2042
 
        close $out_fh or croak $!;
2043
 
        waitpid $pid, 0;
2044
 
        croak $? if $?;
2045
 
        if ($commit !~ /^$sha1$/o) {
2046
 
                die "Failed to commit, invalid sha1: $commit\n";
2047
 
        }
2048
 
        sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
2049
 
        revdb_set($REVDB, $log_msg->{revision}, $commit);
2050
 
 
2051
 
        # this output is read via pipe, do not change:
2052
 
        print "r$log_msg->{revision} = $commit\n";
2053
 
        check_repack();
2054
 
        return $commit;
2055
 
}
2056
 
 
2057
 
sub check_repack {
2058
 
        if ($_repack && (--$_repack_nr == 0)) {
2059
 
                $_repack_nr = $_repack;
2060
 
                sys("git repack $_repack_flags");
2061
 
        }
2062
 
}
2063
 
 
2064
 
sub set_commit_env {
2065
 
        my ($log_msg) = @_;
2066
 
        my $author = $log_msg->{author};
2067
 
        if (!defined $author || length $author == 0) {
2068
 
                $author = '(no author)';
2069
 
        }
2070
 
        my ($name,$email) = defined $users{$author} ?  @{$users{$author}}
2071
 
                                : ($author,"$author\@$SVN_UUID");
2072
 
        $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
2073
 
        $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
2074
 
        $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
2075
 
}
2076
 
 
2077
 
sub apply_mod_line_blob {
2078
 
        my $m = shift;
2079
 
        if ($m->{mode_b} =~ /^120/) {
2080
 
                blob_to_symlink($m->{sha1_b}, $m->{file_b});
2081
 
        } else {
2082
 
                blob_to_file($m->{sha1_b}, $m->{file_b});
2083
 
        }
2084
 
}
2085
 
 
2086
 
sub blob_to_symlink {
2087
 
        my ($blob, $link) = @_;
2088
 
        defined $link or croak "\$link not defined!\n";
2089
 
        croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
2090
 
        if (-l $link || -f _) {
2091
 
                unlink $link or croak $!;
2092
 
        }
2093
 
 
2094
 
        my $dest = `git-cat-file blob $blob`; # no newline, so no chomp
2095
 
        symlink $dest, $link or croak $!;
2096
 
}
2097
 
 
2098
 
sub blob_to_file {
2099
 
        my ($blob, $file) = @_;
2100
 
        defined $file or croak "\$file not defined!\n";
2101
 
        croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
2102
 
        if (-l $file || -f _) {
2103
 
                unlink $file or croak $!;
2104
 
        }
2105
 
 
2106
 
        open my $blob_fh, '>', $file or croak "$!: $file\n";
2107
 
        my $pid = fork;
2108
 
        defined $pid or croak $!;
2109
 
 
2110
 
        if ($pid == 0) {
2111
 
                open STDOUT, '>&', $blob_fh or croak $!;
2112
 
                exec('git-cat-file','blob',$blob) or croak $!;
2113
 
        }
2114
 
        waitpid $pid, 0;
2115
 
        croak $? if $?;
2116
 
 
2117
 
        close $blob_fh or croak $!;
2118
 
}
2119
 
 
2120
 
sub safe_qx {
2121
 
        my $pid = open my $child, '-|';
2122
 
        defined $pid or croak $!;
2123
 
        if ($pid == 0) {
2124
 
                exec(@_) or croak $!;
2125
 
        }
2126
 
        my @ret = (<$child>);
2127
 
        close $child or croak $?;
2128
 
        die $? if $?; # just in case close didn't error out
2129
 
        return wantarray ? @ret : join('',@ret);
2130
 
}
2131
 
 
2132
 
sub svn_compat_check {
2133
 
        if ($_follow_parent) {
2134
 
                print STDERR 'E: --follow-parent functionality is only ',
2135
 
                                "available when SVN libraries are used\n";
2136
 
                exit 1;
2137
 
        }
2138
 
        my @co_help = safe_qx(qw(svn co -h));
2139
 
        unless (grep /ignore-externals/,@co_help) {
2140
 
                print STDERR "W: Installed svn version does not support ",
2141
 
                                "--ignore-externals\n";
2142
 
                $_no_ignore_ext = 1;
2143
 
        }
2144
 
        if (grep /usage: checkout URL\[\@REV\]/,@co_help) {
2145
 
                $_svn_co_url_revs = 1;
2146
 
        }
2147
 
        if (grep /\[TARGET\[\@REV\]\.\.\.\]/, `svn propget -h`) {
2148
 
                $_svn_pg_peg_revs = 1;
2149
 
        }
2150
 
 
2151
 
        # I really, really hope nobody hits this...
2152
 
        unless (grep /stop-on-copy/, (safe_qx(qw(svn log -h)))) {
2153
 
                print STDERR <<'';
2154
 
W: The installed svn version does not support the --stop-on-copy flag in
2155
 
   the log command.
2156
 
   Lets hope the directory you're tracking is not a branch or tag
2157
 
   and was never moved within the repository...
2158
 
 
2159
 
                $_no_stop_copy = 1;
2160
 
        }
2161
 
}
2162
 
 
2163
 
# *sigh*, new versions of svn won't honor -r<rev> without URL@<rev>,
2164
 
# (and they won't honor URL@<rev> without -r<rev>, too!)
2165
 
sub svn_cmd_checkout {
2166
 
        my ($url, $rev, $dir) = @_;
2167
 
        my @cmd = ('svn','co', "-r$rev");
2168
 
        push @cmd, '--ignore-externals' unless $_no_ignore_ext;
2169
 
        $url .= "\@$rev" if $_svn_co_url_revs;
2170
 
        sys(@cmd, $url, $dir);
2171
 
}
2172
 
 
2173
 
sub check_upgrade_needed {
2174
 
        if (!-r $REVDB) {
2175
 
                -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
2176
 
                open my $fh, '>>',$REVDB or croak $!;
2177
 
                close $fh;
2178
 
        }
2179
 
        my $old = eval {
2180
 
                my $pid = open my $child, '-|';
2181
 
                defined $pid or croak $!;
2182
 
                if ($pid == 0) {
2183
 
                        close STDERR;
2184
 
                        exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $!;
2185
 
                }
2186
 
                my @ret = (<$child>);
2187
 
                close $child or croak $?;
2188
 
                die $? if $?; # just in case close didn't error out
2189
 
                return wantarray ? @ret : join('',@ret);
2190
 
        };
2191
 
        return unless $old;
2192
 
        my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
2193
 
        if ($@ || !$head) {
2194
 
                print STDERR "Please run: $0 rebuild --upgrade\n";
2195
 
                exit 1;
2196
 
        }
2197
 
}
2198
 
 
2199
 
# fills %tree_map with a reverse mapping of trees to commits.  Useful
2200
 
# for finding parents to commit on.
2201
 
sub map_tree_joins {
2202
 
        my %seen;
2203
 
        foreach my $br (@_branch_from) {
2204
 
                my $pid = open my $pipe, '-|';
2205
 
                defined $pid or croak $!;
2206
 
                if ($pid == 0) {
2207
 
                        exec(qw(git-rev-list --topo-order --pretty=raw), $br)
2208
 
                                                                or croak $!;
2209
 
                }
2210
 
                while (<$pipe>) {
2211
 
                        if (/^commit ($sha1)$/o) {
2212
 
                                my $commit = $1;
2213
 
 
2214
 
                                # if we've seen a commit,
2215
 
                                # we've seen its parents
2216
 
                                last if $seen{$commit};
2217
 
                                my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
2218
 
                                unless (defined $tree) {
2219
 
                                        die "Failed to parse commit $commit\n";
2220
 
                                }
2221
 
                                push @{$tree_map{$tree}}, $commit;
2222
 
                                $seen{$commit} = 1;
2223
 
                        }
2224
 
                }
2225
 
                close $pipe; # we could be breaking the pipe early
2226
 
        }
2227
 
}
2228
 
 
2229
 
sub load_all_refs {
2230
 
        if (@_branch_from) {
2231
 
                print STDERR '--branch|-b parameters are ignored when ',
2232
 
                        "--branch-all-refs|-B is passed\n";
2233
 
        }
2234
 
 
2235
 
        # don't worry about rev-list on non-commit objects/tags,
2236
 
        # it shouldn't blow up if a ref is a blob or tree...
2237
 
        chomp(@_branch_from = `git-rev-parse --symbolic --all`);
2238
 
}
2239
 
 
2240
708
# '<svn username> = real-name <email address>' mapping based on git-svnimport:
2241
709
sub load_authors {
2242
710
        open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
 
711
        my $log = $cmd eq 'log';
2243
712
        while (<$authors>) {
2244
713
                chomp;
2245
714
                next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
2246
715
                my ($user, $name, $email) = ($1, $2, $3);
2247
 
                $users{$user} = [$name, $email];
2248
 
        }
2249
 
        close $authors or croak $!;
2250
 
}
2251
 
 
2252
 
sub rload_authors {
2253
 
        open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
2254
 
        while (<$authors>) {
2255
 
                chomp;
2256
 
                next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
2257
 
                my ($user, $name, $email) = ($1, $2, $3);
2258
 
                $rusers{"$name <$email>"} = $user;
2259
 
        }
2260
 
        close $authors or croak $!;
2261
 
}
2262
 
 
2263
 
sub svn_propget_base {
2264
 
        my ($p, $f) = @_;
2265
 
        $f .= '@BASE' if $_svn_pg_peg_revs;
2266
 
        return safe_qx(qw/svn propget/, $p, $f);
2267
 
}
2268
 
 
2269
 
sub git_svn_each {
2270
 
        my $sub = shift;
2271
 
        foreach (`git-rev-parse --symbolic --all`) {
2272
 
                next unless s#^refs/remotes/##;
2273
 
                chomp $_;
2274
 
                next unless -f "$GIT_DIR/svn/$_/info/url";
2275
 
                &$sub($_);
2276
 
        }
2277
 
}
2278
 
 
2279
 
sub migrate_revdb {
2280
 
        git_svn_each(sub {
2281
 
                my $id = shift;
2282
 
                defined(my $pid = fork) or croak $!;
2283
 
                if (!$pid) {
2284
 
                        $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
2285
 
                        init_vars();
2286
 
                        exit 0 if -r $REVDB;
2287
 
                        print "Upgrading svn => git mapping...\n";
2288
 
                        -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
2289
 
                        open my $fh, '>>',$REVDB or croak $!;
2290
 
                        close $fh;
2291
 
                        rebuild();
2292
 
                        print "Done upgrading. You may now delete the ",
2293
 
                                "deprecated $GIT_SVN_DIR/revs directory\n";
2294
 
                        exit 0;
2295
 
                }
2296
 
                waitpid $pid, 0;
2297
 
                croak $? if $?;
2298
 
        });
2299
 
}
2300
 
 
2301
 
sub migration_check {
2302
 
        migrate_revdb() unless (-e $REVDB);
2303
 
        return if (-d "$GIT_DIR/svn" || !-d $GIT_DIR);
2304
 
        print "Upgrading repository...\n";
2305
 
        unless (-d "$GIT_DIR/svn") {
2306
 
                mkdir "$GIT_DIR/svn" or croak $!;
2307
 
        }
2308
 
        print "Data from a previous version of git-svn exists, but\n\t",
2309
 
                                "$GIT_SVN_DIR\n\t(required for this version ",
2310
 
                                "($VERSION) of git-svn) does not.\n";
2311
 
 
2312
 
        foreach my $x (`git-rev-parse --symbolic --all`) {
2313
 
                next unless $x =~ s#^refs/remotes/##;
2314
 
                chomp $x;
2315
 
                next unless -f "$GIT_DIR/$x/info/url";
2316
 
                my $u = eval { file_to_s("$GIT_DIR/$x/info/url") };
2317
 
                next unless $u;
2318
 
                my $dn = dirname("$GIT_DIR/svn/$x");
2319
 
                mkpath([$dn]) unless -d $dn;
2320
 
                rename "$GIT_DIR/$x", "$GIT_DIR/svn/$x" or croak "$!: $x";
2321
 
        }
2322
 
        migrate_revdb() if (-d $GIT_SVN_DIR && !-w $REVDB);
2323
 
        print "Done upgrading.\n";
2324
 
}
2325
 
 
2326
 
sub find_rev_before {
2327
 
        my ($r, $id, $eq_ok) = @_;
2328
 
        my $f = "$GIT_DIR/svn/$id/.rev_db";
2329
 
        return (undef,undef) unless -r $f;
2330
 
        --$r unless $eq_ok;
2331
 
        while ($r > 0) {
2332
 
                if (my $c = revdb_get($f, $r)) {
2333
 
                        return ($r, $c);
2334
 
                }
2335
 
                --$r;
2336
 
        }
2337
 
        return (undef, undef);
2338
 
}
2339
 
 
2340
 
sub init_vars {
2341
 
        $GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
2342
 
        $GIT_SVN_DIR = "$GIT_DIR/svn/$GIT_SVN";
2343
 
        $REVDB = "$GIT_SVN_DIR/.rev_db";
2344
 
        $GIT_SVN_INDEX = "$GIT_SVN_DIR/index";
2345
 
        $SVN_URL = undef;
2346
 
        $SVN_WC = "$GIT_SVN_DIR/tree";
2347
 
        %tree_map = ();
2348
 
}
2349
 
 
2350
 
# convert GetOpt::Long specs for use by git-repo-config
 
716
                if ($log) {
 
717
                        $Git::SVN::Log::rusers{"$name <$email>"} = $user;
 
718
                } else {
 
719
                        $users{$user} = [$name, $email];
 
720
                }
 
721
        }
 
722
        close $authors or croak $!;
 
723
}
 
724
 
 
725
# convert GetOpt::Long specs for use by git-config
2351
726
sub read_repo_config {
2352
 
        return unless -d $GIT_DIR;
 
727
        return unless -d $ENV{GIT_DIR};
2353
728
        my $opts = shift;
 
729
        my @config_only;
2354
730
        foreach my $o (keys %$opts) {
 
731
                # if we have mixedCase and a long option-only, then
 
732
                # it's a config-only variable that we don't need for
 
733
                # the command-line.
 
734
                push @config_only, $o if ($o =~ /[A-Z]/ && $o =~ /^[a-z]+$/i);
2355
735
                my $v = $opts->{$o};
2356
 
                my ($key) = ($o =~ /^([a-z\-]+)/);
 
736
                my ($key) = ($o =~ /^([a-zA-Z\-]+)/);
2357
737
                $key =~ s/-//g;
2358
 
                my $arg = 'git-repo-config';
 
738
                my $arg = 'git-config';
2359
739
                $arg .= ' --int' if ($o =~ /[:=]i$/);
2360
740
                $arg .= ' --bool' if ($o !~ /[:=][sfi]$/);
2361
741
                if (ref $v eq 'ARRAY') {
2363
743
                        @$v = @tmp if @tmp;
2364
744
                } else {
2365
745
                        chomp(my $tmp = `$arg --get svn.$key`);
2366
 
                        if ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) {
 
746
                        if ($tmp && !($arg =~ / --bool/ && $tmp eq 'false')) {
2367
747
                                $$v = $tmp;
2368
748
                        }
2369
749
                }
2370
750
        }
2371
 
}
2372
 
 
2373
 
sub set_default_vals {
2374
 
        if (defined $_repack) {
2375
 
                $_repack = 1000 if ($_repack <= 0);
2376
 
                $_repack_nr = $_repack;
2377
 
                $_repack_flags ||= '-d';
2378
 
        }
2379
 
}
2380
 
 
2381
 
sub read_grafts {
2382
 
        my $gr_file = shift;
2383
 
        my ($grafts, $comments) = ({}, {});
2384
 
        if (open my $fh, '<', $gr_file) {
2385
 
                my @tmp;
2386
 
                while (<$fh>) {
2387
 
                        if (/^($sha1)\s+/) {
2388
 
                                my $c = $1;
2389
 
                                if (@tmp) {
2390
 
                                        @{$comments->{$c}} = @tmp;
2391
 
                                        @tmp = ();
2392
 
                                }
2393
 
                                foreach my $p (split /\s+/, $_) {
2394
 
                                        $grafts->{$c}->{$p} = 1;
2395
 
                                }
2396
 
                        } else {
2397
 
                                push @tmp, $_;
2398
 
                        }
2399
 
                }
2400
 
                close $fh or croak $!;
2401
 
                @{$comments->{'END'}} = @tmp if @tmp;
2402
 
        }
2403
 
        return ($grafts, $comments);
2404
 
}
2405
 
 
2406
 
sub write_grafts {
2407
 
        my ($grafts, $comments, $gr_file) = @_;
2408
 
 
2409
 
        open my $fh, '>', $gr_file or croak $!;
2410
 
        foreach my $c (sort keys %$grafts) {
2411
 
                if ($comments->{$c}) {
2412
 
                        print $fh $_ foreach @{$comments->{$c}};
2413
 
                }
2414
 
                my $p = $grafts->{$c};
2415
 
                my %x; # real parents
2416
 
                delete $p->{$c}; # commits are not self-reproducing...
2417
 
                my $pid = open my $ch, '-|';
2418
 
                defined $pid or croak $!;
2419
 
                if (!$pid) {
2420
 
                        exec(qw/git-cat-file commit/, $c) or croak $!;
2421
 
                }
2422
 
                while (<$ch>) {
2423
 
                        if (/^parent ($sha1)/) {
2424
 
                                $x{$1} = $p->{$1} = 1;
2425
 
                        } else {
2426
 
                                last unless /^\S/;
2427
 
                        }
2428
 
                }
2429
 
                close $ch; # breaking the pipe
2430
 
 
2431
 
                # if real parents are the only ones in the grafts, drop it
2432
 
                next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
2433
 
 
2434
 
                my (@ip, @jp, $mb);
2435
 
                my %del = %x;
2436
 
                @ip = @jp = keys %$p;
2437
 
                foreach my $i (@ip) {
2438
 
                        next if $del{$i} || $p->{$i} == 2;
2439
 
                        foreach my $j (@jp) {
2440
 
                                next if $i eq $j || $del{$j} || $p->{$j} == 2;
2441
 
                                $mb = eval { safe_qx('git-merge-base',$i,$j) };
2442
 
                                next unless $mb;
2443
 
                                chomp $mb;
2444
 
                                next if $x{$mb};
2445
 
                                if ($mb eq $j) {
2446
 
                                        delete $p->{$i};
2447
 
                                        $del{$i} = 1;
2448
 
                                } elsif ($mb eq $i) {
2449
 
                                        delete $p->{$j};
2450
 
                                        $del{$j} = 1;
2451
 
                                }
2452
 
                        }
2453
 
                }
2454
 
 
2455
 
                # if real parents are the only ones in the grafts, drop it
2456
 
                next if join(' ',sort keys %$p) eq join(' ',sort keys %x);
2457
 
 
2458
 
                print $fh $c, ' ', join(' ', sort keys %$p),"\n";
2459
 
        }
2460
 
        if ($comments->{'END'}) {
2461
 
                print $fh $_ foreach @{$comments->{'END'}};
2462
 
        }
2463
 
        close $fh or croak $!;
2464
 
}
2465
 
 
2466
 
sub read_url_paths_all {
2467
 
        my ($l_map, $pfx, $p) = @_;
2468
 
        my @dir;
2469
 
        foreach (<$p/*>) {
2470
 
                if (-r "$_/info/url") {
2471
 
                        $pfx .= '/' if $pfx && $pfx !~ m!/$!;
2472
 
                        my $id = $pfx . basename $_;
2473
 
                        my $url = file_to_s("$_/info/url");
2474
 
                        my ($u, $p) = repo_path_split($url);
2475
 
                        $l_map->{$u}->{$p} = $id;
2476
 
                } elsif (-d $_) {
2477
 
                        push @dir, $_;
2478
 
                }
2479
 
        }
2480
 
        foreach (@dir) {
2481
 
                my $x = $_;
2482
 
                $x =~ s!^\Q$GIT_DIR\E/svn/!!o;
2483
 
                read_url_paths_all($l_map, $x, $_);
2484
 
        }
2485
 
}
2486
 
 
2487
 
# this one only gets ids that have been imported, not new ones
2488
 
sub read_url_paths {
2489
 
        my $l_map = {};
2490
 
        git_svn_each(sub { my $x = shift;
2491
 
                        my $url = file_to_s("$GIT_DIR/svn/$x/info/url");
2492
 
                        my ($u, $p) = repo_path_split($url);
2493
 
                        $l_map->{$u}->{$p} = $x;
2494
 
                        });
2495
 
        return $l_map;
 
751
        delete @$opts{@config_only} if @config_only;
2496
752
}
2497
753
 
2498
754
sub extract_metadata {
2509
765
 
2510
766
sub cmt_metadata {
2511
767
        return extract_metadata((grep(/^git-svn-id: /,
2512
 
                safe_qx(qw/git-cat-file commit/, shift)))[-1]);
 
768
                command(qw/cat-file commit/, shift)))[-1]);
2513
769
}
2514
770
 
2515
 
sub get_commit_time {
2516
 
        my $cmt = shift;
2517
 
        defined(my $pid = open my $fh, '-|') or croak $!;
2518
 
        if (!$pid) {
2519
 
                exec qw/git-rev-list --pretty=raw -n1/, $cmt or croak $!;
2520
 
        }
 
771
sub working_head_info {
 
772
        my ($head, $refs) = @_;
 
773
        my ($fh, $ctx) = command_output_pipe('rev-list', $head);
2521
774
        while (<$fh>) {
2522
 
                /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
2523
 
                my ($s, $tz) = ($1, $2);
2524
 
                if ($tz =~ s/^\+//) {
2525
 
                        $s += tz_to_s_offset($tz);
2526
 
                } elsif ($tz =~ s/^\-//) {
2527
 
                        $s -= tz_to_s_offset($tz);
2528
 
                }
2529
 
                close $fh;
2530
 
                return $s;
2531
 
        }
2532
 
        die "Can't get commit time for commit: $cmt\n";
2533
 
}
2534
 
 
2535
 
sub tz_to_s_offset {
2536
 
        my ($tz) = @_;
2537
 
        $tz =~ s/(\d\d)$//;
2538
 
        return ($1 * 60) + ($tz * 3600);
2539
 
}
2540
 
 
2541
 
sub setup_pager { # translated to Perl from pager.c
2542
 
        return unless (-t *STDOUT);
2543
 
        my $pager = $ENV{PAGER};
2544
 
        if (!defined $pager) {
2545
 
                $pager = 'less';
2546
 
        } elsif (length $pager == 0 || $pager eq 'cat') {
2547
 
                return;
2548
 
        }
2549
 
        pipe my $rfd, my $wfd or return;
2550
 
        defined(my $pid = fork) or croak $!;
2551
 
        if (!$pid) {
2552
 
                open STDOUT, '>&', $wfd or croak $!;
2553
 
                return;
2554
 
        }
2555
 
        open STDIN, '<&', $rfd or croak $!;
2556
 
        $ENV{LESS} ||= '-S';
2557
 
        exec $pager or croak "Can't run pager: $!\n";;
2558
 
}
2559
 
 
2560
 
sub get_author_info {
2561
 
        my ($dest, $author, $t, $tz) = @_;
2562
 
        $author =~ s/(?:^\s*|\s*$)//g;
2563
 
        $dest->{a_raw} = $author;
2564
 
        my $_a;
2565
 
        if ($_authors) {
2566
 
                $_a = $rusers{$author} || undef;
2567
 
        }
2568
 
        if (!$_a) {
2569
 
                ($_a) = ($author =~ /<([^>]+)\@[^>]+>$/);
2570
 
        }
2571
 
        $dest->{t} = $t;
2572
 
        $dest->{tz} = $tz;
2573
 
        $dest->{a} = $_a;
2574
 
        # Date::Parse isn't in the standard Perl distro :(
2575
 
        if ($tz =~ s/^\+//) {
2576
 
                $t += tz_to_s_offset($tz);
2577
 
        } elsif ($tz =~ s/^\-//) {
2578
 
                $t -= tz_to_s_offset($tz);
2579
 
        }
2580
 
        $dest->{t_utc} = $t;
2581
 
}
2582
 
 
2583
 
sub process_commit {
2584
 
        my ($c, $r_min, $r_max, $defer) = @_;
2585
 
        if (defined $r_min && defined $r_max) {
2586
 
                if ($r_min == $c->{r} && $r_min == $r_max) {
2587
 
                        show_commit($c);
2588
 
                        return 0;
2589
 
                }
2590
 
                return 1 if $r_min == $r_max;
2591
 
                if ($r_min < $r_max) {
2592
 
                        # we need to reverse the print order
2593
 
                        return 0 if (defined $_limit && --$_limit < 0);
2594
 
                        push @$defer, $c;
2595
 
                        return 1;
2596
 
                }
2597
 
                if ($r_min != $r_max) {
2598
 
                        return 1 if ($r_min < $c->{r});
2599
 
                        return 1 if ($r_max > $c->{r});
2600
 
                }
2601
 
        }
2602
 
        return 0 if (defined $_limit && --$_limit < 0);
2603
 
        show_commit($c);
2604
 
        return 1;
2605
 
}
2606
 
 
2607
 
sub show_commit {
2608
 
        my $c = shift;
2609
 
        if ($_oneline) {
2610
 
                my $x = "\n";
2611
 
                if (my $l = $c->{l}) {
2612
 
                        while ($l->[0] =~ /^\s*$/) { shift @$l }
2613
 
                        $x = $l->[0];
2614
 
                }
2615
 
                $_l_fmt ||= 'A' . length($c->{r});
2616
 
                print 'r',pack($_l_fmt, $c->{r}),' | ';
2617
 
                print "$c->{c} | " if $_show_commit;
2618
 
                print $x;
2619
 
        } else {
2620
 
                show_commit_normal($c);
2621
 
        }
2622
 
}
2623
 
 
2624
 
sub show_commit_changed_paths {
2625
 
        my ($c) = @_;
2626
 
        return unless $c->{changed};
2627
 
        print "Changed paths:\n", @{$c->{changed}};
2628
 
}
2629
 
 
2630
 
sub show_commit_normal {
2631
 
        my ($c) = @_;
2632
 
        print '-' x72, "\nr$c->{r} | ";
2633
 
        print "$c->{c} | " if $_show_commit;
2634
 
        print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
2635
 
                                 localtime($c->{t_utc})), ' | ';
2636
 
        my $nr_line = 0;
2637
 
 
2638
 
        if (my $l = $c->{l}) {
2639
 
                while ($l->[$#$l] eq "\n" && $#$l > 0
2640
 
                                          && $l->[($#$l - 1)] eq "\n") {
2641
 
                        pop @$l;
2642
 
                }
2643
 
                $nr_line = scalar @$l;
2644
 
                if (!$nr_line) {
2645
 
                        print "1 line\n\n\n";
2646
 
                } else {
2647
 
                        if ($nr_line == 1) {
2648
 
                                $nr_line = '1 line';
2649
 
                        } else {
2650
 
                                $nr_line .= ' lines';
2651
 
                        }
2652
 
                        print $nr_line, "\n";
2653
 
                        show_commit_changed_paths($c);
2654
 
                        print "\n";
2655
 
                        print $_ foreach @$l;
2656
 
                }
2657
 
        } else {
2658
 
                print "1 line\n";
2659
 
                show_commit_changed_paths($c);
2660
 
                print "\n";
2661
 
 
2662
 
        }
2663
 
        foreach my $x (qw/raw diff/) {
2664
 
                if ($c->{$x}) {
2665
 
                        print "\n";
2666
 
                        print $_ foreach @{$c->{$x}}
2667
 
                }
2668
 
        }
2669
 
}
2670
 
 
2671
 
sub libsvn_load {
2672
 
        return unless $_use_lib;
2673
 
        $_use_lib = eval {
2674
 
                require SVN::Core;
2675
 
                if ($SVN::Core::VERSION lt '1.1.0') {
2676
 
                        die "Need SVN::Core 1.1.0 or better ",
2677
 
                                        "(got $SVN::Core::VERSION) ",
2678
 
                                        "Falling back to command-line svn\n";
2679
 
                }
2680
 
                require SVN::Ra;
2681
 
                require SVN::Delta;
2682
 
                push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
2683
 
                my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
2684
 
                                        $SVN::Node::dir.$SVN::Node::unknown.
2685
 
                                        $SVN::Node::none.$SVN::Node::file.
2686
 
                                        $SVN::Node::dir.$SVN::Node::unknown;
2687
 
                1;
2688
 
        };
2689
 
}
2690
 
 
2691
 
sub libsvn_connect {
2692
 
        my ($url) = @_;
2693
 
        my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),
2694
 
                          SVN::Client::get_ssl_server_trust_file_provider(),
2695
 
                          SVN::Client::get_username_provider()]);
2696
 
        my $s = eval { SVN::Ra->new(url => $url, auth => $auth) };
2697
 
        return $s;
2698
 
}
2699
 
 
2700
 
sub libsvn_get_file {
2701
 
        my ($gui, $f, $rev, $chg) = @_;
2702
 
        my $p = $f;
2703
 
        if (length $SVN_PATH > 0) {
2704
 
                return unless ($p =~ s#^\Q$SVN_PATH\E/##);
2705
 
        }
2706
 
        print "\t$chg\t$f\n" unless $_q;
2707
 
 
2708
 
        my ($hash, $pid, $in, $out);
2709
 
        my $pool = SVN::Pool->new;
2710
 
        defined($pid = open3($in, $out, '>&STDERR',
2711
 
                                qw/git-hash-object -w --stdin/)) or croak $!;
2712
 
        # redirect STDOUT for SVN 1.1.x compatibility
2713
 
        open my $stdout, '>&', \*STDOUT or croak $!;
2714
 
        open STDOUT, '>&', $in or croak $!;
2715
 
        my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool);
2716
 
        $in->flush == 0 or croak $!;
2717
 
        open STDOUT, '>&', $stdout or croak $!;
2718
 
        close $in or croak $!;
2719
 
        close $stdout or croak $!;
2720
 
        $pool->clear;
2721
 
        chomp($hash = do { local $/; <$out> });
2722
 
        close $out or croak $!;
2723
 
        waitpid $pid, 0;
2724
 
        $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
2725
 
 
2726
 
        my $mode = exists $props->{'svn:executable'} ? '100755' : '100644';
2727
 
        if (exists $props->{'svn:special'}) {
2728
 
                $mode = '120000';
2729
 
                my $link = `git-cat-file blob $hash`;
2730
 
                $link =~ s/^link // or die "svn:special file with contents: <",
2731
 
                                                $link, "> is not understood\n";
2732
 
                defined($pid = open3($in, $out, '>&STDERR',
2733
 
                                qw/git-hash-object -w --stdin/)) or croak $!;
2734
 
                print $in $link;
2735
 
                $in->flush == 0 or croak $!;
2736
 
                close $in or croak $!;
2737
 
                chomp($hash = do { local $/; <$out> });
2738
 
                close $out or croak $!;
2739
 
                waitpid $pid, 0;
2740
 
                $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
2741
 
        }
2742
 
        print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!;
2743
 
}
2744
 
 
2745
 
sub libsvn_log_entry {
2746
 
        my ($rev, $author, $date, $msg, $parents) = @_;
2747
 
        my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
2748
 
                                         (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x)
2749
 
                                or die "Unable to parse date: $date\n";
2750
 
        if (defined $_authors && ! defined $users{$author}) {
2751
 
                die "Author: $author not defined in $_authors file\n";
2752
 
        }
2753
 
        $msg = '' if ($rev == 0 && !defined $msg);
2754
 
        return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
2755
 
                author => $author, msg => $msg."\n", parents => $parents || [] }
2756
 
}
2757
 
 
2758
 
sub process_rm {
2759
 
        my ($gui, $last_commit, $f) = @_;
2760
 
        $f =~ s#^\Q$SVN_PATH\E/?## or return;
2761
 
        # remove entire directories.
2762
 
        if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
2763
 
                defined(my $pid = open my $ls, '-|') or croak $!;
2764
 
                if (!$pid) {
2765
 
                        exec(qw/git-ls-tree -r --name-only -z/,
2766
 
                                $last_commit,'--',$f) or croak $!;
2767
 
                }
2768
 
                local $/ = "\0";
2769
 
                while (<$ls>) {
2770
 
                        print $gui '0 ',0 x 40,"\t",$_ or croak $!;
2771
 
                }
2772
 
                close $ls or croak $?;
2773
 
        } else {
2774
 
                print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
2775
 
        }
2776
 
}
2777
 
 
2778
 
sub libsvn_fetch {
2779
 
        my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
2780
 
        open my $gui, '| git-update-index -z --index-info' or croak $!;
2781
 
        my %amr;
2782
 
        foreach my $f (keys %$paths) {
2783
 
                my $m = $paths->{$f}->action();
2784
 
                $f =~ s#^/+##;
2785
 
                if ($m =~ /^[DR]$/) {
2786
 
                        print "\t$m\t$f\n" unless $_q;
2787
 
                        process_rm($gui, $last_commit, $f);
2788
 
                        next if $m eq 'D';
2789
 
                        # 'R' can be file replacements, too, right?
2790
 
                }
2791
 
                my $pool = SVN::Pool->new;
2792
 
                my $t = $SVN->check_path($f, $rev, $pool);
2793
 
                if ($t == $SVN::Node::file) {
2794
 
                        if ($m =~ /^[AMR]$/) {
2795
 
                                $amr{$f} = $m;
2796
 
                        } else {
2797
 
                                die "Unrecognized action: $m, ($f r$rev)\n";
2798
 
                        }
2799
 
                } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) {
2800
 
                        my @traversed = ();
2801
 
                        libsvn_traverse($gui, '', $f, $rev, \@traversed);
2802
 
                        foreach (@traversed) {
2803
 
                                $amr{$_} = $m;
2804
 
                        }
2805
 
                }
2806
 
                $pool->clear;
2807
 
        }
2808
 
        foreach (keys %amr) {
2809
 
                libsvn_get_file($gui, $_, $rev, $amr{$_});
2810
 
        }
2811
 
        close $gui or croak $?;
2812
 
        return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
2813
 
}
2814
 
 
2815
 
sub svn_grab_base_rev {
2816
 
        defined(my $pid = open my $fh, '-|') or croak $!;
2817
 
        if (!$pid) {
2818
 
                open my $null, '>', '/dev/null' or croak $!;
2819
 
                open STDERR, '>&', $null or croak $!;
2820
 
                exec qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0"
2821
 
                                                                or croak $!;
2822
 
        }
2823
 
        chomp(my $c = do { local $/; <$fh> });
2824
 
        close $fh;
2825
 
        if (defined $c && length $c) {
2826
 
                my ($url, $rev, $uuid) = cmt_metadata($c);
2827
 
                return ($rev, $c) if defined $rev;
2828
 
        }
2829
 
        if ($_no_metadata) {
2830
 
                my $offset = -41; # from tail
2831
 
                my $rl;
2832
 
                open my $fh, '<', $REVDB or
2833
 
                        die "--no-metadata specified and $REVDB not readable\n";
2834
 
                seek $fh, $offset, 2;
2835
 
                $rl = readline $fh;
2836
 
                defined $rl or return (undef, undef);
2837
 
                chomp $rl;
2838
 
                while ($c ne $rl && tell $fh != 0) {
2839
 
                        $offset -= 41;
2840
 
                        seek $fh, $offset, 2;
2841
 
                        $rl = readline $fh;
2842
 
                        defined $rl or return (undef, undef);
2843
 
                        chomp $rl;
2844
 
                }
2845
 
                my $rev = tell $fh;
2846
 
                croak $! if ($rev < -1);
2847
 
                $rev =  ($rev - 41) / 41;
2848
 
                close $fh or croak $!;
2849
 
                return ($rev, $c);
2850
 
        }
2851
 
        return (undef, undef);
2852
 
}
2853
 
 
2854
 
sub libsvn_parse_revision {
2855
 
        my $base = shift;
2856
 
        my $head = $SVN->get_latest_revnum();
2857
 
        if (!defined $_revision || $_revision eq 'BASE:HEAD') {
2858
 
                return ($base + 1, $head) if (defined $base);
2859
 
                return (0, $head);
2860
 
        }
2861
 
        return ($1, $2) if ($_revision =~ /^(\d+):(\d+)$/);
2862
 
        return ($_revision, $_revision) if ($_revision =~ /^\d+$/);
2863
 
        if ($_revision =~ /^BASE:(\d+)$/) {
2864
 
                return ($base + 1, $1) if (defined $base);
2865
 
                return (0, $head);
2866
 
        }
2867
 
        return ($1, $head) if ($_revision =~ /^(\d+):HEAD$/);
2868
 
        die "revision argument: $_revision not understood by git-svn\n",
2869
 
                "Try using the command-line svn client instead\n";
2870
 
}
2871
 
 
2872
 
sub libsvn_traverse {
2873
 
        my ($gui, $pfx, $path, $rev, $files) = @_;
2874
 
        my $cwd = "$pfx/$path";
2875
 
        my $pool = SVN::Pool->new;
2876
 
        $cwd =~ s#^/+##g;
2877
 
        my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool);
2878
 
        foreach my $d (keys %$dirent) {
2879
 
                my $t = $dirent->{$d}->kind;
2880
 
                if ($t == $SVN::Node::dir) {
2881
 
                        libsvn_traverse($gui, $cwd, $d, $rev, $files);
2882
 
                } elsif ($t == $SVN::Node::file) {
2883
 
                        my $file = "$cwd/$d";
2884
 
                        if (defined $files) {
2885
 
                                push @$files, $file;
2886
 
                        } else {
2887
 
                                libsvn_get_file($gui, $file, $rev, 'A');
2888
 
                        }
2889
 
                }
2890
 
        }
2891
 
        $pool->clear;
2892
 
}
2893
 
 
2894
 
sub libsvn_traverse_ignore {
2895
 
        my ($fh, $path, $r) = @_;
 
775
                chomp;
 
776
                my ($url, $rev, $uuid) = cmt_metadata($_);
 
777
                if (defined $url && defined $rev) {
 
778
                        if (my $gs = Git::SVN->find_by_url($url)) {
 
779
                                my $c = $gs->rev_db_get($rev);
 
780
                                if ($c && $c eq $_) {
 
781
                                        close $fh; # break the pipe
 
782
                                        return ($url, $rev, $uuid, $gs);
 
783
                                }
 
784
                        }
 
785
                }
 
786
                unshift @$refs, $_ if $refs;
 
787
        }
 
788
        command_close_pipe($fh, $ctx);
 
789
        (undef, undef, undef, undef);
 
790
}
 
791
 
 
792
package Git::SVN;
 
793
use strict;
 
794
use warnings;
 
795
use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent
 
796
            $_repack $_repack_flags $_use_svm_props $_head
 
797
            $_use_svnsync_props $no_reuse_existing/;
 
798
use Carp qw/croak/;
 
799
use File::Path qw/mkpath/;
 
800
use File::Copy qw/copy/;
 
801
use IPC::Open3;
 
802
 
 
803
my $_repack_nr;
 
804
# properties that we do not log:
 
805
my %SKIP_PROP;
 
806
BEGIN {
 
807
        %SKIP_PROP = map { $_ => 1 } qw/svn:wc:ra_dav:version-url
 
808
                                        svn:special svn:executable
 
809
                                        svn:entry:committed-rev
 
810
                                        svn:entry:last-author
 
811
                                        svn:entry:uuid
 
812
                                        svn:entry:committed-date/;
 
813
 
 
814
        # some options are read globally, but can be overridden locally
 
815
        # per [svn-remote "..."] section.  Command-line options will *NOT*
 
816
        # override options set in an [svn-remote "..."] section
 
817
        my $e;
 
818
        foreach (qw/follow_parent no_metadata use_svm_props
 
819
                    use_svnsync_props/) {
 
820
                my $key = $_;
 
821
                $key =~ tr/_//d;
 
822
                $e .= "sub $_ {
 
823
                        my (\$self) = \@_;
 
824
                        return \$self->{-$_} if exists \$self->{-$_};
 
825
                        my \$k = \"svn-remote.\$self->{repo_id}\.$key\";
 
826
                        eval { command_oneline(qw/config --get/, \$k) };
 
827
                        if (\$@) {
 
828
                                \$self->{-$_} = \$Git::SVN::_$_;
 
829
                        } else {
 
830
                                my \$v = command_oneline(qw/config --bool/,\$k);
 
831
                                \$self->{-$_} = \$v eq 'false' ? 0 : 1;
 
832
                        }
 
833
                        return \$self->{-$_} }\n";
 
834
        }
 
835
        $e .= "1;\n";
 
836
        eval $e or die $@;
 
837
}
 
838
 
 
839
my %LOCKFILES;
 
840
END { unlink keys %LOCKFILES if %LOCKFILES }
 
841
 
 
842
sub resolve_local_globs {
 
843
        my ($url, $fetch, $glob_spec) = @_;
 
844
        return unless defined $glob_spec;
 
845
        my $ref = $glob_spec->{ref};
 
846
        my $path = $glob_spec->{path};
 
847
        foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) {
 
848
                next unless m#^refs/remotes/$ref->{regex}$#;
 
849
                my $p = $1;
 
850
                my $pathname = $path->full_path($p);
 
851
                my $refname = $ref->full_path($p);
 
852
                if (my $existing = $fetch->{$pathname}) {
 
853
                        if ($existing ne $refname) {
 
854
                                die "Refspec conflict:\n",
 
855
                                    "existing: refs/remotes/$existing\n",
 
856
                                    " globbed: refs/remotes/$refname\n";
 
857
                        }
 
858
                        my $u = (::cmt_metadata("refs/remotes/$refname"))[0];
 
859
                        $u =~ s!^\Q$url\E(/|$)!! or die
 
860
                          "refs/remotes/$refname: '$url' not found in '$u'\n";
 
861
                        if ($pathname ne $u) {
 
862
                                warn "W: Refspec glob conflict ",
 
863
                                     "(ref: refs/remotes/$refname):\n",
 
864
                                     "expected path: $pathname\n",
 
865
                                     "    real path: $u\n",
 
866
                                     "Continuing ahead with $u\n";
 
867
                                next;
 
868
                        }
 
869
                } else {
 
870
                        $fetch->{$pathname} = $refname;
 
871
                }
 
872
        }
 
873
}
 
874
 
 
875
sub parse_revision_argument {
 
876
        my ($base, $head) = @_;
 
877
        if (!defined $::_revision || $::_revision eq 'BASE:HEAD') {
 
878
                return ($base, $head);
 
879
        }
 
880
        return ($1, $2) if ($::_revision =~ /^(\d+):(\d+)$/);
 
881
        return ($::_revision, $::_revision) if ($::_revision =~ /^\d+$/);
 
882
        return ($head, $head) if ($::_revision eq 'HEAD');
 
883
        return ($base, $1) if ($::_revision =~ /^BASE:(\d+)$/);
 
884
        return ($1, $head) if ($::_revision =~ /^(\d+):HEAD$/);
 
885
        die "revision argument: $::_revision not understood by git-svn\n";
 
886
}
 
887
 
 
888
sub fetch_all {
 
889
        my ($repo_id, $remotes) = @_;
 
890
        if (ref $repo_id) {
 
891
                my $gs = $repo_id;
 
892
                $repo_id = undef;
 
893
                $repo_id = $gs->{repo_id};
 
894
        }
 
895
        $remotes ||= read_all_remotes();
 
896
        my $remote = $remotes->{$repo_id} or
 
897
                     die "[svn-remote \"$repo_id\"] unknown\n";
 
898
        my $fetch = $remote->{fetch};
 
899
        my $url = $remote->{url} or die "svn-remote.$repo_id.url not defined\n";
 
900
        my (@gs, @globs);
 
901
        my $ra = Git::SVN::Ra->new($url);
 
902
        my $uuid = $ra->get_uuid;
 
903
        my $head = $ra->get_latest_revnum;
 
904
        my $base = defined $fetch ? $head : 0;
 
905
 
 
906
        # read the max revs for wildcard expansion (branches/*, tags/*)
 
907
        foreach my $t (qw/branches tags/) {
 
908
                defined $remote->{$t} or next;
 
909
                push @globs, $remote->{$t};
 
910
                my $max_rev = eval { tmp_config(qw/--int --get/,
 
911
                                         "svn-remote.$repo_id.${t}-maxRev") };
 
912
                if (defined $max_rev && ($max_rev < $base)) {
 
913
                        $base = $max_rev;
 
914
                } elsif (!defined $max_rev) {
 
915
                        $base = 0;
 
916
                }
 
917
        }
 
918
 
 
919
        if ($fetch) {
 
920
                foreach my $p (sort keys %$fetch) {
 
921
                        my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p);
 
922
                        my $lr = $gs->rev_db_max;
 
923
                        if (defined $lr) {
 
924
                                $base = $lr if ($lr < $base);
 
925
                        }
 
926
                        push @gs, $gs;
 
927
                }
 
928
        }
 
929
 
 
930
        ($base, $head) = parse_revision_argument($base, $head);
 
931
        $ra->gs_fetch_loop_common($base, $head, \@gs, \@globs);
 
932
}
 
933
 
 
934
sub read_all_remotes {
 
935
        my $r = {};
 
936
        foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
 
937
                if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) {
 
938
                        $r->{$1}->{fetch}->{$2} = $3;
 
939
                } elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
 
940
                        $r->{$1}->{url} = $2;
 
941
                } elsif (m!^(.+)\.(branches|tags)=
 
942
                           (.*):refs/remotes/(.+)\s*$/!x) {
 
943
                        my ($p, $g) = ($3, $4);
 
944
                        my $rs = $r->{$1}->{$2} = {
 
945
                                          t => $2,
 
946
                                          remote => $1,
 
947
                                          path => Git::SVN::GlobSpec->new($p),
 
948
                                          ref => Git::SVN::GlobSpec->new($g) };
 
949
                        if (length($rs->{ref}->{right}) != 0) {
 
950
                                die "The '*' glob character must be the last ",
 
951
                                    "character of '$g'\n";
 
952
                        }
 
953
                }
 
954
        }
 
955
        $r;
 
956
}
 
957
 
 
958
sub init_vars {
 
959
        if (defined $_repack) {
 
960
                $_repack = 1000 if ($_repack <= 0);
 
961
                $_repack_nr = $_repack;
 
962
                $_repack_flags ||= '-d';
 
963
        }
 
964
}
 
965
 
 
966
sub verify_remotes_sanity {
 
967
        return unless -d $ENV{GIT_DIR};
 
968
        my %seen;
 
969
        foreach (command(qw/config -l/)) {
 
970
                if (m!^svn-remote\.(?:.+)\.fetch=.*:refs/remotes/(\S+)\s*$!) {
 
971
                        if ($seen{$1}) {
 
972
                                die "Remote ref refs/remote/$1 is tracked by",
 
973
                                    "\n  \"$_\"\nand\n  \"$seen{$1}\"\n",
 
974
                                    "Please resolve this ambiguity in ",
 
975
                                    "your git configuration file before ",
 
976
                                    "continuing\n";
 
977
                        }
 
978
                        $seen{$1} = $_;
 
979
                }
 
980
        }
 
981
}
 
982
 
 
983
# we allow more chars than remotes2config.sh...
 
984
sub sanitize_remote_name {
 
985
        my ($name) = @_;
 
986
        $name =~ tr{A-Za-z0-9:,/+-}{.}c;
 
987
        $name;
 
988
}
 
989
 
 
990
sub find_existing_remote {
 
991
        my ($url, $remotes) = @_;
 
992
        return undef if $no_reuse_existing;
 
993
        my $existing;
 
994
        foreach my $repo_id (keys %$remotes) {
 
995
                my $u = $remotes->{$repo_id}->{url} or next;
 
996
                next if $u ne $url;
 
997
                $existing = $repo_id;
 
998
                last;
 
999
        }
 
1000
        $existing;
 
1001
}
 
1002
 
 
1003
sub init_remote_config {
 
1004
        my ($self, $url, $no_write) = @_;
 
1005
        $url =~ s!/+$!!; # strip trailing slash
 
1006
        my $r = read_all_remotes();
 
1007
        my $existing = find_existing_remote($url, $r);
 
1008
        if ($existing) {
 
1009
                unless ($no_write) {
 
1010
                        print STDERR "Using existing ",
 
1011
                                     "[svn-remote \"$existing\"]\n";
 
1012
                }
 
1013
                $self->{repo_id} = $existing;
 
1014
        } else {
 
1015
                my $min_url = Git::SVN::Ra->new($url)->minimize_url;
 
1016
                $existing = find_existing_remote($min_url, $r);
 
1017
                if ($existing) {
 
1018
                        unless ($no_write) {
 
1019
                                print STDERR "Using existing ",
 
1020
                                             "[svn-remote \"$existing\"]\n";
 
1021
                        }
 
1022
                        $self->{repo_id} = $existing;
 
1023
                }
 
1024
                if ($min_url ne $url) {
 
1025
                        unless ($no_write) {
 
1026
                                print STDERR "Using higher level of URL: ",
 
1027
                                             "$url => $min_url\n";
 
1028
                        }
 
1029
                        my $old_path = $self->{path};
 
1030
                        $self->{path} = $url;
 
1031
                        $self->{path} =~ s!^\Q$min_url\E(/|$)!!;
 
1032
                        if (length $old_path) {
 
1033
                                $self->{path} .= "/$old_path";
 
1034
                        }
 
1035
                        $url = $min_url;
 
1036
                }
 
1037
        }
 
1038
        my $orig_url;
 
1039
        if (!$existing) {
 
1040
                # verify that we aren't overwriting anything:
 
1041
                $orig_url = eval {
 
1042
                        command_oneline('config', '--get',
 
1043
                                        "svn-remote.$self->{repo_id}.url")
 
1044
                };
 
1045
                if ($orig_url && ($orig_url ne $url)) {
 
1046
                        die "svn-remote.$self->{repo_id}.url already set: ",
 
1047
                            "$orig_url\nwanted to set to: $url\n";
 
1048
                }
 
1049
        }
 
1050
        my ($xrepo_id, $xpath) = find_ref($self->refname);
 
1051
        if (defined $xpath) {
 
1052
                die "svn-remote.$xrepo_id.fetch already set to track ",
 
1053
                    "$xpath:refs/remotes/", $self->refname, "\n";
 
1054
        }
 
1055
        unless ($no_write) {
 
1056
                command_noisy('config',
 
1057
                              "svn-remote.$self->{repo_id}.url", $url);
 
1058
                command_noisy('config', '--add',
 
1059
                              "svn-remote.$self->{repo_id}.fetch",
 
1060
                              "$self->{path}:".$self->refname);
 
1061
        }
 
1062
        $self->{url} = $url;
 
1063
}
 
1064
 
 
1065
sub find_by_url { # repos_root and, path are optional
 
1066
        my ($class, $full_url, $repos_root, $path) = @_;
 
1067
        return undef unless defined $full_url;
 
1068
        my $remotes = read_all_remotes();
 
1069
        if (defined $full_url && defined $repos_root && !defined $path) {
 
1070
                $path = $full_url;
 
1071
                $path =~ s#^\Q$repos_root\E(?:/|$)##;
 
1072
        }
 
1073
        foreach my $repo_id (keys %$remotes) {
 
1074
                my $u = $remotes->{$repo_id}->{url} or next;
 
1075
                next if defined $repos_root && $repos_root ne $u;
 
1076
 
 
1077
                my $fetch = $remotes->{$repo_id}->{fetch} || {};
 
1078
                foreach (qw/branches tags/) {
 
1079
                        resolve_local_globs($u, $fetch,
 
1080
                                            $remotes->{$repo_id}->{$_});
 
1081
                }
 
1082
                my $p = $path;
 
1083
                unless (defined $p) {
 
1084
                        $p = $full_url;
 
1085
                        $p =~ s#^\Q$u\E(?:/|$)## or next;
 
1086
                }
 
1087
                foreach my $f (keys %$fetch) {
 
1088
                        next if $f ne $p;
 
1089
                        return Git::SVN->new($fetch->{$f}, $repo_id, $f);
 
1090
                }
 
1091
        }
 
1092
        undef;
 
1093
}
 
1094
 
 
1095
sub init {
 
1096
        my ($class, $url, $path, $repo_id, $ref_id, $no_write) = @_;
 
1097
        my $self = _new($class, $repo_id, $ref_id, $path);
 
1098
        if (defined $url) {
 
1099
                $self->init_remote_config($url, $no_write);
 
1100
        }
 
1101
        $self;
 
1102
}
 
1103
 
 
1104
sub find_ref {
 
1105
        my ($ref_id) = @_;
 
1106
        foreach (command(qw/config -l/)) {
 
1107
                next unless m!^svn-remote\.(.+)\.fetch=
 
1108
                              \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x;
 
1109
                my ($repo_id, $path, $ref) = ($1, $2, $3);
 
1110
                if ($ref eq $ref_id) {
 
1111
                        $path = '' if ($path =~ m#^\./?#);
 
1112
                        return ($repo_id, $path);
 
1113
                }
 
1114
        }
 
1115
        (undef, undef, undef);
 
1116
}
 
1117
 
 
1118
sub new {
 
1119
        my ($class, $ref_id, $repo_id, $path) = @_;
 
1120
        if (defined $ref_id && !defined $repo_id && !defined $path) {
 
1121
                ($repo_id, $path) = find_ref($ref_id);
 
1122
                if (!defined $repo_id) {
 
1123
                        die "Could not find a \"svn-remote.*.fetch\" key ",
 
1124
                            "in the repository configuration matching: ",
 
1125
                            "refs/remotes/$ref_id\n";
 
1126
                }
 
1127
        }
 
1128
        my $self = _new($class, $repo_id, $ref_id, $path);
 
1129
        if (!defined $self->{path} || !length $self->{path}) {
 
1130
                my $fetch = command_oneline('config', '--get',
 
1131
                                            "svn-remote.$repo_id.fetch",
 
1132
                                            ":refs/remotes/$ref_id\$") or
 
1133
                     die "Failed to read \"svn-remote.$repo_id.fetch\" ",
 
1134
                         "\":refs/remotes/$ref_id\$\" in config\n";
 
1135
                ($self->{path}, undef) = split(/\s*:\s*/, $fetch);
 
1136
        }
 
1137
        $self->{url} = command_oneline('config', '--get',
 
1138
                                       "svn-remote.$repo_id.url") or
 
1139
                  die "Failed to read \"svn-remote.$repo_id.url\" in config\n";
 
1140
        $self->rebuild;
 
1141
        $self;
 
1142
}
 
1143
 
 
1144
sub refname { "refs/remotes/$_[0]->{ref_id}" }
 
1145
 
 
1146
sub svm_uuid {
 
1147
        my ($self) = @_;
 
1148
        return $self->{svm}->{uuid} if $self->svm;
 
1149
        $self->ra;
 
1150
        unless ($self->{svm}) {
 
1151
                die "SVM UUID not cached, and reading remotely failed\n";
 
1152
        }
 
1153
        $self->{svm}->{uuid};
 
1154
}
 
1155
 
 
1156
sub svm {
 
1157
        my ($self) = @_;
 
1158
        return $self->{svm} if $self->{svm};
 
1159
        my $svm;
 
1160
        # see if we have it in our config, first:
 
1161
        eval {
 
1162
                my $section = "svn-remote.$self->{repo_id}";
 
1163
                $svm = {
 
1164
                  source => tmp_config('--get', "$section.svm-source"),
 
1165
                  uuid => tmp_config('--get', "$section.svm-uuid"),
 
1166
                  replace => tmp_config('--get', "$section.svm-replace"),
 
1167
                }
 
1168
        };
 
1169
        if ($svm && $svm->{source} && $svm->{uuid} && $svm->{replace}) {
 
1170
                $self->{svm} = $svm;
 
1171
        }
 
1172
        $self->{svm};
 
1173
}
 
1174
 
 
1175
sub _set_svm_vars {
 
1176
        my ($self, $ra) = @_;
 
1177
        return $ra if $self->svm;
 
1178
 
 
1179
        my @err = ( "useSvmProps set, but failed to read SVM properties\n",
 
1180
                    "(svm:source, svm:uuid) ",
 
1181
                    "from the following URLs:\n" );
 
1182
        sub read_svm_props {
 
1183
                my ($self, $ra, $path, $r) = @_;
 
1184
                my $props = ($ra->get_dir($path, $r))[2];
 
1185
                my $src = $props->{'svm:source'};
 
1186
                my $uuid = $props->{'svm:uuid'};
 
1187
                return undef if (!$src || !$uuid);
 
1188
 
 
1189
                chomp($src, $uuid);
 
1190
 
 
1191
                $uuid =~ m{^[0-9a-f\-]{30,}$}
 
1192
                    or die "doesn't look right - svm:uuid is '$uuid'\n";
 
1193
 
 
1194
                # the '!' is used to mark the repos_root!/relative/path
 
1195
                $src =~ s{/?!/?}{/};
 
1196
                $src =~ s{/+$}{}; # no trailing slashes please
 
1197
                # username is of no interest
 
1198
                $src =~ s{(^[a-z\+]*://)[^/@]*@}{$1};
 
1199
 
 
1200
                my $replace = $ra->{url};
 
1201
                $replace .= "/$path" if length $path;
 
1202
 
 
1203
                my $section = "svn-remote.$self->{repo_id}";
 
1204
                tmp_config("$section.svm-source", $src);
 
1205
                tmp_config("$section.svm-replace", $replace);
 
1206
                tmp_config("$section.svm-uuid", $uuid);
 
1207
                $self->{svm} = {
 
1208
                        source => $src,
 
1209
                        uuid => $uuid,
 
1210
                        replace => $replace
 
1211
                };
 
1212
        }
 
1213
 
 
1214
        my $r = $ra->get_latest_revnum;
 
1215
        my $path = $self->{path};
 
1216
        my %tried;
 
1217
        while (length $path) {
 
1218
                unless ($tried{"$self->{url}/$path"}) {
 
1219
                        return $ra if $self->read_svm_props($ra, $path, $r);
 
1220
                        $tried{"$self->{url}/$path"} = 1;
 
1221
                }
 
1222
                $path =~ s#/?[^/]+$##;
 
1223
        }
 
1224
        die "Path: '$path' should be ''\n" if $path ne '';
 
1225
        return $ra if $self->read_svm_props($ra, $path, $r);
 
1226
        $tried{"$self->{url}/$path"} = 1;
 
1227
 
 
1228
        if ($ra->{repos_root} eq $self->{url}) {
 
1229
                die @err, (map { "  $_\n" } keys %tried), "\n";
 
1230
        }
 
1231
 
 
1232
        # nope, make sure we're connected to the repository root:
 
1233
        my $ok;
 
1234
        my @tried_b;
 
1235
        $path = $ra->{svn_path};
 
1236
        $ra = Git::SVN::Ra->new($ra->{repos_root});
 
1237
        while (length $path) {
 
1238
                unless ($tried{"$ra->{url}/$path"}) {
 
1239
                        $ok = $self->read_svm_props($ra, $path, $r);
 
1240
                        last if $ok;
 
1241
                        $tried{"$ra->{url}/$path"} = 1;
 
1242
                }
 
1243
                $path =~ s#/?[^/]+$##;
 
1244
        }
 
1245
        die "Path: '$path' should be ''\n" if $path ne '';
 
1246
        $ok ||= $self->read_svm_props($ra, $path, $r);
 
1247
        $tried{"$ra->{url}/$path"} = 1;
 
1248
        if (!$ok) {
 
1249
                die @err, (map { "  $_\n" } keys %tried), "\n";
 
1250
        }
 
1251
        Git::SVN::Ra->new($self->{url});
 
1252
}
 
1253
 
 
1254
sub svnsync {
 
1255
        my ($self) = @_;
 
1256
        return $self->{svnsync} if $self->{svnsync};
 
1257
 
 
1258
        if ($self->no_metadata) {
 
1259
                die "Can't have both 'noMetadata' and ",
 
1260
                    "'useSvnsyncProps' options set!\n";
 
1261
        }
 
1262
        if ($self->rewrite_root) {
 
1263
                die "Can't have both 'useSvnsyncProps' and 'rewriteRoot' ",
 
1264
                    "options set!\n";
 
1265
        }
 
1266
 
 
1267
        my $svnsync;
 
1268
        # see if we have it in our config, first:
 
1269
        eval {
 
1270
                my $section = "svn-remote.$self->{repo_id}";
 
1271
                $svnsync = {
 
1272
                  url => tmp_config('--get', "$section.svnsync-url"),
 
1273
                  uuid => tmp_config('--get', "$section.svnsync-uuid"),
 
1274
                }
 
1275
        };
 
1276
        if ($svnsync && $svnsync->{url} && $svnsync->{uuid}) {
 
1277
                return $self->{svnsync} = $svnsync;
 
1278
        }
 
1279
 
 
1280
        my $err = "useSvnsyncProps set, but failed to read " .
 
1281
                  "svnsync property: svn:sync-from-";
 
1282
        my $rp = $self->ra->rev_proplist(0);
 
1283
 
 
1284
        my $url = $rp->{'svn:sync-from-url'} or die $err . "url\n";
 
1285
        $url =~ m{^[a-z\+]+://} or
 
1286
                   die "doesn't look right - svn:sync-from-url is '$url'\n";
 
1287
 
 
1288
        my $uuid = $rp->{'svn:sync-from-uuid'} or die $err . "uuid\n";
 
1289
        $uuid =~ m{^[0-9a-f\-]{30,}$} or
 
1290
                   die "doesn't look right - svn:sync-from-uuid is '$uuid'\n";
 
1291
 
 
1292
        my $section = "svn-remote.$self->{repo_id}";
 
1293
        tmp_config('--add', "$section.svnsync-uuid", $uuid);
 
1294
        tmp_config('--add', "$section.svnsync-url", $url);
 
1295
        return $self->{svnsync} = { url => $url, uuid => $uuid };
 
1296
}
 
1297
 
 
1298
# this allows us to memoize our SVN::Ra UUID locally and avoid a
 
1299
# remote lookup (useful for 'git svn log').
 
1300
sub ra_uuid {
 
1301
        my ($self) = @_;
 
1302
        unless ($self->{ra_uuid}) {
 
1303
                my $key = "svn-remote.$self->{repo_id}.uuid";
 
1304
                my $uuid = eval { tmp_config('--get', $key) };
 
1305
                if (!$@ && $uuid && $uuid =~ /^([a-f\d\-]{30,})$/) {
 
1306
                        $self->{ra_uuid} = $uuid;
 
1307
                } else {
 
1308
                        die "ra_uuid called without URL\n" unless $self->{url};
 
1309
                        $self->{ra_uuid} = $self->ra->get_uuid;
 
1310
                        tmp_config('--add', $key, $self->{ra_uuid});
 
1311
                }
 
1312
        }
 
1313
        $self->{ra_uuid};
 
1314
}
 
1315
 
 
1316
sub ra {
 
1317
        my ($self) = shift;
 
1318
        my $ra = Git::SVN::Ra->new($self->{url});
 
1319
        if ($self->use_svm_props && !$self->{svm}) {
 
1320
                if ($self->no_metadata) {
 
1321
                        die "Can't have both 'noMetadata' and ",
 
1322
                            "'useSvmProps' options set!\n";
 
1323
                } elsif ($self->use_svnsync_props) {
 
1324
                        die "Can't have both 'useSvnsyncProps' and ",
 
1325
                            "'useSvmProps' options set!\n";
 
1326
                }
 
1327
                $ra = $self->_set_svm_vars($ra);
 
1328
                $self->{-want_revprops} = 1;
 
1329
        }
 
1330
        $ra;
 
1331
}
 
1332
 
 
1333
sub rel_path {
 
1334
        my ($self) = @_;
 
1335
        my $repos_root = $self->ra->{repos_root};
 
1336
        return $self->{path} if ($self->{url} eq $repos_root);
 
1337
        my $url = $self->{url} .
 
1338
                  (length $self->{path} ? "/$self->{path}" : $self->{path});
 
1339
        $url =~ s!^\Q$repos_root\E(?:/+|$)!!g;
 
1340
        $url;
 
1341
}
 
1342
 
 
1343
sub traverse_ignore {
 
1344
        my ($self, $fh, $path, $r) = @_;
2896
1345
        $path =~ s#^/+##g;
2897
 
        my $pool = SVN::Pool->new;
2898
 
        my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
 
1346
        my $ra = $self->ra;
 
1347
        my ($dirent, undef, $props) = $ra->get_dir($path, $r);
2899
1348
        my $p = $path;
2900
 
        $p =~ s#^\Q$SVN_PATH\E/?##;
 
1349
        $p =~ s#^\Q$self->{path}\E(/|$)##;
2901
1350
        print $fh length $p ? "\n# $p\n" : "\n# /\n";
2902
1351
        if (my $s = $props->{'svn:ignore'}) {
2903
1352
                $s =~ s/[\r\n]+/\n/g;
2912
1361
        }
2913
1362
        foreach (sort keys %$dirent) {
2914
1363
                next if $dirent->{$_}->kind != $SVN::Node::dir;
2915
 
                libsvn_traverse_ignore($fh, "$path/$_", $r);
2916
 
        }
2917
 
        $pool->clear;
2918
 
}
2919
 
 
2920
 
sub revisions_eq {
2921
 
        my ($path, $r0, $r1) = @_;
2922
 
        return 1 if $r0 == $r1;
2923
 
        my $nr = 0;
2924
 
        if ($_use_lib) {
2925
 
                # should be OK to use Pool here (r1 - r0) should be small
2926
 
                my $pool = SVN::Pool->new;
2927
 
                libsvn_get_log($SVN, "/$path", $r0, $r1,
2928
 
                                0, 1, 1, sub {$nr++}, $pool);
2929
 
                $pool->clear;
2930
 
        } else {
2931
 
                my ($url, undef) = repo_path_split($SVN_URL);
2932
 
                my $svn_log = svn_log_raw("$url/$path","-r$r0:$r1");
2933
 
                while (next_log_entry($svn_log)) { $nr++ }
2934
 
                close $svn_log->{fh};
2935
 
        }
2936
 
        return 0 if ($nr > 1);
2937
 
        return 1;
2938
 
}
2939
 
 
2940
 
sub libsvn_find_parent_branch {
2941
 
        my ($paths, $rev, $author, $date, $msg) = @_;
2942
 
        my $svn_path = '/'.$SVN_PATH;
 
1364
                $self->traverse_ignore($fh, "$path/$_", $r);
 
1365
        }
 
1366
}
 
1367
 
 
1368
sub last_rev { ($_[0]->last_rev_commit)[0] }
 
1369
sub last_commit { ($_[0]->last_rev_commit)[1] }
 
1370
 
 
1371
# returns the newest SVN revision number and newest commit SHA1
 
1372
sub last_rev_commit {
 
1373
        my ($self) = @_;
 
1374
        if (defined $self->{last_rev} && defined $self->{last_commit}) {
 
1375
                return ($self->{last_rev}, $self->{last_commit});
 
1376
        }
 
1377
        my $c = ::verify_ref($self->refname.'^0');
 
1378
        if ($c && !$self->use_svm_props && !$self->no_metadata) {
 
1379
                my $rev = (::cmt_metadata($c))[1];
 
1380
                if (defined $rev) {
 
1381
                        ($self->{last_rev}, $self->{last_commit}) = ($rev, $c);
 
1382
                        return ($rev, $c);
 
1383
                }
 
1384
        }
 
1385
        my $db_path = $self->db_path;
 
1386
        unless (-e $db_path) {
 
1387
                ($self->{last_rev}, $self->{last_commit}) = (undef, undef);
 
1388
                return (undef, undef);
 
1389
        }
 
1390
        my $offset = -41; # from tail
 
1391
        my $rl;
 
1392
        open my $fh, '<', $db_path or croak "$db_path not readable: $!\n";
 
1393
        sysseek($fh, $offset, 2); # don't care for errors
 
1394
        sysread($fh, $rl, 41) == 41 or return (undef, undef);
 
1395
        chomp $rl;
 
1396
        while (('0' x40) eq $rl && sysseek($fh, 0, 1) != 0) {
 
1397
                $offset -= 41;
 
1398
                sysseek($fh, $offset, 2); # don't care for errors
 
1399
                sysread($fh, $rl, 41) == 41 or return (undef, undef);
 
1400
                chomp $rl;
 
1401
        }
 
1402
        if ($c && $c ne $rl) {
 
1403
                die "$db_path and ", $self->refname,
 
1404
                    " inconsistent!:\n$c != $rl\n";
 
1405
        }
 
1406
        my $rev = sysseek($fh, 0, 1) or croak $!;
 
1407
        $rev =  ($rev - 41) / 41;
 
1408
        close $fh or croak $!;
 
1409
        ($self->{last_rev}, $self->{last_commit}) = ($rev, $c);
 
1410
        return ($rev, $c);
 
1411
}
 
1412
 
 
1413
sub get_fetch_range {
 
1414
        my ($self, $min, $max) = @_;
 
1415
        $max ||= $self->ra->get_latest_revnum;
 
1416
        $min ||= $self->rev_db_max;
 
1417
        (++$min, $max);
 
1418
}
 
1419
 
 
1420
sub tmp_config {
 
1421
        my (@args) = @_;
 
1422
        my $old_def_config = "$ENV{GIT_DIR}/svn/config";
 
1423
        my $config = "$ENV{GIT_DIR}/svn/.metadata";
 
1424
        if (-e $old_def_config && ! -e $config) {
 
1425
                rename $old_def_config, $config or
 
1426
                       die "Failed rename $old_def_config => $config: $!\n";
 
1427
        }
 
1428
        my $old_config = $ENV{GIT_CONFIG};
 
1429
        $ENV{GIT_CONFIG} = $config;
 
1430
        $@ = undef;
 
1431
        my @ret = eval {
 
1432
                unless (-f $config) {
 
1433
                        mkfile($config);
 
1434
                        open my $fh, '>', $config or
 
1435
                            die "Can't open $config: $!\n";
 
1436
                        print $fh "; This file is used internally by ",
 
1437
                                  "git-svn\n" or die
 
1438
                                  "Couldn't write to $config: $!\n";
 
1439
                        print $fh "; You should not have to edit it\n" or
 
1440
                              die "Couldn't write to $config: $!\n";
 
1441
                        close $fh or die "Couldn't close $config: $!\n";
 
1442
                }
 
1443
                command('config', @args);
 
1444
        };
 
1445
        my $err = $@;
 
1446
        if (defined $old_config) {
 
1447
                $ENV{GIT_CONFIG} = $old_config;
 
1448
        } else {
 
1449
                delete $ENV{GIT_CONFIG};
 
1450
        }
 
1451
        die $err if $err;
 
1452
        wantarray ? @ret : $ret[0];
 
1453
}
 
1454
 
 
1455
sub tmp_index_do {
 
1456
        my ($self, $sub) = @_;
 
1457
        my $old_index = $ENV{GIT_INDEX_FILE};
 
1458
        $ENV{GIT_INDEX_FILE} = $self->{index};
 
1459
        $@ = undef;
 
1460
        my @ret = eval {
 
1461
                my ($dir, $base) = ($self->{index} =~ m#^(.*?)/?([^/]+)$#);
 
1462
                mkpath([$dir]) unless -d $dir;
 
1463
                &$sub;
 
1464
        };
 
1465
        my $err = $@;
 
1466
        if (defined $old_index) {
 
1467
                $ENV{GIT_INDEX_FILE} = $old_index;
 
1468
        } else {
 
1469
                delete $ENV{GIT_INDEX_FILE};
 
1470
        }
 
1471
        die $err if $err;
 
1472
        wantarray ? @ret : $ret[0];
 
1473
}
 
1474
 
 
1475
sub assert_index_clean {
 
1476
        my ($self, $treeish) = @_;
 
1477
 
 
1478
        $self->tmp_index_do(sub {
 
1479
                command_noisy('read-tree', $treeish) unless -e $self->{index};
 
1480
                my $x = command_oneline('write-tree');
 
1481
                my ($y) = (command(qw/cat-file commit/, $treeish) =~
 
1482
                           /^tree ($::sha1)/mo);
 
1483
                return if $y eq $x;
 
1484
 
 
1485
                warn "Index mismatch: $y != $x\nrereading $treeish\n";
 
1486
                unlink $self->{index} or die "unlink $self->{index}: $!\n";
 
1487
                command_noisy('read-tree', $treeish);
 
1488
                $x = command_oneline('write-tree');
 
1489
                if ($y ne $x) {
 
1490
                        ::fatal "trees ($treeish) $y != $x\n",
 
1491
                                "Something is seriously wrong...\n";
 
1492
                }
 
1493
        });
 
1494
}
 
1495
 
 
1496
sub get_commit_parents {
 
1497
        my ($self, $log_entry) = @_;
 
1498
        my (%seen, @ret, @tmp);
 
1499
        # legacy support for 'set-tree'; this is only used by set_tree_cb:
 
1500
        if (my $ip = $self->{inject_parents}) {
 
1501
                if (my $commit = delete $ip->{$log_entry->{revision}}) {
 
1502
                        push @tmp, $commit;
 
1503
                }
 
1504
        }
 
1505
        if (my $cur = ::verify_ref($self->refname.'^0')) {
 
1506
                push @tmp, $cur;
 
1507
        }
 
1508
        push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp);
 
1509
        while (my $p = shift @tmp) {
 
1510
                next if $seen{$p};
 
1511
                $seen{$p} = 1;
 
1512
                push @ret, $p;
 
1513
                # MAXPARENT is defined to 16 in commit-tree.c:
 
1514
                last if @ret >= 16;
 
1515
        }
 
1516
        if (@tmp) {
 
1517
                die "r$log_entry->{revision}: No room for parents:\n\t",
 
1518
                    join("\n\t", @tmp), "\n";
 
1519
        }
 
1520
        @ret;
 
1521
}
 
1522
 
 
1523
sub rewrite_root {
 
1524
        my ($self) = @_;
 
1525
        return $self->{-rewrite_root} if exists $self->{-rewrite_root};
 
1526
        my $k = "svn-remote.$self->{repo_id}.rewriteRoot";
 
1527
        my $rwr = eval { command_oneline(qw/config --get/, $k) };
 
1528
        if ($rwr) {
 
1529
                $rwr =~ s#/+$##;
 
1530
                if ($rwr !~ m#^[a-z\+]+://#) {
 
1531
                        die "$rwr is not a valid URL (key: $k)\n";
 
1532
                }
 
1533
        }
 
1534
        $self->{-rewrite_root} = $rwr;
 
1535
}
 
1536
 
 
1537
sub metadata_url {
 
1538
        my ($self) = @_;
 
1539
        ($self->rewrite_root || $self->{url}) .
 
1540
           (length $self->{path} ? '/' . $self->{path} : '');
 
1541
}
 
1542
 
 
1543
sub full_url {
 
1544
        my ($self) = @_;
 
1545
        $self->{url} . (length $self->{path} ? '/' . $self->{path} : '');
 
1546
}
 
1547
 
 
1548
sub do_git_commit {
 
1549
        my ($self, $log_entry) = @_;
 
1550
        my $lr = $self->last_rev;
 
1551
        if (defined $lr && $lr >= $log_entry->{revision}) {
 
1552
                die "Last fetched revision of ", $self->refname,
 
1553
                    " was r$lr, but we are about to fetch: ",
 
1554
                    "r$log_entry->{revision}!\n";
 
1555
        }
 
1556
        if (my $c = $self->rev_db_get($log_entry->{revision})) {
 
1557
                croak "$log_entry->{revision} = $c already exists! ",
 
1558
                      "Why are we refetching it?\n";
 
1559
        }
 
1560
        $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $log_entry->{name};
 
1561
        $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} =
 
1562
                                                          $log_entry->{email};
 
1563
        $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date};
 
1564
 
 
1565
        my $tree = $log_entry->{tree};
 
1566
        if (!defined $tree) {
 
1567
                $tree = $self->tmp_index_do(sub {
 
1568
                                            command_oneline('write-tree') });
 
1569
        }
 
1570
        die "Tree is not a valid sha1: $tree\n" if $tree !~ /^$::sha1$/o;
 
1571
 
 
1572
        my @exec = ('git-commit-tree', $tree);
 
1573
        foreach ($self->get_commit_parents($log_entry)) {
 
1574
                push @exec, '-p', $_;
 
1575
        }
 
1576
        defined(my $pid = open3(my $msg_fh, my $out_fh, '>&STDERR', @exec))
 
1577
                                                                   or croak $!;
 
1578
        print $msg_fh $log_entry->{log} or croak $!;
 
1579
        unless ($self->no_metadata) {
 
1580
                print $msg_fh "\ngit-svn-id: $log_entry->{metadata}\n"
 
1581
                              or croak $!;
 
1582
        }
 
1583
        $msg_fh->flush == 0 or croak $!;
 
1584
        close $msg_fh or croak $!;
 
1585
        chomp(my $commit = do { local $/; <$out_fh> });
 
1586
        close $out_fh or croak $!;
 
1587
        waitpid $pid, 0;
 
1588
        croak $? if $?;
 
1589
        if ($commit !~ /^$::sha1$/o) {
 
1590
                die "Failed to commit, invalid sha1: $commit\n";
 
1591
        }
 
1592
 
 
1593
        $self->rev_db_set($log_entry->{revision}, $commit, 1);
 
1594
 
 
1595
        $self->{last_rev} = $log_entry->{revision};
 
1596
        $self->{last_commit} = $commit;
 
1597
        print "r$log_entry->{revision}";
 
1598
        if (defined $log_entry->{svm_revision}) {
 
1599
                 print " (\@$log_entry->{svm_revision})";
 
1600
                 $self->rev_db_set($log_entry->{svm_revision}, $commit,
 
1601
                                   0, $self->svm_uuid);
 
1602
        }
 
1603
        print " = $commit ($self->{ref_id})\n";
 
1604
        if (defined $_repack && (--$_repack_nr == 0)) {
 
1605
                $_repack_nr = $_repack;
 
1606
                # repack doesn't use any arguments with spaces in them, does it?
 
1607
                print "Running git repack $_repack_flags ...\n";
 
1608
                command_noisy('repack', split(/\s+/, $_repack_flags));
 
1609
                print "Done repacking\n";
 
1610
        }
 
1611
        return $commit;
 
1612
}
 
1613
 
 
1614
sub match_paths {
 
1615
        my ($self, $paths, $r) = @_;
 
1616
        return 1 if $self->{path} eq '';
 
1617
        if (my $path = $paths->{"/$self->{path}"}) {
 
1618
                return ($path->{action} eq 'D') ? 0 : 1;
 
1619
        }
 
1620
        $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//;
 
1621
        if (grep /$self->{path_regex}/, keys %$paths) {
 
1622
                return 1;
 
1623
        }
 
1624
        my $c = '';
 
1625
        foreach (split m#/#, $self->{path}) {
 
1626
                $c .= "/$_";
 
1627
                next unless ($paths->{$c} &&
 
1628
                             ($paths->{$c}->{action} =~ /^[AR]$/));
 
1629
                if ($self->ra->check_path($self->{path}, $r) ==
 
1630
                    $SVN::Node::dir) {
 
1631
                        return 1;
 
1632
                }
 
1633
        }
 
1634
        return 0;
 
1635
}
 
1636
 
 
1637
sub find_parent_branch {
 
1638
        my ($self, $paths, $rev) = @_;
 
1639
        return undef unless $self->follow_parent;
 
1640
        unless (defined $paths) {
 
1641
                my $err_handler = $SVN::Error::handler;
 
1642
                $SVN::Error::handler = \&Git::SVN::Ra::skip_unknown_revs;
 
1643
                $self->ra->get_log([$self->{path}], $rev, $rev, 0, 1, 1, sub {
 
1644
                                   $paths =
 
1645
                                      Git::SVN::Ra::dup_changed_paths($_[0]) });
 
1646
                $SVN::Error::handler = $err_handler;
 
1647
        }
 
1648
        return undef unless defined $paths;
2943
1649
 
2944
1650
        # look for a parent from another branch:
2945
 
        my $i = $paths->{$svn_path} or return;
2946
 
        my $branch_from = $i->copyfrom_path or return;
2947
 
        my $r = $i->copyfrom_rev;
 
1651
        my @b_path_components = split m#/#, $self->rel_path;
 
1652
        my @a_path_components;
 
1653
        my $i;
 
1654
        while (@b_path_components) {
 
1655
                $i = $paths->{'/'.join('/', @b_path_components)};
 
1656
                last if $i && defined $i->{copyfrom_path};
 
1657
                unshift(@a_path_components, pop(@b_path_components));
 
1658
        }
 
1659
        return undef unless defined $i && defined $i->{copyfrom_path};
 
1660
        my $branch_from = $i->{copyfrom_path};
 
1661
        if (@a_path_components) {
 
1662
                print STDERR "branch_from: $branch_from => ";
 
1663
                $branch_from .= '/'.join('/', @a_path_components);
 
1664
                print STDERR $branch_from, "\n";
 
1665
        }
 
1666
        my $r = $i->{copyfrom_rev};
 
1667
        my $repos_root = $self->ra->{repos_root};
 
1668
        my $url = $self->ra->{url};
 
1669
        my $new_url = $repos_root . $branch_from;
2948
1670
        print STDERR  "Found possible branch point: ",
2949
 
                                "$branch_from => $svn_path, $r\n";
 
1671
                      "$new_url => ", $self->full_url, ", $r\n";
2950
1672
        $branch_from =~ s#^/##;
2951
 
        my $l_map = {};
2952
 
        read_url_paths_all($l_map, '', "$GIT_DIR/svn");
2953
 
        my $url = $SVN->{url};
2954
 
        defined $l_map->{$url} or return;
2955
 
        my $id = $l_map->{$url}->{$branch_from};
2956
 
        if (!defined $id && $_follow_parent) {
2957
 
                print STDERR "Following parent: $branch_from\@$r\n";
2958
 
                # auto create a new branch and follow it
2959
 
                $id = basename($branch_from);
2960
 
                $id .= '@'.$r if -r "$GIT_DIR/svn/$id";
2961
 
                while (-r "$GIT_DIR/svn/$id") {
2962
 
                        # just grow a tail if we're not unique enough :x
2963
 
                        $id .= '-';
2964
 
                }
2965
 
        }
2966
 
        return unless defined $id;
2967
 
 
2968
 
        my ($r0, $parent) = find_rev_before($r,$id,1);
2969
 
        if ($_follow_parent && (!defined $r0 || !defined $parent)) {
2970
 
                defined(my $pid = fork) or croak $!;
2971
 
                if (!$pid) {
2972
 
                        $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
2973
 
                        init_vars();
2974
 
                        $SVN_URL = "$url/$branch_from";
2975
 
                        $SVN_LOG = $SVN = undef;
2976
 
                        setup_git_svn();
2977
 
                        # we can't assume SVN_URL exists at r+1:
2978
 
                        $_revision = "0:$r";
2979
 
                        fetch_lib();
2980
 
                        exit 0;
2981
 
                }
2982
 
                waitpid $pid, 0;
2983
 
                croak $? if $?;
2984
 
                ($r0, $parent) = find_rev_before($r,$id,1);
2985
 
        }
2986
 
        return unless (defined $r0 && defined $parent);
2987
 
        if (revisions_eq($branch_from, $r0, $r)) {
2988
 
                unlink $GIT_SVN_INDEX;
2989
 
                print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
2990
 
                sys(qw/git-read-tree/, $parent);
2991
 
                return libsvn_fetch($parent, $paths, $rev,
2992
 
                                        $author, $date, $msg);
2993
 
        }
2994
 
        print STDERR "Nope, branch point not imported or unknown\n";
2995
 
        return undef;
2996
 
}
2997
 
 
2998
 
sub libsvn_get_log {
2999
 
        my ($ra, @args) = @_;
3000
 
        if ($SVN::Core::VERSION le '1.2.0') {
3001
 
                splice(@args, 3, 1);
3002
 
        }
3003
 
        $ra->get_log(@args);
3004
 
}
3005
 
 
3006
 
sub libsvn_new_tree {
3007
 
        if (my $log_entry = libsvn_find_parent_branch(@_)) {
3008
 
                return $log_entry;
3009
 
        }
3010
 
        my ($paths, $rev, $author, $date, $msg) = @_;
3011
 
        open my $gui, '| git-update-index -z --index-info' or croak $!;
3012
 
        libsvn_traverse($gui, '', $SVN_PATH, $rev);
3013
 
        close $gui or croak $?;
3014
 
        return libsvn_log_entry($rev, $author, $date, $msg);
3015
 
}
3016
 
 
3017
 
sub find_graft_path_commit {
3018
 
        my ($tree_paths, $p1, $r1) = @_;
3019
 
        foreach my $x (keys %$tree_paths) {
3020
 
                next unless ($p1 =~ /^\Q$x\E/);
3021
 
                my $i = $tree_paths->{$x};
3022
 
                my ($r0, $parent) = find_rev_before($r1,$i,1);
3023
 
                return $parent if (defined $r0 && $r0 == $r1);
3024
 
                print STDERR "r$r1 of $i not imported\n";
3025
 
                next;
3026
 
        }
3027
 
        return undef;
3028
 
}
3029
 
 
3030
 
sub find_graft_path_parents {
3031
 
        my ($grafts, $tree_paths, $c, $p0, $r0) = @_;
3032
 
        foreach my $x (keys %$tree_paths) {
3033
 
                next unless ($p0 =~ /^\Q$x\E/);
3034
 
                my $i = $tree_paths->{$x};
3035
 
                my ($r, $parent) = find_rev_before($r0, $i, 1);
3036
 
                if (defined $r && defined $parent && revisions_eq($x,$r,$r0)) {
3037
 
                        my ($url_b, undef, $uuid_b) = cmt_metadata($c);
3038
 
                        my ($url_a, undef, $uuid_a) = cmt_metadata($parent);
3039
 
                        next if ($url_a && $url_b && $url_a eq $url_b &&
3040
 
                                                        $uuid_b eq $uuid_a);
3041
 
                        $grafts->{$c}->{$parent} = 1;
3042
 
                }
3043
 
        }
3044
 
}
3045
 
 
3046
 
sub libsvn_graft_file_copies {
3047
 
        my ($grafts, $tree_paths, $path, $paths, $rev) = @_;
3048
 
        foreach (keys %$paths) {
3049
 
                my $i = $paths->{$_};
3050
 
                my ($m, $p0, $r0) = ($i->action, $i->copyfrom_path,
3051
 
                                        $i->copyfrom_rev);
3052
 
                next unless (defined $p0 && defined $r0);
3053
 
 
3054
 
                my $p1 = $_;
3055
 
                $p1 =~ s#^/##;
3056
 
                $p0 =~ s#^/##;
3057
 
                my $c = find_graft_path_commit($tree_paths, $p1, $rev);
3058
 
                next unless $c;
3059
 
                find_graft_path_parents($grafts, $tree_paths, $c, $p0, $r0);
3060
 
        }
3061
 
}
3062
 
 
3063
 
sub set_index {
3064
 
        my $old = $ENV{GIT_INDEX_FILE};
3065
 
        $ENV{GIT_INDEX_FILE} = shift;
3066
 
        return $old;
3067
 
}
3068
 
 
3069
 
sub restore_index {
3070
 
        my ($old) = @_;
3071
 
        if (defined $old) {
3072
 
                $ENV{GIT_INDEX_FILE} = $old;
3073
 
        } else {
3074
 
                delete $ENV{GIT_INDEX_FILE};
3075
 
        }
3076
 
}
3077
 
 
3078
 
sub libsvn_commit_cb {
3079
 
        my ($rev, $date, $committer, $c, $msg, $r_last, $cmt_last) = @_;
3080
 
        if ($_optimize_commits && $rev == ($r_last + 1)) {
3081
 
                my $log = libsvn_log_entry($rev,$committer,$date,$msg);
3082
 
                $log->{tree} = get_tree_from_treeish($c);
3083
 
                my $cmt = git_commit($log, $cmt_last, $c);
3084
 
                my @diff = safe_qx('git-diff-tree', $cmt, $c);
3085
 
                if (@diff) {
3086
 
                        print STDERR "Trees differ: $cmt $c\n",
3087
 
                                        join('',@diff),"\n";
3088
 
                        exit 1;
3089
 
                }
3090
 
        } else {
3091
 
                fetch("$rev=$c");
3092
 
        }
3093
 
}
3094
 
 
3095
 
sub libsvn_ls_fullurl {
3096
 
        my $fullurl = shift;
3097
 
        my ($repo, $path) = repo_path_split($fullurl);
3098
 
        $SVN ||= libsvn_connect($repo);
3099
 
        my @ret;
3100
 
        my $pool = SVN::Pool->new;
3101
 
        my ($dirent, undef, undef) = $SVN->get_dir($path,
3102
 
                                                $SVN->get_latest_revnum, $pool);
3103
 
        foreach my $d (keys %$dirent) {
3104
 
                if ($dirent->{$d}->kind == $SVN::Node::dir) {
3105
 
                        push @ret, "$d/"; # add '/' for compat with cli svn
3106
 
                }
3107
 
        }
3108
 
        $pool->clear;
3109
 
        return @ret;
3110
 
}
3111
 
 
3112
 
 
3113
 
sub libsvn_skip_unknown_revs {
3114
 
        my $err = shift;
3115
 
        my $errno = $err->apr_err();
3116
 
        # Maybe the branch we're tracking didn't
3117
 
        # exist when the repo started, so it's
3118
 
        # not an error if it doesn't, just continue
3119
 
        #
3120
 
        # Wonderfully consistent library, eh?
3121
 
        # 160013 - svn:// and file://
3122
 
        # 175002 - http(s)://
3123
 
        #   More codes may be discovered later...
3124
 
        if ($errno == 175002 || $errno == 160013) {
 
1673
        my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from);
 
1674
        unless ($gs) {
 
1675
                my $ref_id = $self->{ref_id};
 
1676
                $ref_id =~ s/\@\d+$//;
 
1677
                $ref_id .= "\@$r";
 
1678
                # just grow a tail if we're not unique enough :x
 
1679
                $ref_id .= '-' while find_ref($ref_id);
 
1680
                print STDERR "Initializing parent: $ref_id\n";
 
1681
                $gs = Git::SVN->init($new_url, '', $ref_id, $ref_id, 1);
 
1682
        }
 
1683
        my ($r0, $parent) = $gs->find_rev_before($r, 1);
 
1684
        if (!defined $r0 || !defined $parent) {
 
1685
                my ($base, $head) = parse_revision_argument(0, $r);
 
1686
                if ($base <= $r) {
 
1687
                        $gs->fetch($base, $r);
 
1688
                }
 
1689
                ($r0, $parent) = $gs->last_rev_commit;
 
1690
        }
 
1691
        if (defined $r0 && defined $parent) {
 
1692
                print STDERR "Found branch parent: ($self->{ref_id}) $parent\n";
 
1693
                my $ed;
 
1694
                if ($self->ra->can_do_switch) {
 
1695
                        $self->assert_index_clean($parent);
 
1696
                        print STDERR "Following parent with do_switch\n";
 
1697
                        # do_switch works with svn/trunk >= r22312, but that
 
1698
                        # is not included with SVN 1.4.3 (the latest version
 
1699
                        # at the moment), so we can't rely on it
 
1700
                        $self->{last_commit} = $parent;
 
1701
                        $ed = SVN::Git::Fetcher->new($self);
 
1702
                        $gs->ra->gs_do_switch($r0, $rev, $gs,
 
1703
                                              $self->full_url, $ed)
 
1704
                          or die "SVN connection failed somewhere...\n";
 
1705
                } else {
 
1706
                        print STDERR "Following parent with do_update\n";
 
1707
                        $ed = SVN::Git::Fetcher->new($self);
 
1708
                        $self->ra->gs_do_update($rev, $rev, $self, $ed)
 
1709
                          or die "SVN connection failed somewhere...\n";
 
1710
                }
 
1711
                print STDERR "Successfully followed parent\n";
 
1712
                return $self->make_log_entry($rev, [$parent], $ed);
 
1713
        }
 
1714
        return undef;
 
1715
}
 
1716
 
 
1717
sub do_fetch {
 
1718
        my ($self, $paths, $rev) = @_;
 
1719
        my $ed;
 
1720
        my ($last_rev, @parents);
 
1721
        if (my $lc = $self->last_commit) {
 
1722
                # we can have a branch that was deleted, then re-added
 
1723
                # under the same name but copied from another path, in
 
1724
                # which case we'll have multiple parents (we don't
 
1725
                # want to break the original ref, nor lose copypath info):
 
1726
                if (my $log_entry = $self->find_parent_branch($paths, $rev)) {
 
1727
                        push @{$log_entry->{parents}}, $lc;
 
1728
                        return $log_entry;
 
1729
                }
 
1730
                $ed = SVN::Git::Fetcher->new($self);
 
1731
                $last_rev = $self->{last_rev};
 
1732
                $ed->{c} = $lc;
 
1733
                @parents = ($lc);
 
1734
        } else {
 
1735
                $last_rev = $rev;
 
1736
                if (my $log_entry = $self->find_parent_branch($paths, $rev)) {
 
1737
                        return $log_entry;
 
1738
                }
 
1739
                $ed = SVN::Git::Fetcher->new($self);
 
1740
        }
 
1741
        unless ($self->ra->gs_do_update($last_rev, $rev, $self, $ed)) {
 
1742
                die "SVN connection failed somewhere...\n";
 
1743
        }
 
1744
        $self->make_log_entry($rev, \@parents, $ed);
 
1745
}
 
1746
 
 
1747
sub get_untracked {
 
1748
        my ($self, $ed) = @_;
 
1749
        my @out;
 
1750
        my $h = $ed->{empty};
 
1751
        foreach (sort keys %$h) {
 
1752
                my $act = $h->{$_} ? '+empty_dir' : '-empty_dir';
 
1753
                push @out, "  $act: " . uri_encode($_);
 
1754
                warn "W: $act: $_\n";
 
1755
        }
 
1756
        foreach my $t (qw/dir_prop file_prop/) {
 
1757
                $h = $ed->{$t} or next;
 
1758
                foreach my $path (sort keys %$h) {
 
1759
                        my $ppath = $path eq '' ? '.' : $path;
 
1760
                        foreach my $prop (sort keys %{$h->{$path}}) {
 
1761
                                next if $SKIP_PROP{$prop};
 
1762
                                my $v = $h->{$path}->{$prop};
 
1763
                                my $t_ppath_prop = "$t: " .
 
1764
                                                    uri_encode($ppath) . ' ' .
 
1765
                                                    uri_encode($prop);
 
1766
                                if (defined $v) {
 
1767
                                        push @out, "  +$t_ppath_prop " .
 
1768
                                                   uri_encode($v);
 
1769
                                } else {
 
1770
                                        push @out, "  -$t_ppath_prop";
 
1771
                                }
 
1772
                        }
 
1773
                }
 
1774
        }
 
1775
        foreach my $t (qw/absent_file absent_directory/) {
 
1776
                $h = $ed->{$t} or next;
 
1777
                foreach my $parent (sort keys %$h) {
 
1778
                        foreach my $path (sort @{$h->{$parent}}) {
 
1779
                                push @out, "  $t: " .
 
1780
                                           uri_encode("$parent/$path");
 
1781
                                warn "W: $t: $parent/$path ",
 
1782
                                     "Insufficient permissions?\n";
 
1783
                        }
 
1784
                }
 
1785
        }
 
1786
        \@out;
 
1787
}
 
1788
 
 
1789
sub parse_svn_date {
 
1790
        my $date = shift || return '+0000 1970-01-01 00:00:00';
 
1791
        my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
 
1792
                                            (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or
 
1793
                                         croak "Unable to parse date: $date\n";
 
1794
        "+0000 $Y-$m-$d $H:$M:$S";
 
1795
}
 
1796
 
 
1797
sub check_author {
 
1798
        my ($author) = @_;
 
1799
        if (!defined $author || length $author == 0) {
 
1800
                $author = '(no author)';
 
1801
        }
 
1802
        if (defined $::_authors && ! defined $::users{$author}) {
 
1803
                die "Author: $author not defined in $::_authors file\n";
 
1804
        }
 
1805
        $author;
 
1806
}
 
1807
 
 
1808
sub make_log_entry {
 
1809
        my ($self, $rev, $parents, $ed) = @_;
 
1810
        my $untracked = $self->get_untracked($ed);
 
1811
 
 
1812
        open my $un, '>>', "$self->{dir}/unhandled.log" or croak $!;
 
1813
        print $un "r$rev\n" or croak $!;
 
1814
        print $un $_, "\n" foreach @$untracked;
 
1815
        my %log_entry = ( parents => $parents || [], revision => $rev,
 
1816
                          log => '');
 
1817
 
 
1818
        my $headrev;
 
1819
        my $logged = delete $self->{logged_rev_props};
 
1820
        if (!$logged || $self->{-want_revprops}) {
 
1821
                my $rp = $self->ra->rev_proplist($rev);
 
1822
                foreach (sort keys %$rp) {
 
1823
                        my $v = $rp->{$_};
 
1824
                        if (/^svn:(author|date|log)$/) {
 
1825
                                $log_entry{$1} = $v;
 
1826
                        } elsif ($_ eq 'svm:headrev') {
 
1827
                                $headrev = $v;
 
1828
                        } else {
 
1829
                                print $un "  rev_prop: ", uri_encode($_), ' ',
 
1830
                                          uri_encode($v), "\n";
 
1831
                        }
 
1832
                }
 
1833
        } else {
 
1834
                map { $log_entry{$_} = $logged->{$_} } keys %$logged;
 
1835
        }
 
1836
        close $un or croak $!;
 
1837
 
 
1838
        $log_entry{date} = parse_svn_date($log_entry{date});
 
1839
        $log_entry{log} .= "\n";
 
1840
        my $author = $log_entry{author} = check_author($log_entry{author});
 
1841
        my ($name, $email) = defined $::users{$author} ? @{$::users{$author}}
 
1842
                                                       : ($author, undef);
 
1843
        if (defined $headrev && $self->use_svm_props) {
 
1844
                if ($self->rewrite_root) {
 
1845
                        die "Can't have both 'useSvmProps' and 'rewriteRoot' ",
 
1846
                            "options set!\n";
 
1847
                }
 
1848
                my ($uuid, $r) = $headrev =~ m{^([a-f\d\-]{30,}):(\d+)$};
 
1849
                # we don't want "SVM: initializing mirror for junk" ...
 
1850
                return undef if $r == 0;
 
1851
                my $svm = $self->svm;
 
1852
                if ($uuid ne $svm->{uuid}) {
 
1853
                        die "UUID mismatch on SVM path:\n",
 
1854
                            "expected: $svm->{uuid}\n",
 
1855
                            "     got: $uuid\n";
 
1856
                }
 
1857
                my $full_url = $self->full_url;
 
1858
                $full_url =~ s#^\Q$svm->{replace}\E(/|$)#$svm->{source}$1# or
 
1859
                             die "Failed to replace '$svm->{replace}' with ",
 
1860
                                 "'$svm->{source}' in $full_url\n";
 
1861
                # throw away username for storing in records
 
1862
                remove_username($full_url);
 
1863
                $log_entry{metadata} = "$full_url\@$r $uuid";
 
1864
                $log_entry{svm_revision} = $r;
 
1865
                $email ||= "$author\@$uuid"
 
1866
        } elsif ($self->use_svnsync_props) {
 
1867
                my $full_url = $self->svnsync->{url};
 
1868
                $full_url .= "/$self->{path}" if length $self->{path};
 
1869
                my $uuid = $self->svnsync->{uuid};
 
1870
                $log_entry{metadata} = "$full_url\@$rev $uuid";
 
1871
                $email ||= "$author\@$uuid"
 
1872
        } else {
 
1873
                $log_entry{metadata} = $self->metadata_url. "\@$rev " .
 
1874
                                       $self->ra->get_uuid;
 
1875
                $email ||= "$author\@" . $self->ra->get_uuid;
 
1876
        }
 
1877
        $log_entry{name} = $name;
 
1878
        $log_entry{email} = $email;
 
1879
        \%log_entry;
 
1880
}
 
1881
 
 
1882
sub fetch {
 
1883
        my ($self, $min_rev, $max_rev, @parents) = @_;
 
1884
        my ($last_rev, $last_commit) = $self->last_rev_commit;
 
1885
        my ($base, $head) = $self->get_fetch_range($min_rev, $max_rev);
 
1886
        $self->ra->gs_fetch_loop_common($base, $head, [$self]);
 
1887
}
 
1888
 
 
1889
sub set_tree_cb {
 
1890
        my ($self, $log_entry, $tree, $rev, $date, $author) = @_;
 
1891
        $self->{inject_parents} = { $rev => $tree };
 
1892
        $self->fetch(undef, undef);
 
1893
}
 
1894
 
 
1895
sub set_tree {
 
1896
        my ($self, $tree) = (shift, shift);
 
1897
        my $log_entry = ::get_commit_entry($tree);
 
1898
        unless ($self->{last_rev}) {
 
1899
                fatal("Must have an existing revision to commit\n");
 
1900
        }
 
1901
        my %ed_opts = ( r => $self->{last_rev},
 
1902
                        log => $log_entry->{log},
 
1903
                        ra => $self->ra,
 
1904
                        tree_a => $self->{last_commit},
 
1905
                        tree_b => $tree,
 
1906
                        editor_cb => sub {
 
1907
                               $self->set_tree_cb($log_entry, $tree, @_) },
 
1908
                        svn_path => $self->{path} );
 
1909
        if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
 
1910
                print "No changes\nr$self->{last_rev} = $tree\n";
 
1911
        }
 
1912
}
 
1913
 
 
1914
sub rebuild {
 
1915
        my ($self) = @_;
 
1916
        my $db_path = $self->db_path;
 
1917
        return if (-e $db_path && ! -z $db_path);
 
1918
        return unless ::verify_ref($self->refname.'^0');
 
1919
        if (-f $self->{db_root}) {
 
1920
                rename $self->{db_root}, $db_path or die
 
1921
                     "rename $self->{db_root} => $db_path failed: $!\n";
 
1922
                my ($dir, $base) = ($db_path =~ m#^(.*?)/?([^/]+)$#);
 
1923
                symlink $base, $self->{db_root} or die
 
1924
                     "symlink $base => $self->{db_root} failed: $!\n";
3125
1925
                return;
3126
1926
        }
3127
 
        croak "Error from SVN, ($errno): ", $err->expanded_message,"\n";
3128
 
};
3129
 
 
 
1927
        print "Rebuilding $db_path ...\n";
 
1928
        my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname);
 
1929
        my $latest;
 
1930
        my $full_url = $self->full_url;
 
1931
        remove_username($full_url);
 
1932
        my $svn_uuid;
 
1933
        while (<$rev_list>) {
 
1934
                chomp;
 
1935
                my $c = $_;
 
1936
                die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o;
 
1937
                my ($url, $rev, $uuid) = ::cmt_metadata($c);
 
1938
                remove_username($url);
 
1939
 
 
1940
                # ignore merges (from set-tree)
 
1941
                next if (!defined $rev || !$uuid);
 
1942
 
 
1943
                # if we merged or otherwise started elsewhere, this is
 
1944
                # how we break out of it
 
1945
                if ((defined $svn_uuid && ($uuid ne $svn_uuid)) ||
 
1946
                    ($full_url && $url && ($url ne $full_url))) {
 
1947
                        next;
 
1948
                }
 
1949
                $latest ||= $rev;
 
1950
                $svn_uuid ||= $uuid;
 
1951
 
 
1952
                $self->rev_db_set($rev, $c);
 
1953
                print "r$rev = $c\n";
 
1954
        }
 
1955
        command_close_pipe($rev_list, $ctx);
 
1956
        print "Done rebuilding $db_path\n";
 
1957
}
 
1958
 
 
1959
# rev_db:
3130
1960
# Tie::File seems to be prone to offset errors if revisions get sparse,
3131
1961
# it's not that fast, either.  Tie::File is also not in Perl 5.6.  So
3132
1962
# one of my favorite modules is out :<  Next up would be one of the DBM
3137
1967
# to a revision: (41 * rev) is the byte offset.
3138
1968
# A record of 40 0s denotes an empty revision.
3139
1969
# And yes, it's still pretty fast (faster than Tie::File).
3140
 
sub revdb_set {
3141
 
        my ($file, $rev, $commit) = @_;
3142
 
        length $commit == 40 or croak "arg3 must be a full SHA1 hexsum\n";
3143
 
        open my $fh, '+<', $file or croak $!;
 
1970
# These files are disposable unless noMetadata or useSvmProps is set
 
1971
 
 
1972
sub _rev_db_set {
 
1973
        my ($fh, $rev, $commit) = @_;
3144
1974
        my $offset = $rev * 41;
3145
1975
        # assume that append is the common case:
3146
1976
        seek $fh, 0, 2 or croak $!;
3147
1977
        my $pos = tell $fh;
3148
1978
        if ($pos < $offset) {
3149
 
                print $fh (('0' x 40),"\n") x (($offset - $pos) / 41);
 
1979
                for (1 .. (($offset - $pos) / 41)) {
 
1980
                        print $fh (('0' x 40),"\n") or croak $!;
 
1981
                }
3150
1982
        }
3151
1983
        seek $fh, $offset, 0 or croak $!;
3152
 
        print $fh $commit,"\n";
 
1984
        print $fh $commit,"\n" or croak $!;
 
1985
}
 
1986
 
 
1987
sub mkfile {
 
1988
        my ($path) = @_;
 
1989
        unless (-e $path) {
 
1990
                my ($dir, $base) = ($path =~ m#^(.*?)/?([^/]+)$#);
 
1991
                mkpath([$dir]) unless -d $dir;
 
1992
                open my $fh, '>>', $path or die "Couldn't create $path: $!\n";
 
1993
                close $fh or die "Couldn't close (create) $path: $!\n";
 
1994
        }
 
1995
}
 
1996
 
 
1997
sub rev_db_set {
 
1998
        my ($self, $rev, $commit, $update_ref, $uuid) = @_;
 
1999
        length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n";
 
2000
        my $db = $self->db_path($uuid);
 
2001
        my $db_lock = "$db.lock";
 
2002
        my $sig;
 
2003
        if ($update_ref) {
 
2004
                $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
 
2005
                            $SIG{USR1} = $SIG{USR2} = sub { $sig = $_[0] };
 
2006
        }
 
2007
        mkfile($db);
 
2008
 
 
2009
        $LOCKFILES{$db_lock} = 1;
 
2010
        my $sync;
 
2011
        # both of these options make our .rev_db file very, very important
 
2012
        # and we can't afford to lose it because rebuild() won't work
 
2013
        if ($self->use_svm_props || $self->no_metadata) {
 
2014
                $sync = 1;
 
2015
                copy($db, $db_lock) or die "rev_db_set(@_): ",
 
2016
                                           "Failed to copy: ",
 
2017
                                           "$db => $db_lock ($!)\n";
 
2018
        } else {
 
2019
                rename $db, $db_lock or die "rev_db_set(@_): ",
 
2020
                                            "Failed to rename: ",
 
2021
                                            "$db => $db_lock ($!)\n";
 
2022
        }
 
2023
        open my $fh, '+<', $db_lock or die "Couldn't open $db_lock: $!\n";
 
2024
        _rev_db_set($fh, $rev, $commit);
 
2025
        if ($sync) {
 
2026
                $fh->flush or die "Couldn't flush $db_lock: $!\n";
 
2027
                $fh->sync or die "Couldn't sync $db_lock: $!\n";
 
2028
        }
3153
2029
        close $fh or croak $!;
3154
 
}
3155
 
 
3156
 
sub revdb_get {
3157
 
        my ($file, $rev) = @_;
 
2030
        if ($update_ref) {
 
2031
                $_head = $self;
 
2032
                command_noisy('update-ref', '-m', "r$rev",
 
2033
                              $self->refname, $commit);
 
2034
        }
 
2035
        rename $db_lock, $db or die "rev_db_set(@_): ", "Failed to rename: ",
 
2036
                                    "$db_lock => $db ($!)\n";
 
2037
        delete $LOCKFILES{$db_lock};
 
2038
        if ($update_ref) {
 
2039
                $SIG{INT} = $SIG{HUP} = $SIG{TERM} = $SIG{ALRM} = $SIG{PIPE} =
 
2040
                            $SIG{USR1} = $SIG{USR2} = 'DEFAULT';
 
2041
                kill $sig, $$ if defined $sig;
 
2042
        }
 
2043
}
 
2044
 
 
2045
sub rev_db_max {
 
2046
        my ($self) = @_;
 
2047
        $self->rebuild;
 
2048
        my $db_path = $self->db_path;
 
2049
        my @stat = stat $db_path or return 0;
 
2050
        ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n";
 
2051
        my $max = $stat[7] / 41;
 
2052
        (($max > 0) ? $max - 1 : 0);
 
2053
}
 
2054
 
 
2055
sub rev_db_get {
 
2056
        my ($self, $rev, $uuid) = @_;
3158
2057
        my $ret;
3159
2058
        my $offset = $rev * 41;
3160
 
        open my $fh, '<', $file or croak $!;
3161
 
        seek $fh, $offset, 0;
3162
 
        if (tell $fh == $offset) {
3163
 
                $ret = readline $fh;
3164
 
                if (defined $ret) {
3165
 
                        chomp $ret;
3166
 
                        $ret = undef if ($ret =~ /^0{40}$/);
3167
 
                }
 
2059
        my $db_path = $self->db_path($uuid);
 
2060
        return undef unless -e $db_path;
 
2061
        open my $fh, '<', $db_path or croak $!;
 
2062
        if (sysseek($fh, $offset, 0) == $offset) {
 
2063
                my $read = sysread($fh, $ret, 40);
 
2064
                $ret = undef if ($read != 40 || $ret eq ('0'x40));
3168
2065
        }
3169
2066
        close $fh or croak $!;
3170
 
        return $ret;
3171
 
}
3172
 
 
3173
 
sub copy_remote_ref {
3174
 
        my $origin = $_cp_remote ? $_cp_remote : 'origin';
3175
 
        my $ref = "refs/remotes/$GIT_SVN";
3176
 
        if (safe_qx('git-ls-remote', $origin, $ref)) {
3177
 
                sys(qw/git fetch/, $origin, "$ref:$ref");
3178
 
        } elsif ($_cp_remote && !$_upgrade) {
3179
 
                die "Unable to find remote reference: ",
3180
 
                                "refs/remotes/$GIT_SVN on $origin\n";
3181
 
        }
 
2067
        $ret;
 
2068
}
 
2069
 
 
2070
sub find_rev_before {
 
2071
        my ($self, $rev, $eq_ok) = @_;
 
2072
        --$rev unless $eq_ok;
 
2073
        while ($rev > 0) {
 
2074
                if (my $c = $self->rev_db_get($rev)) {
 
2075
                        return ($rev, $c);
 
2076
                }
 
2077
                --$rev;
 
2078
        }
 
2079
        return (undef, undef);
 
2080
}
 
2081
 
 
2082
sub _new {
 
2083
        my ($class, $repo_id, $ref_id, $path) = @_;
 
2084
        unless (defined $repo_id && length $repo_id) {
 
2085
                $repo_id = $Git::SVN::default_repo_id;
 
2086
        }
 
2087
        unless (defined $ref_id && length $ref_id) {
 
2088
                $_[2] = $ref_id = $Git::SVN::default_ref_id;
 
2089
        }
 
2090
        $_[1] = $repo_id = sanitize_remote_name($repo_id);
 
2091
        my $dir = "$ENV{GIT_DIR}/svn/$ref_id";
 
2092
        $_[3] = $path = '' unless (defined $path);
 
2093
        mkpath(["$ENV{GIT_DIR}/svn"]);
 
2094
        bless {
 
2095
                ref_id => $ref_id, dir => $dir, index => "$dir/index",
 
2096
                path => $path, config => "$ENV{GIT_DIR}/svn/config",
 
2097
                db_root => "$dir/.rev_db", repo_id => $repo_id }, $class;
 
2098
}
 
2099
 
 
2100
sub db_path {
 
2101
        my ($self, $uuid) = @_;
 
2102
        $uuid ||= $self->ra_uuid;
 
2103
        "$self->{db_root}.$uuid";
 
2104
}
 
2105
 
 
2106
sub uri_encode {
 
2107
        my ($f) = @_;
 
2108
        $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg;
 
2109
        $f
 
2110
}
 
2111
 
 
2112
sub remove_username {
 
2113
        $_[0] =~ s{^([^:]*://)[^@]+@}{$1};
 
2114
}
 
2115
 
 
2116
package Git::SVN::Prompt;
 
2117
use strict;
 
2118
use warnings;
 
2119
require SVN::Core;
 
2120
use vars qw/$_no_auth_cache $_username/;
 
2121
 
 
2122
sub simple {
 
2123
        my ($cred, $realm, $default_username, $may_save, $pool) = @_;
 
2124
        $may_save = undef if $_no_auth_cache;
 
2125
        $default_username = $_username if defined $_username;
 
2126
        if (defined $default_username && length $default_username) {
 
2127
                if (defined $realm && length $realm) {
 
2128
                        print STDERR "Authentication realm: $realm\n";
 
2129
                        STDERR->flush;
 
2130
                }
 
2131
                $cred->username($default_username);
 
2132
        } else {
 
2133
                username($cred, $realm, $may_save, $pool);
 
2134
        }
 
2135
        $cred->password(_read_password("Password for '" .
 
2136
                                       $cred->username . "': ", $realm));
 
2137
        $cred->may_save($may_save);
 
2138
        $SVN::_Core::SVN_NO_ERROR;
 
2139
}
 
2140
 
 
2141
sub ssl_server_trust {
 
2142
        my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
 
2143
        $may_save = undef if $_no_auth_cache;
 
2144
        print STDERR "Error validating server certificate for '$realm':\n";
 
2145
        if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
 
2146
                print STDERR " - The certificate is not issued by a trusted ",
 
2147
                      "authority. Use the\n",
 
2148
                      "   fingerprint to validate the certificate manually!\n";
 
2149
        }
 
2150
        if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
 
2151
                print STDERR " - The certificate hostname does not match.\n";
 
2152
        }
 
2153
        if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
 
2154
                print STDERR " - The certificate is not yet valid.\n";
 
2155
        }
 
2156
        if ($failures & $SVN::Auth::SSL::EXPIRED) {
 
2157
                print STDERR " - The certificate has expired.\n";
 
2158
        }
 
2159
        if ($failures & $SVN::Auth::SSL::OTHER) {
 
2160
                print STDERR " - The certificate has an unknown error.\n";
 
2161
        }
 
2162
        printf STDERR
 
2163
                "Certificate information:\n".
 
2164
                " - Hostname: %s\n".
 
2165
                " - Valid: from %s until %s\n".
 
2166
                " - Issuer: %s\n".
 
2167
                " - Fingerprint: %s\n",
 
2168
                map $cert_info->$_, qw(hostname valid_from valid_until
 
2169
                                       issuer_dname fingerprint);
 
2170
        my $choice;
 
2171
prompt:
 
2172
        print STDERR $may_save ?
 
2173
              "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
 
2174
              "(R)eject or accept (t)emporarily? ";
 
2175
        STDERR->flush;
 
2176
        $choice = lc(substr(<STDIN> || 'R', 0, 1));
 
2177
        if ($choice =~ /^t$/i) {
 
2178
                $cred->may_save(undef);
 
2179
        } elsif ($choice =~ /^r$/i) {
 
2180
                return -1;
 
2181
        } elsif ($may_save && $choice =~ /^p$/i) {
 
2182
                $cred->may_save($may_save);
 
2183
        } else {
 
2184
                goto prompt;
 
2185
        }
 
2186
        $cred->accepted_failures($failures);
 
2187
        $SVN::_Core::SVN_NO_ERROR;
 
2188
}
 
2189
 
 
2190
sub ssl_client_cert {
 
2191
        my ($cred, $realm, $may_save, $pool) = @_;
 
2192
        $may_save = undef if $_no_auth_cache;
 
2193
        print STDERR "Client certificate filename: ";
 
2194
        STDERR->flush;
 
2195
        chomp(my $filename = <STDIN>);
 
2196
        $cred->cert_file($filename);
 
2197
        $cred->may_save($may_save);
 
2198
        $SVN::_Core::SVN_NO_ERROR;
 
2199
}
 
2200
 
 
2201
sub ssl_client_cert_pw {
 
2202
        my ($cred, $realm, $may_save, $pool) = @_;
 
2203
        $may_save = undef if $_no_auth_cache;
 
2204
        $cred->password(_read_password("Password: ", $realm));
 
2205
        $cred->may_save($may_save);
 
2206
        $SVN::_Core::SVN_NO_ERROR;
 
2207
}
 
2208
 
 
2209
sub username {
 
2210
        my ($cred, $realm, $may_save, $pool) = @_;
 
2211
        $may_save = undef if $_no_auth_cache;
 
2212
        if (defined $realm && length $realm) {
 
2213
                print STDERR "Authentication realm: $realm\n";
 
2214
        }
 
2215
        my $username;
 
2216
        if (defined $_username) {
 
2217
                $username = $_username;
 
2218
        } else {
 
2219
                print STDERR "Username: ";
 
2220
                STDERR->flush;
 
2221
                chomp($username = <STDIN>);
 
2222
        }
 
2223
        $cred->username($username);
 
2224
        $cred->may_save($may_save);
 
2225
        $SVN::_Core::SVN_NO_ERROR;
 
2226
}
 
2227
 
 
2228
sub _read_password {
 
2229
        my ($prompt, $realm) = @_;
 
2230
        print STDERR $prompt;
 
2231
        STDERR->flush;
 
2232
        require Term::ReadKey;
 
2233
        Term::ReadKey::ReadMode('noecho');
 
2234
        my $password = '';
 
2235
        while (defined(my $key = Term::ReadKey::ReadKey(0))) {
 
2236
                last if $key =~ /[\012\015]/; # \n\r
 
2237
                $password .= $key;
 
2238
        }
 
2239
        Term::ReadKey::ReadMode('restore');
 
2240
        print STDERR "\n";
 
2241
        STDERR->flush;
 
2242
        $password;
 
2243
}
 
2244
 
 
2245
package main;
 
2246
 
 
2247
{
 
2248
        my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
 
2249
                                $SVN::Node::dir.$SVN::Node::unknown.
 
2250
                                $SVN::Node::none.$SVN::Node::file.
 
2251
                                $SVN::Node::dir.$SVN::Node::unknown.
 
2252
                                $SVN::Auth::SSL::CNMISMATCH.
 
2253
                                $SVN::Auth::SSL::NOTYETVALID.
 
2254
                                $SVN::Auth::SSL::EXPIRED.
 
2255
                                $SVN::Auth::SSL::UNKNOWNCA.
 
2256
                                $SVN::Auth::SSL::OTHER;
 
2257
}
 
2258
 
 
2259
package SVN::Git::Fetcher;
 
2260
use vars qw/@ISA/;
 
2261
use strict;
 
2262
use warnings;
 
2263
use Carp qw/croak/;
 
2264
use IO::File qw//;
 
2265
use Digest::MD5;
 
2266
 
 
2267
# file baton members: path, mode_a, mode_b, pool, fh, blob, base
 
2268
sub new {
 
2269
        my ($class, $git_svn) = @_;
 
2270
        my $self = SVN::Delta::Editor->new;
 
2271
        bless $self, $class;
 
2272
        $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit};
 
2273
        $self->{empty} = {};
 
2274
        $self->{dir_prop} = {};
 
2275
        $self->{file_prop} = {};
 
2276
        $self->{absent_dir} = {};
 
2277
        $self->{absent_file} = {};
 
2278
        $self->{gii} = $git_svn->tmp_index_do(sub { Git::IndexInfo->new });
 
2279
        $self;
 
2280
}
 
2281
 
 
2282
sub set_path_strip {
 
2283
        my ($self, $path) = @_;
 
2284
        $self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path;
 
2285
}
 
2286
 
 
2287
sub open_root {
 
2288
        { path => '' };
 
2289
}
 
2290
 
 
2291
sub open_directory {
 
2292
        my ($self, $path, $pb, $rev) = @_;
 
2293
        { path => $path };
 
2294
}
 
2295
 
 
2296
sub git_path {
 
2297
        my ($self, $path) = @_;
 
2298
        if ($self->{path_strip}) {
 
2299
                $path =~ s!$self->{path_strip}!! or
 
2300
                  die "Failed to strip path '$path' ($self->{path_strip})\n";
 
2301
        }
 
2302
        $path;
 
2303
}
 
2304
 
 
2305
sub delete_entry {
 
2306
        my ($self, $path, $rev, $pb) = @_;
 
2307
 
 
2308
        my $gpath = $self->git_path($path);
 
2309
        return undef if ($gpath eq '');
 
2310
 
 
2311
        # remove entire directories.
 
2312
        if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) {
 
2313
                my ($ls, $ctx) = command_output_pipe(qw/ls-tree
 
2314
                                                     -r --name-only -z/,
 
2315
                                                     $self->{c}, '--', $gpath);
 
2316
                local $/ = "\0";
 
2317
                while (<$ls>) {
 
2318
                        chomp;
 
2319
                        $self->{gii}->remove($_);
 
2320
                        print "\tD\t$_\n" unless $::_q;
 
2321
                }
 
2322
                print "\tD\t$gpath/\n" unless $::_q;
 
2323
                command_close_pipe($ls, $ctx);
 
2324
                $self->{empty}->{$path} = 0
 
2325
        } else {
 
2326
                $self->{gii}->remove($gpath);
 
2327
                print "\tD\t$gpath\n" unless $::_q;
 
2328
        }
 
2329
        undef;
 
2330
}
 
2331
 
 
2332
sub open_file {
 
2333
        my ($self, $path, $pb, $rev) = @_;
 
2334
        my $gpath = $self->git_path($path);
 
2335
        my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
 
2336
                             =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
 
2337
        unless (defined $mode && defined $blob) {
 
2338
                die "$path was not found in commit $self->{c} (r$rev)\n";
 
2339
        }
 
2340
        { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
 
2341
          pool => SVN::Pool->new, action => 'M' };
 
2342
}
 
2343
 
 
2344
sub add_file {
 
2345
        my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
 
2346
        my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
 
2347
        delete $self->{empty}->{$dir};
 
2348
        { path => $path, mode_a => 100644, mode_b => 100644,
 
2349
          pool => SVN::Pool->new, action => 'A' };
 
2350
}
 
2351
 
 
2352
sub add_directory {
 
2353
        my ($self, $path, $cp_path, $cp_rev) = @_;
 
2354
        my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
 
2355
        delete $self->{empty}->{$dir};
 
2356
        $self->{empty}->{$path} = 1;
 
2357
        { path => $path };
 
2358
}
 
2359
 
 
2360
sub change_dir_prop {
 
2361
        my ($self, $db, $prop, $value) = @_;
 
2362
        $self->{dir_prop}->{$db->{path}} ||= {};
 
2363
        $self->{dir_prop}->{$db->{path}}->{$prop} = $value;
 
2364
        undef;
 
2365
}
 
2366
 
 
2367
sub absent_directory {
 
2368
        my ($self, $path, $pb) = @_;
 
2369
        $self->{absent_dir}->{$pb->{path}} ||= [];
 
2370
        push @{$self->{absent_dir}->{$pb->{path}}}, $path;
 
2371
        undef;
 
2372
}
 
2373
 
 
2374
sub absent_file {
 
2375
        my ($self, $path, $pb) = @_;
 
2376
        $self->{absent_file}->{$pb->{path}} ||= [];
 
2377
        push @{$self->{absent_file}->{$pb->{path}}}, $path;
 
2378
        undef;
 
2379
}
 
2380
 
 
2381
sub change_file_prop {
 
2382
        my ($self, $fb, $prop, $value) = @_;
 
2383
        if ($prop eq 'svn:executable') {
 
2384
                if ($fb->{mode_b} != 120000) {
 
2385
                        $fb->{mode_b} = defined $value ? 100755 : 100644;
 
2386
                }
 
2387
        } elsif ($prop eq 'svn:special') {
 
2388
                $fb->{mode_b} = defined $value ? 120000 : 100644;
 
2389
        } else {
 
2390
                $self->{file_prop}->{$fb->{path}} ||= {};
 
2391
                $self->{file_prop}->{$fb->{path}}->{$prop} = $value;
 
2392
        }
 
2393
        undef;
 
2394
}
 
2395
 
 
2396
sub apply_textdelta {
 
2397
        my ($self, $fb, $exp) = @_;
 
2398
        my $fh = IO::File->new_tmpfile;
 
2399
        $fh->autoflush(1);
 
2400
        # $fh gets auto-closed() by SVN::TxDelta::apply(),
 
2401
        # (but $base does not,) so dup() it for reading in close_file
 
2402
        open my $dup, '<&', $fh or croak $!;
 
2403
        my $base = IO::File->new_tmpfile;
 
2404
        $base->autoflush(1);
 
2405
        if ($fb->{blob}) {
 
2406
                defined (my $pid = fork) or croak $!;
 
2407
                if (!$pid) {
 
2408
                        open STDOUT, '>&', $base or croak $!;
 
2409
                        print STDOUT 'link ' if ($fb->{mode_a} == 120000);
 
2410
                        exec qw/git-cat-file blob/, $fb->{blob} or croak $!;
 
2411
                }
 
2412
                waitpid $pid, 0;
 
2413
                croak $? if $?;
 
2414
 
 
2415
                if (defined $exp) {
 
2416
                        seek $base, 0, 0 or croak $!;
 
2417
                        my $md5 = Digest::MD5->new;
 
2418
                        $md5->addfile($base);
 
2419
                        my $got = $md5->hexdigest;
 
2420
                        die "Checksum mismatch: $fb->{path} $fb->{blob}\n",
 
2421
                            "expected: $exp\n",
 
2422
                            "     got: $got\n" if ($got ne $exp);
 
2423
                }
 
2424
        }
 
2425
        seek $base, 0, 0 or croak $!;
 
2426
        $fb->{fh} = $dup;
 
2427
        $fb->{base} = $base;
 
2428
        [ SVN::TxDelta::apply($base, $fh, undef, $fb->{path}, $fb->{pool}) ];
 
2429
}
 
2430
 
 
2431
sub close_file {
 
2432
        my ($self, $fb, $exp) = @_;
 
2433
        my $hash;
 
2434
        my $path = $self->git_path($fb->{path});
 
2435
        if (my $fh = $fb->{fh}) {
 
2436
                seek($fh, 0, 0) or croak $!;
 
2437
                my $md5 = Digest::MD5->new;
 
2438
                $md5->addfile($fh);
 
2439
                my $got = $md5->hexdigest;
 
2440
                die "Checksum mismatch: $path\n",
 
2441
                    "expected: $exp\n    got: $got\n" if ($got ne $exp);
 
2442
                seek($fh, 0, 0) or croak $!;
 
2443
                if ($fb->{mode_b} == 120000) {
 
2444
                        read($fh, my $buf, 5) == 5 or croak $!;
 
2445
                        $buf eq 'link ' or die "$path has mode 120000",
 
2446
                                               "but is not a link\n";
 
2447
                }
 
2448
                defined(my $pid = open my $out,'-|') or die "Can't fork: $!\n";
 
2449
                if (!$pid) {
 
2450
                        open STDIN, '<&', $fh or croak $!;
 
2451
                        exec qw/git-hash-object -w --stdin/ or croak $!;
 
2452
                }
 
2453
                chomp($hash = do { local $/; <$out> });
 
2454
                close $out or croak $!;
 
2455
                close $fh or croak $!;
 
2456
                $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
 
2457
                close $fb->{base} or croak $!;
 
2458
        } else {
 
2459
                $hash = $fb->{blob} or die "no blob information\n";
 
2460
        }
 
2461
        $fb->{pool}->clear;
 
2462
        $self->{gii}->update($fb->{mode_b}, $hash, $path) or croak $!;
 
2463
        print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $::_q;
 
2464
        undef;
 
2465
}
 
2466
 
 
2467
sub abort_edit {
 
2468
        my $self = shift;
 
2469
        $self->{nr} = $self->{gii}->{nr};
 
2470
        delete $self->{gii};
 
2471
        $self->SUPER::abort_edit(@_);
 
2472
}
 
2473
 
 
2474
sub close_edit {
 
2475
        my $self = shift;
 
2476
        $self->{git_commit_ok} = 1;
 
2477
        $self->{nr} = $self->{gii}->{nr};
 
2478
        delete $self->{gii};
 
2479
        $self->SUPER::close_edit(@_);
3182
2480
}
3183
2481
 
3184
2482
package SVN::Git::Editor;
3185
 
use vars qw/@ISA/;
 
2483
use vars qw/@ISA $_rmdir $_cp_similarity $_find_copies_harder $_rename_limit/;
3186
2484
use strict;
3187
2485
use warnings;
3188
2486
use Carp qw/croak/;
3189
2487
use IO::File;
 
2488
use Digest::MD5;
3190
2489
 
3191
2490
sub new {
3192
 
        my $class = shift;
3193
 
        my $git_svn = shift;
3194
 
        my $self = SVN::Delta::Editor->new(@_);
 
2491
        my ($class, $opts) = @_;
 
2492
        foreach (qw/svn_path r ra tree_a tree_b log editor_cb/) {
 
2493
                die "$_ required!\n" unless (defined $opts->{$_});
 
2494
        }
 
2495
 
 
2496
        my $pool = SVN::Pool->new;
 
2497
        my $mods = generate_diff($opts->{tree_a}, $opts->{tree_b});
 
2498
        my $types = check_diff_paths($opts->{ra}, $opts->{svn_path},
 
2499
                                     $opts->{r}, $mods);
 
2500
 
 
2501
        # $opts->{ra} functions should not be used after this:
 
2502
        my @ce  = $opts->{ra}->get_commit_editor($opts->{log},
 
2503
                                                $opts->{editor_cb}, $pool);
 
2504
        my $self = SVN::Delta::Editor->new(@ce, $pool);
3195
2505
        bless $self, $class;
3196
 
        foreach (qw/svn_path c r ra /) {
3197
 
                die "$_ required!\n" unless (defined $git_svn->{$_});
3198
 
                $self->{$_} = $git_svn->{$_};
 
2506
        foreach (qw/svn_path r tree_a tree_b/) {
 
2507
                $self->{$_} = $opts->{$_};
3199
2508
        }
3200
 
        $self->{pool} = SVN::Pool->new;
 
2509
        $self->{url} = $opts->{ra}->{url};
 
2510
        $self->{mods} = $mods;
 
2511
        $self->{types} = $types;
 
2512
        $self->{pool} = $pool;
3201
2513
        $self->{bat} = { '' => $self->open_root($self->{r}, $self->{pool}) };
3202
2514
        $self->{rm} = { };
3203
 
        require Digest::MD5;
 
2515
        $self->{path_prefix} = length $self->{svn_path} ?
 
2516
                               "$self->{svn_path}/" : '';
3204
2517
        return $self;
3205
2518
}
3206
2519
 
 
2520
sub generate_diff {
 
2521
        my ($tree_a, $tree_b) = @_;
 
2522
        my @diff_tree = qw(diff-tree -z -r);
 
2523
        if ($_cp_similarity) {
 
2524
                push @diff_tree, "-C$_cp_similarity";
 
2525
        } else {
 
2526
                push @diff_tree, '-C';
 
2527
        }
 
2528
        push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
 
2529
        push @diff_tree, "-l$_rename_limit" if defined $_rename_limit;
 
2530
        push @diff_tree, $tree_a, $tree_b;
 
2531
        my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
 
2532
        local $/ = "\0";
 
2533
        my $state = 'meta';
 
2534
        my @mods;
 
2535
        while (<$diff_fh>) {
 
2536
                chomp $_; # this gets rid of the trailing "\0"
 
2537
                if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
 
2538
                                        $::sha1\s($::sha1)\s
 
2539
                                        ([MTCRAD])\d*$/xo) {
 
2540
                        push @mods, {   mode_a => $1, mode_b => $2,
 
2541
                                        sha1_b => $3, chg => $4 };
 
2542
                        if ($4 =~ /^(?:C|R)$/) {
 
2543
                                $state = 'file_a';
 
2544
                        } else {
 
2545
                                $state = 'file_b';
 
2546
                        }
 
2547
                } elsif ($state eq 'file_a') {
 
2548
                        my $x = $mods[$#mods] or croak "Empty array\n";
 
2549
                        if ($x->{chg} !~ /^(?:C|R)$/) {
 
2550
                                croak "Error parsing $_, $x->{chg}\n";
 
2551
                        }
 
2552
                        $x->{file_a} = $_;
 
2553
                        $state = 'file_b';
 
2554
                } elsif ($state eq 'file_b') {
 
2555
                        my $x = $mods[$#mods] or croak "Empty array\n";
 
2556
                        if (exists $x->{file_a} && $x->{chg} !~ /^(?:C|R)$/) {
 
2557
                                croak "Error parsing $_, $x->{chg}\n";
 
2558
                        }
 
2559
                        if (!exists $x->{file_a} && $x->{chg} =~ /^(?:C|R)$/) {
 
2560
                                croak "Error parsing $_, $x->{chg}\n";
 
2561
                        }
 
2562
                        $x->{file_b} = $_;
 
2563
                        $state = 'meta';
 
2564
                } else {
 
2565
                        croak "Error parsing $_\n";
 
2566
                }
 
2567
        }
 
2568
        command_close_pipe($diff_fh, $ctx);
 
2569
        \@mods;
 
2570
}
 
2571
 
 
2572
sub check_diff_paths {
 
2573
        my ($ra, $pfx, $rev, $mods) = @_;
 
2574
        my %types;
 
2575
        $pfx .= '/' if length $pfx;
 
2576
 
 
2577
        sub type_diff_paths {
 
2578
                my ($ra, $types, $path, $rev) = @_;
 
2579
                my @p = split m#/+#, $path;
 
2580
                my $c = shift @p;
 
2581
                unless (defined $types->{$c}) {
 
2582
                        $types->{$c} = $ra->check_path($c, $rev);
 
2583
                }
 
2584
                while (@p) {
 
2585
                        $c .= '/' . shift @p;
 
2586
                        next if defined $types->{$c};
 
2587
                        $types->{$c} = $ra->check_path($c, $rev);
 
2588
                }
 
2589
        }
 
2590
 
 
2591
        foreach my $m (@$mods) {
 
2592
                foreach my $f (qw/file_a file_b/) {
 
2593
                        next unless defined $m->{$f};
 
2594
                        my ($dir) = ($m->{$f} =~ m#^(.*?)/?(?:[^/]+)$#);
 
2595
                        if (length $pfx.$dir && ! defined $types{$dir}) {
 
2596
                                type_diff_paths($ra, \%types, $pfx.$dir, $rev);
 
2597
                        }
 
2598
                }
 
2599
        }
 
2600
        \%types;
 
2601
}
 
2602
 
3207
2603
sub split_path {
3208
2604
        return ($_[0] =~ m#^(.*?)/?([^/]+)$#);
3209
2605
}
3210
2606
 
3211
2607
sub repo_path {
3212
 
        (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]"
3213
 
                                        : $_[0]->{svn_path}
 
2608
        my ($self, $path) = @_;
 
2609
        $self->{path_prefix}.(defined $path ? $path : '');
3214
2610
}
3215
2611
 
3216
2612
sub url_path {
3217
2613
        my ($self, $path) = @_;
3218
 
        $self->{ra}->{url} . '/' . $self->repo_path($path);
 
2614
        $self->{url} . '/' . $self->repo_path($path);
3219
2615
}
3220
2616
 
3221
2617
sub rmdirs {
3222
 
        my ($self, $q) = @_;
 
2618
        my ($self) = @_;
3223
2619
        my $rm = $self->{rm};
3224
2620
        delete $rm->{''}; # we never delete the url we're tracking
3225
2621
        return unless %$rm;
3237
2633
        delete $rm->{''}; # we never delete the url we're tracking
3238
2634
        return unless %$rm;
3239
2635
 
3240
 
        defined(my $pid = open my $fh,'-|') or croak $!;
3241
 
        if (!$pid) {
3242
 
                exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!;
3243
 
        }
 
2636
        my ($fh, $ctx) = command_output_pipe(qw/ls-tree --name-only -r -z/,
 
2637
                                             $self->{tree_b});
3244
2638
        local $/ = "\0";
3245
 
        my @svn_path = split m#/#, $self->{svn_path};
3246
2639
        while (<$fh>) {
3247
2640
                chomp;
3248
 
                my @dn = (@svn_path, (split m#/#, $_));
 
2641
                my @dn = split m#/#, $_;
3249
2642
                while (pop @dn) {
3250
2643
                        delete $rm->{join '/', @dn};
3251
2644
                }
3254
2647
                        return;
3255
2648
                }
3256
2649
        }
3257
 
        close $fh;
 
2650
        command_close_pipe($fh, $ctx);
3258
2651
 
3259
2652
        my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});
3260
2653
        foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
3261
2654
                $self->close_directory($bat->{$d}, $p);
3262
2655
                my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
3263
 
                print "\tD+\t/$d/\n" unless $q;
 
2656
                print "\tD+\t$d/\n" unless $::_q;
3264
2657
                $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
3265
2658
                delete $bat->{$d};
3266
2659
        }
3268
2661
 
3269
2662
sub open_or_add_dir {
3270
2663
        my ($self, $full_path, $baton) = @_;
3271
 
        my $p = SVN::Pool->new;
3272
 
        my $t = $self->{ra}->check_path($full_path, $self->{r}, $p);
3273
 
        $p->clear;
 
2664
        my $t = $self->{types}->{$full_path};
 
2665
        if (!defined $t) {
 
2666
                die "$full_path not known in r$self->{r} or we have a bug!\n";
 
2667
        }
3274
2668
        if ($t == $SVN::Node::none) {
3275
2669
                return $self->add_directory($full_path, $baton,
3276
2670
                                                undef, -1, $self->{pool});
3287
2681
sub ensure_path {
3288
2682
        my ($self, $path) = @_;
3289
2683
        my $bat = $self->{bat};
3290
 
        $path = $self->repo_path($path);
3291
 
        return $bat->{''} unless (length $path);
3292
 
        my @p = split m#/+#, $path;
 
2684
        my $repo_path = $self->repo_path($path);
 
2685
        return $bat->{''} unless (length $repo_path);
 
2686
        my @p = split m#/+#, $repo_path;
3293
2687
        my $c = shift @p;
3294
2688
        $bat->{$c} ||= $self->open_or_add_dir($c, $bat->{''});
3295
2689
        while (@p) {
3301
2695
}
3302
2696
 
3303
2697
sub A {
3304
 
        my ($self, $m, $q) = @_;
 
2698
        my ($self, $m) = @_;
3305
2699
        my ($dir, $file) = split_path($m->{file_b});
3306
2700
        my $pbat = $self->ensure_path($dir);
3307
2701
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
3308
2702
                                        undef, -1);
3309
 
        print "\tA\t$m->{file_b}\n" unless $q;
 
2703
        print "\tA\t$m->{file_b}\n" unless $::_q;
3310
2704
        $self->chg_file($fbat, $m);
3311
2705
        $self->close_file($fbat,undef,$self->{pool});
3312
2706
}
3313
2707
 
3314
2708
sub C {
3315
 
        my ($self, $m, $q) = @_;
 
2709
        my ($self, $m) = @_;
3316
2710
        my ($dir, $file) = split_path($m->{file_b});
3317
2711
        my $pbat = $self->ensure_path($dir);
3318
2712
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
3319
2713
                                $self->url_path($m->{file_a}), $self->{r});
3320
 
        print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $q;
 
2714
        print "\tC\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
3321
2715
        $self->chg_file($fbat, $m);
3322
2716
        $self->close_file($fbat,undef,$self->{pool});
3323
2717
}
3331
2725
}
3332
2726
 
3333
2727
sub R {
3334
 
        my ($self, $m, $q) = @_;
 
2728
        my ($self, $m) = @_;
3335
2729
        my ($dir, $file) = split_path($m->{file_b});
3336
2730
        my $pbat = $self->ensure_path($dir);
3337
2731
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
3338
2732
                                $self->url_path($m->{file_a}), $self->{r});
3339
 
        print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $q;
 
2733
        print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
3340
2734
        $self->chg_file($fbat, $m);
3341
2735
        $self->close_file($fbat,undef,$self->{pool});
3342
2736
 
3346
2740
}
3347
2741
 
3348
2742
sub M {
3349
 
        my ($self, $m, $q) = @_;
 
2743
        my ($self, $m) = @_;
3350
2744
        my ($dir, $file) = split_path($m->{file_b});
3351
2745
        my $pbat = $self->ensure_path($dir);
3352
2746
        my $fbat = $self->open_file($self->repo_path($m->{file_b}),
3353
2747
                                $pbat,$self->{r},$self->{pool});
3354
 
        print "\t$m->{chg}\t$m->{file_b}\n" unless $q;
 
2748
        print "\t$m->{chg}\t$m->{file_b}\n" unless $::_q;
3355
2749
        $self->chg_file($fbat, $m);
3356
2750
        $self->close_file($fbat,undef,$self->{pool});
3357
2751
}
3402
2796
}
3403
2797
 
3404
2798
sub D {
3405
 
        my ($self, $m, $q) = @_;
 
2799
        my ($self, $m) = @_;
3406
2800
        my ($dir, $file) = split_path($m->{file_b});
3407
2801
        my $pbat = $self->ensure_path($dir);
3408
 
        print "\tD\t$m->{file_b}\n" unless $q;
 
2802
        print "\tD\t$m->{file_b}\n" unless $::_q;
3409
2803
        $self->delete_entry($m->{file_b}, $pbat);
3410
2804
}
3411
2805
 
3422
2816
sub abort_edit {
3423
2817
        my ($self) = @_;
3424
2818
        $self->SUPER::abort_edit($self->{pool});
 
2819
}
 
2820
 
 
2821
sub DESTROY {
 
2822
        my $self = shift;
 
2823
        $self->SUPER::DESTROY(@_);
3425
2824
        $self->{pool}->clear;
3426
2825
}
3427
2826
 
 
2827
# this drives the editor
 
2828
sub apply_diff {
 
2829
        my ($self) = @_;
 
2830
        my $mods = $self->{mods};
 
2831
        my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
 
2832
        foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
 
2833
                my $f = $m->{chg};
 
2834
                if (defined $o{$f}) {
 
2835
                        $self->$f($m);
 
2836
                } else {
 
2837
                        fatal("Invalid change type: $f\n");
 
2838
                }
 
2839
        }
 
2840
        $self->rmdirs if $_rmdir;
 
2841
        if (@$mods == 0) {
 
2842
                $self->abort_edit;
 
2843
        } else {
 
2844
                $self->close_edit;
 
2845
        }
 
2846
        return scalar @$mods;
 
2847
}
 
2848
 
 
2849
package Git::SVN::Ra;
 
2850
use vars qw/@ISA $config_dir $_log_window_size/;
 
2851
use strict;
 
2852
use warnings;
 
2853
my ($can_do_switch, %ignored_err, $RA);
 
2854
 
 
2855
BEGIN {
 
2856
        # enforce temporary pool usage for some simple functions
 
2857
        my $e;
 
2858
        foreach (qw/get_latest_revnum get_uuid get_repos_root/) {
 
2859
                $e .= "sub $_ {
 
2860
                        my \$self = shift;
 
2861
                        my \$pool = SVN::Pool->new;
 
2862
                        my \@ret = \$self->SUPER::$_(\@_,\$pool);
 
2863
                        \$pool->clear;
 
2864
                        wantarray ? \@ret : \$ret[0]; }\n";
 
2865
        }
 
2866
 
 
2867
        # get_dir needs $pool held in cache for dirents to work,
 
2868
        # check_path is cacheable and rev_proplist is close enough
 
2869
        # for our purposes.
 
2870
        foreach (qw/check_path get_dir rev_proplist/) {
 
2871
                $e .= "my \%${_}_cache; my \$${_}_rev = 0; sub $_ {
 
2872
                        my \$self = shift;
 
2873
                        my \$r = pop;
 
2874
                        my \$k = join(\"\\0\", \@_);
 
2875
                        if (my \$x = \$${_}_cache{\$r}->{\$k}) {
 
2876
                                return wantarray ? \@\$x : \$x->[0];
 
2877
                        }
 
2878
                        my \$pool = SVN::Pool->new;
 
2879
                        my \@ret = \$self->SUPER::$_(\@_, \$r, \$pool);
 
2880
                        if (\$r != \$${_}_rev) {
 
2881
                                \%${_}_cache = ( pool => [] );
 
2882
                                \$${_}_rev = \$r;
 
2883
                        }
 
2884
                        \$${_}_cache{\$r}->{\$k} = \\\@ret;
 
2885
                        push \@{\$${_}_cache{pool}}, \$pool;
 
2886
                        wantarray ? \@ret : \$ret[0]; }\n";
 
2887
        }
 
2888
        $e .= "\n1;";
 
2889
        eval $e or die $@;
 
2890
}
 
2891
 
 
2892
sub new {
 
2893
        my ($class, $url) = @_;
 
2894
        $url =~ s!/+$!!;
 
2895
        return $RA if ($RA && $RA->{url} eq $url);
 
2896
        $RA->{pool}->clear if $RA;
 
2897
 
 
2898
        SVN::_Core::svn_config_ensure($config_dir, undef);
 
2899
        my ($baton, $callbacks) = SVN::Core::auth_open_helper([
 
2900
            SVN::Client::get_simple_provider(),
 
2901
            SVN::Client::get_ssl_server_trust_file_provider(),
 
2902
            SVN::Client::get_simple_prompt_provider(
 
2903
              \&Git::SVN::Prompt::simple, 2),
 
2904
            SVN::Client::get_ssl_client_cert_prompt_provider(
 
2905
              \&Git::SVN::Prompt::ssl_client_cert, 2),
 
2906
            SVN::Client::get_ssl_client_cert_pw_prompt_provider(
 
2907
              \&Git::SVN::Prompt::ssl_client_cert_pw, 2),
 
2908
            SVN::Client::get_username_provider(),
 
2909
            SVN::Client::get_ssl_server_trust_prompt_provider(
 
2910
              \&Git::SVN::Prompt::ssl_server_trust),
 
2911
            SVN::Client::get_username_prompt_provider(
 
2912
              \&Git::SVN::Prompt::username, 2),
 
2913
          ]);
 
2914
        my $config = SVN::Core::config_get_config($config_dir);
 
2915
        my $self = SVN::Ra->new(url => $url, auth => $baton,
 
2916
                              config => $config,
 
2917
                              pool => SVN::Pool->new,
 
2918
                              auth_provider_callbacks => $callbacks);
 
2919
        $self->{svn_path} = $url;
 
2920
        $self->{repos_root} = $self->get_repos_root;
 
2921
        $self->{svn_path} =~ s#^\Q$self->{repos_root}\E(/|$)##;
 
2922
        $RA = bless $self, $class;
 
2923
}
 
2924
 
 
2925
sub DESTROY {
 
2926
        # do not call the real DESTROY since we store ourselves in $RA
 
2927
}
 
2928
 
 
2929
sub get_log {
 
2930
        my ($self, @args) = @_;
 
2931
        my $pool = SVN::Pool->new;
 
2932
        splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0');
 
2933
        my $ret = $self->SUPER::get_log(@args, $pool);
 
2934
        $pool->clear;
 
2935
        $ret;
 
2936
}
 
2937
 
 
2938
sub get_commit_editor {
 
2939
        my ($self, $log, $cb, $pool) = @_;
 
2940
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
 
2941
        $self->SUPER::get_commit_editor($log, $cb, @lock, $pool);
 
2942
}
 
2943
 
 
2944
sub gs_do_update {
 
2945
        my ($self, $rev_a, $rev_b, $gs, $editor) = @_;
 
2946
        my $new = ($rev_a == $rev_b);
 
2947
        my $path = $gs->{path};
 
2948
 
 
2949
        if ($new && -e $gs->{index}) {
 
2950
                unlink $gs->{index} or die
 
2951
                  "Couldn't unlink index: $gs->{index}: $!\n";
 
2952
        }
 
2953
        my $pool = SVN::Pool->new;
 
2954
        $editor->set_path_strip($path);
 
2955
        my (@pc) = split m#/#, $path;
 
2956
        my $reporter = $self->do_update($rev_b, (@pc ? shift @pc : ''),
 
2957
                                        1, $editor, $pool);
 
2958
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
 
2959
 
 
2960
        # Since we can't rely on svn_ra_reparent being available, we'll
 
2961
        # just have to do some magic with set_path to make it so
 
2962
        # we only want a partial path.
 
2963
        my $sp = '';
 
2964
        my $final = join('/', @pc);
 
2965
        while (@pc) {
 
2966
                $reporter->set_path($sp, $rev_b, 0, @lock, $pool);
 
2967
                $sp .= '/' if length $sp;
 
2968
                $sp .= shift @pc;
 
2969
        }
 
2970
        die "BUG: '$sp' != '$final'\n" if ($sp ne $final);
 
2971
 
 
2972
        $reporter->set_path($sp, $rev_a, $new, @lock, $pool);
 
2973
 
 
2974
        $reporter->finish_report($pool);
 
2975
        $pool->clear;
 
2976
        $editor->{git_commit_ok};
 
2977
}
 
2978
 
 
2979
# this requires SVN 1.4.3 or later (do_switch didn't work before 1.4.3, and
 
2980
# svn_ra_reparent didn't work before 1.4)
 
2981
sub gs_do_switch {
 
2982
        my ($self, $rev_a, $rev_b, $gs, $url_b, $editor) = @_;
 
2983
        my $path = $gs->{path};
 
2984
        my $pool = SVN::Pool->new;
 
2985
 
 
2986
        my $full_url = $self->{url};
 
2987
        my $old_url = $full_url;
 
2988
        $full_url .= "/$path" if length $path;
 
2989
        my ($ra, $reparented);
 
2990
        if ($old_url ne $full_url) {
 
2991
                if ($old_url !~ m#^svn(\+ssh)?://#) {
 
2992
                        SVN::_Ra::svn_ra_reparent($self->{session}, $full_url,
 
2993
                                                  $pool);
 
2994
                        $self->{url} = $full_url;
 
2995
                        $reparented = 1;
 
2996
                } else {
 
2997
                        $ra = Git::SVN::Ra->new($full_url);
 
2998
                }
 
2999
        }
 
3000
        $ra ||= $self;
 
3001
        my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
 
3002
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
 
3003
        $reporter->set_path('', $rev_a, 0, @lock, $pool);
 
3004
        $reporter->finish_report($pool);
 
3005
 
 
3006
        if ($reparented) {
 
3007
                SVN::_Ra::svn_ra_reparent($self->{session}, $old_url, $pool);
 
3008
                $self->{url} = $old_url;
 
3009
        }
 
3010
 
 
3011
        $pool->clear;
 
3012
        $editor->{git_commit_ok};
 
3013
}
 
3014
 
 
3015
sub gs_fetch_loop_common {
 
3016
        my ($self, $base, $head, $gsv, $globs) = @_;
 
3017
        return if ($base > $head);
 
3018
        my $inc = $_log_window_size;
 
3019
        my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
 
3020
        my %common;
 
3021
        my $common_max = scalar @$gsv;
 
3022
 
 
3023
        foreach my $gs (@$gsv) {
 
3024
                my @tmp = split m#/#, $gs->{path};
 
3025
                my $p = '';
 
3026
                foreach (@tmp) {
 
3027
                        $p .= length($p) ? "/$_" : $_;
 
3028
                        $common{$p} ||= 0;
 
3029
                        $common{$p}++;
 
3030
                }
 
3031
        }
 
3032
        $globs ||= [];
 
3033
        $common_max += scalar @$globs;
 
3034
        foreach my $glob (@$globs) {
 
3035
                my @tmp = split m#/#, $glob->{path}->{left};
 
3036
                my $p = '';
 
3037
                foreach (@tmp) {
 
3038
                        $p .= length($p) ? "/$_" : $_;
 
3039
                        $common{$p} ||= 0;
 
3040
                        $common{$p}++;
 
3041
                }
 
3042
        }
 
3043
 
 
3044
        my $longest_path = '';
 
3045
        foreach (sort {length $b <=> length $a} keys %common) {
 
3046
                if ($common{$_} == $common_max) {
 
3047
                        $longest_path = $_;
 
3048
                        last;
 
3049
                }
 
3050
        }
 
3051
        while (1) {
 
3052
                my %revs;
 
3053
                my $err;
 
3054
                my $err_handler = $SVN::Error::handler;
 
3055
                $SVN::Error::handler = sub {
 
3056
                        ($err) = @_;
 
3057
                        skip_unknown_revs($err);
 
3058
                };
 
3059
                sub _cb {
 
3060
                        my ($paths, $r, $author, $date, $log) = @_;
 
3061
                        [ dup_changed_paths($paths),
 
3062
                          { author => $author, date => $date, log => $log } ];
 
3063
                }
 
3064
                $self->get_log([$longest_path], $min, $max, 0, 1, 1,
 
3065
                               sub { $revs{$_[1]} = _cb(@_) });
 
3066
                if ($err && $max >= $head) {
 
3067
                        print STDERR "Path '$longest_path' ",
 
3068
                                     "was probably deleted:\n",
 
3069
                                     $err->expanded_message,
 
3070
                                     "\nWill attempt to follow ",
 
3071
                                     "revisions r$min .. r$max ",
 
3072
                                     "committed before the deletion\n";
 
3073
                        my $hi = $max;
 
3074
                        while (--$hi >= $min) {
 
3075
                                my $ok;
 
3076
                                $self->get_log([$longest_path], $min, $hi,
 
3077
                                               0, 1, 1, sub {
 
3078
                                               $ok ||= $_[1];
 
3079
                                               $revs{$_[1]} = _cb(@_) });
 
3080
                                if ($ok) {
 
3081
                                        print STDERR "r$min .. r$ok OK\n";
 
3082
                                        last;
 
3083
                                }
 
3084
                        }
 
3085
                }
 
3086
                $SVN::Error::handler = $err_handler;
 
3087
 
 
3088
                my %exists = map { $_->{path} => $_ } @$gsv;
 
3089
                foreach my $r (sort {$a <=> $b} keys %revs) {
 
3090
                        my ($paths, $logged) = @{$revs{$r}};
 
3091
 
 
3092
                        foreach my $gs ($self->match_globs(\%exists, $paths,
 
3093
                                                           $globs, $r)) {
 
3094
                                if ($gs->rev_db_max >= $r) {
 
3095
                                        next;
 
3096
                                }
 
3097
                                next unless $gs->match_paths($paths, $r);
 
3098
                                $gs->{logged_rev_props} = $logged;
 
3099
                                if (my $last_commit = $gs->last_commit) {
 
3100
                                        $gs->assert_index_clean($last_commit);
 
3101
                                }
 
3102
                                my $log_entry = $gs->do_fetch($paths, $r);
 
3103
                                if ($log_entry) {
 
3104
                                        $gs->do_git_commit($log_entry);
 
3105
                                }
 
3106
                        }
 
3107
                        foreach my $g (@$globs) {
 
3108
                                my $k = "svn-remote.$g->{remote}." .
 
3109
                                        "$g->{t}-maxRev";
 
3110
                                Git::SVN::tmp_config($k, $r);
 
3111
                        }
 
3112
                }
 
3113
                # pre-fill the .rev_db since it'll eventually get filled in
 
3114
                # with '0' x40 if something new gets committed
 
3115
                foreach my $gs (@$gsv) {
 
3116
                        next if defined $gs->rev_db_get($max);
 
3117
                        $gs->rev_db_set($max, 0 x40);
 
3118
                }
 
3119
                foreach my $g (@$globs) {
 
3120
                        my $k = "svn-remote.$g->{remote}.$g->{t}-maxRev";
 
3121
                        Git::SVN::tmp_config($k, $max);
 
3122
                }
 
3123
                last if $max >= $head;
 
3124
                $min = $max + 1;
 
3125
                $max += $inc;
 
3126
                $max = $head if ($max > $head);
 
3127
        }
 
3128
}
 
3129
 
 
3130
sub match_globs {
 
3131
        my ($self, $exists, $paths, $globs, $r) = @_;
 
3132
 
 
3133
        sub get_dir_check {
 
3134
                my ($self, $exists, $g, $r) = @_;
 
3135
                my @x = eval { $self->get_dir($g->{path}->{left}, $r) };
 
3136
                return unless scalar @x == 3;
 
3137
                my $dirents = $x[0];
 
3138
                foreach my $de (keys %$dirents) {
 
3139
                        next if $dirents->{$de}->kind != $SVN::Node::dir;
 
3140
                        my $p = $g->{path}->full_path($de);
 
3141
                        next if $exists->{$p};
 
3142
                        next if (length $g->{path}->{right} &&
 
3143
                                 ($self->check_path($p, $r) !=
 
3144
                                  $SVN::Node::dir));
 
3145
                        $exists->{$p} = Git::SVN->init($self->{url}, $p, undef,
 
3146
                                         $g->{ref}->full_path($de), 1);
 
3147
                }
 
3148
        }
 
3149
        foreach my $g (@$globs) {
 
3150
                if (my $path = $paths->{"/$g->{path}->{left}"}) {
 
3151
                        if ($path->{action} =~ /^[AR]$/) {
 
3152
                                get_dir_check($self, $exists, $g, $r);
 
3153
                        }
 
3154
                }
 
3155
                foreach (keys %$paths) {
 
3156
                        if (/$g->{path}->{left_regex}/ &&
 
3157
                            !/$g->{path}->{regex}/) {
 
3158
                                next if $paths->{$_}->{action} !~ /^[AR]$/;
 
3159
                                get_dir_check($self, $exists, $g, $r);
 
3160
                        }
 
3161
                        next unless /$g->{path}->{regex}/;
 
3162
                        my $p = $1;
 
3163
                        my $pathname = $g->{path}->full_path($p);
 
3164
                        next if $exists->{$pathname};
 
3165
                        next if ($self->check_path($pathname, $r) !=
 
3166
                                 $SVN::Node::dir);
 
3167
                        $exists->{$pathname} = Git::SVN->init(
 
3168
                                              $self->{url}, $pathname, undef,
 
3169
                                              $g->{ref}->full_path($p), 1);
 
3170
                }
 
3171
                my $c = '';
 
3172
                foreach (split m#/#, $g->{path}->{left}) {
 
3173
                        $c .= "/$_";
 
3174
                        next unless ($paths->{$c} &&
 
3175
                                     ($paths->{$c}->{action} =~ /^[AR]$/));
 
3176
                        get_dir_check($self, $exists, $g, $r);
 
3177
                }
 
3178
        }
 
3179
        values %$exists;
 
3180
}
 
3181
 
 
3182
sub minimize_url {
 
3183
        my ($self) = @_;
 
3184
        return $self->{url} if ($self->{url} eq $self->{repos_root});
 
3185
        my $url = $self->{repos_root};
 
3186
        my @components = split(m!/!, $self->{svn_path});
 
3187
        my $c = '';
 
3188
        do {
 
3189
                $url .= "/$c" if length $c;
 
3190
                eval { (ref $self)->new($url)->get_latest_revnum };
 
3191
        } while ($@ && ($c = shift @components));
 
3192
        $url;
 
3193
}
 
3194
 
 
3195
sub can_do_switch {
 
3196
        my $self = shift;
 
3197
        unless (defined $can_do_switch) {
 
3198
                my $pool = SVN::Pool->new;
 
3199
                my $rep = eval {
 
3200
                        $self->do_switch(1, '', 0, $self->{url},
 
3201
                                         SVN::Delta::Editor->new, $pool);
 
3202
                };
 
3203
                if ($@) {
 
3204
                        $can_do_switch = 0;
 
3205
                } else {
 
3206
                        $rep->abort_report($pool);
 
3207
                        $can_do_switch = 1;
 
3208
                }
 
3209
                $pool->clear;
 
3210
        }
 
3211
        $can_do_switch;
 
3212
}
 
3213
 
 
3214
sub skip_unknown_revs {
 
3215
        my ($err) = @_;
 
3216
        my $errno = $err->apr_err();
 
3217
        # Maybe the branch we're tracking didn't
 
3218
        # exist when the repo started, so it's
 
3219
        # not an error if it doesn't, just continue
 
3220
        #
 
3221
        # Wonderfully consistent library, eh?
 
3222
        # 160013 - svn:// and file://
 
3223
        # 175002 - http(s)://
 
3224
        # 175007 - http(s):// (this repo required authorization, too...)
 
3225
        #   More codes may be discovered later...
 
3226
        if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
 
3227
                my $err_key = $err->expanded_message;
 
3228
                # revision numbers change every time, filter them out
 
3229
                $err_key =~ s/\d+/\0/g;
 
3230
                $err_key = "$errno\0$err_key";
 
3231
                unless ($ignored_err{$err_key}) {
 
3232
                        warn "W: Ignoring error from SVN, path probably ",
 
3233
                             "does not exist: ($errno): ",
 
3234
                             $err->expanded_message,"\n";
 
3235
                        $ignored_err{$err_key} = 1;
 
3236
                }
 
3237
                return;
 
3238
        }
 
3239
        die "Error from SVN, ($errno): ", $err->expanded_message,"\n";
 
3240
}
 
3241
 
 
3242
# svn_log_changed_path_t objects passed to get_log are likely to be
 
3243
# overwritten even if only the refs are copied to an external variable,
 
3244
# so we should dup the structures in their entirety.  Using an externally
 
3245
# passed pool (instead of our temporary and quickly cleared pool in
 
3246
# Git::SVN::Ra) does not help matters at all...
 
3247
sub dup_changed_paths {
 
3248
        my ($paths) = @_;
 
3249
        return undef unless $paths;
 
3250
        my %ret;
 
3251
        foreach my $p (keys %$paths) {
 
3252
                my $i = $paths->{$p};
 
3253
                my %s = map { $_ => $i->$_ }
 
3254
                              qw/copyfrom_path copyfrom_rev action/;
 
3255
                $ret{$p} = \%s;
 
3256
        }
 
3257
        \%ret;
 
3258
}
 
3259
 
 
3260
package Git::SVN::Log;
 
3261
use strict;
 
3262
use warnings;
 
3263
use POSIX qw/strftime/;
 
3264
use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline
 
3265
            %rusers $show_commit $incremental/;
 
3266
my $l_fmt;
 
3267
 
 
3268
sub cmt_showable {
 
3269
        my ($c) = @_;
 
3270
        return 1 if defined $c->{r};
 
3271
 
 
3272
        # big commit message got truncated by the 16k pretty buffer in rev-list
 
3273
        if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
 
3274
                                $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
 
3275
                @{$c->{l}} = ();
 
3276
                my @log = command(qw/cat-file commit/, $c->{c});
 
3277
 
 
3278
                # shift off the headers
 
3279
                shift @log while ($log[0] ne '');
 
3280
                shift @log;
 
3281
 
 
3282
                # TODO: make $c->{l} not have a trailing newline in the future
 
3283
                @{$c->{l}} = map { "$_\n" } grep !/^git-svn-id: /, @log;
 
3284
 
 
3285
                (undef, $c->{r}, undef) = ::extract_metadata(
 
3286
                                (grep(/^git-svn-id: /, @log))[-1]);
 
3287
        }
 
3288
        return defined $c->{r};
 
3289
}
 
3290
 
 
3291
sub log_use_color {
 
3292
        return 1 if $color;
 
3293
        my ($dc, $dcvar);
 
3294
        $dcvar = 'color.diff';
 
3295
        $dc = `git-config --get $dcvar`;
 
3296
        if ($dc eq '') {
 
3297
                # nothing at all; fallback to "diff.color"
 
3298
                $dcvar = 'diff.color';
 
3299
                $dc = `git-config --get $dcvar`;
 
3300
        }
 
3301
        chomp($dc);
 
3302
        if ($dc eq 'auto') {
 
3303
                my $pc;
 
3304
                $pc = `git-config --get color.pager`;
 
3305
                if ($pc eq '') {
 
3306
                        # does not have it -- fallback to pager.color
 
3307
                        $pc = `git-config --bool --get pager.color`;
 
3308
                }
 
3309
                else {
 
3310
                        $pc = `git-config --bool --get color.pager`;
 
3311
                        if ($?) {
 
3312
                                $pc = 'false';
 
3313
                        }
 
3314
                }
 
3315
                chomp($pc);
 
3316
                if (-t *STDOUT || (defined $pager && $pc eq 'true')) {
 
3317
                        return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
 
3318
                }
 
3319
                return 0;
 
3320
        }
 
3321
        return 0 if $dc eq 'never';
 
3322
        return 1 if $dc eq 'always';
 
3323
        chomp($dc = `git-config --bool --get $dcvar`);
 
3324
        return ($dc eq 'true');
 
3325
}
 
3326
 
 
3327
sub git_svn_log_cmd {
 
3328
        my ($r_min, $r_max, @args) = @_;
 
3329
        my $head = 'HEAD';
 
3330
        foreach my $x (@args) {
 
3331
                last if $x eq '--';
 
3332
                next unless ::verify_ref("$x^0");
 
3333
                $head = $x;
 
3334
                last;
 
3335
        }
 
3336
 
 
3337
        my ($url, $rev, $uuid, $gs) = ::working_head_info($head);
 
3338
        $gs ||= Git::SVN->_new;
 
3339
        my @cmd = (qw/log --abbrev-commit --pretty=raw --default/,
 
3340
                   $gs->refname);
 
3341
        push @cmd, '-r' unless $non_recursive;
 
3342
        push @cmd, qw/--raw --name-status/ if $verbose;
 
3343
        push @cmd, '--color' if log_use_color();
 
3344
        return @cmd unless defined $r_max;
 
3345
        if ($r_max == $r_min) {
 
3346
                push @cmd, '--max-count=1';
 
3347
                if (my $c = $gs->rev_db_get($r_max)) {
 
3348
                        push @cmd, $c;
 
3349
                }
 
3350
        } else {
 
3351
                my ($c_min, $c_max);
 
3352
                $c_max = $gs->rev_db_get($r_max);
 
3353
                $c_min = $gs->rev_db_get($r_min);
 
3354
                if (defined $c_min && defined $c_max) {
 
3355
                        if ($r_max > $r_max) {
 
3356
                                push @cmd, "$c_min..$c_max";
 
3357
                        } else {
 
3358
                                push @cmd, "$c_max..$c_min";
 
3359
                        }
 
3360
                } elsif ($r_max > $r_min) {
 
3361
                        push @cmd, $c_max;
 
3362
                } else {
 
3363
                        push @cmd, $c_min;
 
3364
                }
 
3365
        }
 
3366
        return @cmd;
 
3367
}
 
3368
 
 
3369
# adapted from pager.c
 
3370
sub config_pager {
 
3371
        $pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
 
3372
        if (!defined $pager) {
 
3373
                $pager = 'less';
 
3374
        } elsif (length $pager == 0 || $pager eq 'cat') {
 
3375
                $pager = undef;
 
3376
        }
 
3377
}
 
3378
 
 
3379
sub run_pager {
 
3380
        return unless -t *STDOUT;
 
3381
        pipe my $rfd, my $wfd or return;
 
3382
        defined(my $pid = fork) or ::fatal "Can't fork: $!\n";
 
3383
        if (!$pid) {
 
3384
                open STDOUT, '>&', $wfd or
 
3385
                                     ::fatal "Can't redirect to stdout: $!\n";
 
3386
                return;
 
3387
        }
 
3388
        open STDIN, '<&', $rfd or ::fatal "Can't redirect stdin: $!\n";
 
3389
        $ENV{LESS} ||= 'FRSX';
 
3390
        exec $pager or ::fatal "Can't run pager: $! ($pager)\n";
 
3391
}
 
3392
 
 
3393
sub tz_to_s_offset {
 
3394
        my ($tz) = @_;
 
3395
        $tz =~ s/(\d\d)$//;
 
3396
        return ($1 * 60) + ($tz * 3600);
 
3397
}
 
3398
 
 
3399
sub get_author_info {
 
3400
        my ($dest, $author, $t, $tz) = @_;
 
3401
        $author =~ s/(?:^\s*|\s*$)//g;
 
3402
        $dest->{a_raw} = $author;
 
3403
        my $au;
 
3404
        if ($::_authors) {
 
3405
                $au = $rusers{$author} || undef;
 
3406
        }
 
3407
        if (!$au) {
 
3408
                ($au) = ($author =~ /<([^>]+)\@[^>]+>$/);
 
3409
        }
 
3410
        $dest->{t} = $t;
 
3411
        $dest->{tz} = $tz;
 
3412
        $dest->{a} = $au;
 
3413
        # Date::Parse isn't in the standard Perl distro :(
 
3414
        if ($tz =~ s/^\+//) {
 
3415
                $t += tz_to_s_offset($tz);
 
3416
        } elsif ($tz =~ s/^\-//) {
 
3417
                $t -= tz_to_s_offset($tz);
 
3418
        }
 
3419
        $dest->{t_utc} = $t;
 
3420
}
 
3421
 
 
3422
sub process_commit {
 
3423
        my ($c, $r_min, $r_max, $defer) = @_;
 
3424
        if (defined $r_min && defined $r_max) {
 
3425
                if ($r_min == $c->{r} && $r_min == $r_max) {
 
3426
                        show_commit($c);
 
3427
                        return 0;
 
3428
                }
 
3429
                return 1 if $r_min == $r_max;
 
3430
                if ($r_min < $r_max) {
 
3431
                        # we need to reverse the print order
 
3432
                        return 0 if (defined $limit && --$limit < 0);
 
3433
                        push @$defer, $c;
 
3434
                        return 1;
 
3435
                }
 
3436
                if ($r_min != $r_max) {
 
3437
                        return 1 if ($r_min < $c->{r});
 
3438
                        return 1 if ($r_max > $c->{r});
 
3439
                }
 
3440
        }
 
3441
        return 0 if (defined $limit && --$limit < 0);
 
3442
        show_commit($c);
 
3443
        return 1;
 
3444
}
 
3445
 
 
3446
sub show_commit {
 
3447
        my $c = shift;
 
3448
        if ($oneline) {
 
3449
                my $x = "\n";
 
3450
                if (my $l = $c->{l}) {
 
3451
                        while ($l->[0] =~ /^\s*$/) { shift @$l }
 
3452
                        $x = $l->[0];
 
3453
                }
 
3454
                $l_fmt ||= 'A' . length($c->{r});
 
3455
                print 'r',pack($l_fmt, $c->{r}),' | ';
 
3456
                print "$c->{c} | " if $show_commit;
 
3457
                print $x;
 
3458
        } else {
 
3459
                show_commit_normal($c);
 
3460
        }
 
3461
}
 
3462
 
 
3463
sub show_commit_changed_paths {
 
3464
        my ($c) = @_;
 
3465
        return unless $c->{changed};
 
3466
        print "Changed paths:\n", @{$c->{changed}};
 
3467
}
 
3468
 
 
3469
sub show_commit_normal {
 
3470
        my ($c) = @_;
 
3471
        print '-' x72, "\nr$c->{r} | ";
 
3472
        print "$c->{c} | " if $show_commit;
 
3473
        print "$c->{a} | ", strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)",
 
3474
                                 localtime($c->{t_utc})), ' | ';
 
3475
        my $nr_line = 0;
 
3476
 
 
3477
        if (my $l = $c->{l}) {
 
3478
                while ($l->[$#$l] eq "\n" && $#$l > 0
 
3479
                                          && $l->[($#$l - 1)] eq "\n") {
 
3480
                        pop @$l;
 
3481
                }
 
3482
                $nr_line = scalar @$l;
 
3483
                if (!$nr_line) {
 
3484
                        print "1 line\n\n\n";
 
3485
                } else {
 
3486
                        if ($nr_line == 1) {
 
3487
                                $nr_line = '1 line';
 
3488
                        } else {
 
3489
                                $nr_line .= ' lines';
 
3490
                        }
 
3491
                        print $nr_line, "\n";
 
3492
                        show_commit_changed_paths($c);
 
3493
                        print "\n";
 
3494
                        print $_ foreach @$l;
 
3495
                }
 
3496
        } else {
 
3497
                print "1 line\n";
 
3498
                show_commit_changed_paths($c);
 
3499
                print "\n";
 
3500
 
 
3501
        }
 
3502
        foreach my $x (qw/raw stat diff/) {
 
3503
                if ($c->{$x}) {
 
3504
                        print "\n";
 
3505
                        print $_ foreach @{$c->{$x}}
 
3506
                }
 
3507
        }
 
3508
}
 
3509
 
 
3510
sub cmd_show_log {
 
3511
        my (@args) = @_;
 
3512
        my ($r_min, $r_max);
 
3513
        my $r_last = -1; # prevent dupes
 
3514
        if (defined $TZ) {
 
3515
                $ENV{TZ} = $TZ;
 
3516
        } else {
 
3517
                delete $ENV{TZ};
 
3518
        }
 
3519
        if (defined $::_revision) {
 
3520
                if ($::_revision =~ /^(\d+):(\d+)$/) {
 
3521
                        ($r_min, $r_max) = ($1, $2);
 
3522
                } elsif ($::_revision =~ /^\d+$/) {
 
3523
                        $r_min = $r_max = $::_revision;
 
3524
                } else {
 
3525
                        ::fatal "-r$::_revision is not supported, use ",
 
3526
                                "standard \'git log\' arguments instead\n";
 
3527
                }
 
3528
        }
 
3529
 
 
3530
        config_pager();
 
3531
        @args = (git_svn_log_cmd($r_min, $r_max, @args), @args);
 
3532
        my $log = command_output_pipe(@args);
 
3533
        run_pager();
 
3534
        my (@k, $c, $d, $stat);
 
3535
        my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
 
3536
        while (<$log>) {
 
3537
                if (/^${esc_color}commit ($::sha1_short)/o) {
 
3538
                        my $cmt = $1;
 
3539
                        if ($c && cmt_showable($c) && $c->{r} != $r_last) {
 
3540
                                $r_last = $c->{r};
 
3541
                                process_commit($c, $r_min, $r_max, \@k) or
 
3542
                                                                goto out;
 
3543
                        }
 
3544
                        $d = undef;
 
3545
                        $c = { c => $cmt };
 
3546
                } elsif (/^${esc_color}author (.+) (\d+) ([\-\+]?\d+)$/o) {
 
3547
                        get_author_info($c, $1, $2, $3);
 
3548
                } elsif (/^${esc_color}(?:tree|parent|committer) /o) {
 
3549
                        # ignore
 
3550
                } elsif (/^${esc_color}:\d{6} \d{6} $::sha1_short/o) {
 
3551
                        push @{$c->{raw}}, $_;
 
3552
                } elsif (/^${esc_color}[ACRMDT]\t/) {
 
3553
                        # we could add $SVN->{svn_path} here, but that requires
 
3554
                        # remote access at the moment (repo_path_split)...
 
3555
                        s#^(${esc_color})([ACRMDT])\t#$1   $2 #o;
 
3556
                        push @{$c->{changed}}, $_;
 
3557
                } elsif (/^${esc_color}diff /o) {
 
3558
                        $d = 1;
 
3559
                        push @{$c->{diff}}, $_;
 
3560
                } elsif ($d) {
 
3561
                        push @{$c->{diff}}, $_;
 
3562
                } elsif (/^\ .+\ \|\s*\d+\ $esc_color[\+\-]*
 
3563
                          $esc_color*[\+\-]*$esc_color$/x) {
 
3564
                        $stat = 1;
 
3565
                        push @{$c->{stat}}, $_;
 
3566
                } elsif ($stat && /^ \d+ files changed, \d+ insertions/) {
 
3567
                        push @{$c->{stat}}, $_;
 
3568
                        $stat = undef;
 
3569
                } elsif (/^${esc_color}    (git-svn-id:.+)$/o) {
 
3570
                        ($c->{url}, $c->{r}, undef) = ::extract_metadata($1);
 
3571
                } elsif (s/^${esc_color}    //o) {
 
3572
                        push @{$c->{l}}, $_;
 
3573
                }
 
3574
        }
 
3575
        if ($c && defined $c->{r} && $c->{r} != $r_last) {
 
3576
                $r_last = $c->{r};
 
3577
                process_commit($c, $r_min, $r_max, \@k);
 
3578
        }
 
3579
        if (@k) {
 
3580
                my $swap = $r_max;
 
3581
                $r_max = $r_min;
 
3582
                $r_min = $swap;
 
3583
                process_commit($_, $r_min, $r_max) foreach reverse @k;
 
3584
        }
 
3585
out:
 
3586
        close $log;
 
3587
        print '-' x72,"\n" unless $incremental || $oneline;
 
3588
}
 
3589
 
 
3590
package Git::SVN::Migration;
 
3591
# these version numbers do NOT correspond to actual version numbers
 
3592
# of git nor git-svn.  They are just relative.
 
3593
#
 
3594
# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD
 
3595
#
 
3596
# v1 layout: .git/$id/info/url, refs/remotes/$id
 
3597
#
 
3598
# v2 layout: .git/svn/$id/info/url, refs/remotes/$id
 
3599
#
 
3600
# v3 layout: .git/svn/$id, refs/remotes/$id
 
3601
#            - info/url may remain for backwards compatibility
 
3602
#            - this is what we migrate up to this layout automatically,
 
3603
#            - this will be used by git svn init on single branches
 
3604
# v3.1 layout (auto migrated):
 
3605
#            - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink
 
3606
#              for backwards compatibility
 
3607
#
 
3608
# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id
 
3609
#            - this is only created for newly multi-init-ed
 
3610
#              repositories.  Similar in spirit to the
 
3611
#              --use-separate-remotes option in git-clone (now default)
 
3612
#            - we do not automatically migrate to this (following
 
3613
#              the example set by core git)
 
3614
use strict;
 
3615
use warnings;
 
3616
use Carp qw/croak/;
 
3617
use File::Path qw/mkpath/;
 
3618
use File::Basename qw/dirname basename/;
 
3619
use vars qw/$_minimize/;
 
3620
 
 
3621
sub migrate_from_v0 {
 
3622
        my $git_dir = $ENV{GIT_DIR};
 
3623
        return undef unless -d $git_dir;
 
3624
        my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
 
3625
        my $migrated = 0;
 
3626
        while (<$fh>) {
 
3627
                chomp;
 
3628
                my ($id, $orig_ref) = ($_, $_);
 
3629
                next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#;
 
3630
                next unless -f "$git_dir/$id/info/url";
 
3631
                my $new_ref = "refs/remotes/$id";
 
3632
                if (::verify_ref("$new_ref^0")) {
 
3633
                        print STDERR "W: $orig_ref is probably an old ",
 
3634
                                     "branch used by an ancient version of ",
 
3635
                                     "git-svn.\n",
 
3636
                                     "However, $new_ref also exists.\n",
 
3637
                                     "We will not be able ",
 
3638
                                     "to use this branch until this ",
 
3639
                                     "ambiguity is resolved.\n";
 
3640
                        next;
 
3641
                }
 
3642
                print STDERR "Migrating from v0 layout...\n" if !$migrated;
 
3643
                print STDERR "Renaming ref: $orig_ref => $new_ref\n";
 
3644
                command_noisy('update-ref', $new_ref, $orig_ref);
 
3645
                command_noisy('update-ref', '-d', $orig_ref, $orig_ref);
 
3646
                $migrated++;
 
3647
        }
 
3648
        command_close_pipe($fh, $ctx);
 
3649
        print STDERR "Done migrating from v0 layout...\n" if $migrated;
 
3650
        $migrated;
 
3651
}
 
3652
 
 
3653
sub migrate_from_v1 {
 
3654
        my $git_dir = $ENV{GIT_DIR};
 
3655
        my $migrated = 0;
 
3656
        return $migrated unless -d $git_dir;
 
3657
        my $svn_dir = "$git_dir/svn";
 
3658
 
 
3659
        # just in case somebody used 'svn' as their $id at some point...
 
3660
        return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url";
 
3661
 
 
3662
        print STDERR "Migrating from a git-svn v1 layout...\n";
 
3663
        mkpath([$svn_dir]);
 
3664
        print STDERR "Data from a previous version of git-svn exists, but\n\t",
 
3665
                     "$svn_dir\n\t(required for this version ",
 
3666
                     "($::VERSION) of git-svn) does not. exist\n";
 
3667
        my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
 
3668
        while (<$fh>) {
 
3669
                my $x = $_;
 
3670
                next unless $x =~ s#^refs/remotes/##;
 
3671
                chomp $x;
 
3672
                next unless -f "$git_dir/$x/info/url";
 
3673
                my $u = eval { ::file_to_s("$git_dir/$x/info/url") };
 
3674
                next unless $u;
 
3675
                my $dn = dirname("$git_dir/svn/$x");
 
3676
                mkpath([$dn]) unless -d $dn;
 
3677
                if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID:
 
3678
                        mkpath(["$git_dir/svn/svn"]);
 
3679
                        print STDERR " - $git_dir/$x/info => ",
 
3680
                                        "$git_dir/svn/$x/info\n";
 
3681
                        rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or
 
3682
                               croak "$!: $x";
 
3683
                        # don't worry too much about these, they probably
 
3684
                        # don't exist with repos this old (save for index,
 
3685
                        # and we can easily regenerate that)
 
3686
                        foreach my $f (qw/unhandled.log index .rev_db/) {
 
3687
                                rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f";
 
3688
                        }
 
3689
                } else {
 
3690
                        print STDERR " - $git_dir/$x => $git_dir/svn/$x\n";
 
3691
                        rename "$git_dir/$x", "$git_dir/svn/$x" or
 
3692
                               croak "$!: $x";
 
3693
                }
 
3694
                $migrated++;
 
3695
        }
 
3696
        command_close_pipe($fh, $ctx);
 
3697
        print STDERR "Done migrating from a git-svn v1 layout\n";
 
3698
        $migrated;
 
3699
}
 
3700
 
 
3701
sub read_old_urls {
 
3702
        my ($l_map, $pfx, $path) = @_;
 
3703
        my @dir;
 
3704
        foreach (<$path/*>) {
 
3705
                if (-r "$_/info/url") {
 
3706
                        $pfx .= '/' if $pfx && $pfx !~ m!/$!;
 
3707
                        my $ref_id = $pfx . basename $_;
 
3708
                        my $url = ::file_to_s("$_/info/url");
 
3709
                        $l_map->{$ref_id} = $url;
 
3710
                } elsif (-d $_) {
 
3711
                        push @dir, $_;
 
3712
                }
 
3713
        }
 
3714
        foreach (@dir) {
 
3715
                my $x = $_;
 
3716
                $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
 
3717
                read_old_urls($l_map, $x, $_);
 
3718
        }
 
3719
}
 
3720
 
 
3721
sub migrate_from_v2 {
 
3722
        my @cfg = command(qw/config -l/);
 
3723
        return if grep /^svn-remote\..+\.url=/, @cfg;
 
3724
        my %l_map;
 
3725
        read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn");
 
3726
        my $migrated = 0;
 
3727
 
 
3728
        foreach my $ref_id (sort keys %l_map) {
 
3729
                eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) };
 
3730
                if ($@) {
 
3731
                        Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
 
3732
                }
 
3733
                $migrated++;
 
3734
        }
 
3735
        $migrated;
 
3736
}
 
3737
 
 
3738
sub minimize_connections {
 
3739
        my $r = Git::SVN::read_all_remotes();
 
3740
        my $new_urls = {};
 
3741
        my $root_repos = {};
 
3742
        foreach my $repo_id (keys %$r) {
 
3743
                my $url = $r->{$repo_id}->{url} or next;
 
3744
                my $fetch = $r->{$repo_id}->{fetch} or next;
 
3745
                my $ra = Git::SVN::Ra->new($url);
 
3746
 
 
3747
                # skip existing cases where we already connect to the root
 
3748
                if (($ra->{url} eq $ra->{repos_root}) ||
 
3749
                    (Git::SVN::sanitize_remote_name($ra->{repos_root}) eq
 
3750
                     $repo_id)) {
 
3751
                        $root_repos->{$ra->{url}} = $repo_id;
 
3752
                        next;
 
3753
                }
 
3754
 
 
3755
                my $root_ra = Git::SVN::Ra->new($ra->{repos_root});
 
3756
                my $root_path = $ra->{url};
 
3757
                $root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##;
 
3758
                foreach my $path (keys %$fetch) {
 
3759
                        my $ref_id = $fetch->{$path};
 
3760
                        my $gs = Git::SVN->new($ref_id, $repo_id, $path);
 
3761
 
 
3762
                        # make sure we can read when connecting to
 
3763
                        # a higher level of a repository
 
3764
                        my ($last_rev, undef) = $gs->last_rev_commit;
 
3765
                        if (!defined $last_rev) {
 
3766
                                $last_rev = eval {
 
3767
                                        $root_ra->get_latest_revnum;
 
3768
                                };
 
3769
                                next if $@;
 
3770
                        }
 
3771
                        my $new = $root_path;
 
3772
                        $new .= length $path ? "/$path" : '';
 
3773
                        eval {
 
3774
                                $root_ra->get_log([$new], $last_rev, $last_rev,
 
3775
                                                  0, 0, 1, sub { });
 
3776
                        };
 
3777
                        next if $@;
 
3778
                        $new_urls->{$ra->{repos_root}}->{$new} =
 
3779
                                { ref_id => $ref_id,
 
3780
                                  old_repo_id => $repo_id,
 
3781
                                  old_path => $path };
 
3782
                }
 
3783
        }
 
3784
 
 
3785
        my @emptied;
 
3786
        foreach my $url (keys %$new_urls) {
 
3787
                # see if we can re-use an existing [svn-remote "repo_id"]
 
3788
                # instead of creating a(n ugly) new section:
 
3789
                my $repo_id = $root_repos->{$url} ||
 
3790
                              Git::SVN::sanitize_remote_name($url);
 
3791
 
 
3792
                my $fetch = $new_urls->{$url};
 
3793
                foreach my $path (keys %$fetch) {
 
3794
                        my $x = $fetch->{$path};
 
3795
                        Git::SVN->init($url, $path, $repo_id, $x->{ref_id});
 
3796
                        my $pfx = "svn-remote.$x->{old_repo_id}";
 
3797
 
 
3798
                        my $old_fetch = quotemeta("$x->{old_path}:".
 
3799
                                                  "refs/remotes/$x->{ref_id}");
 
3800
                        command_noisy(qw/config --unset/,
 
3801
                                      "$pfx.fetch", '^'. $old_fetch . '$');
 
3802
                        delete $r->{$x->{old_repo_id}}->
 
3803
                               {fetch}->{$x->{old_path}};
 
3804
                        if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) {
 
3805
                                command_noisy(qw/config --unset/,
 
3806
                                              "$pfx.url");
 
3807
                                push @emptied, $x->{old_repo_id}
 
3808
                        }
 
3809
                }
 
3810
        }
 
3811
        if (@emptied) {
 
3812
                my $file = $ENV{GIT_CONFIG} || $ENV{GIT_CONFIG_LOCAL} ||
 
3813
                           "$ENV{GIT_DIR}/config";
 
3814
                print STDERR <<EOF;
 
3815
The following [svn-remote] sections in your config file ($file) are empty
 
3816
and can be safely removed:
 
3817
EOF
 
3818
                print STDERR "[svn-remote \"$_\"]\n" foreach @emptied;
 
3819
        }
 
3820
}
 
3821
 
 
3822
sub migration_check {
 
3823
        migrate_from_v0();
 
3824
        migrate_from_v1();
 
3825
        migrate_from_v2();
 
3826
        minimize_connections() if $_minimize;
 
3827
}
 
3828
 
 
3829
package Git::IndexInfo;
 
3830
use strict;
 
3831
use warnings;
 
3832
use Git qw/command_input_pipe command_close_pipe/;
 
3833
 
 
3834
sub new {
 
3835
        my ($class) = @_;
 
3836
        my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/);
 
3837
        bless { gui => $gui, ctx => $ctx, nr => 0}, $class;
 
3838
}
 
3839
 
 
3840
sub remove {
 
3841
        my ($self, $path) = @_;
 
3842
        if (print { $self->{gui} } '0 ', 0 x 40, "\t", $path, "\0") {
 
3843
                return ++$self->{nr};
 
3844
        }
 
3845
        undef;
 
3846
}
 
3847
 
 
3848
sub update {
 
3849
        my ($self, $mode, $hash, $path) = @_;
 
3850
        if (print { $self->{gui} } $mode, ' ', $hash, "\t", $path, "\0") {
 
3851
                return ++$self->{nr};
 
3852
        }
 
3853
        undef;
 
3854
}
 
3855
 
 
3856
sub DESTROY {
 
3857
        my ($self) = @_;
 
3858
        command_close_pipe($self->{gui}, $self->{ctx});
 
3859
}
 
3860
 
 
3861
package Git::SVN::GlobSpec;
 
3862
use strict;
 
3863
use warnings;
 
3864
 
 
3865
sub new {
 
3866
        my ($class, $glob) = @_;
 
3867
        my $re = $glob;
 
3868
        $re =~ s!/+$!!g; # no need for trailing slashes
 
3869
        my $nr = ($re =~ s!^(.*)\*(.*)$!\(\[^/\]+\)!g);
 
3870
        my ($left, $right) = ($1, $2);
 
3871
        if ($nr > 1) {
 
3872
                die "Only one '*' wildcard expansion ",
 
3873
                    "is supported (got $nr): '$glob'\n";
 
3874
        } elsif ($nr == 0) {
 
3875
                die "One '*' is needed for glob: '$glob'\n";
 
3876
        }
 
3877
        $re = quotemeta($left) . $re . quotemeta($right);
 
3878
        if (length $left && !($left =~ s!/+$!!g)) {
 
3879
                die "Missing trailing '/' on left side of: '$glob' ($left)\n";
 
3880
        }
 
3881
        if (length $right && !($right =~ s!^/+!!g)) {
 
3882
                die "Missing leading '/' on right side of: '$glob' ($right)\n";
 
3883
        }
 
3884
        my $left_re = qr/^\/\Q$left\E(\/|$)/;
 
3885
        bless { left => $left, right => $right, left_regex => $left_re,
 
3886
                regex => qr/$re/, glob => $glob }, $class;
 
3887
}
 
3888
 
 
3889
sub full_path {
 
3890
        my ($self, $path) = @_;
 
3891
        return (length $self->{left} ? "$self->{left}/" : '') .
 
3892
               $path . (length $self->{right} ? "/$self->{right}" : '');
 
3893
}
 
3894
 
3428
3895
__END__
3429
3896
 
3430
3897
Data structures:
3431
3898
 
3432
 
$svn_log hashref (as returned by svn_log_raw)
3433
 
{
3434
 
        fh => file handle of the log file,
3435
 
        state => state of the log file parser (sep/msg/rev/msg_start...)
3436
 
}
3437
 
 
3438
 
$log_msg hashref as returned by next_log_entry($svn_log)
3439
 
{
3440
 
        msg => 'whitespace-formatted log entry
 
3899
 
 
3900
$remotes = { # returned by read_all_remotes()
 
3901
        'svn' => {
 
3902
                # svn-remote.svn.url=https://svn.musicpd.org
 
3903
                url => 'https://svn.musicpd.org',
 
3904
                # svn-remote.svn.fetch=mpd/trunk:trunk
 
3905
                fetch => {
 
3906
                        'mpd/trunk' => 'trunk',
 
3907
                },
 
3908
                # svn-remote.svn.tags=mpd/tags/*:tags/*
 
3909
                tags => {
 
3910
                        path => {
 
3911
                                left => 'mpd/tags',
 
3912
                                right => '',
 
3913
                                regex => qr!mpd/tags/([^/]+)$!,
 
3914
                                glob => 'tags/*',
 
3915
                        },
 
3916
                        ref => {
 
3917
                                left => 'tags',
 
3918
                                right => '',
 
3919
                                regex => qr!tags/([^/]+)$!,
 
3920
                                glob => 'tags/*',
 
3921
                        },
 
3922
                }
 
3923
        }
 
3924
};
 
3925
 
 
3926
$log_entry hashref as returned by libsvn_log_entry()
 
3927
{
 
3928
        log => 'whitespace-formatted log entry
3441
3929
',                                              # trailing newline is preserved
3442
3930
        revision => '8',                        # integer
3443
3931
        date => '2004-02-24T17:01:44.108345Z',  # commit date
3445
3933
};
3446
3934
 
3447
3935
 
 
3936
# this is generated by generate_diff();
3448
3937
@mods = array of diff-index line hashes, each element represents one line
3449
3938
        of diff-index output
3450
3939