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';