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