##// END OF EJS Templates
expand paths in aliases
Alexander Solovyov -
r10793:16df09a5 stable
parent child Browse files
Show More
@@ -1,517 +1,518 b''
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
10 10 import util, commands, hg, fancyopts, extensions, hook, error
11 11 import cmdutil, encoding
12 12 import ui as _ui
13 13
14 14 def run():
15 15 "run the command in sys.argv"
16 16 sys.exit(dispatch(sys.argv[1:]))
17 17
18 18 def dispatch(args):
19 19 "run the command specified in args"
20 20 try:
21 21 u = _ui.ui()
22 22 if '--traceback' in args:
23 23 u.setconfig('ui', 'traceback', 'on')
24 24 except util.Abort, inst:
25 25 sys.stderr.write(_("abort: %s\n") % inst)
26 26 return -1
27 27 except error.ConfigError, inst:
28 28 sys.stderr.write(_("hg: %s\n") % inst)
29 29 return -1
30 30 return _runcatch(u, args)
31 31
32 32 def _runcatch(ui, args):
33 33 def catchterm(*args):
34 34 raise error.SignalInterrupt
35 35
36 36 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
37 37 num = getattr(signal, name, None)
38 38 if num:
39 39 signal.signal(num, catchterm)
40 40
41 41 try:
42 42 try:
43 43 # enter the debugger before command execution
44 44 if '--debugger' in args:
45 45 pdb.set_trace()
46 46 try:
47 47 return _dispatch(ui, args)
48 48 finally:
49 49 ui.flush()
50 50 except:
51 51 # enter the debugger when we hit an exception
52 52 if '--debugger' in args:
53 53 pdb.post_mortem(sys.exc_info()[2])
54 54 ui.traceback()
55 55 raise
56 56
57 57 # Global exception handling, alphabetically
58 58 # Mercurial-specific first, followed by built-in and library exceptions
59 59 except error.AmbiguousCommand, inst:
60 60 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
61 61 (inst.args[0], " ".join(inst.args[1])))
62 62 except error.ConfigError, inst:
63 63 ui.warn(_("hg: %s\n") % inst.args[0])
64 64 except error.LockHeld, inst:
65 65 if inst.errno == errno.ETIMEDOUT:
66 66 reason = _('timed out waiting for lock held by %s') % inst.locker
67 67 else:
68 68 reason = _('lock held by %s') % inst.locker
69 69 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
70 70 except error.LockUnavailable, inst:
71 71 ui.warn(_("abort: could not lock %s: %s\n") %
72 72 (inst.desc or inst.filename, inst.strerror))
73 73 except error.ParseError, inst:
74 74 if inst.args[0]:
75 75 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
76 76 commands.help_(ui, inst.args[0])
77 77 else:
78 78 ui.warn(_("hg: %s\n") % inst.args[1])
79 79 commands.help_(ui, 'shortlist')
80 80 except error.RepoError, inst:
81 81 ui.warn(_("abort: %s!\n") % inst)
82 82 except error.ResponseError, inst:
83 83 ui.warn(_("abort: %s") % inst.args[0])
84 84 if not isinstance(inst.args[1], basestring):
85 85 ui.warn(" %r\n" % (inst.args[1],))
86 86 elif not inst.args[1]:
87 87 ui.warn(_(" empty string\n"))
88 88 else:
89 89 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
90 90 except error.RevlogError, inst:
91 91 ui.warn(_("abort: %s!\n") % inst)
92 92 except error.SignalInterrupt:
93 93 ui.warn(_("killed!\n"))
94 94 except error.UnknownCommand, inst:
95 95 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
96 96 try:
97 97 # check if the command is in a disabled extension
98 98 # (but don't check for extensions themselves)
99 99 commands.help_(ui, inst.args[0], unknowncmd=True)
100 100 except error.UnknownCommand:
101 101 commands.help_(ui, 'shortlist')
102 102 except util.Abort, inst:
103 103 ui.warn(_("abort: %s\n") % inst)
104 104 except ImportError, inst:
105 105 m = str(inst).split()[-1]
106 106 ui.warn(_("abort: could not import module %s!\n") % m)
107 107 if m in "mpatch bdiff".split():
108 108 ui.warn(_("(did you forget to compile extensions?)\n"))
109 109 elif m in "zlib".split():
110 110 ui.warn(_("(is your Python install correct?)\n"))
111 111 except IOError, inst:
112 112 if hasattr(inst, "code"):
113 113 ui.warn(_("abort: %s\n") % inst)
114 114 elif hasattr(inst, "reason"):
115 115 try: # usually it is in the form (errno, strerror)
116 116 reason = inst.reason.args[1]
117 117 except: # it might be anything, for example a string
118 118 reason = inst.reason
119 119 ui.warn(_("abort: error: %s\n") % reason)
120 120 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
121 121 if ui.debugflag:
122 122 ui.warn(_("broken pipe\n"))
123 123 elif getattr(inst, "strerror", None):
124 124 if getattr(inst, "filename", None):
125 125 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
126 126 else:
127 127 ui.warn(_("abort: %s\n") % inst.strerror)
128 128 else:
129 129 raise
130 130 except OSError, inst:
131 131 if getattr(inst, "filename", None):
132 132 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
133 133 else:
134 134 ui.warn(_("abort: %s\n") % inst.strerror)
135 135 except KeyboardInterrupt:
136 136 try:
137 137 ui.warn(_("interrupted!\n"))
138 138 except IOError, inst:
139 139 if inst.errno == errno.EPIPE:
140 140 if ui.debugflag:
141 141 ui.warn(_("\nbroken pipe\n"))
142 142 else:
143 143 raise
144 144 except MemoryError:
145 145 ui.warn(_("abort: out of memory\n"))
146 146 except SystemExit, inst:
147 147 # Commands shouldn't sys.exit directly, but give a return code.
148 148 # Just in case catch this and and pass exit code to caller.
149 149 return inst.code
150 150 except socket.error, inst:
151 151 ui.warn(_("abort: %s\n") % inst.args[-1])
152 152 except:
153 153 ui.warn(_("** unknown exception encountered, details follow\n"))
154 154 ui.warn(_("** report bug details to "
155 155 "http://mercurial.selenic.com/bts/\n"))
156 156 ui.warn(_("** or mercurial@selenic.com\n"))
157 157 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
158 158 % util.version())
159 159 ui.warn(_("** Extensions loaded: %s\n")
160 160 % ", ".join([x[0] for x in extensions.extensions()]))
161 161 raise
162 162
163 163 return -1
164 164
165 165 def aliasargs(fn):
166 166 if hasattr(fn, 'args'):
167 167 return fn.args
168 168 return []
169 169
170 170 class cmdalias(object):
171 171 def __init__(self, name, definition, cmdtable):
172 172 self.name = name
173 173 self.definition = definition
174 174 self.args = []
175 175 self.opts = []
176 176 self.help = ''
177 177 self.norepo = True
178 178 self.badalias = False
179 179
180 180 try:
181 181 cmdutil.findcmd(self.name, cmdtable, True)
182 182 self.shadows = True
183 183 except error.UnknownCommand:
184 184 self.shadows = False
185 185
186 186 if not self.definition:
187 187 def fn(ui, *args):
188 188 ui.warn(_("no definition for alias '%s'\n") % self.name)
189 189 return 1
190 190 self.fn = fn
191 191 self.badalias = True
192 192
193 193 return
194 194
195 195 args = shlex.split(self.definition)
196 196 cmd = args.pop(0)
197 args = map(util.expandpath, args)
197 198
198 199 try:
199 200 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
200 201 if len(tableentry) > 2:
201 202 self.fn, self.opts, self.help = tableentry
202 203 else:
203 204 self.fn, self.opts = tableentry
204 205
205 206 self.args = aliasargs(self.fn) + args
206 207 if cmd not in commands.norepo.split(' '):
207 208 self.norepo = False
208 209 if self.help.startswith("hg " + cmd):
209 210 # drop prefix in old-style help lines so hg shows the alias
210 211 self.help = self.help[4 + len(cmd):]
211 212 self.__doc__ = self.fn.__doc__
212 213
213 214 except error.UnknownCommand:
214 215 def fn(ui, *args):
215 216 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
216 217 % (self.name, cmd))
217 218 try:
218 219 # check if the command is in a disabled extension
219 220 commands.help_(ui, cmd, unknowncmd=True)
220 221 except error.UnknownCommand:
221 222 pass
222 223 return 1
223 224 self.fn = fn
224 225 self.badalias = True
225 226 except error.AmbiguousCommand:
226 227 def fn(ui, *args):
227 228 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
228 229 % (self.name, cmd))
229 230 return 1
230 231 self.fn = fn
231 232 self.badalias = True
232 233
233 234 def __call__(self, ui, *args, **opts):
234 235 if self.shadows:
235 236 ui.debug("alias '%s' shadows command\n" % self.name)
236 237
237 238 return self.fn(ui, *args, **opts)
238 239
239 240 def addaliases(ui, cmdtable):
240 241 # aliases are processed after extensions have been loaded, so they
241 242 # may use extension commands. Aliases can also use other alias definitions,
242 243 # but only if they have been defined prior to the current definition.
243 244 for alias, definition in ui.configitems('alias'):
244 245 aliasdef = cmdalias(alias, definition, cmdtable)
245 246 cmdtable[alias] = (aliasdef, aliasdef.opts, aliasdef.help)
246 247 if aliasdef.norepo:
247 248 commands.norepo += ' %s' % alias
248 249
249 250 def _parse(ui, args):
250 251 options = {}
251 252 cmdoptions = {}
252 253
253 254 try:
254 255 args = fancyopts.fancyopts(args, commands.globalopts, options)
255 256 except fancyopts.getopt.GetoptError, inst:
256 257 raise error.ParseError(None, inst)
257 258
258 259 if args:
259 260 cmd, args = args[0], args[1:]
260 261 aliases, entry = cmdutil.findcmd(cmd, commands.table,
261 262 ui.config("ui", "strict"))
262 263 cmd = aliases[0]
263 264 args = aliasargs(entry[0]) + args
264 265 defaults = ui.config("defaults", cmd)
265 266 if defaults:
266 267 args = map(util.expandpath, shlex.split(defaults)) + args
267 268 c = list(entry[1])
268 269 else:
269 270 cmd = None
270 271 c = []
271 272
272 273 # combine global options into local
273 274 for o in commands.globalopts:
274 275 c.append((o[0], o[1], options[o[1]], o[3]))
275 276
276 277 try:
277 278 args = fancyopts.fancyopts(args, c, cmdoptions, True)
278 279 except fancyopts.getopt.GetoptError, inst:
279 280 raise error.ParseError(cmd, inst)
280 281
281 282 # separate global options back out
282 283 for o in commands.globalopts:
283 284 n = o[1]
284 285 options[n] = cmdoptions[n]
285 286 del cmdoptions[n]
286 287
287 288 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
288 289
289 290 def _parseconfig(ui, config):
290 291 """parse the --config options from the command line"""
291 292 for cfg in config:
292 293 try:
293 294 name, value = cfg.split('=', 1)
294 295 section, name = name.split('.', 1)
295 296 if not section or not name:
296 297 raise IndexError
297 298 ui.setconfig(section, name, value)
298 299 except (IndexError, ValueError):
299 300 raise util.Abort(_('malformed --config option: %r '
300 301 '(use --config section.name=value)') % cfg)
301 302
302 303 def _earlygetopt(aliases, args):
303 304 """Return list of values for an option (or aliases).
304 305
305 306 The values are listed in the order they appear in args.
306 307 The options and values are removed from args.
307 308 """
308 309 try:
309 310 argcount = args.index("--")
310 311 except ValueError:
311 312 argcount = len(args)
312 313 shortopts = [opt for opt in aliases if len(opt) == 2]
313 314 values = []
314 315 pos = 0
315 316 while pos < argcount:
316 317 if args[pos] in aliases:
317 318 if pos + 1 >= argcount:
318 319 # ignore and let getopt report an error if there is no value
319 320 break
320 321 del args[pos]
321 322 values.append(args.pop(pos))
322 323 argcount -= 2
323 324 elif args[pos][:2] in shortopts:
324 325 # short option can have no following space, e.g. hg log -Rfoo
325 326 values.append(args.pop(pos)[2:])
326 327 argcount -= 1
327 328 else:
328 329 pos += 1
329 330 return values
330 331
331 332 def runcommand(lui, repo, cmd, fullargs, ui, options, d):
332 333 # run pre-hook, and abort if it fails
333 334 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
334 335 if ret:
335 336 return ret
336 337 ret = _runcommand(ui, options, cmd, d)
337 338 # run post-hook, passing command result
338 339 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
339 340 result = ret)
340 341 return ret
341 342
342 343 _loaded = set()
343 344 def _dispatch(ui, args):
344 345 # read --config before doing anything else
345 346 # (e.g. to change trust settings for reading .hg/hgrc)
346 347 _parseconfig(ui, _earlygetopt(['--config'], args))
347 348
348 349 # check for cwd
349 350 cwd = _earlygetopt(['--cwd'], args)
350 351 if cwd:
351 352 os.chdir(cwd[-1])
352 353
353 354 # read the local repository .hgrc into a local ui object
354 355 path = cmdutil.findrepo(os.getcwd()) or ""
355 356 if not path:
356 357 lui = ui
357 358 else:
358 359 try:
359 360 lui = ui.copy()
360 361 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
361 362 except IOError:
362 363 pass
363 364
364 365 # now we can expand paths, even ones in .hg/hgrc
365 366 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
366 367 if rpath:
367 368 path = lui.expandpath(rpath[-1])
368 369 lui = ui.copy()
369 370 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
370 371
371 372 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
372 373 # reposetup. Programs like TortoiseHg will call _dispatch several
373 374 # times so we keep track of configured extensions in _loaded.
374 375 extensions.loadall(lui)
375 376 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
376 377
377 378 # (uisetup and extsetup are handled in extensions.loadall)
378 379
379 380 for name, module in exts:
380 381 cmdtable = getattr(module, 'cmdtable', {})
381 382 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
382 383 if overrides:
383 384 ui.warn(_("extension '%s' overrides commands: %s\n")
384 385 % (name, " ".join(overrides)))
385 386 commands.table.update(cmdtable)
386 387 _loaded.add(name)
387 388
388 389 # (reposetup is handled in hg.repository)
389 390
390 391 addaliases(lui, commands.table)
391 392
392 393 # check for fallback encoding
393 394 fallback = lui.config('ui', 'fallbackencoding')
394 395 if fallback:
395 396 encoding.fallbackencoding = fallback
396 397
397 398 fullargs = args
398 399 cmd, func, args, options, cmdoptions = _parse(lui, args)
399 400
400 401 if options["config"]:
401 402 raise util.Abort(_("Option --config may not be abbreviated!"))
402 403 if options["cwd"]:
403 404 raise util.Abort(_("Option --cwd may not be abbreviated!"))
404 405 if options["repository"]:
405 406 raise util.Abort(_(
406 407 "Option -R has to be separated from other options (e.g. not -qR) "
407 408 "and --repository may only be abbreviated as --repo!"))
408 409
409 410 if options["encoding"]:
410 411 encoding.encoding = options["encoding"]
411 412 if options["encodingmode"]:
412 413 encoding.encodingmode = options["encodingmode"]
413 414 if options["time"]:
414 415 def get_times():
415 416 t = os.times()
416 417 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
417 418 t = (t[0], t[1], t[2], t[3], time.clock())
418 419 return t
419 420 s = get_times()
420 421 def print_time():
421 422 t = get_times()
422 423 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
423 424 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
424 425 atexit.register(print_time)
425 426
426 427 if options['verbose'] or options['debug'] or options['quiet']:
427 428 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
428 429 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
429 430 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
430 431 if options['traceback']:
431 432 ui.setconfig('ui', 'traceback', 'on')
432 433 if options['noninteractive']:
433 434 ui.setconfig('ui', 'interactive', 'off')
434 435
435 436 if options['help']:
436 437 return commands.help_(ui, cmd, options['version'])
437 438 elif options['version']:
438 439 return commands.version_(ui)
439 440 elif not cmd:
440 441 return commands.help_(ui, 'shortlist')
441 442
442 443 repo = None
443 444 if cmd not in commands.norepo.split():
444 445 try:
445 446 repo = hg.repository(ui, path=path)
446 447 ui = repo.ui
447 448 if not repo.local():
448 449 raise util.Abort(_("repository '%s' is not local") % path)
449 450 ui.setconfig("bundle", "mainreporoot", repo.root)
450 451 except error.RepoError:
451 452 if cmd not in commands.optionalrepo.split():
452 453 if args and not path: # try to infer -R from command args
453 454 repos = map(cmdutil.findrepo, args)
454 455 guess = repos[0]
455 456 if guess and repos.count(guess) == len(repos):
456 457 return _dispatch(ui, ['--repository', guess] + fullargs)
457 458 if not path:
458 459 raise error.RepoError(_("There is no Mercurial repository"
459 460 " here (.hg not found)"))
460 461 raise
461 462 args.insert(0, repo)
462 463 elif rpath:
463 464 ui.warn("warning: --repository ignored\n")
464 465
465 466 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
466 467 return runcommand(lui, repo, cmd, fullargs, ui, options, d)
467 468
468 469 def _runcommand(ui, options, cmd, cmdfunc):
469 470 def checkargs():
470 471 try:
471 472 return cmdfunc()
472 473 except error.SignatureError:
473 474 raise error.ParseError(cmd, _("invalid arguments"))
474 475
475 476 if options['profile']:
476 477 format = ui.config('profiling', 'format', default='text')
477 478
478 479 if not format in ['text', 'kcachegrind']:
479 480 ui.warn(_("unrecognized profiling format '%s'"
480 481 " - Ignored\n") % format)
481 482 format = 'text'
482 483
483 484 output = ui.config('profiling', 'output')
484 485
485 486 if output:
486 487 path = ui.expandpath(output)
487 488 ostream = open(path, 'wb')
488 489 else:
489 490 ostream = sys.stderr
490 491
491 492 try:
492 493 from mercurial import lsprof
493 494 except ImportError:
494 495 raise util.Abort(_(
495 496 'lsprof not available - install from '
496 497 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
497 498 p = lsprof.Profiler()
498 499 p.enable(subcalls=True)
499 500 try:
500 501 return checkargs()
501 502 finally:
502 503 p.disable()
503 504
504 505 if format == 'kcachegrind':
505 506 import lsprofcalltree
506 507 calltree = lsprofcalltree.KCacheGrind(p)
507 508 calltree.output(ostream)
508 509 else:
509 510 # format == 'text'
510 511 stats = lsprof.Stats(p.getstats())
511 512 stats.sort()
512 513 stats.pprint(top=10, file=ostream, climit=5)
513 514
514 515 if output:
515 516 ostream.close()
516 517 else:
517 518 return checkargs()
@@ -1,61 +1,66 b''
1 1 #!/bin/sh
2 2
3 3 cat >> $HGRCPATH <<EOF
4 4 [alias]
5 5 myinit = init
6 6 cleanstatus = status -c
7 7 unknown = bargle
8 8 ambiguous = s
9 9 recursive = recursive
10 10 nodefinition =
11 11 mylog = log
12 12 lognull = log -r null
13 13 shortlog = log --template '{rev} {node|short} | {date|isodate}\n'
14 14 dln = lognull --debug
15 15 nousage = rollback
16 put = export -r 0 -o "\$PWD/%R.diff"
16 17
17 18 [defaults]
18 19 mylog = -q
19 20 lognull = -q
20 21 log = -v
21 22 EOF
22 23
23 24 echo '% basic'
24 25 hg myinit alias
25 26
26 27 echo '% unknown'
27 28 hg unknown
28 29 hg help unknown
29 30
30 31 echo '% ambiguous'
31 32 hg ambiguous
32 33 hg help ambiguous
33 34
34 35 echo '% recursive'
35 36 hg recursive
36 37 hg help recursive
37 38
38 39 echo '% no definition'
39 40 hg nodef
40 41 hg help nodef
41 42
42 43 cd alias
43 44
44 45 echo '% no usage'
45 46 hg nousage
46 47
47 48 echo foo > foo
48 49 hg ci -Amfoo
49 50
50 51 echo '% with opts'
51 52 hg cleanst
52 53
53 54 echo '% with opts and whitespace'
54 55 hg shortlog
55 56
56 57 echo '% interaction with defaults'
57 58 hg mylog
58 59 hg lognull
59 60
60 61 echo '% properly recursive'
61 62 hg dln
63
64 echo '% path expanding'
65 hg put
66 cat 0.diff
@@ -1,32 +1,45 b''
1 1 % basic
2 2 % unknown
3 3 alias 'unknown' resolves to unknown command 'bargle'
4 4 alias 'unknown' resolves to unknown command 'bargle'
5 5 % ambiguous
6 6 alias 'ambiguous' resolves to ambiguous command 's'
7 7 alias 'ambiguous' resolves to ambiguous command 's'
8 8 % recursive
9 9 alias 'recursive' resolves to unknown command 'recursive'
10 10 alias 'recursive' resolves to unknown command 'recursive'
11 11 % no definition
12 12 no definition for alias 'nodefinition'
13 13 no definition for alias 'nodefinition'
14 14 % no usage
15 15 no rollback information available
16 16 adding foo
17 17 % with opts
18 18 C foo
19 19 % with opts and whitespace
20 20 0 e63c23eaa88a | 1970-01-01 00:00 +0000
21 21 % interaction with defaults
22 22 0:e63c23eaa88a
23 23 -1:000000000000
24 24 % properly recursive
25 25 changeset: -1:0000000000000000000000000000000000000000
26 26 parent: -1:0000000000000000000000000000000000000000
27 27 parent: -1:0000000000000000000000000000000000000000
28 28 manifest: -1:0000000000000000000000000000000000000000
29 29 user:
30 30 date: Thu Jan 01 00:00:00 1970 +0000
31 31 extra: branch=default
32 32
33 % path expanding
34 # HG changeset patch
35 # User test
36 # Date 0 0
37 # Node ID e63c23eaa88ae77967edcf4ea194d31167c478b0
38 # Parent 0000000000000000000000000000000000000000
39 foo
40
41 diff -r 000000000000 -r e63c23eaa88a foo
42 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
43 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
44 @@ -0,0 +1,1 @@
45 +foo
General Comments 0
You need to be logged in to leave comments. Login now