Mercurial > hg > mercurial-crew-with-dirclash
annotate contrib/churn.py @ 5192:33015dac5df5
convert: fix mercurial_sink.putcommit
Changeset 4ebc8693ce72 added some code to putcommit to avoid creating a
revision that touches no files, but this can break regular conversions
from some repositories:
- conceptually, since we're converting a repo, we should try to make
the new hg repo as similar as possible to the original repo - we
should create a new changeset, even if the original revision didn't
touch any files (maybe the commit message had some important bit);
- even if a "regular" revision that doesn't touch any file may seem
weird (and maybe even broken), it's completely legitimate for a merge
revision to not touch any file, and, if we just skip it, the
converted repo will end up with wrong history and possibly an extra
head.
As an example, say the crew and main hg repos are sync'ed. Somebody
sends an important patch to the mailing list. Matt quickly applies
and pushes it. But at the same time somebody also applies it to crew
and pushes it. Suppose the commit message ended up being a bit
different (say, there was a typo and somebody didn't fix it) or that
the date ended up being different (because of different patch-applying
scripts): the changeset hashes will be different, but the manifests
will be the same.
Since both changesets were pushed to public repos, it's hard to recall
them. If both are merged, the manifest from the resulting merge
revision will have the exact same contents as its parents - i.e. the
merge revision really doesn't touch any file at all.
To keep the file filtering stuff "working", the generic code was changed
to skip empty revisions if we're filtering the repo, fixing a bug in the
process (we want parents[0] instead of tip).
author | Alexis S. L. Carvalho <alexis@cecm.usp.br> |
---|---|
date | Fri, 17 Aug 2007 20:18:05 -0300 |
parents | 9bbc0217209b |
children | 041bd297f01e |
rev | line source |
---|---|
3037 | 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 | |
3042
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3040
diff
changeset
|
14 from mercurial.i18n import gettext as _ |
3886
abaee83ce0a6
Replace demandload with new demandimport
Matt Mackall <mpm@selenic.com>
parents:
3792
diff
changeset
|
15 from mercurial import hg, mdiff, cmdutil, ui, util, templater, node |
4937
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: |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
25 import termios, fcntl, struct |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
26 buf = 'abcd' |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
27 for dev in (sys.stdout, sys.stdin): |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
28 try: |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
29 if buf != 'abcd': |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
30 break |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
31 fd = dev.fileno() |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
32 if not os.isatty(fd): |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
33 continue |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
34 buf = fcntl.ioctl(fd, termios.TIOCGWINSZ, buf) |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
35 except ValueError: |
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 if buf != 'abcd': |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
38 return struct.unpack('hh', buf)[1] |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
39 except ImportError: |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
40 pass |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
41 return 80 |
3037 | 42 |
43 def __gather(ui, repo, node1, node2): | |
44 def dirtywork(f, mmap1, mmap2): | |
45 lines = 0 | |
46 | |
3039
2d35d7c6f251
[churn] Trivial cleanup suggested by Thomas
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3038
diff
changeset
|
47 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:
3038
diff
changeset
|
48 tn = mmap2 and repo.file(f).read(mmap2[f]) or None |
3037 | 49 |
50 diff = mdiff.unidiff(to, "", tn, "", f).split("\n") | |
51 | |
52 for line in diff: | |
3040
fe0e3508ec6e
[churn] Trivial cleanup
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3039
diff
changeset
|
53 if not line: |
3037 | 54 continue # skip EOF |
3039
2d35d7c6f251
[churn] Trivial cleanup suggested by Thomas
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3038
diff
changeset
|
55 if line.startswith(" "): |
3037 | 56 continue # context line |
3039
2d35d7c6f251
[churn] Trivial cleanup suggested by Thomas
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3038
diff
changeset
|
57 if line.startswith("--- ") or line.startswith("+++ "): |
3037 | 58 continue # begining of diff |
3039
2d35d7c6f251
[churn] Trivial cleanup suggested by Thomas
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3038
diff
changeset
|
59 if line.startswith("@@ "): |
3037 | 60 continue # info line |
61 | |
62 # changed lines | |
63 lines += 1 | |
64 | |
65 return lines | |
66 | |
67 ## | |
68 | |
69 lines = 0 | |
70 | |
3045
8d344bc72e68
[churn] repo.changes was renamed to repo.status
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3043
diff
changeset
|
71 changes = repo.status(node1, node2, None, util.always)[:5] |
3037 | 72 |
73 modified, added, removed, deleted, unknown = changes | |
74 | |
75 who = repo.changelog.read(node2)[1] | |
76 who = templater.email(who) # get the email of the person | |
77 | |
78 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0]) | |
79 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0]) | |
80 for f in modified: | |
81 lines += dirtywork(f, mmap1, mmap2) | |
82 | |
83 for f in added: | |
84 lines += dirtywork(f, None, mmap2) | |
3215
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3087
diff
changeset
|
85 |
3037 | 86 for f in removed: |
87 lines += dirtywork(f, mmap1, None) | |
88 | |
89 for f in deleted: | |
90 lines += dirtywork(f, mmap1, mmap2) | |
91 | |
92 for f in unknown: | |
93 lines += dirtywork(f, mmap1, mmap2) | |
94 | |
95 return (who, lines) | |
96 | |
3047
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
97 def gather_stats(ui, repo, amap, revs=None, progress=False): |
3037 | 98 stats = {} |
3215
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3087
diff
changeset
|
99 |
3037 | 100 cl = repo.changelog |
101 | |
3042
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3040
diff
changeset
|
102 if not revs: |
3043 | 103 revs = range(0, cl.count()) |
3042
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3040
diff
changeset
|
104 |
3047
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
105 nr_revs = len(revs) |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
106 cur_rev = 0 |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
107 |
3042
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3040
diff
changeset
|
108 for rev in revs: |
3048
7ffaf5aba4d8
[churn] Fix progress bar not incrementing when merge cset is encountered
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3047
diff
changeset
|
109 cur_rev += 1 # next revision |
7ffaf5aba4d8
[churn] Fix progress bar not incrementing when merge cset is encountered
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3047
diff
changeset
|
110 |
3037 | 111 node2 = cl.node(rev) |
112 node1 = cl.parents(node2)[0] | |
113 | |
3047
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
114 if cl.parents(node2)[1] != node.nullid: |
3046
461573aa02ef
[churn] Ignore merge csets
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3045
diff
changeset
|
115 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,)) |
3047
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
116 continue |
3046
461573aa02ef
[churn] Ignore merge csets
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3045
diff
changeset
|
117 |
3037 | 118 who, lines = __gather(ui, repo, node1, node2) |
119 | |
120 # remap the owner if possible | |
121 if amap.has_key(who): | |
122 ui.note("using '%s' alias for '%s'\n" % (amap[who], who)) | |
123 who = amap[who] | |
124 | |
125 if not stats.has_key(who): | |
126 stats[who] = 0 | |
127 stats[who] += lines | |
128 | |
129 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who)) | |
130 | |
3047
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
131 if progress: |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
132 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs): |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
133 ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),)) |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
134 sys.stdout.flush() |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
135 |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
136 if progress: |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
137 ui.write("done\n") |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
138 sys.stdout.flush() |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
139 |
3037 | 140 return stats |
141 | |
3042
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3040
diff
changeset
|
142 def churn(ui, repo, **opts): |
3037 | 143 "Graphs the number of lines changed" |
3215
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3087
diff
changeset
|
144 |
3037 | 145 def pad(s, l): |
146 if len(s) < l: | |
147 return s + " " * (l-len(s)) | |
148 return s[0:l] | |
149 | |
150 def graph(n, maximum, width, char): | |
151 n = int(n * width / float(maximum)) | |
3215
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3087
diff
changeset
|
152 |
3037 | 153 return char * (n) |
154 | |
155 def get_aliases(f): | |
156 aliases = {} | |
157 | |
158 for l in f.readlines(): | |
159 l = l.strip() | |
160 alias, actual = l.split(" ") | |
161 aliases[alias] = actual | |
162 | |
163 return aliases | |
3215
53e843840349
Whitespace/Tab cleanup
Thomas Arendsen Hein <thomas@intevation.de>
parents:
3087
diff
changeset
|
164 |
3037 | 165 amap = {} |
3042
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3040
diff
changeset
|
166 aliases = opts.get('aliases') |
3037 | 167 if aliases: |
168 try: | |
169 f = open(aliases,"r") | |
170 except OSError, e: | |
171 print "Error: " + e | |
172 return | |
173 | |
174 amap = get_aliases(f) | |
175 f.close() | |
3042
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3040
diff
changeset
|
176 |
3792
4670470b97bd
Fix revrange() call in the churn contrib
Edouard Gomez <ed.gomez@free.fr>
parents:
3215
diff
changeset
|
177 revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])] |
3042
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3040
diff
changeset
|
178 revs.sort() |
3047
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
179 stats = gather_stats(ui, repo, amap, revs, opts.get('progress')) |
3037 | 180 |
181 # make a list of tuples (name, lines) and sort it in descending order | |
182 ordered = stats.items() | |
3057
50e0392d51df
Fix for Python 2.3 compatibility.
Shun-ichi GOTO <shunichi.goto@gmail.com>
parents:
3049
diff
changeset
|
183 ordered.sort(lambda x, y: cmp(y[1], x[1])) |
3037 | 184 |
185 maximum = ordered[0][1] | |
186 | |
4937
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
187 width = get_tty_width() |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
188 ui.note(_("assuming %i character terminal\n") % width) |
9bbc0217209b
churn: get current terminal width if possible
Christian Ebert <blacktrash@gmx.net>
parents:
3963
diff
changeset
|
189 width -= 1 |
3037 | 190 |
191 for i in ordered: | |
192 person = i[0] | |
193 lines = i[1] | |
194 print "%s %6d %s" % (pad(person, 20), lines, | |
195 graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*')) | |
196 | |
197 cmdtable = { | |
198 "churn": | |
199 (churn, | |
3042
c0be8990e819
Add revision range support
Brendan Cully <brendan@kublai.com>
parents:
3040
diff
changeset
|
200 [('r', 'rev', [], _('limit statistics to the specified revisions')), |
3047
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
201 ('', 'aliases', '', _('file with email aliases')), |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
202 ('', 'progress', None, _('show progress'))], |
dd1a142988d3
[churn] progress meter
Josef "Jeff" Sipek <jeffpc@josefsipek.net>
parents:
3046
diff
changeset
|
203 'hg churn [-r revision range] [-a file] [--progress]'), |
3037 | 204 } |