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