1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
#!/usr/bin/perl -w
#
# svn-graph.pl - produce a GraphViz .dot graph for the branch history
# of a node
#
# ====================================================================
# Copyright (c) 2000-2004 CollabNet. All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
# This software consists of voluntary contributions made by many
# individuals. For exact contribution history, see the revision
# history and logs, available at http://subversion.tigris.org/.
# ====================================================================
#
# TODO:
# - take some command line parameters (url, start & end revs,
# node we're tracking, etc)
# - calculate the repository root at runtime so the user can pass
# the node of interest as a single URL
# - produce the graphical output ourselves (SVG?) instead
# of using .dot?
#
use strict;
# Turn off output buffering
$|=1;
require SVN::Core;
require SVN::Ra;
# CONFIGURE ME: The URL of the Subversion repository we wish to graph.
# See TODO.
my $REPOS_URL = 'file:///some/repository';
#my $REPOS_URL = 'http://svn.collab.net/repos/svn';
# Point at the root of a repository so we get can look at
# every revision.
my $ra = SVN::Ra->new($REPOS_URL);
# We're going to look at all revisions
my $youngest = $ra->get_latest_revnum();
my $startrev = 1;
# This is the node we're interested in
my $startpath = "/trunk";
# The "interesting" nodes are potential sources for copies. This list
# grows as we move through time.
# The "tracking" nodes are the most recent revisions of paths we're
# following as we move through time. If we hit a delete of a path
# we remove it from the tracking array (i.e. we're no longer interested
# in it).
my %interesting = ("$startpath:$startrev",1);
my %tracking = ("$startpath", $startrev);
my %codeline_changes_forward = ();
my %codeline_changes_back = ();
my %copysource = ();
my %copydest = ();
# This function is a callback which is called for every revision
# as we traverse
sub process_revision {
my $changed_paths = shift;
my $revision = shift || "";
my $author = shift || "";
my $date = shift || "";
my $message = shift || "";
my $pool = shift;
print STDERR "$revision\r";
foreach my $path (keys %$changed_paths) {
my $copyfrom_path = $$changed_paths{$path}->copyfrom_path;
my $copyfrom_rev = $$changed_paths{$path}->copyfrom_rev;
my $action = $$changed_paths{$path}->action;
# See if we're deleting one of our tracking nodes
if ($action eq "D" and exists($tracking{$path}))
{
print "\t\"$path:$tracking{$path}\" ";
print "[label=\"$path:$tracking{$path}\\nDeleted in r$revision\",color=red];\n";
delete($tracking{$path});
next;
}
# If this is a copy, work out if it was from somewhere interesting
if (defined($copyfrom_path) &&
exists($interesting{$copyfrom_path.":".$copyfrom_rev}))
{
$interesting{$path.":".$revision} = 1;
$tracking{$path} = $revision;
print "\t\"$copyfrom_path:$copyfrom_rev\" -> ";
print " \"$path:$revision\" [label=\"copy at r$revision\",weight=1,color=green];\n";
$copysource{"$copyfrom_path:$copyfrom_rev"} = 1;
$copydest{"$path:$revision"} = 1;
}
# For each change, we'll move up the path, updating any parents
# that we're tracking (i.e. a change to /trunk/asdf/foo updates
# /trunk). We mark that parent as interesting (a potential source
# for copies), draw a link, and update it's tracking revision.
while ($path =~ m:/:) {
if (exists($tracking{$path}) && $tracking{$path} != $revision) {
$codeline_changes_forward{"$path:$tracking{$path}"} =
"$path:$revision";
$codeline_changes_back{"$path:$revision"} =
"$path:$tracking{$path}";
$interesting{$path.":".$revision} = 1;
$tracking{$path} = $revision;
}
$path =~ s:/[^/]+$::;
}
}
}
# And we can do it all with just one call to SVN :)
print "digraph tree {\n";
$ra->get_log(['/'], $startrev, $youngest, 1, 0, \&process_revision);
# Now ensure that everything is linked.
foreach my $codeline_change (keys %codeline_changes_forward) {
# If this node is not the first in its codeline chain, and it isn't
# the source of a copy, it won't be the source of an edge
if (exists($codeline_changes_back{$codeline_change}) &&
!exists($copysource{$codeline_change})) {
next;
}
# If this node is the first in it's chain, or the source of
# a copy, then we'll print it, and then find the next in
# the chain that needs to be printed too
if (!exists($codeline_changes_back{$codeline_change}) or
exists($copysource{$codeline_change}) ) {
print "\t\"$codeline_change\" -> ";
my $nextchange = $codeline_changes_forward{$codeline_change};
my $changecount = 1;
while (defined($nextchange)) {
if (exists($copysource{$nextchange}) or
!exists($codeline_changes_forward{$nextchange}) ) {
print "\"$nextchange\" [weight=100,label=\"$changecount change(s)\",style=bold];";
last;
}
$changecount++;
$nextchange = $codeline_changes_forward{$nextchange};
}
print "\n";
}
}
print "}\n";
print STDERR "\n";
|