[PATCH 1/1] Import git-forest

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

 



Signed-off-by: Jan Engelhardt <jengelh@xxxxxxxxxxxxxxx>
---
 contrib/git-forest/git-forest     |  422 +++++++++++++++++++++++++++++
 contrib/git-forest/git-forest.txt |   53 ++++
 2 files changed, 475 insertions(+), 0 deletions(-)
 create mode 100755 contrib/git-forest/git-forest
 create mode 100644 contrib/git-forest/git-forest.txt

diff --git a/contrib/git-forest/git-forest b/contrib/git-forest/git-forest
new file mode 100755
index 0000000..c38b218
--- /dev/null
+++ b/contrib/git-forest/git-forest
@@ -0,0 +1,422 @@
+#!/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 $Show_all = 0;
+my $Show_rebase = 1;
+my $Style    = 2;
+my $With_sha = 0;
+my %Color    = (
+	"default" => "\e[0m",
+	"at"      => "\e[1;30m",
+	"hhead"   => "\e[1;31m",
+	"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(
+		"all"      => \$Show_all,
+		"no-color" => sub { %Color = (); },
+		"no-rebase" => sub { $Show_rebase = 0; },
+		"style=i"  => \$Style,
+		"sha"      => \$With_sha,
+	);
+	if ($Show_all) {
+		unshift(@ARGV, "--all", "HEAD");
+	}
+	&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);
+
+	my $rebase = -e $Repo->repo_path()."/.dotest-merge/git-rebase-todo" &&
+	             $Show_rebase;
+	if ($rebase) {
+		my $up  = $Repo->command("rev-parse", ".dotest-merge/upstream");
+		my $old = $Repo->command("rev-parse", ".dotest-merge/head");
+		chomp $up;
+		chomp $old;
+		unshift(@{$ret->{$up}}, "rebase/upstream");
+		unshift(@{$ret->{$old}}, "rebase/old-HEAD");
+	}
+
+	my $head = $Repo->command("rev-parse", "HEAD");
+	chomp $head;
+	if ($rebase) {
+		unshift(@{$ret->{$head}}, "rebase/inprogress");
+	}
+	unshift(@{$ret->{$head}}, "HEAD");
+
+	return $ret;
+}
+
+sub ref_print ($)
+{
+	foreach my $symbol (@{shift @_}) {
+		print $Color{at}, "[";
+		if ($symbol eq "HEAD" || $symbol =~ m{^rebase/}) {
+			print $Color{hhead}, $symbol;
+		} elsif ($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/â??â?¦â??â? â?¬â?£â??â?©â??â??â??â??â??/â??â?¤â??â??â?ªâ?¡â??â?§â??â??â??â??â??/;
+	} elsif ($Style == 4) {
+		$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..5dfd478
--- /dev/null
+++ b/contrib/git-forest/git-forest.txt
@@ -0,0 +1,53 @@
+
+Usage: git-forest [OPTIONS...] [REVLISTOPTIONS] [REFSPEC...]
+
+--no-rebase
+	Do not show rebase/ pseudo-refs
+--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.
+--style=4
+	Use double-line visuals in vertical direction,
+	and single-line ones in horizontal direction.
+--sha
+	Display SHAs for each commit
+
+All other options and arguments are passed down to git-log.
+Commonly useful options are --all and --topo-order.
+
+Example:
+	git-forest --all | less -RS
+	git-forest origin/master | less -RS
+
+This tool does not try to minimize the empty space between branches
+like gitk does. Take it as a feature.
+
+Notes on interpretation:
+
+Connections with four "legs" ('â?¬' or variants thereof, like 'â?ª') are
+meant to be interpreted as being connected only in the horizontal and
+vertical direction, NOT around-the-corner.
+
+Connections with three or less "legs" ('â? ', 'â?©', 'â?¦', etc.) imply
+a connection to all connected directions.
+
+Consider the following example history. The merge at 070 has merged
+together the branches with commits f21, 886 and 8f8, but NOT d3e.
+
+	â??â??[HEAD]â??â??[master]â??â??-â??â??(0c6)
+	â? â??â?? 
+	â?? â?? -â??â??(710)
+	â?? â?? -â??â??(070)
+	â? â??â?¬â??â?¦â??â??
+	â?? â?? â?? â?? -â??â??(d3e)
+	â?? â?? â?? â?? -â??â??(886)
+	â?? â?? â?? â?? -â??â??(8f8)
+	â?? â? â??â?? â??
+	â?? â??   â?? -â??â??(199)
+	â?? â??   â?? -â??â??(f21)
+	â? â??â?©â??â??â??â?? 
+	â?? -â??â??(15f)
-- 
1.5.4.4

--
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