Mercurial > hg
annotate hgext/churn.py @ 6858:8f256bf98219
Add support for multiple possible bisect results (issue1228, issue1182)
The real reason for both issue is that bisect can not handle cases where there
are multiple possibilities for the result.
Example (from issue1228):
rev 0 -> good
rev 1 -> skipped
rev 2 -> skipped
rev 3 -> skipped
rev 4 -> bad
Note that this patch does not only fix the reported Assertion Error but also
the problem of a non converging bisect:
hg init
for i in `seq 3`; do echo $i > $i; hg add $i; hg ci -m$i; done
hg bisect -b 2
hg bisect -g 0
hg bisect -s
From this state on, you can:
a) mark as bad forever (non converging!)
b) mark as good to get an inconsistent state
c) skip for the Assertion Error
Minor description and code edits by pmezard.
author | Bernhard Leiner <bleiner@gmail.com> |
---|---|
date | Sat, 02 Aug 2008 22:10:10 +0200 |
parents | 05a62e8ddf3d |
children | b114a8c7998f |
rev | line source |
---|---|
3040 | 1 # churn.py - create a graph showing who changed the most lines |
2 # | |
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net> | |
4 # | |
5 # This software may be used and distributed according to the terms | |
6 # of the GNU General Public License, incorporated herein by reference. | |
7 # | |
8 # | |
9 # Aliases map file format is simple one alias per line in the following | |
10 # format: | |
11 # | |
12 # <alias email> <actual email> | |
13 | |
3045
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3043
diff
changeset
|
14 from mercurial.i18n import gettext as _ |
6212 | 15 from mercurial import mdiff, cmdutil, util, node |
4955
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
16 import os, sys |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
17 |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
18 def get_tty_width(): |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
19 if 'COLUMNS' in os.environ: |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
20 try: |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
21 return int(os.environ['COLUMNS']) |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
22 except ValueError: |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
23 pass |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
24 try: |
5419
041bd297f01e
churn: simplify code to get terminal width
Christian Ebert <blacktrash@gmx.net>
parents:
4955
diff
changeset
|
25 import termios, array, fcntl |
4955
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
26 for dev in (sys.stdout, sys.stdin): |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
27 try: |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
28 fd = dev.fileno() |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
29 if not os.isatty(fd): |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
30 continue |
5419
041bd297f01e
churn: simplify code to get terminal width
Christian Ebert <blacktrash@gmx.net>
parents:
4955
diff
changeset
|
31 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8) |
041bd297f01e
churn: simplify code to get terminal width
Christian Ebert <blacktrash@gmx.net>
parents:
4955
diff
changeset
|
32 return array.array('h', arri)[1] |
4955
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
33 except ValueError: |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
34 pass |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
35 except ImportError: |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
36 pass |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
37 return 80 |
3040 | 38 |
39 def __gather(ui, repo, node1, node2): | |
40 def dirtywork(f, mmap1, mmap2): | |
41 lines = 0 | |
42 | |
3042
2d35d7c6f251
[churn] Trivial cleanup suggested by Thomas
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3041
diff
changeset
|
43 to = mmap1 and repo.file(f).read(mmap1[f]) or None |
2d35d7c6f251
[churn] Trivial cleanup suggested by Thomas
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3041
diff
changeset
|
44 tn = mmap2 and repo.file(f).read(mmap2[f]) or None |
3040 | 45 |
5482
e5eedd74e70f
Use both the from and to name in mdiff.unidiff.
Dustin Sallings <dustin@spy.net>
parents:
5419
diff
changeset
|
46 diff = mdiff.unidiff(to, "", tn, "", f, f).split("\n") |
3040 | 47 |
48 for line in diff: | |
3043
fe0e3508ec6e
[churn] Trivial cleanup
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3042
diff
changeset
|
49 if not line: |
3040 | 50 continue # skip EOF |
3042
2d35d7c6f251
[churn] Trivial cleanup suggested by Thomas
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3041
diff
changeset
|
51 if line.startswith(" "): |
3040 | 52 continue # context line |
3042
2d35d7c6f251
[churn] Trivial cleanup suggested by Thomas
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3041
diff
changeset
|
53 if line.startswith("--- ") or line.startswith("+++ "): |
3040 | 54 continue # begining of diff |
3042
2d35d7c6f251
[churn] Trivial cleanup suggested by Thomas
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3041
diff
changeset
|
55 if line.startswith("@@ "): |
3040 | 56 continue # info line |
57 | |
58 # changed lines | |
59 lines += 1 | |
60 | |
61 return lines | |
62 | |
63 ## | |
64 | |
65 lines = 0 | |
66 | |
3048
8d344bc72e68
[churn] repo.changes was renamed to repo.status
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
67 changes = repo.status(node1, node2, None, util.always)[:5] |
3040 | 68 |
69 modified, added, removed, deleted, unknown = changes | |
70 | |
71 who = repo.changelog.read(node2)[1] | |
5975
75d9fe70c654
templater: move email function to util
Matt Mackall <mpm@selenic.com>
parents:
5915
diff
changeset
|
72 who = util.email(who) # get the email of the person |
3040 | 73 |
74 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0]) | |
75 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0]) | |
76 for f in modified: | |
77 lines += dirtywork(f, mmap1, mmap2) | |
78 | |
79 for f in added: | |
80 lines += dirtywork(f, None, mmap2) | |
3223
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3090
diff
changeset
|
81 |
3040 | 82 for f in removed: |
83 lines += dirtywork(f, mmap1, None) | |
84 | |
85 for f in deleted: | |
86 lines += dirtywork(f, mmap1, mmap2) | |
87 | |
88 for f in unknown: | |
89 lines += dirtywork(f, mmap1, mmap2) | |
90 | |
91 return (who, lines) | |
92 | |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
93 def gather_stats(ui, repo, amap, revs=None, progress=False): |
3040 | 94 stats = {} |
3223
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3090
diff
changeset
|
95 |
3040 | 96 cl = repo.changelog |
97 | |
3045
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3043
diff
changeset
|
98 if not revs: |
3046 | 99 revs = range(0, cl.count()) |
3045
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3043
diff
changeset
|
100 |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
101 nr_revs = len(revs) |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
102 cur_rev = 0 |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
103 |
3045
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3043
diff
changeset
|
104 for rev in revs: |
3051
7ffaf5aba4d8
[churn] Fix progress bar not incrementing when merge cset is encountered
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3050
diff
changeset
|
105 cur_rev += 1 # next revision |
7ffaf5aba4d8
[churn] Fix progress bar not incrementing when merge cset is encountered
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3050
diff
changeset
|
106 |
3040 | 107 node2 = cl.node(rev) |
108 node1 = cl.parents(node2)[0] | |
109 | |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
110 if cl.parents(node2)[1] != node.nullid: |
3049
461573aa02ef
[churn] Ignore merge csets
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3048
diff
changeset
|
111 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,)) |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
112 continue |
3049
461573aa02ef
[churn] Ignore merge csets
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3048
diff
changeset
|
113 |
3040 | 114 who, lines = __gather(ui, repo, node1, node2) |
115 | |
116 # remap the owner if possible | |
5915
d0576d065993
Prefer i in d over d.has_key(i)
Christian Ebert <blacktrash@gmx.net>
parents:
5588
diff
changeset
|
117 if who in amap: |
3040 | 118 ui.note("using '%s' alias for '%s'\n" % (amap[who], who)) |
119 who = amap[who] | |
120 | |
5915
d0576d065993
Prefer i in d over d.has_key(i)
Christian Ebert <blacktrash@gmx.net>
parents:
5588
diff
changeset
|
121 if not who in stats: |
3040 | 122 stats[who] = 0 |
123 stats[who] += lines | |
124 | |
125 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who)) | |
126 | |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
127 if progress: |
5588
083b6e3142a2
churn: avoid division by zero
Matt Mackall <mpm@selenic.com>
parents:
5482
diff
changeset
|
128 nr_revs = max(nr_revs, 1) |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
129 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs): |
5990
71d675f4b1f8
Fixed typo: Gnerating -> Generating
Thomas Arendsen Hein <thomas@intevation.de>
parents:
5989
diff
changeset
|
130 ui.write("\rGenerating stats: %d%%" % (int(100.0*cur_rev/nr_revs),)) |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
131 sys.stdout.flush() |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
132 |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
133 if progress: |
5989
a7817ad608ea
added \r for progress counting in churn extension
Armin Ronacher <armin.ronacher@active-4.com>
parents:
5976
diff
changeset
|
134 ui.write("\r") |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
135 sys.stdout.flush() |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
136 |
3040 | 137 return stats |
138 | |
3045
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3043
diff
changeset
|
139 def churn(ui, repo, **opts): |
3040 | 140 "Graphs the number of lines changed" |
3223
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3090
diff
changeset
|
141 |
3040 | 142 def pad(s, l): |
143 if len(s) < l: | |
144 return s + " " * (l-len(s)) | |
145 return s[0:l] | |
146 | |
147 def graph(n, maximum, width, char): | |
5588
083b6e3142a2
churn: avoid division by zero
Matt Mackall <mpm@selenic.com>
parents:
5482
diff
changeset
|
148 maximum = max(1, maximum) |
3040 | 149 n = int(n * width / float(maximum)) |
3223
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3090
diff
changeset
|
150 |
3040 | 151 return char * (n) |
152 | |
153 def get_aliases(f): | |
154 aliases = {} | |
155 | |
156 for l in f.readlines(): | |
157 l = l.strip() | |
6421
ecde0baee570
churn: allow whitespaces as delimiter in aliases
Kirill Smelkov <kirr@mns.spb.ru>
parents:
6348
diff
changeset
|
158 alias, actual = l.split() |
3040 | 159 aliases[alias] = actual |
160 | |
161 return aliases | |
3223
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3090
diff
changeset
|
162 |
3040 | 163 amap = {} |
3045
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3043
diff
changeset
|
164 aliases = opts.get('aliases') |
3040 | 165 if aliases: |
166 try: | |
167 f = open(aliases,"r") | |
168 except OSError, e: | |
169 print "Error: " + e | |
170 return | |
171 | |
172 amap = get_aliases(f) | |
173 f.close() | |
3045
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3043
diff
changeset
|
174 |
3792
4670470b97bd
Fix revrange() call in the churn contrib
Edouard Gomez <ed.gomez@free.fr>
parents:
3223
diff
changeset
|
175 revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])] |
3045
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3043
diff
changeset
|
176 revs.sort() |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
177 stats = gather_stats(ui, repo, amap, revs, opts.get('progress')) |
3040 | 178 |
179 # make a list of tuples (name, lines) and sort it in descending order | |
180 ordered = stats.items() | |
5588
083b6e3142a2
churn: avoid division by zero
Matt Mackall <mpm@selenic.com>
parents:
5482
diff
changeset
|
181 if not ordered: |
083b6e3142a2
churn: avoid division by zero
Matt Mackall <mpm@selenic.com>
parents:
5482
diff
changeset
|
182 return |
6223
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
183 ordered.sort(lambda x, y: cmp(y[1], x[1])) |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
184 max_churn = ordered[0][1] |
3040 | 185 |
6223
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
186 tty_width = get_tty_width() |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
187 ui.note(_("assuming %i character terminal\n") % tty_width) |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
188 tty_width -= 1 |
3040 | 189 |
6223
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
190 max_user_width = max([len(user) for user, churn in ordered]) |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
191 |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
192 graph_width = tty_width - max_user_width - 1 - 6 - 2 - 2 |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
193 |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
194 for user, churn in ordered: |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
195 print "%s %6d %s" % (pad(user, max_user_width), |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
196 churn, |
bab6c8f2bb1a
churn: show comitter email addresses unclipped (bug 1023)
Stephen Deasey <sdeasey@gmail.com>
parents:
6212
diff
changeset
|
197 graph(churn, max_churn, graph_width, '*')) |
3040 | 198 |
199 cmdtable = { | |
200 "churn": | |
201 (churn, | |
3045
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3043
diff
changeset
|
202 [('r', 'rev', [], _('limit statistics to the specified revisions')), |
3050
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
203 ('', 'aliases', '', _('file with email aliases')), |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3049
diff
changeset
|
204 ('', 'progress', None, _('show progress'))], |
6841
05a62e8ddf3d
churn: fix documentation for churn options
Dirkjan Ochtman <dirkjan@ochtman.nl>
parents:
6421
diff
changeset
|
205 'hg churn [-r REVISIONS] [--aliases FILE] [--progress]'), |
3040 | 206 } |