#!/usr/bin/perl
# Print a version tree of a Perforce controlled file
#
# Usage: p4tree files...
#
# Copyright (c) 1997-1999 by Jeremy Fitzhardinge.
# Copyright (c) 2000 by Thomas Quinot.
#
# Released under the GNU General Public License.
#
# 1997-1999 Jeremy Fitzhardinge <jeremy@goop.org> created the original
# p4tree code. See http://www.goop.org/~jeremy/p4/.
# 2000 Thomas Quinot <thomas@cuivre.fr.eu.org> adapted it to
# produce dot files.
#
# $Id: //depot/scripts/p4/p4tree#5 $
require 5.002;
my (%vcg_tmpl) = (
'prologue', <<__EOF__,
graph: {\n
title: "%s"
display_edge_labels: yes
late_edge_labels: yes
fine_tuning: yes
edge.priority: 10
arrowmode: free
straight_phase: yes
__EOF__
'node', <<__EOF__,
node: {
title: "%s"
label: "%s"
}
__EOF__
'edge', <<__EOF__,
edge: {
sourcename: "%s"
targetname: "%s"
linestyle: %s
color: %s
}
__EOF__
'prioedge', <<__EOF__,
nearedge: {
sourcename: "%s"
targetname: "%s"
linestyle: %s
color: %s
priority: 5
}
__EOF__
'epilogue', "}\n"
);
my (%dot_tmpl) = (
'prologue', <<__EOF__,
digraph "%s" {
node [ shape = box ];
edge [ weight = 10 ];
__EOF__
'node', <<__EOF__,
%s [ label = "%s" ];
__EOF__
'edge', <<__EOF__,
%s -> %s [ style = "%s" color = "%s" ];
__EOF__
'prioedge', <<__EOF__,
%s -> %s [ style = "%s" color = "%s" weight = 5 ];
__EOF__
'epilogue', "}\n"
);
%targets = (
'vcg', \%vcg_tmpl,
'dot', \%dot_tmpl
);
sub usage () {
print STDERR "Usage: $0 [ -t TITLE ] [ -T (dot|vcg) ] FILE [ FILE... ]\n";
die ();
}
sub p4 ($@) {
my ($op, @args) = (@_);
my $cmd = "p4 $op ".(join " ", @args);
# print STDERR "doing $cmd\n";
system $cmd || die "Perforce command $cmd failed\n";
}
sub canon($) {
my ($f) = @_;
$f =~ s,/+,/,g;
$f =~ s,/\.$,/,;
return $f;
}
my %node_dict;
my $last_node_id = 0;
sub node_id () {
my ($nodename) = @_;
if (!exists $node_dict{$nodename}) {
++$last_node_id;
$node_dict{$nodename} = "n$last_node_id";
}
return $node_dict{$nodename};
}
#
# Parse JCL
#
my (%tmpl) = %vcg_tmpl;
my ($title) = "Revision history";
require "getopts.pl";
&Getopts ("t:T:") || &usage ();
if (defined ($opt_T)) {
if (exists $targets{$opt_T}) {
$tmplref = $targets{$opt_T};
%tmpl = %$tmplref;
} else {
print STDERR "Unknown target: $opt_T\n";
&usage ();
}
}
$title = $opt_t if (defined ($opt_t));
@files = @ARGV;
# %db = (
# "//depot/depotname" => [
# { # indexed by version
# "op" => "add/delete/edit/branch",
# "date" => "1997/08/01",
# "who" => "jeremy@ixodes",
# "comment" => "fingled the wazzit",
# "change" => 1234,
# "links" => [
# [ "branch", "from", \%other, 2, 3 ],
# [ "merge", "from", \%other ]
# ]
# }
# ]
# )
file:
foreach $file (@files) {
my $currentfile;
my $currentver;
my $depotname;
$file =~ s/#.*$//;
open P4, "p4 filelog $file|";
while(<P4>) {
chop;
if (/^\/\//) {
$depotname = $_;
$currentfile = $db{$_};
next file if $done{$_};
$done{$_} = "doing";
print STDERR "depotname = $_\n";
next;
} elsif (/^\.\.\. ([^\s]+) (.*)$/) {
my $rest = $2;
if ($1 =~ /#([0-9]+)/) {
my $ver = $1;
$rest =~ /change ([0-9]+) ([a-z]+) on ([^ ]+) by ([^ ]+) \(([^)]+)\) '(.*)'$/ || die "bad line: $rest";
$currentver = $currentfile->[$ver];
$currentver->{"ver"} = $ver;
$currentver->{"change"} = $1;
$currentver->{"op"} = $2;
$currentver->{"date"} = $3;
$currentver->{"who"} = $4;
$currentver->{"type"} = $5;
$currentver->{"comment"} = $6;
$currentver->{"comment"} =~ s/"/\\"/g;
$currentfile->[$ver] = $currentver;
} elsif ($1 eq "...") {
$rest =~ /^([a-z]+) ([a-z]+) ([^\#]+)\#([0-9]+)(,\#([0-9]+))?$/ || die "bad line: $rest";
my $link = [$1, $2, $3, $4, $6];
my $links = $currentver->{"links"};
push @files, $3;
$links = [ @$links, $link ];
$currentver->{"links"} = $links;
}
$db{$depotname} = $currentfile;
$done{$depotname} = "done";
} else {
die "Unrecognised line $_\n";
}
}
close P4;
die "Failed to get file details for $file: $?\n" if $?;
}
#open VCG, "| xvcg -";
#open VCG, "| cat -";
printf $tmpl{prologue}, $title;
my $order = 1;
foreach $f (keys %db) {
my $vers = $db{$f};
my ($prev, $style);
foreach $v (@$vers) {
next if !defined $v;
my $links = $v->{"links"};
next if (!defined $links && $v->{"op"} !~ /add|delete|branch/);
my $nodename = "$f#$v->{\"ver\"}";
printf $tmpl{node}, &node_id($nodename), "$f\@$v->{\"change\"}\\n$v->{\"op\"}: $v->{\"comment\"}";
printf $tmpl{edge}, &node_id($prev), &node_id($nodename), $style, "black" if $prev;
if ($links) {
for $l (@$links) {
my $style;
my $colour = "black";
my $arrow = "";
next if $l->[1] eq "from";
my $target = "$l->[2]#";
if (defined $l->[4]) {
$target .= $l->[4];
} else {
$target .= $l->[3];
}
if ($l->[0] eq 'branch') {
$style = "solid";
$colour = "blue";
} elsif ($l->[0] eq 'merge' || $l->[0] eq 'copy') {
$style = "dotted";
$colour = "blue";
$arrow = "arrowstyle: line";
} else {
$style = "solid";
}
# print "edge: { sourcename: \"$nodename\" targetname: \"$target\" label: \"$l->[0]\" linestyle: $style priority: $pri color: $colour }\n";
#print "nearedge: { sourcename: \"$nodename\" targetname: \"$target\" $arrow linestyle: $style priority: $pri color: $colour }\n";
#print &node_id($nodename) . " -> " . &node_id($target) . " [ style = $style, color = $colour ];\n";
printf $tmpl{prioedge}, &node_id($nodename), &node_id($target), $style, $colour;
}
}
$prev = $nodename;
$style = ($v->{"op"} eq "delete") ? "invisible" : "solid";
}
$order++;
}
print $tmpl{epilogue};
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 334 | Thomas Quinot |
Produce a graph describing a file's integration history. Possible targets are VCG and GraphViz' dot. |