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