[patch] Import "git-forest" into contrib/

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



commit b6612a2efe93660be7ecdb799625015efedadff1
Author: Jan Engelhardt <jengelh@xxxxxxxxxxxxxxx>
Date:   Tue Mar 18 19:24:33 2008 +0100

    Import "git-forest" into contrib/

diff --git a/contrib/git-forest/git-forest b/contrib/git-forest/git-forest
new file mode 100755
index 0000000..f5d6f81
--- /dev/null
+++ b/contrib/git-forest/git-forest
@@ -0,0 +1,391 @@
+#!/usr/bin/perl
+#
+#	git-森林
+#	text-based tree visualisation
+#	Copyright © Jan Engelhardt <jengelh [at] gmx de>, 2008
+#
+#	This program is free software; you can redistribute it and/or modify
+#	it under the terms of the GNU General Public License as published by
+#	the Free Software Foundation; either version 2 or 3 of the license.
+#
+use Getopt::Long;
+use Git;
+use strict;
+use encoding "utf8";
+my $Repo     = Git->repository($ENV{"GIT_DIR"} || ".");
+my $Style    = 2;
+my $With_sha = 0;
+my %Color    = (
+	"default" => "\e[0m",
+	"at"      => "\e[1;30m",
+	"head"    => "\e[1;32m",
+	"ref"     => "\e[1;34m",
+	"remote"  => "\e[1;35m",
+	"sha"     => "\e[0;31m",
+	"tag"     => "\e[1;33m",
+	"tree"    => "\e[0;33m",
+);
+
+&main();
+
+sub main ()
+{
+	&Getopt::Long::Configure(qw(bundling pass_through));
+	&GetOptions(
+		"no-color" => sub { %Color = (); },
+		"style=i"  => \$Style,
+		"sha"      => \$With_sha,
+	);
+	&process();
+}
+
+sub process ()
+{
+	my(@vine);
+	my $refs = &get_refs();
+	my($fh, $fhc) = $Repo->command_output_pipe("log", "--date-order",
+	                "--pretty=format:<%H><%h><%P>%s", @ARGV);
+
+	while (defined(my $line = <$fh>)) {
+		chomp $line;
+		my($sha, $mini_sha, $parents, $msg) =
+			($line =~ /^<(.*?)><(.*?)><(.*?)>(.*)/s);
+		my @parents = split(" ", $parents);
+
+		&vine_branch(\@vine, $sha);
+		my $ra = &vine_commit(\@vine, $sha, \@parents);
+
+		if (exists($refs->{$sha})) {
+			print &vis_post(&vis_commit($ra,
+			      $Color{at}."─".$Color{default}));
+			&ref_print($refs->{$sha});
+		} else {
+			print &vis_post(&vis_commit($ra, " "));
+		}
+		if ($With_sha) {
+			print $msg, $Color{at}, "──(", $Color{sha}, $mini_sha,
+			      $Color{at}, ")", $Color{default}, "\n";
+		} else {
+			print $msg, "\n";
+		}
+
+		&vine_merge(\@vine, $sha, \@parents);
+	}
+	$Repo->command_close_pipe($fh, $fhc);
+}
+
+sub get_refs ()
+{
+	my($fh, $c) = $Repo->command_output_pipe("show-ref");
+	my $ret = {};
+
+	while (defined(my $ln = <$fh>)) {
+		chomp $ln;
+		if (length($ln) == 0) {
+			next;
+		}
+
+		my($sha, $name) = ($ln =~ /^(\S+)\s+(.*)/s);
+		if (!exists($ret->{$sha})) {
+			$ret->{$sha} = [];
+		}
+		push(@{$ret->{$sha}}, $name);
+		if ($name =~ m{^refs/tags/}) {
+			my $sub_sha = $Repo->command("log", "-1",
+			              "--pretty=format:%H", $name);
+			chomp $sub_sha;
+			if ($sha ne $sub_sha) {
+				push(@{$ret->{$sub_sha}}, $name);
+			}
+		}
+	}
+
+	$Repo->command_close_pipe($fh, $c);
+	return $ret;
+}
+
+sub ref_print ($)
+{
+	foreach my $symbol (@{shift @_}) {
+		print $Color{at}, "[";
+		if ($symbol =~ m{^refs/(remotes/[^/]+)/(.*)}s) {
+			print $Color{remote}, $1, $Color{head}, "/$2";
+		} elsif ($symbol =~ m{^refs/heads/(.*)}s) {
+			print $Color{head}, $1;
+		} elsif ($symbol =~ m{^refs/tags/(.*)}s) {
+			print $Color{tag}, $1;
+		} elsif ($symbol =~ m{^refs/(.*)}s) {
+			print $Color{ref}, $1;
+		}
+		print $Color{at}, "]──", $Color{default};
+	}
+}
+
+sub vine_branch ($$)
+{
+	my($vine, $rev) = @_;
+	my $idx;
+
+	my $left = "╠";
+	my $matched = 0;
+	my $ret;
+
+	for ($idx = 0; $idx < scalar(@$vine); ++$idx) {
+		if (!defined($vine->[$idx])) {
+			$ret .= "═";
+			next;
+		} elsif ($vine->[$idx] ne $rev) {
+			$ret .= "╪";
+			next;
+		}
+		if ($matched == 0) {
+			$ret .= "╠";
+		} else {
+			$ret .= "╩";
+			$vine->[$idx] = undef;
+		}
+		++$matched;
+	}
+
+	if ($matched < 2) {
+		return;
+	}
+
+	while (!defined($vine->[$#$vine])) {
+		pop(@$vine);
+	}
+
+	print &vis_post(&vis_branch($ret)), "\n";
+}
+
+sub vine_commit ($$$)
+{
+	my($vine, $rev, $parents) = @_;
+	my $ret;
+
+	for (my $i = 0; $i <= $#$vine; ++$i) {
+		if (!defined($vine->[$i])) {
+			$ret .= " ";
+		} elsif ($vine->[$i] eq $rev) {
+			$ret .= "╟";
+		} else {
+			$ret .= "║";
+		}
+	}
+
+	if ($ret !~ /╟/) {
+		# Not having produced a ╟ before means this is a HEAD
+		$ret .= "╓";
+		push(@$vine, $rev);
+	}
+
+	while (scalar(@$vine) > 0 && !defined($vine->[$#$vine])) {
+		pop(@$vine);
+	}
+
+	if (scalar(@$parents) == 0) {
+		# tree root
+		$ret =~ s/╟/╙/g;
+	}
+
+	return $ret;
+}
+
+#
+#	Generate vine graphics for a merge
+#
+sub vine_merge ($$$)
+{
+	my($vine, $rev, $parents) = @_;
+	my $orig_vine = -1;
+	my @slot;
+	my($ret, $max);
+
+	for (my $i = 0; $i <= $#$vine; ++$i) {
+		if ($vine->[$i] eq $rev) {
+			$orig_vine = $i;
+			last;
+		}
+	}
+
+	if ($orig_vine == -1) {
+		die "vine_commit() did not add this vine.";
+	}
+
+	if (scalar(@$parents) <= 1) {
+		#
+		# A single parent does not need a visual. Update and return.
+		#
+		$vine->[$orig_vine] = $parents->[0];
+
+		while (scalar(@$vine) > 0 && !defined($vine->[$#$vine])) {
+			pop(@$vine);
+		}
+		return;
+	}
+
+	#
+	# Find some good spots to split out into.
+	#
+	push(@slot, $orig_vine);
+	my $parent = 0;
+
+	for (my $seeker = 2; $parent < $#$parents &&
+	    $seeker < 2 + 2 * $#$vine; ++$seeker)
+	{
+		my $idx = ($seeker % 2 == 0) ? -1 : 1;
+		$idx   *= int($seeker / 2);
+		$idx   += $orig_vine;
+
+		if ($idx >= 0 && $idx <= $#$vine && !defined($vine->[$idx])) {
+			push(@slot, $idx);
+			++$parent;
+		}
+	}
+	for (my $idx = $orig_vine + 1; $parent < $#$parents; ++$idx) {
+		if (!defined($vine->[$idx])) {
+			push(@slot, $idx);
+			++$parent;
+		}
+	}
+
+	if (scalar(@slot) != scalar(@$parents)) {
+		die "Serious internal problem";
+	}
+
+	@slot = sort { $a <=> $b } @slot;
+	$max  = scalar(@$vine) + scalar(@slot);
+
+	for (my $i = 0; $i < $max; ++$i) {
+		if ($#slot >= 0 && $i == $slot[0]) {
+			shift @slot;
+			$vine->[$i] = shift @$parents;
+			$ret .= ($i == $orig_vine) ? "S" : "s";
+		} elsif (defined($vine->[$i])) {
+			$ret .= "║";
+		} else {
+			$ret .= " ";
+		}
+
+	}
+
+	$ret =~ s/ +$//gs;
+	print &vis_post(&vis_merge($ret)), "\n";
+}
+
+sub vis_branch ($)
+{
+	# Sample input: ╬═╠╬╬╬╩╬═╬╬╬╬╬╬╩╬╩═╬╬
+	my $ra = shift @_;
+	my $i;
+
+	$ra =~ s{^(.+?)╠}{
+		$_ = $1;
+		$_ =~ tr/╪═/║ /;
+		$_ =~ s/(.)/$1 /gs;
+		$_ .= '╠';
+	}es;
+	$ra =~ s{(╠.*)╩}{
+		$_ = $1;
+		$_ =~ s/(.)/$1═/gs;
+		$_ .= '╝';
+	}es;
+	$ra =~ s{╝(.*)$}{
+		$_ = $1;
+		$_ =~ tr/╪═/║ /;
+		$_ =~ s/(.)/$1 /gs;
+		$_ = "╝ $_";
+	}es;
+	return $ra;
+}
+
+sub vis_commit ($$)
+{
+	my($ra, $sep) = @_;
+	my($l, $r) = ($ra =~ /^(.*?)([╟╓╙].*)/);
+	$l =~ s/(.)/$1 /gs;
+	$r =~ s/(.)/$1 /gs;
+	$r =~ s/ /$sep/gs;
+	return $l.$r;
+}
+
+sub vis_merge ($)
+{
+	my $s = shift @_;
+
+	if ($s =~ s/(s.*)S(.*s)/&vis_merge3($1, $2)/es) {
+		;
+	} elsif ($s =~ /(?:s.*)S/s) {
+		while ($s =~ s/(s.*)║(.*S)/$1╪$2/s) {
+			;
+		}
+		$s =~ s/(s.*)S/&vis_merge2L($1)."╣"/es;
+	} elsif ($s =~ /S(?:.*s)/s) {
+		while ($s =~ s/(S.*)║(.*s)/$1╪$2/s) {
+			;
+		}
+		$s =~ /S(.*s)/;
+		$s =~ s/S(.*s)/"╠".&vis_merge2R($1)/es;
+	} else {
+		# $s =~ s/S/║/s;
+		die "Should not come here";
+	}
+	$s =~ s{(.)}{&vis_merge1($1)}egs;
+	return $s;
+}
+
+sub vis_merge1 ($)
+{
+	if ($_[0] eq "╔" || $_[0] eq "╦" || $_[0] eq "╠" || $_[0] eq "╪") {
+		return $_[0]."═";
+	} else {
+		return $_[0]." ";
+	}
+}
+
+sub vis_merge2L ($)
+{
+	my $l = shift @_;
+	$l =~ s/^s/╔/;
+	$l =~ s/s/╦/g;
+	return $l;
+}
+
+sub vis_merge2R ($)
+{
+	my $r = shift @_;
+	$r =~ s/s$/╗/;
+	$r =~ s/s/╦/g;
+	return $r;
+}
+
+sub vis_merge3 ($$)
+{
+	my($l, $r) = shift @_;
+	$l =~ s/^s/╔/;
+	$l =~ s/s/╦/g;
+	$r =~ s/s$/╗/;
+	$r =~ s/s/╦/g;
+	return "$l╪$r";
+}
+
+#
+#	post-process vine graphic
+#
+sub vis_post ($)
+{
+	my $s = shift @_;
+
+	if ($Style == 1) {
+		$s =~ tr/╔╦╗╠╬╣╚╩╝║═╟╓╙╪/┌┬┐├┼┤└┴┘│─├┌└┼/;
+	} elsif ($Style == 2) {
+		$s =~ tr/╪/╬/;
+	} elsif ($Style == 3) {
+		$s =~ tr/╔╦╗╠╬╣╚╩╝║╟╓╙/╒╤╕╞╪╡╘╧╛│├┌└/;
+	}
+
+	if ($Color{default} ne "") {
+		$s =~ s{\Q$Color{default}\E}{$&$Color{tree}}g;
+	}
+	return $Color{tree}, $s, $Color{default};
+}
diff --git a/contrib/git-forest/git-forest.txt b/contrib/git-forest/git-forest.txt
new file mode 100644
index 0000000..0adfd6d
--- /dev/null
+++ b/contrib/git-forest/git-forest.txt
@@ -0,0 +1,34 @@
+
+Options:
+
+--style=1
+	Use single-line visuals
+--style=2
+	Use double-line visuals (default)
+--style=3
+	Use single-line visuals in vertical direction,
+	and double-line ones in horizontal direction.
+--sha
+	Display SHAs for each commit
+
+All other options and arguments are passed down to git-log.
+Commonly useful are --all and --topo-order; along with the
+tag name or commit range.
+
+This tool does not try to minimize the empty space between branches
+like gitk does. Take it as a feature.
+
+Notes on interpretation:
+
+'╬' (or variants thereof, like ╪) is meant to be a "path bridge", i.e.
+traversal is only "allowed" horizontal OR vertical direction.
+
+Branching:
+C D E F G
+╠═╬═╩═╩═╝  A->{C,E,F,G} and B->D.
+A B
+
+Merging:
+E F
+╠═╬═╦═╗    {A,C,D}->E, B->F.
+A B C D

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux