##// END OF EJS Templates
hgk: don't use mutable default argument value...
Pierre-Yves David -
r31409:dddc4812 default
parent child Browse files
Show More
@@ -1,348 +1,350 b''
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''browse the repository in a graphical way
8 '''browse the repository in a graphical way
9
9
10 The hgk extension allows browsing the history of a repository in a
10 The hgk extension allows browsing the history of a repository in a
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 distributed with Mercurial.)
12 distributed with Mercurial.)
13
13
14 hgk consists of two parts: a Tcl script that does the displaying and
14 hgk consists of two parts: a Tcl script that does the displaying and
15 querying of information, and an extension to Mercurial named hgk.py,
15 querying of information, and an extension to Mercurial named hgk.py,
16 which provides hooks for hgk to get information. hgk can be found in
16 which provides hooks for hgk to get information. hgk can be found in
17 the contrib directory, and the extension is shipped in the hgext
17 the contrib directory, and the extension is shipped in the hgext
18 repository, and needs to be enabled.
18 repository, and needs to be enabled.
19
19
20 The :hg:`view` command will launch the hgk Tcl script. For this command
20 The :hg:`view` command will launch the hgk Tcl script. For this command
21 to work, hgk must be in your search path. Alternately, you can specify
21 to work, hgk must be in your search path. Alternately, you can specify
22 the path to hgk in your configuration file::
22 the path to hgk in your configuration file::
23
23
24 [hgk]
24 [hgk]
25 path = /location/of/hgk
25 path = /location/of/hgk
26
26
27 hgk can make use of the extdiff extension to visualize revisions.
27 hgk can make use of the extdiff extension to visualize revisions.
28 Assuming you had already configured extdiff vdiff command, just add::
28 Assuming you had already configured extdiff vdiff command, just add::
29
29
30 [hgk]
30 [hgk]
31 vdiff=vdiff
31 vdiff=vdiff
32
32
33 Revisions context menu will now display additional entries to fire
33 Revisions context menu will now display additional entries to fire
34 vdiff on hovered and selected revisions.
34 vdiff on hovered and selected revisions.
35 '''
35 '''
36
36
37 from __future__ import absolute_import
37 from __future__ import absolute_import
38
38
39 import os
39 import os
40
40
41 from mercurial.i18n import _
41 from mercurial.i18n import _
42 from mercurial.node import (
42 from mercurial.node import (
43 nullid,
43 nullid,
44 nullrev,
44 nullrev,
45 short,
45 short,
46 )
46 )
47 from mercurial import (
47 from mercurial import (
48 cmdutil,
48 cmdutil,
49 commands,
49 commands,
50 obsolete,
50 obsolete,
51 patch,
51 patch,
52 scmutil,
52 scmutil,
53 )
53 )
54
54
55 cmdtable = {}
55 cmdtable = {}
56 command = cmdutil.command(cmdtable)
56 command = cmdutil.command(cmdtable)
57 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
57 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
58 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
58 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 # be specifying the version(s) of Mercurial they are tested with, or
59 # be specifying the version(s) of Mercurial they are tested with, or
60 # leave the attribute unspecified.
60 # leave the attribute unspecified.
61 testedwith = 'ships-with-hg-core'
61 testedwith = 'ships-with-hg-core'
62
62
63 @command('debug-diff-tree',
63 @command('debug-diff-tree',
64 [('p', 'patch', None, _('generate patch')),
64 [('p', 'patch', None, _('generate patch')),
65 ('r', 'recursive', None, _('recursive')),
65 ('r', 'recursive', None, _('recursive')),
66 ('P', 'pretty', None, _('pretty')),
66 ('P', 'pretty', None, _('pretty')),
67 ('s', 'stdin', None, _('stdin')),
67 ('s', 'stdin', None, _('stdin')),
68 ('C', 'copy', None, _('detect copies')),
68 ('C', 'copy', None, _('detect copies')),
69 ('S', 'search', "", _('search'))],
69 ('S', 'search', "", _('search'))],
70 ('[OPTION]... NODE1 NODE2 [FILE]...'),
70 ('[OPTION]... NODE1 NODE2 [FILE]...'),
71 inferrepo=True)
71 inferrepo=True)
72 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
72 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
73 """diff trees from two commits"""
73 """diff trees from two commits"""
74 def __difftree(repo, node1, node2, files=[]):
74 def __difftree(repo, node1, node2, files=None):
75 assert node2 is not None
75 assert node2 is not None
76 if files is None:
77 files = []
76 mmap = repo[node1].manifest()
78 mmap = repo[node1].manifest()
77 mmap2 = repo[node2].manifest()
79 mmap2 = repo[node2].manifest()
78 m = scmutil.match(repo[node1], files)
80 m = scmutil.match(repo[node1], files)
79 modified, added, removed = repo.status(node1, node2, m)[:3]
81 modified, added, removed = repo.status(node1, node2, m)[:3]
80 empty = short(nullid)
82 empty = short(nullid)
81
83
82 for f in modified:
84 for f in modified:
83 # TODO get file permissions
85 # TODO get file permissions
84 ui.write((":100664 100664 %s %s M\t%s\t%s\n") %
86 ui.write((":100664 100664 %s %s M\t%s\t%s\n") %
85 (short(mmap[f]), short(mmap2[f]), f, f))
87 (short(mmap[f]), short(mmap2[f]), f, f))
86 for f in added:
88 for f in added:
87 ui.write((":000000 100664 %s %s N\t%s\t%s\n") %
89 ui.write((":000000 100664 %s %s N\t%s\t%s\n") %
88 (empty, short(mmap2[f]), f, f))
90 (empty, short(mmap2[f]), f, f))
89 for f in removed:
91 for f in removed:
90 ui.write((":100664 000000 %s %s D\t%s\t%s\n") %
92 ui.write((":100664 000000 %s %s D\t%s\t%s\n") %
91 (short(mmap[f]), empty, f, f))
93 (short(mmap[f]), empty, f, f))
92 ##
94 ##
93
95
94 while True:
96 while True:
95 if opts['stdin']:
97 if opts['stdin']:
96 try:
98 try:
97 line = raw_input().split(' ')
99 line = raw_input().split(' ')
98 node1 = line[0]
100 node1 = line[0]
99 if len(line) > 1:
101 if len(line) > 1:
100 node2 = line[1]
102 node2 = line[1]
101 else:
103 else:
102 node2 = None
104 node2 = None
103 except EOFError:
105 except EOFError:
104 break
106 break
105 node1 = repo.lookup(node1)
107 node1 = repo.lookup(node1)
106 if node2:
108 if node2:
107 node2 = repo.lookup(node2)
109 node2 = repo.lookup(node2)
108 else:
110 else:
109 node2 = node1
111 node2 = node1
110 node1 = repo.changelog.parents(node1)[0]
112 node1 = repo.changelog.parents(node1)[0]
111 if opts['patch']:
113 if opts['patch']:
112 if opts['pretty']:
114 if opts['pretty']:
113 catcommit(ui, repo, node2, "")
115 catcommit(ui, repo, node2, "")
114 m = scmutil.match(repo[node1], files)
116 m = scmutil.match(repo[node1], files)
115 diffopts = patch.difffeatureopts(ui)
117 diffopts = patch.difffeatureopts(ui)
116 diffopts.git = True
118 diffopts.git = True
117 chunks = patch.diff(repo, node1, node2, match=m,
119 chunks = patch.diff(repo, node1, node2, match=m,
118 opts=diffopts)
120 opts=diffopts)
119 for chunk in chunks:
121 for chunk in chunks:
120 ui.write(chunk)
122 ui.write(chunk)
121 else:
123 else:
122 __difftree(repo, node1, node2, files=files)
124 __difftree(repo, node1, node2, files=files)
123 if not opts['stdin']:
125 if not opts['stdin']:
124 break
126 break
125
127
126 def catcommit(ui, repo, n, prefix, ctx=None):
128 def catcommit(ui, repo, n, prefix, ctx=None):
127 nlprefix = '\n' + prefix
129 nlprefix = '\n' + prefix
128 if ctx is None:
130 if ctx is None:
129 ctx = repo[n]
131 ctx = repo[n]
130 # use ctx.node() instead ??
132 # use ctx.node() instead ??
131 ui.write(("tree %s\n" % short(ctx.changeset()[0])))
133 ui.write(("tree %s\n" % short(ctx.changeset()[0])))
132 for p in ctx.parents():
134 for p in ctx.parents():
133 ui.write(("parent %s\n" % p))
135 ui.write(("parent %s\n" % p))
134
136
135 date = ctx.date()
137 date = ctx.date()
136 description = ctx.description().replace("\0", "")
138 description = ctx.description().replace("\0", "")
137 ui.write(("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])))
139 ui.write(("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])))
138
140
139 if 'committer' in ctx.extra():
141 if 'committer' in ctx.extra():
140 ui.write(("committer %s\n" % ctx.extra()['committer']))
142 ui.write(("committer %s\n" % ctx.extra()['committer']))
141
143
142 ui.write(("revision %d\n" % ctx.rev()))
144 ui.write(("revision %d\n" % ctx.rev()))
143 ui.write(("branch %s\n" % ctx.branch()))
145 ui.write(("branch %s\n" % ctx.branch()))
144 if obsolete.isenabled(repo, obsolete.createmarkersopt):
146 if obsolete.isenabled(repo, obsolete.createmarkersopt):
145 if ctx.obsolete():
147 if ctx.obsolete():
146 ui.write(("obsolete\n"))
148 ui.write(("obsolete\n"))
147 ui.write(("phase %s\n\n" % ctx.phasestr()))
149 ui.write(("phase %s\n\n" % ctx.phasestr()))
148
150
149 if prefix != "":
151 if prefix != "":
150 ui.write("%s%s\n" % (prefix,
152 ui.write("%s%s\n" % (prefix,
151 description.replace('\n', nlprefix).strip()))
153 description.replace('\n', nlprefix).strip()))
152 else:
154 else:
153 ui.write(description + "\n")
155 ui.write(description + "\n")
154 if prefix:
156 if prefix:
155 ui.write('\0')
157 ui.write('\0')
156
158
157 @command('debug-merge-base', [], _('REV REV'))
159 @command('debug-merge-base', [], _('REV REV'))
158 def base(ui, repo, node1, node2):
160 def base(ui, repo, node1, node2):
159 """output common ancestor information"""
161 """output common ancestor information"""
160 node1 = repo.lookup(node1)
162 node1 = repo.lookup(node1)
161 node2 = repo.lookup(node2)
163 node2 = repo.lookup(node2)
162 n = repo.changelog.ancestor(node1, node2)
164 n = repo.changelog.ancestor(node1, node2)
163 ui.write(short(n) + "\n")
165 ui.write(short(n) + "\n")
164
166
165 @command('debug-cat-file',
167 @command('debug-cat-file',
166 [('s', 'stdin', None, _('stdin'))],
168 [('s', 'stdin', None, _('stdin'))],
167 _('[OPTION]... TYPE FILE'),
169 _('[OPTION]... TYPE FILE'),
168 inferrepo=True)
170 inferrepo=True)
169 def catfile(ui, repo, type=None, r=None, **opts):
171 def catfile(ui, repo, type=None, r=None, **opts):
170 """cat a specific revision"""
172 """cat a specific revision"""
171 # in stdin mode, every line except the commit is prefixed with two
173 # in stdin mode, every line except the commit is prefixed with two
172 # spaces. This way the our caller can find the commit without magic
174 # spaces. This way the our caller can find the commit without magic
173 # strings
175 # strings
174 #
176 #
175 prefix = ""
177 prefix = ""
176 if opts['stdin']:
178 if opts['stdin']:
177 try:
179 try:
178 (type, r) = raw_input().split(' ')
180 (type, r) = raw_input().split(' ')
179 prefix = " "
181 prefix = " "
180 except EOFError:
182 except EOFError:
181 return
183 return
182
184
183 else:
185 else:
184 if not type or not r:
186 if not type or not r:
185 ui.warn(_("cat-file: type or revision not supplied\n"))
187 ui.warn(_("cat-file: type or revision not supplied\n"))
186 commands.help_(ui, 'cat-file')
188 commands.help_(ui, 'cat-file')
187
189
188 while r:
190 while r:
189 if type != "commit":
191 if type != "commit":
190 ui.warn(_("aborting hg cat-file only understands commits\n"))
192 ui.warn(_("aborting hg cat-file only understands commits\n"))
191 return 1
193 return 1
192 n = repo.lookup(r)
194 n = repo.lookup(r)
193 catcommit(ui, repo, n, prefix)
195 catcommit(ui, repo, n, prefix)
194 if opts['stdin']:
196 if opts['stdin']:
195 try:
197 try:
196 (type, r) = raw_input().split(' ')
198 (type, r) = raw_input().split(' ')
197 except EOFError:
199 except EOFError:
198 break
200 break
199 else:
201 else:
200 break
202 break
201
203
202 # git rev-tree is a confusing thing. You can supply a number of
204 # git rev-tree is a confusing thing. You can supply a number of
203 # commit sha1s on the command line, and it walks the commit history
205 # commit sha1s on the command line, and it walks the commit history
204 # telling you which commits are reachable from the supplied ones via
206 # telling you which commits are reachable from the supplied ones via
205 # a bitmask based on arg position.
207 # a bitmask based on arg position.
206 # you can specify a commit to stop at by starting the sha1 with ^
208 # you can specify a commit to stop at by starting the sha1 with ^
207 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
209 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
208 def chlogwalk():
210 def chlogwalk():
209 count = len(repo)
211 count = len(repo)
210 i = count
212 i = count
211 l = [0] * 100
213 l = [0] * 100
212 chunk = 100
214 chunk = 100
213 while True:
215 while True:
214 if chunk > i:
216 if chunk > i:
215 chunk = i
217 chunk = i
216 i = 0
218 i = 0
217 else:
219 else:
218 i -= chunk
220 i -= chunk
219
221
220 for x in xrange(chunk):
222 for x in xrange(chunk):
221 if i + x >= count:
223 if i + x >= count:
222 l[chunk - x:] = [0] * (chunk - x)
224 l[chunk - x:] = [0] * (chunk - x)
223 break
225 break
224 if full is not None:
226 if full is not None:
225 if (i + x) in repo:
227 if (i + x) in repo:
226 l[x] = repo[i + x]
228 l[x] = repo[i + x]
227 l[x].changeset() # force reading
229 l[x].changeset() # force reading
228 else:
230 else:
229 if (i + x) in repo:
231 if (i + x) in repo:
230 l[x] = 1
232 l[x] = 1
231 for x in xrange(chunk - 1, -1, -1):
233 for x in xrange(chunk - 1, -1, -1):
232 if l[x] != 0:
234 if l[x] != 0:
233 yield (i + x, full is not None and l[x] or None)
235 yield (i + x, full is not None and l[x] or None)
234 if i == 0:
236 if i == 0:
235 break
237 break
236
238
237 # calculate and return the reachability bitmask for sha
239 # calculate and return the reachability bitmask for sha
238 def is_reachable(ar, reachable, sha):
240 def is_reachable(ar, reachable, sha):
239 if len(ar) == 0:
241 if len(ar) == 0:
240 return 1
242 return 1
241 mask = 0
243 mask = 0
242 for i in xrange(len(ar)):
244 for i in xrange(len(ar)):
243 if sha in reachable[i]:
245 if sha in reachable[i]:
244 mask |= 1 << i
246 mask |= 1 << i
245
247
246 return mask
248 return mask
247
249
248 reachable = []
250 reachable = []
249 stop_sha1 = []
251 stop_sha1 = []
250 want_sha1 = []
252 want_sha1 = []
251 count = 0
253 count = 0
252
254
253 # figure out which commits they are asking for and which ones they
255 # figure out which commits they are asking for and which ones they
254 # want us to stop on
256 # want us to stop on
255 for i, arg in enumerate(args):
257 for i, arg in enumerate(args):
256 if arg.startswith('^'):
258 if arg.startswith('^'):
257 s = repo.lookup(arg[1:])
259 s = repo.lookup(arg[1:])
258 stop_sha1.append(s)
260 stop_sha1.append(s)
259 want_sha1.append(s)
261 want_sha1.append(s)
260 elif arg != 'HEAD':
262 elif arg != 'HEAD':
261 want_sha1.append(repo.lookup(arg))
263 want_sha1.append(repo.lookup(arg))
262
264
263 # calculate the graph for the supplied commits
265 # calculate the graph for the supplied commits
264 for i, n in enumerate(want_sha1):
266 for i, n in enumerate(want_sha1):
265 reachable.append(set())
267 reachable.append(set())
266 visit = [n]
268 visit = [n]
267 reachable[i].add(n)
269 reachable[i].add(n)
268 while visit:
270 while visit:
269 n = visit.pop(0)
271 n = visit.pop(0)
270 if n in stop_sha1:
272 if n in stop_sha1:
271 continue
273 continue
272 for p in repo.changelog.parents(n):
274 for p in repo.changelog.parents(n):
273 if p not in reachable[i]:
275 if p not in reachable[i]:
274 reachable[i].add(p)
276 reachable[i].add(p)
275 visit.append(p)
277 visit.append(p)
276 if p in stop_sha1:
278 if p in stop_sha1:
277 continue
279 continue
278
280
279 # walk the repository looking for commits that are in our
281 # walk the repository looking for commits that are in our
280 # reachability graph
282 # reachability graph
281 for i, ctx in chlogwalk():
283 for i, ctx in chlogwalk():
282 if i not in repo:
284 if i not in repo:
283 continue
285 continue
284 n = repo.changelog.node(i)
286 n = repo.changelog.node(i)
285 mask = is_reachable(want_sha1, reachable, n)
287 mask = is_reachable(want_sha1, reachable, n)
286 if mask:
288 if mask:
287 parentstr = ""
289 parentstr = ""
288 if parents:
290 if parents:
289 pp = repo.changelog.parents(n)
291 pp = repo.changelog.parents(n)
290 if pp[0] != nullid:
292 if pp[0] != nullid:
291 parentstr += " " + short(pp[0])
293 parentstr += " " + short(pp[0])
292 if pp[1] != nullid:
294 if pp[1] != nullid:
293 parentstr += " " + short(pp[1])
295 parentstr += " " + short(pp[1])
294 if not full:
296 if not full:
295 ui.write("%s%s\n" % (short(n), parentstr))
297 ui.write("%s%s\n" % (short(n), parentstr))
296 elif full == "commit":
298 elif full == "commit":
297 ui.write("%s%s\n" % (short(n), parentstr))
299 ui.write("%s%s\n" % (short(n), parentstr))
298 catcommit(ui, repo, n, ' ', ctx)
300 catcommit(ui, repo, n, ' ', ctx)
299 else:
301 else:
300 (p1, p2) = repo.changelog.parents(n)
302 (p1, p2) = repo.changelog.parents(n)
301 (h, h1, h2) = map(short, (n, p1, p2))
303 (h, h1, h2) = map(short, (n, p1, p2))
302 (i1, i2) = map(repo.changelog.rev, (p1, p2))
304 (i1, i2) = map(repo.changelog.rev, (p1, p2))
303
305
304 date = ctx.date()[0]
306 date = ctx.date()[0]
305 ui.write("%s %s:%s" % (date, h, mask))
307 ui.write("%s %s:%s" % (date, h, mask))
306 mask = is_reachable(want_sha1, reachable, p1)
308 mask = is_reachable(want_sha1, reachable, p1)
307 if i1 != nullrev and mask > 0:
309 if i1 != nullrev and mask > 0:
308 ui.write("%s:%s " % (h1, mask)),
310 ui.write("%s:%s " % (h1, mask)),
309 mask = is_reachable(want_sha1, reachable, p2)
311 mask = is_reachable(want_sha1, reachable, p2)
310 if i2 != nullrev and mask > 0:
312 if i2 != nullrev and mask > 0:
311 ui.write("%s:%s " % (h2, mask))
313 ui.write("%s:%s " % (h2, mask))
312 ui.write("\n")
314 ui.write("\n")
313 if maxnr and count >= maxnr:
315 if maxnr and count >= maxnr:
314 break
316 break
315 count += 1
317 count += 1
316
318
317 # git rev-list tries to order things by date, and has the ability to stop
319 # git rev-list tries to order things by date, and has the ability to stop
318 # at a given commit without walking the whole repo. TODO add the stop
320 # at a given commit without walking the whole repo. TODO add the stop
319 # parameter
321 # parameter
320 @command('debug-rev-list',
322 @command('debug-rev-list',
321 [('H', 'header', None, _('header')),
323 [('H', 'header', None, _('header')),
322 ('t', 'topo-order', None, _('topo-order')),
324 ('t', 'topo-order', None, _('topo-order')),
323 ('p', 'parents', None, _('parents')),
325 ('p', 'parents', None, _('parents')),
324 ('n', 'max-count', 0, _('max-count'))],
326 ('n', 'max-count', 0, _('max-count'))],
325 ('[OPTION]... REV...'))
327 ('[OPTION]... REV...'))
326 def revlist(ui, repo, *revs, **opts):
328 def revlist(ui, repo, *revs, **opts):
327 """print revisions"""
329 """print revisions"""
328 if opts['header']:
330 if opts['header']:
329 full = "commit"
331 full = "commit"
330 else:
332 else:
331 full = None
333 full = None
332 copy = [x for x in revs]
334 copy = [x for x in revs]
333 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
335 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
334
336
335 @command('view',
337 @command('view',
336 [('l', 'limit', '',
338 [('l', 'limit', '',
337 _('limit number of changes displayed'), _('NUM'))],
339 _('limit number of changes displayed'), _('NUM'))],
338 _('[-l LIMIT] [REVRANGE]'))
340 _('[-l LIMIT] [REVRANGE]'))
339 def view(ui, repo, *etc, **opts):
341 def view(ui, repo, *etc, **opts):
340 "start interactive history viewer"
342 "start interactive history viewer"
341 os.chdir(repo.root)
343 os.chdir(repo.root)
342 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
344 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
343 if repo.filtername is None:
345 if repo.filtername is None:
344 optstr += '--hidden'
346 optstr += '--hidden'
345
347
346 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
348 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
347 ui.debug("running %s\n" % cmd)
349 ui.debug("running %s\n" % cmd)
348 ui.system(cmd, blockedtag='hgk_view')
350 ui.system(cmd, blockedtag='hgk_view')
General Comments 0
You need to be logged in to leave comments. Login now