Extract Git::SVN::Migration from git-svn.
Straight cut & paste. Signed-off-by: Eric Wong <normalperson@yhbt.net>
Michael G. Schwern committed
Jul 26, 2012 at 17:26 UTC
b772cb9994e085c4a55820092011ee2b8997b0ac
4 files changed
+262
-258
git-svn.perl
+1
-257
@@ -12,6 +12,7 @@ $VERSION = '@@GIT_VERSION@@';
12
13
use Git::SVN;
14
use Git::SVN::Log;
15
+use Git::SVN::Migration;
16
use Git::SVN::Utils qw(fatal can_compress);
17
18
use Git qw(
@@ -2041,263 +2042,6 @@ sub gc_directory {
2042
}
2043
2044
2044
-package Git::SVN::Migration;
2045
-# these version numbers do NOT correspond to actual version numbers
2046
-# of git nor git-svn. They are just relative.
2047
-#
2048
-# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD
2049
-#
2050
-# v1 layout: .git/$id/info/url, refs/remotes/$id
2051
-#
2052
-# v2 layout: .git/svn/$id/info/url, refs/remotes/$id
2053
-#
2054
-# v3 layout: .git/svn/$id, refs/remotes/$id
2055
-# - info/url may remain for backwards compatibility
2056
-# - this is what we migrate up to this layout automatically,
2057
-# - this will be used by git svn init on single branches
2058
-# v3.1 layout (auto migrated):
2059
-# - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink
2060
-# for backwards compatibility
2061
-#
2062
-# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id
2063
-# - this is only created for newly multi-init-ed
2064
-# repositories. Similar in spirit to the
2065
-# --use-separate-remotes option in git-clone (now default)
2066
-# - we do not automatically migrate to this (following
2067
-# the example set by core git)
2068
-#
2069
-# v5 layout: .rev_db.$UUID => .rev_map.$UUID
2070
-# - newer, more-efficient format that uses 24-bytes per record
2071
-# with no filler space.
2072
-# - use xxd -c24 < .rev_map.$UUID to view and debug
2073
-# - This is a one-way migration, repositories updated to the
2074
-# new format will not be able to use old git-svn without
2075
-# rebuilding the .rev_db. Rebuilding the rev_db is not
2076
-# possible if noMetadata or useSvmProps are set; but should
2077
-# be no problem for users that use the (sensible) defaults.
2078
-use strict;
2079
-use warnings;
2080
-use Carp qw/croak/;
2081
-use File::Path qw/mkpath/;
2082
-use File::Basename qw/dirname basename/;
2083
-
2084
-our $_minimize;
2085
-use Git qw(
2086
- command
2087
- command_noisy
2088
- command_output_pipe
2089
- command_close_pipe
2090
-);
2091
-
2092
-sub migrate_from_v0 {
2093
- my $git_dir = $ENV{GIT_DIR};
2094
- return undef unless -d $git_dir;
2095
- my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
2096
- my $migrated = 0;
2097
- while (<$fh>) {
2098
- chomp;
2099
- my ($id, $orig_ref) = ($_, $_);
2100
- next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#;
2101
- next unless -f "$git_dir/$id/info/url";
2102
- my $new_ref = "refs/remotes/$id";
2103
- if (::verify_ref("$new_ref^0")) {
2104
- print STDERR "W: $orig_ref is probably an old ",
2105
- "branch used by an ancient version of ",
2106
- "git-svn.\n",
2107
- "However, $new_ref also exists.\n",
2108
- "We will not be able ",
2109
- "to use this branch until this ",
2110
- "ambiguity is resolved.\n";
2111
- next;
2112
- }
2113
- print STDERR "Migrating from v0 layout...\n" if !$migrated;
2114
- print STDERR "Renaming ref: $orig_ref => $new_ref\n";
2115
- command_noisy('update-ref', $new_ref, $orig_ref);
2116
- command_noisy('update-ref', '-d', $orig_ref, $orig_ref);
2117
- $migrated++;
2118
- }
2119
- command_close_pipe($fh, $ctx);
2120
- print STDERR "Done migrating from v0 layout...\n" if $migrated;
2121
- $migrated;
2122
-}
2123
-
2124
-sub migrate_from_v1 {
2125
- my $git_dir = $ENV{GIT_DIR};
2126
- my $migrated = 0;
2127
- return $migrated unless -d $git_dir;
2128
- my $svn_dir = "$git_dir/svn";
2129
-
2130
- # just in case somebody used 'svn' as their $id at some point...
2131
- return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url";
2132
-
2133
- print STDERR "Migrating from a git-svn v1 layout...\n";
2134
- mkpath([$svn_dir]);
2135
- print STDERR "Data from a previous version of git-svn exists, but\n\t",
2136
- "$svn_dir\n\t(required for this version ",
2137
- "($::VERSION) of git-svn) does not exist.\n";
2138
- my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
2139
- while (<$fh>) {
2140
- my $x = $_;
2141
- next unless $x =~ s#^refs/remotes/##;
2142
- chomp $x;
2143
- next unless -f "$git_dir/$x/info/url";
2144
- my $u = eval { ::file_to_s("$git_dir/$x/info/url") };
2145
- next unless $u;
2146
- my $dn = dirname("$git_dir/svn/$x");
2147
- mkpath([$dn]) unless -d $dn;
2148
- if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID:
2149
- mkpath(["$git_dir/svn/svn"]);
2150
- print STDERR " - $git_dir/$x/info => ",
2151
- "$git_dir/svn/$x/info\n";
2152
- rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or
2153
- croak "$!: $x";
2154
- # don't worry too much about these, they probably
2155
- # don't exist with repos this old (save for index,
2156
- # and we can easily regenerate that)
2157
- foreach my $f (qw/unhandled.log index .rev_db/) {
2158
- rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f";
2159
- }
2160
- } else {
2161
- print STDERR " - $git_dir/$x => $git_dir/svn/$x\n";
2162
- rename "$git_dir/$x", "$git_dir/svn/$x" or
2163
- croak "$!: $x";
2164
- }
2165
- $migrated++;
2166
- }
2167
- command_close_pipe($fh, $ctx);
2168
- print STDERR "Done migrating from a git-svn v1 layout\n";
2169
- $migrated;
2170
-}
2171
-
2172
-sub read_old_urls {
2173
- my ($l_map, $pfx, $path) = @_;
2174
- my @dir;
2175
- foreach (<$path/*>) {
2176
- if (-r "$_/info/url") {
2177
- $pfx .= '/' if $pfx && $pfx !~ m!/$!;
2178
- my $ref_id = $pfx . basename $_;
2179
- my $url = ::file_to_s("$_/info/url");
2180
- $l_map->{$ref_id} = $url;
2181
- } elsif (-d $_) {
2182
- push @dir, $_;
2183
- }
2184
- }
2185
- foreach (@dir) {
2186
- my $x = $_;
2187
- $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
2188
- read_old_urls($l_map, $x, $_);
2189
- }
2190
-}
2191
-
2192
-sub migrate_from_v2 {
2193
- my @cfg = command(qw/config -l/);
2194
- return if grep /^svn-remote\..+\.url=/, @cfg;
2195
- my %l_map;
2196
- read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn");
2197
- my $migrated = 0;
2198
-
2199
- require Git::SVN;
2200
- foreach my $ref_id (sort keys %l_map) {
2201
- eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) };
2202
- if ($@) {
2203
- Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
2204
- }
2205
- $migrated++;
2206
- }
2207
- $migrated;
2208
-}
2209
-
2210
-sub minimize_connections {
2211
- require Git::SVN;
2212
- require Git::SVN::Ra;
2213
-
2214
- my $r = Git::SVN::read_all_remotes();
2215
- my $new_urls = {};
2216
- my $root_repos = {};
2217
- foreach my $repo_id (keys %$r) {
2218
- my $url = $r->{$repo_id}->{url} or next;
2219
- my $fetch = $r->{$repo_id}->{fetch} or next;
2220
- my $ra = Git::SVN::Ra->new($url);
2221
-
2222
- # skip existing cases where we already connect to the root
2223
- if (($ra->{url} eq $ra->{repos_root}) ||
2224
- ($ra->{repos_root} eq $repo_id)) {
2225
- $root_repos->{$ra->{url}} = $repo_id;
2226
- next;
2227
- }
2228
-
2229
- my $root_ra = Git::SVN::Ra->new($ra->{repos_root});
2230
- my $root_path = $ra->{url};
2231
- $root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##;
2232
- foreach my $path (keys %$fetch) {
2233
- my $ref_id = $fetch->{$path};
2234
- my $gs = Git::SVN->new($ref_id, $repo_id, $path);
2235
-
2236
- # make sure we can read when connecting to
2237
- # a higher level of a repository
2238
- my ($last_rev, undef) = $gs->last_rev_commit;
2239
- if (!defined $last_rev) {
2240
- $last_rev = eval {
2241
- $root_ra->get_latest_revnum;
2242
- };
2243
- next if $@;
2244
- }
2245
- my $new = $root_path;
2246
- $new .= length $path ? "/$path" : '';
2247
- eval {
2248
- $root_ra->get_log([$new], $last_rev, $last_rev,
2249
- 0, 0, 1, sub { });
2250
- };
2251
- next if $@;
2252
- $new_urls->{$ra->{repos_root}}->{$new} =
2253
- { ref_id => $ref_id,
2254
- old_repo_id => $repo_id,
2255
- old_path => $path };
2256
- }
2257
- }
2258
-
2259
- my @emptied;
2260
- foreach my $url (keys %$new_urls) {
2261
- # see if we can re-use an existing [svn-remote "repo_id"]
2262
- # instead of creating a(n ugly) new section:
2263
- my $repo_id = $root_repos->{$url} || $url;
2264
-
2265
- my $fetch = $new_urls->{$url};
2266
- foreach my $path (keys %$fetch) {
2267
- my $x = $fetch->{$path};
2268
- Git::SVN->init($url, $path, $repo_id, $x->{ref_id});
2269
- my $pfx = "svn-remote.$x->{old_repo_id}";
2270
-
2271
- my $old_fetch = quotemeta("$x->{old_path}:".
2272
- "$x->{ref_id}");
2273
- command_noisy(qw/config --unset/,
2274
- "$pfx.fetch", '^'. $old_fetch . '$');
2275
- delete $r->{$x->{old_repo_id}}->
2276
- {fetch}->{$x->{old_path}};
2277
- if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) {
2278
- command_noisy(qw/config --unset/,
2279
- "$pfx.url");
2280
- push @emptied, $x->{old_repo_id}
2281
- }
2282
- }
2283
- }
2284
- if (@emptied) {
2285
- my $file = $ENV{GIT_CONFIG} || "$ENV{GIT_DIR}/config";
2286
- print STDERR <<EOF;
2287
-The following [svn-remote] sections in your config file ($file) are empty
2288
-and can be safely removed:
2289
-EOF
2290
- print STDERR "[svn-remote \"$_\"]\n" foreach @emptied;
2291
- }
2292
-}
2293
-
2294
-sub migration_check {
2295
- migrate_from_v0();
2296
- migrate_from_v1();
2297
- migrate_from_v2();
2298
- minimize_connections() if $_minimize;
2299
-}
2300
-
2045
package Git::IndexInfo;
2046
use strict;
2047
use warnings;
perl/Git/SVN/Migration.pm
new
+258
@@ -0,0 +1,258 @@
1
+package Git::SVN::Migration;
2
+# these version numbers do NOT correspond to actual version numbers
3
+# of git nor git-svn. They are just relative.
4
+#
5
+# v0 layout: .git/$id/info/url, refs/heads/$id-HEAD
6
+#
7
+# v1 layout: .git/$id/info/url, refs/remotes/$id
8
+#
9
+# v2 layout: .git/svn/$id/info/url, refs/remotes/$id
10
+#
11
+# v3 layout: .git/svn/$id, refs/remotes/$id
12
+# - info/url may remain for backwards compatibility
13
+# - this is what we migrate up to this layout automatically,
14
+# - this will be used by git svn init on single branches
15
+# v3.1 layout (auto migrated):
16
+# - .rev_db => .rev_db.$UUID, .rev_db will remain as a symlink
17
+# for backwards compatibility
18
+#
19
+# v4 layout: .git/svn/$repo_id/$id, refs/remotes/$repo_id/$id
20
+# - this is only created for newly multi-init-ed
21
+# repositories. Similar in spirit to the
22
+# --use-separate-remotes option in git-clone (now default)
23
+# - we do not automatically migrate to this (following
24
+# the example set by core git)
25
+#
26
+# v5 layout: .rev_db.$UUID => .rev_map.$UUID
27
+# - newer, more-efficient format that uses 24-bytes per record
28
+# with no filler space.
29
+# - use xxd -c24 < .rev_map.$UUID to view and debug
30
+# - This is a one-way migration, repositories updated to the
31
+# new format will not be able to use old git-svn without
32
+# rebuilding the .rev_db. Rebuilding the rev_db is not
33
+# possible if noMetadata or useSvmProps are set; but should
34
+# be no problem for users that use the (sensible) defaults.
35
+use strict;
36
+use warnings;
37
+use Carp qw/croak/;
38
+use File::Path qw/mkpath/;
39
+use File::Basename qw/dirname basename/;
40
+
41
+our $_minimize;
42
+use Git qw(
43
+ command
44
+ command_noisy
45
+ command_output_pipe
46
+ command_close_pipe
47
+);
48
+
49
+sub migrate_from_v0 {
50
+ my $git_dir = $ENV{GIT_DIR};
51
+ return undef unless -d $git_dir;
52
+ my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
53
+ my $migrated = 0;
54
+ while (<$fh>) {
55
+ chomp;
56
+ my ($id, $orig_ref) = ($_, $_);
57
+ next unless $id =~ s#^refs/heads/(.+)-HEAD$#$1#;
58
+ next unless -f "$git_dir/$id/info/url";
59
+ my $new_ref = "refs/remotes/$id";
60
+ if (::verify_ref("$new_ref^0")) {
61
+ print STDERR "W: $orig_ref is probably an old ",
62
+ "branch used by an ancient version of ",
63
+ "git-svn.\n",
64
+ "However, $new_ref also exists.\n",
65
+ "We will not be able ",
66
+ "to use this branch until this ",
67
+ "ambiguity is resolved.\n";
68
+ next;
69
+ }
70
+ print STDERR "Migrating from v0 layout...\n" if !$migrated;
71
+ print STDERR "Renaming ref: $orig_ref => $new_ref\n";
72
+ command_noisy('update-ref', $new_ref, $orig_ref);
73
+ command_noisy('update-ref', '-d', $orig_ref, $orig_ref);
74
+ $migrated++;
75
+ }
76
+ command_close_pipe($fh, $ctx);
77
+ print STDERR "Done migrating from v0 layout...\n" if $migrated;
78
+ $migrated;
79
+}
80
+
81
+sub migrate_from_v1 {
82
+ my $git_dir = $ENV{GIT_DIR};
83
+ my $migrated = 0;
84
+ return $migrated unless -d $git_dir;
85
+ my $svn_dir = "$git_dir/svn";
86
+
87
+ # just in case somebody used 'svn' as their $id at some point...
88
+ return $migrated if -d $svn_dir && ! -f "$svn_dir/info/url";
89
+
90
+ print STDERR "Migrating from a git-svn v1 layout...\n";
91
+ mkpath([$svn_dir]);
92
+ print STDERR "Data from a previous version of git-svn exists, but\n\t",
93
+ "$svn_dir\n\t(required for this version ",
94
+ "($::VERSION) of git-svn) does not exist.\n";
95
+ my ($fh, $ctx) = command_output_pipe(qw/rev-parse --symbolic --all/);
96
+ while (<$fh>) {
97
+ my $x = $_;
98
+ next unless $x =~ s#^refs/remotes/##;
99
+ chomp $x;
100
+ next unless -f "$git_dir/$x/info/url";
101
+ my $u = eval { ::file_to_s("$git_dir/$x/info/url") };
102
+ next unless $u;
103
+ my $dn = dirname("$git_dir/svn/$x");
104
+ mkpath([$dn]) unless -d $dn;
105
+ if ($x eq 'svn') { # they used 'svn' as GIT_SVN_ID:
106
+ mkpath(["$git_dir/svn/svn"]);
107
+ print STDERR " - $git_dir/$x/info => ",
108
+ "$git_dir/svn/$x/info\n";
109
+ rename "$git_dir/$x/info", "$git_dir/svn/$x/info" or
110
+ croak "$!: $x";
111
+ # don't worry too much about these, they probably
112
+ # don't exist with repos this old (save for index,
113
+ # and we can easily regenerate that)
114
+ foreach my $f (qw/unhandled.log index .rev_db/) {
115
+ rename "$git_dir/$x/$f", "$git_dir/svn/$x/$f";
116
+ }
117
+ } else {
118
+ print STDERR " - $git_dir/$x => $git_dir/svn/$x\n";
119
+ rename "$git_dir/$x", "$git_dir/svn/$x" or
120
+ croak "$!: $x";
121
+ }
122
+ $migrated++;
123
+ }
124
+ command_close_pipe($fh, $ctx);
125
+ print STDERR "Done migrating from a git-svn v1 layout\n";
126
+ $migrated;
127
+}
128
+
129
+sub read_old_urls {
130
+ my ($l_map, $pfx, $path) = @_;
131
+ my @dir;
132
+ foreach (<$path/*>) {
133
+ if (-r "$_/info/url") {
134
+ $pfx .= '/' if $pfx && $pfx !~ m!/$!;
135
+ my $ref_id = $pfx . basename $_;
136
+ my $url = ::file_to_s("$_/info/url");
137
+ $l_map->{$ref_id} = $url;
138
+ } elsif (-d $_) {
139
+ push @dir, $_;
140
+ }
141
+ }
142
+ foreach (@dir) {
143
+ my $x = $_;
144
+ $x =~ s!^\Q$ENV{GIT_DIR}\E/svn/!!o;
145
+ read_old_urls($l_map, $x, $_);
146
+ }
147
+}
148
+
149
+sub migrate_from_v2 {
150
+ my @cfg = command(qw/config -l/);
151
+ return if grep /^svn-remote\..+\.url=/, @cfg;
152
+ my %l_map;
153
+ read_old_urls(\%l_map, '', "$ENV{GIT_DIR}/svn");
154
+ my $migrated = 0;
155
+
156
+ require Git::SVN;
157
+ foreach my $ref_id (sort keys %l_map) {
158
+ eval { Git::SVN->init($l_map{$ref_id}, '', undef, $ref_id) };
159
+ if ($@) {
160
+ Git::SVN->init($l_map{$ref_id}, '', $ref_id, $ref_id);
161
+ }
162
+ $migrated++;
163
+ }
164
+ $migrated;
165
+}
166
+
167
+sub minimize_connections {
168
+ require Git::SVN;
169
+ require Git::SVN::Ra;
170
+
171
+ my $r = Git::SVN::read_all_remotes();
172
+ my $new_urls = {};
173
+ my $root_repos = {};
174
+ foreach my $repo_id (keys %$r) {
175
+ my $url = $r->{$repo_id}->{url} or next;
176
+ my $fetch = $r->{$repo_id}->{fetch} or next;
177
+ my $ra = Git::SVN::Ra->new($url);
178
+
179
+ # skip existing cases where we already connect to the root
180
+ if (($ra->{url} eq $ra->{repos_root}) ||
181
+ ($ra->{repos_root} eq $repo_id)) {
182
+ $root_repos->{$ra->{url}} = $repo_id;
183
+ next;
184
+ }
185
+
186
+ my $root_ra = Git::SVN::Ra->new($ra->{repos_root});
187
+ my $root_path = $ra->{url};
188
+ $root_path =~ s#^\Q$ra->{repos_root}\E(/|$)##;
189
+ foreach my $path (keys %$fetch) {
190
+ my $ref_id = $fetch->{$path};
191
+ my $gs = Git::SVN->new($ref_id, $repo_id, $path);
192
+
193
+ # make sure we can read when connecting to
194
+ # a higher level of a repository
195
+ my ($last_rev, undef) = $gs->last_rev_commit;
196
+ if (!defined $last_rev) {
197
+ $last_rev = eval {
198
+ $root_ra->get_latest_revnum;
199
+ };
200
+ next if $@;
201
+ }
202
+ my $new = $root_path;
203
+ $new .= length $path ? "/$path" : '';
204
+ eval {
205
+ $root_ra->get_log([$new], $last_rev, $last_rev,
206
+ 0, 0, 1, sub { });
207
+ };
208
+ next if $@;
209
+ $new_urls->{$ra->{repos_root}}->{$new} =
210
+ { ref_id => $ref_id,
211
+ old_repo_id => $repo_id,
212
+ old_path => $path };
213
+ }
214
+ }
215
+
216
+ my @emptied;
217
+ foreach my $url (keys %$new_urls) {
218
+ # see if we can re-use an existing [svn-remote "repo_id"]
219
+ # instead of creating a(n ugly) new section:
220
+ my $repo_id = $root_repos->{$url} || $url;
221
+
222
+ my $fetch = $new_urls->{$url};
223
+ foreach my $path (keys %$fetch) {
224
+ my $x = $fetch->{$path};
225
+ Git::SVN->init($url, $path, $repo_id, $x->{ref_id});
226
+ my $pfx = "svn-remote.$x->{old_repo_id}";
227
+
228
+ my $old_fetch = quotemeta("$x->{old_path}:".
229
+ "$x->{ref_id}");
230
+ command_noisy(qw/config --unset/,
231
+ "$pfx.fetch", '^'. $old_fetch . '$');
232
+ delete $r->{$x->{old_repo_id}}->
233
+ {fetch}->{$x->{old_path}};
234
+ if (!keys %{$r->{$x->{old_repo_id}}->{fetch}}) {
235
+ command_noisy(qw/config --unset/,
236
+ "$pfx.url");
237
+ push @emptied, $x->{old_repo_id}
238
+ }
239
+ }
240
+ }
241
+ if (@emptied) {
242
+ my $file = $ENV{GIT_CONFIG} || "$ENV{GIT_DIR}/config";
243
+ print STDERR <<EOF;
244
+The following [svn-remote] sections in your config file ($file) are empty
245
+and can be safely removed:
246
+EOF
247
+ print STDERR "[svn-remote \"$_\"]\n" foreach @emptied;
248
+ }
249
+}
250
+
251
+sub migration_check {
252
+ migrate_from_v0();
253
+ migrate_from_v1();
254
+ migrate_from_v2();
255
+ minimize_connections() if $_minimize;
256
+}
257
+
258
+1;
perl/Makefile
+1
@@ -34,6 +34,7 @@ modules += Git/SVN/Memoize/YAML
34
modules += Git/SVN/Fetcher
35
modules += Git/SVN/Editor
36
modules += Git/SVN/Log
37
+modules += Git/SVN/Migration
38
modules += Git/SVN/Prompt
39
modules += Git/SVN/Ra
40
modules += Git/SVN/Utils
t/Git-SVN/00compile.t
+2
-1
@@ -3,9 +3,10 @@
3
use strict;
4
use warnings;
5
6
-use Test::More tests => 4;
6
+use Test::More tests => 5;
7
8
require_ok 'Git::SVN';
9
require_ok 'Git::SVN::Utils';
10
require_ok 'Git::SVN::Ra';
11
require_ok 'Git::SVN::Log';
12
+require_ok 'Git::SVN::Migration';