package VCP::Dest::branch_diagram ; =head1 NAME VCP::Dest::branch_diagram - An experimental diagram drawing "destination" =head1 SYNOPSIS vcp <source> branch_diagram:foo.png vcp <source> branch_diagram:foo.png --skip=none ## for verbose output =head1 DESCRIPTION This generates (using GraphViz) a diagram of the branch structure of the source repository. Note: You must install graphviz, from AT&T (specifically, the C<dot> command) and the GraphViz.pm Perl module for this to work. =head1 OPTIONS =over =item --skip --skip=5 Set the revision "skip" threshold. This is the minimum number of revisions you should see in a "# skipped" message in the resulting graph. use C<--skip=none> to prevent skipping. The default is 5. =back =head1 EXAMPLES vcp \ p4:public.perforce.com:1666://public/perforce/webkeeper/mod_webkeep.c \ --rev-root= \ --follow-branch-into \ branch_diagram:foo3.png The --rev-root= is because the presumed rev root is "//public/perforce/webkeeper" and perforce branches sail off in to other directories. vcp \ cvs:/home/barries/src/VCP/tmp/xfree:/xc/doc/Imakefile \ branch_diagram:foo3.png =cut $VERSION = 1 ; @ISA = qw( VCP::Dest ); use strict ; use Carp ; use File::Basename ; use File::Path ; use File::Spec ; use VCP::Debug ':debug' ; use VCP::Dest ; use VCP::Logger qw( pr ); use VCP::Rev ; use VCP::Revs ; use VCP::Utils qw( empty start_dir_rel2abs ); #use base qw( VCP::Dest ) ; #use fields ( # 'BD_SKIP_THRESHOLD', ## Where to start skipping. # 'BD_BRANCH_COLORS', ## A hash of branch_id to color # 'BD_REV_FOO', ## Data we need to keep per rev # 'BD_REVS', ## a HASH of Revs, keyed by id # 'BD_BRANCH_IDS', ## A HASH of branch_ids so we can assign colors #) ; #=item new # #Creates a new instance of a VCP::Dest::branch_diagram. # #=cut sub new { my $self = shift->SUPER::new( @_ ) ; ## Parse the options my ( $spec, $options ) = @_ ; $self->parse_repo_spec( $spec ) if defined $spec; $self->parse_options( $options ); return $self ; } sub options_spec { my $self = shift ; return ( $self->SUPER::options_spec, "skip=s" => \$self->{BD_SKIP_THRESHOLD}, ); } sub init { my $self = shift ; $self->{BD_BRANCH_COLORS} = {}; $self->{BD_REV_FOO} = {}; $self->{BD_SKIP_THRESHOLD} = 5 unless defined $self->{BD_SKIP_THRESHOLD}; $self->repo_id( "branch_diagram:" . ( $self->repo_server || "" ) ); } sub backfill { my $self = shift ; my $r ; ( $r ) = @_ ; confess unless defined $self && defined $self->header ; return 1 ; } sub handle_header { my $self = shift ; $self->SUPER::handle_header( @_ ) ; } sub handle_rev { my $self = shift ; my ( $r ) = @_; $self->{BD_REVS}->{$r->id} = $r; $self->{BD_BRANCH_IDS}->{$r->branch_id || "" } = undef; $self->{BD_REV_FOO}->{$r->previous_id}->{COUNT}++ if defined $r->previous_id; } sub _add_rev_node { my $self = shift ; my ( $g, $r ) = @_; my $action = $r->action || ""; my $label = join( "", $r->rev_id, defined $r->change_id ? ( "@", $r->change_id ) : (), $r->is_placeholder_rev ? "(placeholder)" : $r->is_base_rev ? "(base rev)" : ( "(", $r->action, ")" ), ); $label =~ s#([|<>\[\]{}"])#\\$1#g; { my @labels = $r->labels; @labels = ( @labels[0,1,2], "...", @labels[-3,-2,-1] ) if @labels > 7; $label = "{$label|{" . join( "", map { $_ =~ s#([|<>\[\]{}"])#\\$1#g; $_ . "\\l" } @labels ) . "}}" if @labels; } my @color; my @edge_fontcolor; my @bgcolor; ## NOTE: this version of GraphViz seems to accept but ignore the ## "fillcolor" attribute. if ( ! empty $r->branch_id ) { my $color = $self->{BD_BRANCH_COLORS}->{$r->branch_id}; @color = ( color => $color ); @edge_fontcolor = ( fontcolor => $color ); # @bgcolor = ( style => "filled", color => $color ); @bgcolor = ( style => "filled", fillcolor => $color ); @bgcolor = (); } my $group = join "", $r->name, "(", $r->branch_id || "", ")"; $g->add_node( $r->id, label => $label, cluster => $r->name, fontsize => 10, fontname => "Helvetica", shape => "record", height => 0, width => 0, group => $group, color => "black", @bgcolor, ); my $prev_r = $self->{BD_REV_FOO}->{$r->id}->{PREVIOUS}; $prev_r = $self->{BD_REVS}->{$r->previous_id} unless defined $prev_r || empty $r->previous_id; return unless $prev_r; my $branch_id = $r->branch_id || ""; my $is_new_branch = $prev_r && $branch_id ne ( $prev_r->branch_id || "" ); my $branch_label = ""; $branch_label = $branch_id if $is_new_branch; if ( $prev_r ) { my $prev_id = $prev_r->id; my $skipped = $self->{BD_REV_FOO}->{$r->id}->{SKIPPED}; if ( $skipped ) { my $k = "..." . $r->id; $g->add_node( $k, label => "$skipped skipped", cluster => $r->name, fontsize => 10, fontname => "Helvetica", shape => "record", height => 0, width => 0, peripheries => 0, group => $group, @edge_fontcolor, ); $g->add_edge( { label => $branch_label, from => $prev_id, to => $k, fontsize => 10, fontname => "Helvetica", @color, @edge_fontcolor, arrowhead => "none", length $branch_label ? ( weight => 0 ) : (), } ); $prev_id = $k, $branch_label = ""; } $g->add_edge( { label => $branch_label, fontsize => 10, fontname => "Helvetica", from => $prev_id, to => $r->id, @edge_fontcolor, @color, length $branch_label ? ( weight => 0 ) : (), } ); } } sub handle_footer { my $self = shift ; my $fn = $self->repo_filespec; $fn = start_dir_rel2abs $fn ; my ( $ext ) = ( $fn =~ /\.([^.]*)\z/ ); my $method = "as_$ext"; pr "Generating branch diagram in ", $fn; my %seen_colors; my $total; my $count; my @colors = map $_->[-1], reverse sort { $a->[0] <=> $b->[0] } grep { my $avg = $_->[0]; $total += $avg; ++$count; ( $_->[-1] =~ /gray/ ? $avg > 125 : $avg > 100 ) && $avg < 160 } map [ ( $_->[0] + $_->[1] + $_->[2] ) / 3, @$_], grep $_->[-1] =~ /\A[a-z\s]+\z/, grep !$seen_colors{$_->[-1]}++, map { $_->[-1] =~ s/grey/gray/g; $_ } map [ split /\s+/, $_, 4 ], split /\n+\s*/, `showrgb`; #use Slay::PerlUtil; dump \@colors; #die; for ( keys %{$self->{BD_BRANCH_IDS}} ) { my $c = shift @colors; @colors = ( @colors, $c ); $self->{BD_BRANCH_COLORS}->{$_->branch_id} = $c; } #use Slay::PerlUtil; dump \%branch_colors; my %names; my $foo = $self->{BD_REV_FOO}; $foo->{$_->id}->{VIS} = 1 for grep { my $f = $foo->{$_->id}; ( $f->{COUNT} || 0 ) != 1 || empty( $_->previous_id ) || $_->labels; } values %{$self->{BD_REVS}}; for my $r ( values %{$self->{BD_REVS}} ) { $names{$r->name} = 1; next unless $foo->{$r->id}->{VIS}; if ( ! empty( $r->previous_id ) ) { my $prev_r = $self->{BD_REVS}->{$r->previous_id}; my @skipped; while ( ! $foo->{$prev_r->id}->{VIS} && ! empty( $prev_r->previous_id ) ) { push @skipped, $prev_r; $prev_r = $self->{BD_REVS}->{$prev_r->previous_id}; } ## Only skip enough to matter if ( $self->{BD_SKIP_THRESHOLD} ne "none" && @skipped >= $self->{BD_SKIP_THRESHOLD} ) { $foo->{$r->id}->{SKIPPED} = @skipped; $foo->{$r->id}->{PREVIOUS} = $prev_r; } else { $foo->{$_->id}->{VIS} = 1 for @skipped; } } } ## Load lazily so options parsing can be tested. require GraphViz; my $g = GraphViz->new( # rankdir => "LR", ## Wide .pngs can't be created, go tall. nodesep => 0.1, ranksep => 0.25, ordering => "out", keys %names <= 1 ? ( label => scalar keys %names ) : () ); ## Sort by name to get predictable ordering of files from left to right ## Sort by branch ID to put all branches to right (because ## ordering => "out" set on the graph) for my $r ( sort { $a->name cmp $b->name || ( $a->branch_id || "" ) cmp ( $b->branch_id || "" ) } values %{$self->{BD_REVS}} ) { next unless $foo->{$r->id}->{VIS}; $self->_add_rev_node( $g, $r ); } if ( $method eq "as_debug" ) { open F, ">$fn" or die "$!: $fn"; print F $g->_as_debug( $fn ) or die "$!: writing $fn"; close F or die "$!: $fn"; } else { $g->$method( $fn ); } } =head1 AUTHOR Barrie Slaymaker <barries@slaysys.com> =head1 COPYRIGHT Copyright (c) 2000, 2001, 2002 Perforce Software, Inc. All rights reserved. See L<VCP::License|VCP::License> (C<vcp help license>) for the terms of use. =cut 1
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 6118 | Dimitry Andric | Integ from //public/revml to //guest/dimitry_andric/revml/main. | ||
//guest/perforce_software/revml/lib/VCP/Dest/branch_diagram.pm | |||||
#26 | 4021 | Barrie Slaymaker |
- Remove all phashes and all base & fields pragmas - Work around SWASHGET error |
||
#25 | 4012 | Barrie Slaymaker | - Remove dependance on pseudohashes (deprecated Perl feature) | ||
#24 | 3858 | Barrie Slaymaker | - Final tweaks for 0.5 release | ||
#23 | 3813 | Barrie Slaymaker | - VCP::Rev::previous() is no more | ||
#22 | 3532 | John Fetkovich |
changed File::Spec->rel2abs( blah, start_dir ) to start_dir_rel2abs blah everywhere. which does the same thing and is defined in VCP::Utils |
||
#21 | 3489 | Barrie Slaymaker | - Document options emitted to .vcp files. | ||
#20 | 3460 | Barrie Slaymaker |
- Revamp Plugin/Source/Dest hierarchy to allow for reguritating options in to .vcp files |
||
#19 | 3400 | Barrie Slaymaker |
- Make dest file be relative to vcp's starting dir - Print and log the dest file location for the user to see |
||
#18 | 3208 | John Fetkovich | documentation (pod) fixes. | ||
#17 | 3133 | Barrie Slaymaker |
Make destinations call back to sources to check out files to simplify the architecture (is_metadata_only() no longer needed) and make it more optimizable (checkouts can be batched). |
||
#16 | 3019 | Barrie Slaymaker | Display the action/placeholder/base rev status | ||
#15 | 2929 | John Fetkovich | added empty() call | ||
#14 | 2838 | John Fetkovich | Use parse_options rather than using Getopt::Long directly. | ||
#13 | 2802 | John Fetkovich |
Added a source_repo_id to each revision, and repo_id to each Source and Dest. The repo_ids include repository type (cvs,p4,revml,vss,...) and the repo_server fields. Changed the $self->...->set() and $self->...->get() lines in VCP::Dest::* to pass in a conglomerated key value, by passing in the key as an ARRAY ref. Also various restructuring in VCP::DB.pm, VCP::DB_file.pm and VCP::DB_file::sdbm.pm related to this change. |
||
#12 | 2753 | John Fetkovich | POD fixes | ||
#11 | 2329 | Barrie Slaymaker |
Silly GraphViz seems to bungle colors. Should sniff the version. |
||
#10 | 2322 | Barrie Slaymaker | Fix jack-in-the-bug options parsing exposed by .vcp files | ||
#9 | 2200 | Barrie Slaymaker | Add some examples to branch_diagram.pm | ||
#8 | 2148 | Barrie Slaymaker |
Elide the middle of the list of labels so revs with many labels aren't huge |
||
#7 | 2042 | Barrie Slaymaker | Basic source::p4 branching support | ||
#6 | 2026 | Barrie Slaymaker | VCP::8::cvs now supoprt branching | ||
#5 | 2017 | Barrie Slaymaker |
Interim checkin of id=/base_version_id for revml: and branch_diagram: |
||
#4 | 2015 | Barrie Slaymaker | submit changes | ||
#3 | 2011 | Barrie Slaymaker | Tweak images to be smaller, more readable (IMHO) | ||
#2 | 2009 | Barrie Slaymaker |
lots of fixes, improve core support for branches and VCP::Source::cvs now supports branches. |
||
#1 | 2006 | Barrie Slaymaker |
more preparations for branching support, handling of cvs :foo:... CVSROOT specs, misc fixes, improvements |