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