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 |
|
|
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 |
|
|
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#: </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 |
|
|
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