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