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