File: svn-graph.pl

package info (click to toggle)
subversion 1.4.2dfsg1-3
  • links: PTS
  • area: main
  • in suites: etch
  • size: 37,284 kB
  • ctags: 32,888
  • sloc: ansic: 406,472; python: 38,378; sh: 15,438; cpp: 9,604; ruby: 8,313; perl: 5,308; java: 4,576; lisp: 3,860; xml: 3,298; makefile: 856
file content (161 lines) | stat: -rwxr-xr-x 5,519 bytes parent folder | download | duplicates (2)
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";