Follow-up to r29036: Now that the "mergeinfo" transaction file is no
[svn.git] / contrib / client-side / svn_update.pl
blob9d84e7f611180060c0f6538ab8c1d9345ea18f25
1 #!/usr/bin/perl -w
3 # ====================================================================
5 # svn_update.pl
7 # Put this in your path somewhere and make sure it has exec perms.
8 # Do your thing w/subversion but when you would use 'svn update'
9 # call this script instead.
11 # ====================================================================
12 # Copyright (c) 2000-2004 CollabNet. All rights reserved.
14 # This software is licensed as described in the file COPYING, which
15 # you should have received as part of this distribution. The terms
16 # are also available at http://subversion.tigris.org/license-1.html.
17 # If newer versions of this license are posted there, you may use a
18 # newer version instead, at your option.
20 # This software consists of voluntary contributions made by many
21 # individuals. For exact contribution history, see the revision
22 # history and logs, available at http://subversion.tigris.org/.
23 # ====================================================================
26 # WHY THE NEED FOR THIS SCRIPT?
28 # Currently, the subversion server will attempt to stream all file
29 # data to the client at once for _each_ merge candidate. For cases
30 # that have >1 file and/or the complexity of the merge for any
31 # given file(s) that would require >n minutes, where n is the
32 # server's magic timeout (5 min.??), the server will timeout. This
33 # leaves the client/user in an unswell state. See issue #2048 for
34 # details http://subversion.tigris.org/issues/show_bug.cgi?id=2048.
36 # One solution is to wrap the 'svn update' command in a script that
37 # will perform the update one file at a time. The problem with
38 # this solution is that it defeats the beneficial atomic nature of
39 # subversion for this type of action. If commits are still coming
40 # in to the repository, the value of "HEAD" might change between each
41 # of these update operations.
43 # Another solution, the one that this script utilizes, passes the
44 # --diff3-cmd directive to 'svn update' using a command which forces
45 # a failed contextual merge ('/bin/false', for example). These faux
46 # merge failures cause subversion to leave all of the accounting files
47 # involved in a merge behind and puts them into the 'conflict'
48 # state. Since all the data required for all the merges took place
49 # at that exact moment atomicity is preserved and life is swell.
51 #######################################################################
53 # This is required for copy() command. I believe it's a standard
54 # module. If there's a doubt run this from a shell:
55 # perl -e 'use File::Copy;'
56 # If you don't get any complaint from perl you're good. Otherwise
57 # comment this line out and change the $backup_mine_files to 0.
58 use File::Copy;
60 # This forces backing up of the .mine files for reference even
61 # after the resolved command. The backups will be stored as
62 # <filename.username>
63 $backup_mine_files=1;
65 # Choose your favorite graphical diff app. If it's not here, just add
66 # the full path to it and the style of the options to the
67 # %DIFF3CMD_hash below.
68 $DIFF3CMD="xcleardiff";
70 # Override the diff3-cmd in the config file here.
71 # If this is an empty string it'll use the config file's
72 # setting.
73 #$d3cmdoverride="";
74 $d3cmdoverride="/bin/false";
76 # Add more diff programs here.
77 # For the internal, discovered, file parameters:
78 # +A+ ==> mine
79 # +B+ ==> older
80 # +C+ ==> latest
81 # +D+ ==> Destination (The output of your merged code would go here.
82 # This would, generally, be whatever
83 # $(basename <+A+> .mine) would evaluate to.
84 # But you can feel free to do something like these:
85 # "+B+ +C+ +A+ -out +D+.bob"
86 # "+B+ +A+ +C+ -out /tmp/bob"
87 # Just note that the '+' (plus) are to limit the false positives in the
88 # search and replace sub.
90 # HAVING THE CORRECT PATH, AND ARGS FOR YOUR DIFF PROGRAM, IS CRITICAL!!
91 %DIFF3CMD_hash=(# Ahh...hallowed be thy name.
92 "/opt/atria/bin/xcleardiff" => "+A+ +B+ +C+ -out +D+",
93 # This one is slow.
94 "/usr/bin/kdiff3" => "+A+ +B+ +C+ -o +D+",
95 # This one's slow and it sucks!(BUGGY)
96 "/usr/bin/xxdiff" => "-M +D+ +A+ +B+ +C+",
97 # This one's even worse (no output filename).
98 "/usr/bin/meld" => "+A+ +B+ +C+",
101 sub exec_cmd
103 my @args=@_;
104 my $CMD=$args[0];
105 my @retData;
106 my $i=0;
108 open (FH,$CMD) || die("Can't '$CMD': $!\n");
109 while($_=<FH>)
111 chomp($_);
112 $retData[$i]=$_;
113 $i++;
115 close(FH);
117 return \@retData;
119 } # exec_cmd
121 sub diff_it
123 my @args=@_;
124 my $A=$args[0]; # mine
125 my $B=$args[1]; # older
126 my $C=$args[2]; # latest
127 my $D=$args[3]; # output of merge (Destination)
128 my @rdat;
130 # What's is the diff of choice?
131 if( $CHOSENDIFF eq "" )
133 # Glean our choice.
134 diff_of_choice();
137 # $CHOSENDIFF has data. We deal with the args.
138 ($diffcmd,$diff_format)=(split /:/,$CHOSENDIFF);
140 # This works.
141 $diff_format=~s/\+A\+/$A/g;
142 $diff_format=~s/\+B\+/$B/g;
143 $diff_format=~s/\+C\+/$C/g;
144 $diff_format=~s/\+D\+/$D/g;
146 @rdat=@{exec_cmd("$diffcmd $diff_format 2>/dev/null |")};
148 return @rdat;
150 } #diff_it
152 sub diff_of_choice
154 foreach $diff_app (sort(keys(%DIFF3CMD_hash)))
156 if( ${diff_app}=~m/${DIFF3CMD}/o )
158 $CHOSENDIFF="$diff_app:$DIFF3CMD_hash{$diff_app}";
161 if( $CHOSENDIFF eq "" )
163 # Big problem. Some kind of disconnect w/the choice and the hash.
164 # Most likely a typo.
165 print "It would seem that the '${DIFF3CMD}' was not found in\n";
166 print "the hash of diff applications I know about. Please\n";
167 print "investigate and correct.\n";
168 exit(1);
171 } #diff_of_choice
173 sub svn_update_info
175 my @data;
176 my @file_array;
177 my $j;
179 # Check to see if the d3cmdoverride is set so
180 # we don't fail here if it wasn't.
181 if( ${d3cmdoverride} eq "" )
183 $d3cmdoverride_final="";
185 else
187 $d3cmdoverride_final="--diff3-cmd=${d3cmdoverride}";
190 @data=@{exec_cmd("svn update ${d3cmdoverride_final} | grep ^C | awk '{print \$2}' |")};
191 for($j=0;$j<(scalar(@data));$j++)
193 push( @file_array, exec_cmd("svn info $data[$j] |") );
196 return @file_array;
198 } # svn_update_info
200 sub parse_it
202 my @file_array=@_;
203 my @file;
204 my $fname;
205 my $fpath_tmp;
206 my $fpath;
207 my $file_ref;
208 my $sbox_repo_base;
209 my $sbox_repo_changed;
210 my $sbox_repo_latest;
211 my @cleanup_array;
212 my @commit_array;
213 my $rdat;
214 my $retline;
215 my $i;
217 foreach $file_ref (@file_array)
219 @file=@{$file_ref};
220 for($i=0; $i < (scalar(@file)-1);$i++)
222 if( $file[$i]=~m/Name:/o )
224 # Key off of "Name:" and then back up one to get "Path:".
225 # This way the calculations will be correct when chopping off
226 # the name portion on the path bit.
227 $fname=(split /Name: /,$file[$i])[1];
228 $fpath_tmp=(split /Path: /,$file[$i-1])[1];
229 $fpath=(substr($fpath_tmp,0,(length($fpath_tmp)-(length($fname)+1))));
231 elsif( $file[$i]=~m/Conflict Previous Base File:/o )
233 $sbox_repo_base=(split /Conflict Previous Base File: /,$file[$i])[1];
235 elsif( $file[$i]=~m/Conflict Previous Working File:/o )
237 $sbox_repo_changed=(split /Conflict Previous Working File: /,
238 $file[$i])[1];
240 elsif( $file[$i]=~m/Conflict Current Base File:/o )
242 $sbox_repo_latest=(split /Conflict Current Base File: /,$file[$i])[1];
246 # print "\n----------------------------------------------\n";
247 # print " \$fpath: '$fpath'\n";
248 # print " \$fname: '$fname'\n";
249 # print "[A](mine) \$sbox_repo_changed: '$sbox_repo_changed'\n";
250 # print "[B](older) \$sbox_repo_base: '$sbox_repo_base'\n";
251 # print "[C](latest)\$sbox_repo_latest: '$sbox_repo_latest'\n";
252 # print "\n----------------------------------------------\n";
254 # Send them in standard, ABCD, order.
255 @rdat=diff_it("${fpath}/${sbox_repo_changed}",
256 "${fpath}/${sbox_repo_base}",
257 "${fpath}/${sbox_repo_latest}",
258 "${fpath}/${fname}");
260 # Print out any return data. Shouldn't be much of anything due to
261 # the redirection to /dev/null in the invocation of whatever diff
262 # command is used.
263 print "\nOutput from diff3 command.\n";
264 print "-----------------------\n";
265 foreach $retline (@rdat)
267 print "$retline\n";
269 print "---------END-----------\n";
271 # Add the files we may wish to remove to an array. Keep *.mine
272 # files in case something bad happened.
273 push(@cleanup_array,
274 "${fpath}/${sbox_repo_base}",
275 "${fpath}/${sbox_repo_latest}",
276 "${fpath}/${fname}.orig");
278 # Make copies of *.mine for ctya purposes.
279 if ( ${backup_mine_files} > 0 )
281 copy("${fpath}/${sbox_repo_changed}",
282 "${fpath}/${fname}.${ENV{USERNAME}}");
285 # Return an array that contains all the files we might wish to
286 # commit.
287 push(@commit_array,"${fpath}/${fname}");
290 return \@cleanup_array, \@commit_array;
292 } #parse_it
294 sub clean_up
296 my @args=@_;
297 my @rdat;
299 foreach $file (@{$args[0]})
301 unlink($file);
304 # Need to tell subversion we have resolved the conflicts.
305 @rdat=@{exec_cmd("svn -R resolved . |")};
306 print "\nOutput from subversion 'resolved' command.\n";
307 print "-----------------------\n";
308 foreach $line (@rdat)
310 print "$line\n";
312 print "---------END-----------\n";
314 } #clean_up
316 sub main
318 my @args=@_;
319 my $cleanup_aref;
320 my $commit_aref;
321 my $file;
323 ($cleanup_aref,$commit_aref)=parse_it(svn_update_info());
324 clean_up($cleanup_aref);
326 print "\nDon't forget to commit these files:\n";
327 print "-----------------------\n";
328 foreach $file (@{$commit_aref})
330 print "$file\n";
332 print "---------END-----------\n";
334 } #main
336 # Special global.
337 $CHOSENDIFF="";
339 # Get the ball rolling.
340 main(@ARGV);