Your IP : 216.73.217.87
| Current Path : /usr/X11R6/bin/ |
|
|
| Current File : //usr/X11R6/bin/svnintegrate |
#!/usr/bin/perl
#
# Copyright (C) 2006 Thiago Macieira <thiago@kde.org>
#
# This file is distributed under the Artistic License version 2.1
#
# Usage:
# svnintegrate [-n] [-v] [-q] [-b branchname] [filename]
#
# Where:
# -n dry run - do not actually run commands that modify sources
# can be used to determine the changeset that would be backported
# -v verbose - show the commands being executed
# -q quiet - do not output some unnecessary messages
# -b branch the branch to merge into [default: trunk]
# filename the file whose last change should be integrated
# if unspecified, the last change for the current directory will
# be used
#
# Also note that svnintegrate will try to locate ALL files modified
# in the changeset being integrated. So you don't need to specify all
# of them in the command line. However, they must all be present in
# the current directory or a subdirectory.
# (If they cannot be found, svnintegrate will tell you)
#
# About branch names:
# svnintegrate tries hard to understand the KDE branch-naming
# scheme. So, in many times, it is enough to simply tell it the
# version of the branch you're integrating into.
#
# Examples:
# switch integrating from integrate to
# -b trunk /branches/KDE/3.5 /trunk/KDE
# -b trunk/extragear/graphics
# /branches/amarok/1.3 /trunk/extragear/graphics/amarok
# -b 3.5 /trunk/KDE/kdelibs /branches/KDE/3.5/kdelibs
# -b 3.5 /branches/KDE/3.4 /branches/KDE/3.5
# -b tags/3.5.0 /branches/KDE/3.5 /tags/KDE/3.5.0
# -b work/my-branch /branches/KDE/3.4 /branches/work/my-branch
# -b work/my-branch /branches/KDE/3.4/kdelibs /branches/work/my-branch
# -b work/my-branch /trunk/KDE/kdepim/kmail /branches/work/my-branch
#
#
# Wanted features:
# Better branch guessing:
# - support for integrating from trunk/{playground,kdereview,extragear}
# - support for integrating from branches/work
# Other features:
# - support for integrating to checked-out branch
# - support for integrating multiple revisions
#
use XML::DOM;
use strict;
my $dirname;
my $svnroot;
my $lastDirCommitRevision;
my $revision;
my $lastCommitAuthor;
my $lastCommitMsg;
my $lastCommitDate;
my @changedPaths;
my @filenames;
my @switchedFilenames;
my @conflictedFilenames;
my $dryRun;
my $quiet;
my $verbose;
my $branch;
my $from;
my $to;
my $EDITOR;
my $PAGER;
sub getSvnInfo()
{
open(INFO, "-|", "svn", "info", "--xml") or die("Could not run svn");
my $info;
while (<INFO>)
{
$info .= $_;
}
close(INFO);
# now parse
my $parser = new XML::DOM::Parser;
my $doc = $parser->parse($info);
$dirname = $doc->getElementsByTagName("url")->item(0)->getFirstChild()->toString();
$svnroot = $doc->getElementsByTagName("root")->item(0)->getFirstChild()->toString();
$lastDirCommitRevision = $doc->getElementsByTagName("commit")->item(0)->getAttribute("revision");
# trim the root
$dirname = substr $dirname, length($svnroot);
$doc->dispose();
}
sub getLastCommitInfo($)
{
my $target = @_[0];
my $rev = "-rCOMMITTED";
$rev = "-r$revision" if (defined($revision));
open(LOG, "-|", "svn", "log", "--xml", "-v", $rev, $target)
or die("Could not run svn");
my $log;
while (<LOG>)
{
$log .= $_;
}
close(LOG);
# now parse it
my $parser = new XML::DOM::Parser;
my $doc = $parser->parse($log);
unless ($doc->getElementsByTagName("logentry")->getLength())
{
print STDERR "Cannot find revision $revision in the current directory.\n";
exit(1);
}
$revision = $doc->getElementsByTagName("logentry")->item(0)->getAttribute("revision");
$lastCommitMsg = $doc->getElementsByTagName("msg")->item(0)->getFirstChild()->toString();
$lastCommitAuthor = $doc->getElementsByTagName("author")->item(0)->getFirstChild()->toString();
$lastCommitDate = $doc->getElementsByTagName("date")->item(0)->getFirstChild()->toString();
my @changed = $doc->getElementsByTagName("path");
foreach my $path (@changed)
{
push(@changedPaths, $path->getFirstChild()->toString());
}
$doc->dispose();
}
sub transformToBranch($)
{
my $path = @_[0];
if ($path =~ m,^$from(/.*)?,)
{
$path = $to . $1;
}
else
{
print STDERR "Could not apply conversion \"$from\" -> \"$to\" in $dirname\n";
exit 1;
}
}
sub checkBranches()
{
if (!$from || !$to)
{
$branch = "trunk" unless ($branch);
if ($branch eq "trunk")
{
$to = "/trunk/KDE";
if ($dirname =~ m,^(/branches/KDE/[^/]+)/,)
{
$from = $1;
}
else
{
print STDERR "Cannot apply automatic conversion to trunk on $dirname\n";
print STDERR "Please use the -f and -t options\n";
exit 1;
}
}
else
{
if ($dirname =~ m,^/trunk,)
{
$from = "/trunk";
}
elsif ($dirname =~ m,^/branches/KDE/([^/]+)/,)
{
$from = "/branches/KDE/$1";
}
else
{
print STDERR "Cannot apply automatic conversion to branch $branch on $dirname\n";
print STDERR "Please use the -f and -t options\n";
exit 1;
}
$to = "/branches/KDE/$branch";
}
}
my $target = transformToBranch($dirname);
print "Porting from $dirname to $target\n";
if ($target eq $dirname)
{
print STDERR "Source and target branches are the same.\n";
exit 1;
}
}
sub showLog()
{
my $prettyDate = $lastCommitDate;
$prettyDate =~ s/^(.*)T(.*)\..*Z$/\1 \2 +0000/;
# mimic svn log:
print "------------------------------------------------------------------------\n";
print "r$revision | $lastCommitAuthor | $prettyDate\n";
print "\n";
print $lastCommitMsg;
print "\n\nChanged files:\n";
# file list shown by findAllFiles
}
sub findAllFiles()
{
foreach my $path (@changedPaths)
{
my $entry = $path;
if (!($entry =~ s#^$dirname/##o))
{
print STDERR "Cannot find file \'$svnroot$entry\' in current directory.\n";
print STDERR "Maybe you need to go up?\n";
exit 1;
}
print " $entry\n";
push(@filenames, $entry);
# check that it is clean
open(STATUS, "-|", "svn", "status", $entry);
my $status;
while (<STATUS>)
{
$status .= $_;
}
close(STATUS);
if (length($status))
{
print STDERR "File \'$entry\' has local modifications or is not clean. Cannot continue.\n";
exit 1;
}
}
print "------------------------------------------------------------------------\n";
}
sub run(@)
{
if ($dryRun || $verbose)
{
print join(" ", @_) . "\n";
}
if (!$dryRun)
{
if ((scalar @_) > 1)
{
open(PIPE, "-|", @_);
}
else
{
my $command = @_[0];
system($command);
return "";
}
my $output;
while (<PIPE>)
{
$output .= $_;
}
print $output unless ($quiet);
return $output;
}
return "";
}
sub rollback()
{
print "\nRolling back changes\n";
for my $file (@switchedFilenames)
{
run("svn", "revert", $file);
run("svn", "switch", "-r", $revision, "$svnroot$dirname/$file", $file);
}
run("rm", "svn-commit.tmp") if (-e "svn-commit.tmp");
run("rm", "svn-commit.tmp~") if (-e "svn-commit.tmp~");
}
sub switchAllFiles()
{
my $target = transformToBranch($dirname);
print "Switching files to branch $target\n"
unless ($quiet);
foreach my $file (@filenames)
{
my $output = run("svn", "switch", $svnroot . $target . "/$file", $file);
push(@switchedFilenames, $file);
}
}
sub handleConflict($)
{
my $file = @_[0];
my $target = transformToBranch($dirname);
my $leftname = "$file.merge-left.r" . ($revision - 1);
my $rightname = "$file.merge-right.r$revision";
my $workingname = "$file.working";
my $showmenu = 1;
while (1)
{
if ($showmenu)
{
print "\nFile \'$file\' has conflicts while merging.\n";
print "What do you want to do?\n";
print "\t1. Edit file with $EDITOR\n";
print "\t2. Show current diff to be committed\n";
print "\t3. Show the change between " . ($revision - 1) . " and $revision\n";
print "\t4. View original $dirname/$file (\"left\": before the change)\n";
print "\t5. View current $dirname/$file (\"right\": after the change)\n";
print "\t6. View current $target/$file (\"working\")\n";
print "\t7. Accept original $dirname/$file (\"left\")\n";
print "\t8. Accept current $dirname/$file (\"right\")\n";
print "\t9. Accept current $target/$file (\"working\": do not apply merge)\n";
print "\t0. Revert all and quit\n";
}
$showmenu = 0;
my $answer = <STDIN>;
$answer =~ s/\r?\n$//;
if ($answer eq "1")
{
run($EDITOR, $file);
print "Is it resolved? (Y/n)";
$answer = <STDIN>;
print "\n";
if ($answer =~ /y/i or length($answer) == 0)
{
run("svn", "resolved", $file);
return;
}
}
elsif ($answer eq "2")
{
run("svn diff $file | $PAGER");
}
elsif ($answer eq "3")
{
run("diff -u $leftname $rightname | $PAGER");
}
elsif ($answer eq "4")
{
run($PAGER, "$leftname");
}
elsif ($answer eq "5")
{
run($PAGER, "$rightname");
}
elsif ($answer eq "6")
{
run($PAGER, "$workingname");
}
elsif ($answer eq "7")
{
run("mv", "$leftname", $file);
run("svn", "resolved", $file);
return;
}
elsif ($answer eq "8")
{
run("mv", "$rightname", $file);
run("svn", "resolved", $file);
return;
}
elsif ($answer eq "9")
{
run("mv", "$workingname", $file);
run("svn", "resolved", $file);
return;
}
elsif ($answer eq "0")
{
rollback();
exit 0;
}
else
{
$showmenu = 1;
}
}
}
sub mergeRevision()
{
print "Merging revision $revision\n"
unless ($quiet);
my $output = run("svn", "merge", "-r", ($revision - 1) . ":" . $revision, $svnroot . $dirname);
my @lines = split(/\r?\n/, $output);
if (scalar @lines)
{
foreach my $line (@lines)
{
if ($line =~ /^C +(.+)$/)
{
push(@conflictedFilenames, $1)
}
if ($line =~ /^D +(.+)$/)
{
print STDERR "I cannot handle file deletions, sorry (\'$1\').\n";
print STDERR "You will probably have to run \'svn up\' to recover the file.\n";
rollback();
exit 1;
}
}
}
else
{
print "No files were changed: this changeset has already been integrated.\n";
rollback();
exit(0);
}
foreach my $conflict (@conflictedFilenames)
{
handleConflict($conflict);
}
}
sub createLogMessage()
{
open(LOG, ">svn-commit.tmp");
print LOG "INTEGRATION:$dirname $revision\n";
my @lines = split(/\r?\n/, $lastCommitMsg);
foreach my $line (@lines)
{
next if ($line =~ /^CC.?MAIL:.*/); # don't resend emails
next if ($line =~ /^GUI:/);
$line =~ s/^(BUG|FEATURE):/CCBUG:/; # change bug closing to comment
print LOG "$line\n";
}
close(LOG);
}
sub editLogMessage()
{
print "\nMerging successful\n";
while (1)
{
print "Press (C) to commit, (D) to see the diff, (Q) to quit or (E) to edit the log\n";
my $answer = <STDIN>;
$answer =~ s/\r?\n$//;
$answer =~ tr/A-Z/a-z/;
if ($answer eq "q")
{
rollback();
exit(0);
}
elsif ($answer eq "e")
{
run($EDITOR, "svn-commit.tmp");
}
elsif ($answer eq "d")
{
run("svn diff ". join(" ", @filenames) . " | $PAGER");
}
elsif ($answer eq "c")
{
return;
}
}
}
sub commit()
{
print "Committing changes\n";
run("svn commit -F svn-commit.tmp " . join(" ", @filenames));
print "Restoring old state\n";
rollback();
}
$EDITOR = $ENV{"EDITOR"};
$PAGER = $ENV{"PAGER"};
$EDITOR = "vi" unless ($EDITOR);
$PAGER = "less" unless ($PAGER);
$dryRun = 0;
$quiet = 0;
$verbose = 0;
while (@ARGV)
{
my $arg = shift @ARGV;
if ($arg eq "-n")
{
$dryRun = 1;
}
elsif ($arg eq "-v")
{
$verbose = 1;
}
elsif ($arg eq "-q")
{
$quiet = 1;
}
elsif ($arg eq "-b")
{
unless (@ARGV)
{
print STDERR "Option -b requires an argument\n";
exit 1;
}
$branch = shift @ARGV;
}
elsif ($arg eq "-r")
{
unless (@ARGV)
{
print STDERR "Option -r requires an argument\n";
exit 1;
}
$revision = shift @ARGV;
}
elsif ($arg eq "-f")
{
unless (@ARGV)
{
print STDERR "Option -f requires an argument\n";
exit 1;
}
$from = shift @ARGV;
}
elsif ($arg eq "-t")
{
unless (@ARGV)
{
print STDERR "Option -t requires an argument\n";
exit 1;
}
$to = shift @ARGV;
}
else
{
unshift @ARGV, $arg;
last;
}
}
getSvnInfo();
getLastCommitInfo($ARGV[0]);
checkBranches();
showLog();
findAllFiles();
switchAllFiles();
exit(0) if ($dryRun);
mergeRevision();
createLogMessage();
editLogMessage();
commit();