summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Gruenbacher <agruen@suse.de>2004-03-13 14:22:59 +0000
committerAndreas Gruenbacher <agruen@suse.de>2004-03-13 14:22:59 +0000
commit4df003370511fcabc0c05d7920f03be08094aeb7 (patch)
tree3fdff33db9837461df082cfbca222125b7bf1d2c
parent4e99416de0eecaada3f8f5492f3976ad81120700 (diff)
downloadquilt-4df003370511fcabc0c05d7920f03be08094aeb7.tar.gz
- Add `quilt graph' command for generating a dependency graph
between patches. This requires the graphviz package for removing transitive edges (optional) and for rendering the graph. Please note that the graph command itself is minimal, while the underlying scripts/dependency-graph is more flexible. - The spec file was missing the quilt.1 man page.
-rw-r--r--Makefile.in8
-rw-r--r--quilt.changes9
-rw-r--r--quilt.spec.in1
-rw-r--r--quilt/graph.in142
-rw-r--r--scripts/dependency-graph.in314
5 files changed, 470 insertions, 4 deletions
diff --git a/Makefile.in b/Makefile.in
index 8790446..2712f87 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -55,16 +55,16 @@ BIN := $(BIN_IN)
SRC += $(BIN_SRC:%=bin/%)
DIRT += $(BIN_IN:%=bin/%)
-QUILT_IN := add applied delete diff files import new next patches \
- pop previous push refresh remove series setup top unapplied \
- fold fork snapshot edit
+QUILT_IN := add applied delete diff edit files fold fork graph import \
+ new next patches pop previous push refresh remove series \
+ setup snapshot top unapplied
QUILT_SRC := $(QUILT_IN:%=%.in)
QUILT := $(QUILT_IN)
SRC += $(QUILT_SRC:%=quilt/%)
DIRT += $(QUILT_IN:%=quilt/%)
-SCRIPTS_IN := apatch rpatch patchfns parse-patch spec2series
+SCRIPTS_IN := apatch rpatch patchfns parse-patch spec2series dependency-graph
SCRIPTS_SRC := $(SCRIPTS_IN:%=%.in)
SCRIPTS := $(SCRIPTS_IN)
SRC += $(SCRIPTS_SRC:%=scripts/%)
diff --git a/quilt.changes b/quilt.changes
index de6e2bc..abf8ca4 100644
--- a/quilt.changes
+++ b/quilt.changes
@@ -1,4 +1,13 @@
-------------------------------------------------------------------
+Sat Mar 13 15:16:49 CET 2004 - agruen@suse.de
+
+- Add `quilt graph' command for generating a dependency graph
+ between patches. This requires the graphviz package for
+ removing transitive edges (optional) and for rendering the
+ graph. Please note that the graph command itself is minimal,
+ while the underlying scripts/dependency-graph is more flexible.
+
+-------------------------------------------------------------------
Wed Mar 10 10:46:56 CET 2004 - agruen@suse.de
- Quilt push/pop: exit with a non-zero status when beyond series.
diff --git a/quilt.spec.in b/quilt.spec.in
index 3907ea7..e71d5ef 100644
--- a/quilt.spec.in
+++ b/quilt.spec.in
@@ -49,6 +49,7 @@ rm -rf $RPM_BUILD_ROOT
/usr/lib/quilt/
/etc/bash_completion.d/quilt
%doc %{_mandir}/man1/guards.1.gz
+%doc %{_mandir}/man1/quilt.1.gz
%doc %{_docdir}/%{name}-%{version}/README
%doc %{_docdir}/%{name}-%{version}/quilt.pdf
diff --git a/quilt/graph.in b/quilt/graph.in
new file mode 100644
index 0000000..3ce1f73
--- /dev/null
+++ b/quilt/graph.in
@@ -0,0 +1,142 @@
+#! @BASH@
+
+# This script is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# See the COPYING and AUTHORS files for more details.
+
+# Read in library functions
+if [ "$(type -t patch_file_name)" != function ]
+then
+ if ! [ -r @SCRIPTS@/patchfns ]
+ then
+ echo "Cannot read library @SCRIPTS@/patchfns" >&2
+ exit 1
+ fi
+ . @SCRIPTS@/patchfns
+fi
+
+usage()
+{
+ local redirect
+ if [ x$1 != x-h ]
+ then
+ redirect='>&2'
+ fi
+ echo $"Usage: quilt graph [-P path|--all] [--reduce] [--edge-labels=files]" $redirect
+
+ if [ x$1 = x-h ]
+ then
+ echo $"
+Generate a dot(1) directed graph showing the dependencies between
+applied patches. A patch depends on another patch if both touch the same
+file. Unless otherwise specified, the graph includes all patches that
+the topmost patch depends on.
+
+-P patch
+ Instead of the topmost patch, create a graph for the specified
+ patch. The graph will include all other patches that this patch
+ depends on, as well as all patches that depend on this patch.
+
+--all Generate a graph including all patches and their dependencies.
+
+--reduce
+ Eliminate transitive edges from the graph.
+
+--edge-labels=files
+ Label graph edges with the file names that the adjacent patches
+ modify.
+
+-T ps Directly produce a PostScript output file."
+ exit 0
+ else
+ exit 1
+ fi
+}
+
+options=`getopt -o P:T:h --long all,reduce,edge-labels: -- "$@"`
+
+if [ $? -ne 0 ]
+then
+ usage
+fi
+
+eval set -- "$options"
+
+while true
+do
+ case "$1" in
+ -P)
+ if ! patch=$(find_patch $2)
+ then
+ echo $"Patch $2 is not in series" >&2
+ exit 1
+ fi
+ if ! is_applied $patch
+ then
+ echo $"Patch $patch is not applied" >&2
+ exit 1
+ fi
+ shift 2 ;;
+
+ -T) if [ "$2" != ps ]; then
+ usage
+ fi
+ opt_format=ps
+ shift 2 ;;
+
+ --all)
+ opt_all=1
+ shift ;;
+
+ --reduce)
+ opt_reduce=1
+ shift ;;
+
+ --edge-labels)
+ if [ "$2" != files ]
+ then
+ usage
+ fi
+ opt_edge_labels=$2
+ shift 2 ;;
+
+ -h)
+ usage -h ;;
+
+ --)
+ shift
+ break ;;
+ esac
+done
+
+if [ $# -ne 0 -o \( -n "$patch" -a -n "$opt_all" \) ]
+then
+ usage
+fi
+
+if [ -z "$opt_all" -a -z "$patch" ]
+then
+ patch=$(top_patch)
+ if [ -z "$patch" ]
+ then
+ echo $"No patches applied" >&2
+ exit 1
+ fi
+fi
+
+options=
+[ -n "$patch" ] && options="--select-patch $patch"
+[ -n "$opt_reduce" ] && options="$options --reduce"
+[ "$opt_edge_labels" = files ] && options="$options --edge-files"
+
+pipe=
+[ -n "$opt_format" ] && pipe="| dot -T$opt_format"
+
+applied_patches \
+| eval @SCRIPTS@/dependency-graph $options - $pipe
+### Local Variables:
+### mode: shell-script
+### End:
+# vim:filetype=sh
diff --git a/scripts/dependency-graph.in b/scripts/dependency-graph.in
new file mode 100644
index 0000000..a58e92b
--- /dev/null
+++ b/scripts/dependency-graph.in
@@ -0,0 +1,314 @@
+#!@PERL@ -w
+
+# This script is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# See the COPYING and AUTHORS files for more details.
+
+# Generate a dot-style graph of dependencies between patches.
+
+use Getopt::Long;
+use FileHandle;
+use strict;
+
+# Constants
+my $short_edge_style = "color=grey";
+my $close_node_style = "color=grey";
+my $highlighted_node_style = "style=bold";
+
+# Command line arguments
+my $help = 0;
+my $short_edge_thresh = 0; # threshold for coloring as "short", 0 = disable
+my $long_edge_thresh = 0; # threshold for coloring as "long",0 = disable
+my $edge_labels; # label all edges with filenames
+my $short_edge_labels; # label short edges with filenames
+my $long_edge_labels; # label long edges with filenames
+my $edge_length_labels; # distance between patches as edge labels
+my $node_numbers; # include sequence numbers
+my $show_isolated_nodes; # also include isolated nodes
+my $reduce; # remove transitive edges
+my $mangle_patchnames; # filter for compacting filenames
+my $selected_patch; # only include patches related on this patch
+my $selected_distance = -1; # infinity
+my @highlight_patches; # a list of patches to highlight
+
+unless (GetOptions(
+ "h|help" => \$help,
+ "short-edge=i" => \$short_edge_thresh,
+ "long-edge=i" => \$long_edge_thresh,
+ "edge-files" => \$edge_labels,
+ "short-edge-files" => \$short_edge_labels,
+ "long-edge-files" => \$long_edge_labels,
+ "edge-length" => \$edge_length_labels,
+ "node-numbers" => \$node_numbers,
+ "isolated" => \$show_isolated_nodes,
+ "reduce" => \$reduce,
+ "mangle-patchnames=s" => \$mangle_patchnames,
+ "select-patch=s" => \$selected_patch,
+ "select-distance=i" => \$selected_distance,
+ "highlight=s" => \@highlight_patches ) && !$help) {
+ my $basename = $0;
+ $basename =~ s:.*/::;
+ my $fd = $help ? *STDOUT : *STDERR;
+ print $fd <<EOF;
+SYNOPSIS: $basename [-h] [--short-edge=num] [--long-edge=num]
+ [--short-edge-files] [--long-edge-files] [--edge-length]
+ [--node-numbers] [--isolated] [--reduce] [--mangle-patchnames=filter]
+ [--select-patch=patch] [--select-distance=num] [--highlight=patch]
+
+--short-edge=num, --long-edge=num
+ Define the maximum edge length of short edges, and minimum edge
+ length of long edges. Short edges are de-emphasized, and long
+ edges are emphasized. The default is to treat all edges equally.
+
+-edge-files, --short-edge-files, --long-edge-files
+ Include conflicting filenames on all edges, short edges, or long
+ edges.
+
+--edge-length
+ Use the edge lengths as edge labels. Cannot be used together with
+ filename labels.
+
+--node-numbers
+ Include the sequence numbers of patches in the patch series in
+ node labels.
+
+--isolated
+ Do not suppress isolated nodes.
+
+--reduce
+ Remove transitive edges.
+
+--mangle-patchnames=filter
+ Define a filter command for transforming patch names into node
+ labels. The filter is passed each patch name on a separate line,
+ and must return the edge label for each patch on a separate line
+ (example: sed -e 's/^prefix//').
+
+--select-patch=patch
+ Reduce the graph to nodes that depend on the specified patch,
+ and nodes that the specified patch depends on (recursively).
+
+--select-distance=num
+ Limit the depth of recusion for --select-patch. The default is
+ to recurse exhaustively.
+
+--highlight=patch
+ Highlight the specified patch. This option can be specified more
+ than once.
+EOF
+ exit $help ? 0 : 1;
+}
+
+# Fetch the list of patches (all of them must be applied)
+my @patches;
+if (@ARGV) {
+ if (@ARGV == 1 && $ARGV[0] eq "-") {
+ @patches = map { chomp ; $_ } <STDIN>;
+ } else {
+ @patches = @ARGV;
+ }
+} else {
+ my $fh = new FileHandle("< .pc/applied-patches")
+ or die ".pc/applied-patches: $!\n";
+ @patches = map { chomp; $_ } <$fh>;
+ $fh->close();
+}
+
+# Fetch the list of files
+my @nodes;
+my $n = 0;
+foreach my $patch (@patches) {
+ if (! -d ".pc/$patch") {
+ print STDERR ".pc/$patch does not exist; skipping\n";
+ next;
+ }
+ my @files = split(/\n/, `cd .pc/$patch ; find -type f ! -name .timestamp`);
+ @files = map { s:\./::; $_ } @files;
+ push @nodes, {number=>$n++, name=>$patch, file=>$patch, files=>[ @files ] };
+}
+
+# If a patch is selected, limit the graph to nodes that depend on this patch,
+# and nodes that are dependent on this patch.
+if ($selected_patch) {
+ for ($n = 0; $n < @nodes; $n++) {
+ last if $nodes[$n]{file} eq $selected_patch;
+ }
+ die "Patch $selected_patch not included\n"
+ if ($n == @nodes);
+
+ my $selected_node = $nodes[$n];
+ push @{$selected_node->{attrs}}, $highlighted_node_style;
+
+ my %sel;
+ map { $sel{$_} = 1 } @{$selected_node->{files}};
+ map { $_->{files} = [ grep { exists $sel{$_} } @{$_->{files}} ] } @nodes;
+}
+
+# Optionally highlight a list of patches
+foreach my $patch (@highlight_patches) {
+ for ($n = 0; $n < @nodes; $n++) {
+ last if $nodes[$n]{file} eq $patch;
+ }
+ die "Patch $patch not included\n"
+ if ($n == @nodes);
+
+ my $node = $nodes[$n];
+ push @{$node->{attrs}}, $highlighted_node_style;
+ $node->{colorized} = 1;
+}
+
+# If a patch name mangling filter is selected, pipe all patchnames through
+# it.
+if ($mangle_patchnames) {
+ # FIXME: which patches ...
+ my $filter = new FileHandle("cat .pc/applied-patches | $mangle_patchnames |");
+ $n = 0;
+ foreach my $name (<$filter>) {
+ last unless $n < @nodes;
+ chomp $name;
+ if ($name eq "") {
+ delete $nodes[$n++]{name};
+ } else {
+ $nodes[$n++]{name} = $name;
+ }
+ }
+ $filter->close();
+}
+
+my %files_seen; # remember the last patch that touched each file
+my %used_nodes; # nodes to which at least one edge is attached
+my %edges;
+
+foreach my $node (@nodes) {
+ my $number = $node->{number};
+ foreach my $file (@{$node->{files}}) {
+ if (exists $files_seen{$file}) {
+ push @{$edges{"$number:$files_seen{$file}"}{names}}, $file;
+ $used_nodes{$number} = 1;
+ $used_nodes{$files_seen{$file}} = 1;
+ }
+ $files_seen{$file} = $number;
+ }
+}
+
+# Create adjacency lists
+foreach my $node (@nodes) {
+ @{$node->{to}} = ();
+ @{$node->{from}} = ();
+}
+foreach my $key (keys %edges) {
+ my ($from, $to) = split /:/, $key;
+ push @{$nodes[$from]{to}}, $to;
+ push @{$nodes[$to]{from}}, $from;
+}
+
+# Colorize nodes that are close to each other
+foreach my $node (@nodes) {
+ if (!exists $node->{colorized} && !exists $used_nodes{$node->{number}}) {
+ $node->{colorized} = 1;
+ push @{$node->{attrs}}, $close_node_style;
+ }
+}
+
+# Colorize short and long edges
+foreach my $node (@nodes) {
+ my $close = 1;
+ foreach my $node2 (map {$nodes[$_]} @{$node->{to}}) {
+ if (abs($node2->{number} - $node->{number}) > $short_edge_thresh) {
+ $close = 0
+ }
+ }
+ foreach my $node2 (map {$nodes[$_]} @{$node->{from}}) {
+ if (abs($node2->{number} - $node->{number}) > $short_edge_thresh) {
+ $close = 0
+ }
+ }
+ if (!exists $node->{colorized} && $close) {
+ $node->{colorized} = 1;
+ push @{$node->{attrs}}, $close_node_style;
+ }
+}
+
+# Add node labels
+foreach my $node (@nodes) {
+ my @label = ();
+ push @label, $node->{number} + 1
+ if ($node_numbers);
+ push @label, $node->{name}
+ if exists $node->{name};
+ push @{$node->{attrs}}, "label=\"" . join(": ", @label) . "\"";
+}
+
+# Add edge labels
+foreach my $key (keys %edges) {
+ my ($from, $to) = split /:/, $key;
+ if ($edge_length_labels) {
+ push @{$edges{$key}->{attrs}}, "label=\"" . abs($to - $from) . "\""
+ if abs($to - $from) > 1;
+ } elsif (abs($to - $from) < $short_edge_thresh) {
+ push @{$edges{$key}->{attrs}}, $short_edge_style;
+ if ($edge_labels || $short_edge_labels) {
+ push @{$edges{$key}->{attrs}},
+ "label=\"" . join("\\n", @{$edges{$key}{names}}) . "\"";
+ }
+ } else {
+ if ($long_edge_thresh && abs($to - $from) > $long_edge_thresh) {
+ push @{$edges{$key}->{attrs}}, "style=bold";
+ if ($edge_labels || $long_edge_labels) {
+ push @{$edges{$key}->{attrs}},
+ "label=\"" . join("\\n", @{$edges{$key}{names}}) . "\"";
+ }
+ } else {
+ if ($edge_labels) {
+ push @{$edges{$key}->{attrs}},
+ "label=\"" . join("\\n", @{$edges{$key}{names}}) . "\"";
+ }
+ }
+ }
+ # Compute a pseudo edge length so that neato works acceptably.
+ push @{$edges{$key}{attrs}}, "len=\"" .
+ sprintf("%.2f", log(abs($to - $from) + 3)) . "\"";
+}
+
+#foreach my $node (@nodes) {
+# push @{$node->{attrs}}, "shape=box";
+#}
+
+# Open output file / pipe
+my $out;
+if ($reduce) {
+ $out = new FileHandle("| tred")
+ or die "tred: $!\n";
+} else {
+ $out = new FileHandle("> /dev/stdout")
+ or die "$!\n";
+}
+
+# Write graph
+print $out "digraph dependencies {\n";
+#print "\tsize=\"11,8\"\n";
+foreach my $node (@nodes) {
+ next unless $show_isolated_nodes || exists $used_nodes{$node->{number}};
+ print $out "\tn$node->{number}";
+ if (exists $node->{attrs}) {
+ print $out " [" .
+ join(",", @{$node->{attrs}}) . "]";
+ }
+ print $out ";\n";
+}
+
+sub w($) {
+ my @n = split /:/, shift;
+ return $n[0] * 10000 + $n[1];
+}
+foreach my $key (sort { w($a) <=> w($b) } keys %edges) {
+ my ($from, $to) = split /:/, $key;
+ print $out "\tn$to -> n$from";
+ if (exists $edges{$key}{attrs}) {
+ print $out " [" . join(",", @{$edges{$key}{attrs}}) . "]";
+ }
+ print $out ";\n";
+}
+print $out "}\n";