Meta/topic: allow more than two letters of topic hierarchy name
[git] / git-topic.perl
1 #!/usr/bin/perl -w
2 #
3 # Copyright (c) 2006 Junio C Hamano
4 #
5
6 use strict;
7 use Getopt::Long;
8
9 my $topic_pattern = '??*/*';
10 my $base = 'next';
11 my @stage = qw(next pu);
12 my @mark = ('.', '?', '-', '+');
13 my $all = 0;
14
15 my @custom_stage;
16 my @custom_mark;
17 GetOptions("topic=s" => \$topic_pattern,
18            "base=s" => \$base,
19            "stage=s" => \@custom_stage,
20            "mark=s" => \@custom_mark,
21            "all!" => \$all)
22     or die;
23
24 if (@custom_stage) { @stage = @custom_stage; }
25 if (@custom_mark) { @mark = @custom_mark; }
26
27 sub read_revs_short {
28         my (@args) = @_;
29         my @revs;
30         open(REVS, '-|', qw(git rev-list --no-merges), @args)
31             or die;
32         while (<REVS>) {
33                 chomp;
34                 push @revs, $_;
35         }
36         close(REVS);
37         return @revs;
38 }
39
40 sub read_revs {
41         my ($bottom, $top, $mask) = @_;
42         my @revs;
43         open(REVS, '-|', qw(git rev-list --pretty=oneline --no-merges),
44              "$bottom..$top")
45             or die;
46         while (<REVS>) {
47                 chomp;
48                 my ($sha1, $topic) = /^([0-9a-f]{40}) (.*)$/;
49                 push @revs, [$sha1, $topic, $mask];
50         }
51         close(REVS);
52         return @revs;
53 }
54
55 sub rebase_marker {
56         my ($topic, $stage, $in_next) = @_;
57         my @not_in_topic = read_revs_short('^master', "^$topic", "$stage");
58
59         # @$in_next is what is in $stage but not in $base.
60         # @not_in_topic excludes what came from $topic from @$in_next.
61         # $topic can be rebased if these two set matches, because
62         # no commits in $topic has been merged to $stage yet.
63         if (@not_in_topic != @$in_next) {
64                 # we cannot rebase it anymore
65                 return ' ';
66         }
67         if (read_revs_short('master', "^$topic")) {
68                 # there is something that is in master but not in topic.
69                 return '^';
70         }
71         # topic is up to date.
72         return '*';
73 }
74
75 sub describe_topic {
76         my ($topic) = @_;
77         open(CONF, '-|', qw(git repo-config --get),
78                 "branch.$topic.description")
79                 or die;
80         my $it = join('',<CONF>);
81         close(CONF);
82         chomp($it);
83         if ($it) {
84                 wrap_print("  $it");
85         }
86 }
87
88 sub wrap_print {
89         my ($string) = @_;
90         format STDOUT =
91 ~^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
92         $string
93  ~~^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
94         $string
95 .
96         write;
97 }
98
99 open(TOPIC, '-|', qw(git for-each-ref),
100         '--sort=-authordate',
101         '--format=%(objectname) %(authordate) %(refname)',
102         "refs/heads/$topic_pattern")
103         or die;
104
105 my @in_next = read_revs_short('^master', $stage[0]);
106
107 while (<TOPIC>) {
108         chomp;
109         my ($sha1, $date, $topic) = m|^([0-9a-f]{40})\s(.*?)\srefs/heads/(.+)$|
110                 or next;
111         my @revs = read_revs($base, $sha1, (1<<@stage)-1);
112         next unless (@revs || $all);
113
114         my %revs = map { $_->[0] => $_ } @revs; # fast index
115         for (my $i = 0; $i < @stage; $i++) {
116                 for my $item (read_revs_short("^$stage[$i]", $sha1)) {
117                         if (exists $revs{$item}) {
118                                 $revs{$item}[2] &= ~(1 << $i);
119                         }
120                 }
121         }
122
123         print '*' . rebase_marker($sha1, $stage[0], \@in_next);
124         my $count = "";
125         if (1 < @revs) {
126                 $count = " " . (scalar @revs) . " commits";
127         }
128         elsif (@revs) {
129                 $count = " 1 commit";
130         }
131         print " $topic ($date)$count\n";
132         describe_topic($topic);
133         for my $item (@revs) {
134                 my $mark = $item->[2];
135                 if ($mark < @mark) {
136                         $mark = $mark[$mark];
137                 }
138                 wrap_print("$mark $item->[1]");
139         }
140 }