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