##// END OF EJS Templates
hgk: declare commands using decorator
Gregory Szorc -
r21250:8d354d58 default
parent child Browse files
Show More
@@ -1,356 +1,348
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 import os
38 from mercurial import commands, util, patch, revlog, scmutil
38 from mercurial import cmdutil, commands, util, patch, revlog, scmutil
39 39 from mercurial.node import nullid, nullrev, short
40 40 from mercurial.i18n import _
41 41
42 cmdtable = {}
43 command = cmdutil.command(cmdtable)
42 44 testedwith = 'internal'
43 45
46 @command('debug-diff-tree',
47 [('p', 'patch', None, _('generate patch')),
48 ('r', 'recursive', None, _('recursive')),
49 ('P', 'pretty', None, _('pretty')),
50 ('s', 'stdin', None, _('stdin')),
51 ('C', 'copy', None, _('detect copies')),
52 ('S', 'search', "", _('search'))],
53 ('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...'))
44 54 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
45 55 """diff trees from two commits"""
46 56 def __difftree(repo, node1, node2, files=[]):
47 57 assert node2 is not None
48 58 mmap = repo[node1].manifest()
49 59 mmap2 = repo[node2].manifest()
50 60 m = scmutil.match(repo[node1], files)
51 61 modified, added, removed = repo.status(node1, node2, m)[:3]
52 62 empty = short(nullid)
53 63
54 64 for f in modified:
55 65 # TODO get file permissions
56 66 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
57 67 (short(mmap[f]), short(mmap2[f]), f, f))
58 68 for f in added:
59 69 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
60 70 (empty, short(mmap2[f]), f, f))
61 71 for f in removed:
62 72 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
63 73 (short(mmap[f]), empty, f, f))
64 74 ##
65 75
66 76 while True:
67 77 if opts['stdin']:
68 78 try:
69 79 line = raw_input().split(' ')
70 80 node1 = line[0]
71 81 if len(line) > 1:
72 82 node2 = line[1]
73 83 else:
74 84 node2 = None
75 85 except EOFError:
76 86 break
77 87 node1 = repo.lookup(node1)
78 88 if node2:
79 89 node2 = repo.lookup(node2)
80 90 else:
81 91 node2 = node1
82 92 node1 = repo.changelog.parents(node1)[0]
83 93 if opts['patch']:
84 94 if opts['pretty']:
85 95 catcommit(ui, repo, node2, "")
86 96 m = scmutil.match(repo[node1], files)
87 97 chunks = patch.diff(repo, node1, node2, match=m,
88 98 opts=patch.diffopts(ui, {'git': True}))
89 99 for chunk in chunks:
90 100 ui.write(chunk)
91 101 else:
92 102 __difftree(repo, node1, node2, files=files)
93 103 if not opts['stdin']:
94 104 break
95 105
96 106 def catcommit(ui, repo, n, prefix, ctx=None):
97 107 nlprefix = '\n' + prefix
98 108 if ctx is None:
99 109 ctx = repo[n]
100 110 # use ctx.node() instead ??
101 111 ui.write(("tree %s\n" % short(ctx.changeset()[0])))
102 112 for p in ctx.parents():
103 113 ui.write(("parent %s\n" % p))
104 114
105 115 date = ctx.date()
106 116 description = ctx.description().replace("\0", "")
107 117 lines = description.splitlines()
108 118 if lines and lines[-1].startswith('committer:'):
109 119 committer = lines[-1].split(': ')[1].rstrip()
110 120 else:
111 121 committer = ""
112 122
113 123 ui.write(("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])))
114 124 if committer != '':
115 125 ui.write(("committer %s %s %s\n" % (committer, int(date[0]), date[1])))
116 126 ui.write(("revision %d\n" % ctx.rev()))
117 127 ui.write(("branch %s\n" % ctx.branch()))
118 128 ui.write(("phase %s\n\n" % ctx.phasestr()))
119 129
120 130 if prefix != "":
121 131 ui.write("%s%s\n" % (prefix,
122 132 description.replace('\n', nlprefix).strip()))
123 133 else:
124 134 ui.write(description + "\n")
125 135 if prefix:
126 136 ui.write('\0')
127 137
138 @command('debug-merge-base', [], _('hg debug-merge-base REV REV'))
128 139 def base(ui, repo, node1, node2):
129 140 """output common ancestor information"""
130 141 node1 = repo.lookup(node1)
131 142 node2 = repo.lookup(node2)
132 143 n = repo.changelog.ancestor(node1, node2)
133 144 ui.write(short(n) + "\n")
134 145
146 @command('debug-cat-file',
147 [('s', 'stdin', None, _('stdin'))],
148 _('hg debug-cat-file [OPTION]... TYPE FILE'))
135 149 def catfile(ui, repo, type=None, r=None, **opts):
136 150 """cat a specific revision"""
137 151 # in stdin mode, every line except the commit is prefixed with two
138 152 # spaces. This way the our caller can find the commit without magic
139 153 # strings
140 154 #
141 155 prefix = ""
142 156 if opts['stdin']:
143 157 try:
144 158 (type, r) = raw_input().split(' ')
145 159 prefix = " "
146 160 except EOFError:
147 161 return
148 162
149 163 else:
150 164 if not type or not r:
151 165 ui.warn(_("cat-file: type or revision not supplied\n"))
152 166 commands.help_(ui, 'cat-file')
153 167
154 168 while r:
155 169 if type != "commit":
156 170 ui.warn(_("aborting hg cat-file only understands commits\n"))
157 171 return 1
158 172 n = repo.lookup(r)
159 173 catcommit(ui, repo, n, prefix)
160 174 if opts['stdin']:
161 175 try:
162 176 (type, r) = raw_input().split(' ')
163 177 except EOFError:
164 178 break
165 179 else:
166 180 break
167 181
168 182 # git rev-tree is a confusing thing. You can supply a number of
169 183 # commit sha1s on the command line, and it walks the commit history
170 184 # telling you which commits are reachable from the supplied ones via
171 185 # a bitmask based on arg position.
172 186 # you can specify a commit to stop at by starting the sha1 with ^
173 187 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
174 188 def chlogwalk():
175 189 count = len(repo)
176 190 i = count
177 191 l = [0] * 100
178 192 chunk = 100
179 193 while True:
180 194 if chunk > i:
181 195 chunk = i
182 196 i = 0
183 197 else:
184 198 i -= chunk
185 199
186 200 for x in xrange(chunk):
187 201 if i + x >= count:
188 202 l[chunk - x:] = [0] * (chunk - x)
189 203 break
190 204 if full is not None:
191 205 l[x] = repo[i + x]
192 206 l[x].changeset() # force reading
193 207 else:
194 208 l[x] = 1
195 209 for x in xrange(chunk - 1, -1, -1):
196 210 if l[x] != 0:
197 211 yield (i + x, full is not None and l[x] or None)
198 212 if i == 0:
199 213 break
200 214
201 215 # calculate and return the reachability bitmask for sha
202 216 def is_reachable(ar, reachable, sha):
203 217 if len(ar) == 0:
204 218 return 1
205 219 mask = 0
206 220 for i in xrange(len(ar)):
207 221 if sha in reachable[i]:
208 222 mask |= 1 << i
209 223
210 224 return mask
211 225
212 226 reachable = []
213 227 stop_sha1 = []
214 228 want_sha1 = []
215 229 count = 0
216 230
217 231 # figure out which commits they are asking for and which ones they
218 232 # want us to stop on
219 233 for i, arg in enumerate(args):
220 234 if arg.startswith('^'):
221 235 s = repo.lookup(arg[1:])
222 236 stop_sha1.append(s)
223 237 want_sha1.append(s)
224 238 elif arg != 'HEAD':
225 239 want_sha1.append(repo.lookup(arg))
226 240
227 241 # calculate the graph for the supplied commits
228 242 for i, n in enumerate(want_sha1):
229 243 reachable.append(set())
230 244 visit = [n]
231 245 reachable[i].add(n)
232 246 while visit:
233 247 n = visit.pop(0)
234 248 if n in stop_sha1:
235 249 continue
236 250 for p in repo.changelog.parents(n):
237 251 if p not in reachable[i]:
238 252 reachable[i].add(p)
239 253 visit.append(p)
240 254 if p in stop_sha1:
241 255 continue
242 256
243 257 # walk the repository looking for commits that are in our
244 258 # reachability graph
245 259 for i, ctx in chlogwalk():
246 260 n = repo.changelog.node(i)
247 261 mask = is_reachable(want_sha1, reachable, n)
248 262 if mask:
249 263 parentstr = ""
250 264 if parents:
251 265 pp = repo.changelog.parents(n)
252 266 if pp[0] != nullid:
253 267 parentstr += " " + short(pp[0])
254 268 if pp[1] != nullid:
255 269 parentstr += " " + short(pp[1])
256 270 if not full:
257 271 ui.write("%s%s\n" % (short(n), parentstr))
258 272 elif full == "commit":
259 273 ui.write("%s%s\n" % (short(n), parentstr))
260 274 catcommit(ui, repo, n, ' ', ctx)
261 275 else:
262 276 (p1, p2) = repo.changelog.parents(n)
263 277 (h, h1, h2) = map(short, (n, p1, p2))
264 278 (i1, i2) = map(repo.changelog.rev, (p1, p2))
265 279
266 280 date = ctx.date()[0]
267 281 ui.write("%s %s:%s" % (date, h, mask))
268 282 mask = is_reachable(want_sha1, reachable, p1)
269 283 if i1 != nullrev and mask > 0:
270 284 ui.write("%s:%s " % (h1, mask)),
271 285 mask = is_reachable(want_sha1, reachable, p2)
272 286 if i2 != nullrev and mask > 0:
273 287 ui.write("%s:%s " % (h2, mask))
274 288 ui.write("\n")
275 289 if maxnr and count >= maxnr:
276 290 break
277 291 count += 1
278 292
293 @command('debug-rev-parse',
294 [('', 'default', '', _('ignored'))],
295 _('hg debug-rev-parse REV'))
279 296 def revparse(ui, repo, *revs, **opts):
280 297 """parse given revisions"""
281 298 def revstr(rev):
282 299 if rev == 'HEAD':
283 300 rev = 'tip'
284 301 return revlog.hex(repo.lookup(rev))
285 302
286 303 for r in revs:
287 304 revrange = r.split(':', 1)
288 305 ui.write('%s\n' % revstr(revrange[0]))
289 306 if len(revrange) == 2:
290 307 ui.write('^%s\n' % revstr(revrange[1]))
291 308
292 309 # git rev-list tries to order things by date, and has the ability to stop
293 310 # at a given commit without walking the whole repo. TODO add the stop
294 311 # parameter
312 @command('debug-rev-list',
313 [('H', 'header', None, _('header')),
314 ('t', 'topo-order', None, _('topo-order')),
315 ('p', 'parents', None, _('parents')),
316 ('n', 'max-count', 0, _('max-count'))],
317 ('hg debug-rev-list [OPTION]... REV...'))
295 318 def revlist(ui, repo, *revs, **opts):
296 319 """print revisions"""
297 320 if opts['header']:
298 321 full = "commit"
299 322 else:
300 323 full = None
301 324 copy = [x for x in revs]
302 325 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
303 326
327 @command('debug-config', [], _('hg debug-config'))
304 328 def config(ui, repo, **opts):
305 329 """print extension options"""
306 330 def writeopt(name, value):
307 331 ui.write(('k=%s\nv=%s\n' % (name, value)))
308 332
309 333 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
310 334
311 335
336 @command('view',
337 [('l', 'limit', '',
338 _('limit number of changes displayed'), _('NUM'))],
339 _('hg view [-l LIMIT] [REVRANGE]'))
312 340 def view(ui, repo, *etc, **opts):
313 341 "start interactive history viewer"
314 342 os.chdir(repo.root)
315 343 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
316 344 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
317 345 ui.debug("running %s\n" % cmd)
318 346 util.system(cmd)
319 347
320 cmdtable = {
321 "^view":
322 (view,
323 [('l', 'limit', '',
324 _('limit number of changes displayed'), _('NUM'))],
325 _('hg view [-l LIMIT] [REVRANGE]')),
326 "debug-diff-tree":
327 (difftree,
328 [('p', 'patch', None, _('generate patch')),
329 ('r', 'recursive', None, _('recursive')),
330 ('P', 'pretty', None, _('pretty')),
331 ('s', 'stdin', None, _('stdin')),
332 ('C', 'copy', None, _('detect copies')),
333 ('S', 'search', "", _('search'))],
334 _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
335 "debug-cat-file":
336 (catfile,
337 [('s', 'stdin', None, _('stdin'))],
338 _('hg debug-cat-file [OPTION]... TYPE FILE')),
339 "debug-config":
340 (config, [], _('hg debug-config')),
341 "debug-merge-base":
342 (base, [], _('hg debug-merge-base REV REV')),
343 "debug-rev-parse":
344 (revparse,
345 [('', 'default', '', _('ignored'))],
346 _('hg debug-rev-parse REV')),
347 "debug-rev-list":
348 (revlist,
349 [('H', 'header', None, _('header')),
350 ('t', 'topo-order', None, _('topo-order')),
351 ('p', 'parents', None, _('parents')),
352 ('n', 'max-count', 0, _('max-count'))],
353 _('hg debug-rev-list [OPTION]... REV...')),
354 }
355
356 348 commands.inferrepo += " debug-diff-tree debug-cat-file"
General Comments 0
You need to be logged in to leave comments. Login now