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