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