##// END OF EJS Templates
merge with brendan
Benoit Boissinot -
r3058:11e3396e merge default
parent child Browse files
Show More
@@ -0,0 +1,179 b''
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
14 from mercurial.demandload import *
15 from mercurial.i18n import gettext as _
16 demandload(globals(), 'time sys signal os')
17 demandload(globals(), 'mercurial:hg,mdiff,fancyopts,commands,ui,util,templater,node')
18
19 def __gather(ui, repo, node1, node2):
20 def dirtywork(f, mmap1, mmap2):
21 lines = 0
22
23 to = mmap1 and repo.file(f).read(mmap1[f]) or None
24 tn = mmap2 and repo.file(f).read(mmap2[f]) or None
25
26 diff = mdiff.unidiff(to, "", tn, "", f).split("\n")
27
28 for line in diff:
29 if not line:
30 continue # skip EOF
31 if line.startswith(" "):
32 continue # context line
33 if line.startswith("--- ") or line.startswith("+++ "):
34 continue # begining of diff
35 if line.startswith("@@ "):
36 continue # info line
37
38 # changed lines
39 lines += 1
40
41 return lines
42
43 ##
44
45 lines = 0
46
47 changes = repo.status(node1, node2, None, util.always)[:5]
48
49 modified, added, removed, deleted, unknown = changes
50
51 who = repo.changelog.read(node2)[1]
52 who = templater.email(who) # get the email of the person
53
54 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
55 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
56 for f in modified:
57 lines += dirtywork(f, mmap1, mmap2)
58
59 for f in added:
60 lines += dirtywork(f, None, mmap2)
61
62 for f in removed:
63 lines += dirtywork(f, mmap1, None)
64
65 for f in deleted:
66 lines += dirtywork(f, mmap1, mmap2)
67
68 for f in unknown:
69 lines += dirtywork(f, mmap1, mmap2)
70
71 return (who, lines)
72
73 def gather_stats(ui, repo, amap, revs=None, progress=False):
74 stats = {}
75
76 cl = repo.changelog
77
78 if not revs:
79 revs = range(0, cl.count())
80
81 nr_revs = len(revs)
82 cur_rev = 0
83
84 for rev in revs:
85 cur_rev += 1 # next revision
86
87 node2 = cl.node(rev)
88 node1 = cl.parents(node2)[0]
89
90 if cl.parents(node2)[1] != node.nullid:
91 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
92 continue
93
94 who, lines = __gather(ui, repo, node1, node2)
95
96 # remap the owner if possible
97 if amap.has_key(who):
98 ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
99 who = amap[who]
100
101 if not stats.has_key(who):
102 stats[who] = 0
103 stats[who] += lines
104
105 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
106
107 if progress:
108 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
109 ui.write("%d%%.." % (int(100.0*cur_rev/nr_revs),))
110 sys.stdout.flush()
111
112 if progress:
113 ui.write("done\n")
114 sys.stdout.flush()
115
116 return stats
117
118 def churn(ui, repo, **opts):
119 "Graphs the number of lines changed"
120
121 def pad(s, l):
122 if len(s) < l:
123 return s + " " * (l-len(s))
124 return s[0:l]
125
126 def graph(n, maximum, width, char):
127 n = int(n * width / float(maximum))
128
129 return char * (n)
130
131 def get_aliases(f):
132 aliases = {}
133
134 for l in f.readlines():
135 l = l.strip()
136 alias, actual = l.split(" ")
137 aliases[alias] = actual
138
139 return aliases
140
141 amap = {}
142 aliases = opts.get('aliases')
143 if aliases:
144 try:
145 f = open(aliases,"r")
146 except OSError, e:
147 print "Error: " + e
148 return
149
150 amap = get_aliases(f)
151 f.close()
152
153 revs = [int(r) for r in commands.revrange(ui, repo, opts['rev'])]
154 revs.sort()
155 stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
156
157 # make a list of tuples (name, lines) and sort it in descending order
158 ordered = stats.items()
159 ordered.sort(cmp=lambda x, y: cmp(y[1], x[1]))
160
161 maximum = ordered[0][1]
162
163 ui.note("Assuming 80 character terminal\n")
164 width = 80 - 1
165
166 for i in ordered:
167 person = i[0]
168 lines = i[1]
169 print "%s %6d %s" % (pad(person, 20), lines,
170 graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*'))
171
172 cmdtable = {
173 "churn":
174 (churn,
175 [('r', 'rev', [], _('limit statistics to the specified revisions')),
176 ('', 'aliases', '', _('file with email aliases')),
177 ('', 'progress', None, _('show progress'))],
178 'hg churn [-r revision range] [-a file] [--progress]'),
179 }
@@ -135,6 +135,21 b' decode/encode::'
135 135 # them to the working dir
136 136 **.txt = tempfile: unix2dos -n INFILE OUTFILE
137 137
138 defaults::
139 Use the [defaults] section to define command defaults, i.e. the
140 default options/arguments to pass to the specified commands.
141
142 The following example makes 'hg log' run in verbose mode, and
143 'hg status' show only the modified files, by default.
144
145 [defaults]
146 log = -v
147 status = -m
148
149 The actual commands, instead of their aliases, must be used when
150 defining command defaults. The command defaults will also be
151 applied to the aliases of the commands defined.
152
138 153 email::
139 154 Settings for extensions that send email messages.
140 155 from;;
@@ -127,12 +127,7 b' def clone(ui, source, dest=None, pull=Fa'
127 127 if self.dir_:
128 128 self.rmtree(self.dir_, True)
129 129
130 dest_repo = None
131 try:
132 dest_repo = repository(ui, dest)
133 raise util.Abort(_("destination '%s' already exists." % dest))
134 except RepoError:
135 dest_repo = repository(ui, dest, create=True)
130 dest_repo = repository(ui, dest, create=True)
136 131
137 132 dest_path = None
138 133 dir_cleanup = None
@@ -31,8 +31,16 b' class localrepository(repo.repository):'
31 31 path = p
32 32 self.path = os.path.join(path, ".hg")
33 33
34 if not create and not os.path.isdir(self.path):
35 raise repo.RepoError(_("repository %s not found") % path)
34 if not os.path.isdir(self.path):
35 if create:
36 if not os.path.exists(path):
37 os.mkdir(path)
38 os.mkdir(self.path)
39 os.mkdir(self.join("data"))
40 else:
41 raise repo.RepoError(_("repository %s not found") % path)
42 elif create:
43 raise repo.RepoError(_("repository %s already exists") % path)
36 44
37 45 self.root = os.path.abspath(path)
38 46 self.origroot = path
@@ -75,12 +83,6 b' class localrepository(repo.repository):'
75 83 self.decodepats = None
76 84 self.transhandle = None
77 85
78 if create:
79 if not os.path.exists(path):
80 os.mkdir(path)
81 os.mkdir(self.path)
82 os.mkdir(self.join("data"))
83
84 86 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
85 87
86 88 def url(self):
@@ -34,9 +34,10 b' class sshrepository(remoterepository):'
34 34 if create:
35 35 try:
36 36 self.validate_repo(ui, sshcmd, args, remotecmd)
37 return # the repo is good, nothing more to do
38 37 except hg.RepoError:
39 38 pass
39 else:
40 raise hg.RepoError(_("repository %s already exists") % path)
40 41
41 42 cmd = '%s %s "%s init %s"'
42 43 cmd = cmd % (sshcmd, args, remotecmd, self.path)
@@ -52,6 +53,9 b' class sshrepository(remoterepository):'
52 53 return self._url
53 54
54 55 def validate_repo(self, ui, sshcmd, args, remotecmd):
56 # cleanup up previous run
57 self.cleanup()
58
55 59 cmd = '%s %s "%s -R %s serve --stdio"'
56 60 cmd = cmd % (sshcmd, args, remotecmd, self.path)
57 61
@@ -90,7 +94,7 b' class sshrepository(remoterepository):'
90 94 if not l: break
91 95 self.ui.status(_("remote: "), l)
92 96
93 def __del__(self):
97 def cleanup(self):
94 98 try:
95 99 self.pipeo.close()
96 100 self.pipei.close()
@@ -101,6 +105,8 b' class sshrepository(remoterepository):'
101 105 except:
102 106 pass
103 107
108 __del__ = cleanup
109
104 110 def do_cmd(self, cmd, **args):
105 111 self.ui.debug(_("sending %s command\n") % cmd)
106 112 self.pipeo.write("%s\n" % cmd)
@@ -54,7 +54,7 b' class ui(object):'
54 54 def updateopts(self, verbose=False, debug=False, quiet=False,
55 55 interactive=True, traceback=False, config=[]):
56 56 self.quiet = (self.quiet or quiet) and not verbose and not debug
57 self.verbose = (self.verbose or verbose) or debug
57 self.verbose = ((self.verbose or verbose) or debug) and not self.quiet
58 58 self.debugflag = (self.debugflag or debug)
59 59 self.interactive = (self.interactive and interactive)
60 60 self.traceback = self.traceback or traceback
@@ -8,7 +8,7 b' error = error-gitweb.tmpl'
8 8 naventry = '<a href="?cmd=changelog;rev=#rev#;style=gitweb">#label|escape#</a> '
9 9 navshortentry = '<a href="?cmd=shortlog;rev=#rev#;style=gitweb">#label|escape#</a> '
10 10 filedifflink = '<a href="?cmd=filediff;node=#node#;file=#file|urlescape#;style=gitweb">#file|escape#</a> '
11 filenodelink = '<tr class="light"><td><a class="list" href="">#file|escape#</a></td><td></td><td class="link"><a href="?cmd=file;filenode=#filenode#;file=#file|urlescape#;style=gitweb">file</a> | <!-- FIXME: <a href="?fd=#filenode|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?cmd=filelog;filenode=#filenode|short#;file=#file|urlescape#;style=gitweb">revisions</a></td></tr>'
11 filenodelink = '<tr class="light"><td><a class="list" href="">#file|escape#</a></td><td></td><td class="link"><a href="?cmd=file;filenode=#filenode#;file=#file|urlescape#;style=gitweb">file</a> | <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> | <!-- FIXME: <a href="?fd=#filenode|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?cmd=filelog;filenode=#filenode|short#;file=#file|urlescape#;style=gitweb">revisions</a></td></tr>'
12 12 fileellipses = '...'
13 13 changelogentry = changelogentry-gitweb.tmpl
14 14 searchentry = changelogentry-gitweb.tmpl
@@ -46,5 +46,5 b' filediffchild = \'<tr><th class="child">c'
46 46 filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
47 47 shortlog = shortlog-gitweb.tmpl
48 48 shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
49 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
49 filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="?f=#node|short#;file=#file|urlescape#;style=gitweb">file</a> | <!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
50 50 archiveentry = ' | <a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
@@ -27,6 +27,9 b' hg init local'
27 27 echo this > local/foo
28 28 hg ci --cwd local -A -m "init" -d "1000000 0"
29 29
30 echo "#test failure"
31 hg init local
32
30 33 echo "# init+push to remote2"
31 34 hg init -e ./dummyssh ssh://user@dummy/remote2
32 35 hg incoming -R remote2 local
@@ -35,6 +38,12 b' hg push -R local -e ./dummyssh ssh://use'
35 38 echo "# clone to remote1"
36 39 hg clone -e ./dummyssh local ssh://user@dummy/remote1
37 40
41 echo "# init to existing repo"
42 hg init -e ./dummyssh ssh://user@dummy/remote1
43
44 echo "# clone to existing repo"
45 hg clone -e ./dummyssh local ssh://user@dummy/remote1
46
38 47 echo "# output of dummyssh"
39 48 cat dummylog
40 49
@@ -1,6 +1,9 b''
1 1 # creating 'local'
2 2 adding foo
3 #test failure
4 abort: repository local already exists!
3 5 # init+push to remote2
6 remote: abort: repository remote2 not found!
4 7 changeset: 0:c4e059d443be
5 8 tag: tip
6 9 user: test
@@ -14,20 +17,25 b' remote: adding manifests'
14 17 remote: adding file changes
15 18 remote: added 1 changesets with 1 changes to 1 files
16 19 # clone to remote1
20 remote: abort: repository remote1 not found!
17 21 searching for changes
18 remote: abort: repository remote1 not found!
19 22 remote: adding changesets
20 23 remote: adding manifests
21 24 remote: adding file changes
22 25 remote: added 1 changesets with 1 changes to 1 files
26 # init to existing repo
27 abort: repository ssh://user@dummy/remote1 already exists!
28 # clone to existing repo
29 abort: repository ssh://user@dummy/remote1 already exists!
23 30 # output of dummyssh
24 31 Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio 3: 4: 5:
25 32 Got arguments 1:user@dummy 2:hg init remote2 3: 4: 5:
26 33 Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio 3: 4: 5:
27 34 Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio 3: 4: 5:
28 35 Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio 3: 4: 5:
36 Got arguments 1:user@dummy 2:hg init remote1 3: 4: 5:
29 37 Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio 3: 4: 5:
30 Got arguments 1:user@dummy 2:hg init remote1 3: 4: 5:
38 Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio 3: 4: 5:
31 39 Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio 3: 4: 5:
32 40 # comparing repositories
33 41 0:c4e059d443be
General Comments 0
You need to be logged in to leave comments. Login now