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