##// END OF EJS Templates
hg commit: add -t and -l options...
mpm@selenic.com -
r289:266396e3 default
parent child Browse files
Show More
@@ -1,608 +1,616
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, re, sys, signal
9 9 import fancyopts, ui, hg
10 10 from demandload import *
11 11 demandload(globals(), "mdiff time hgweb traceback")
12 12
13 13 class UnknownCommand(Exception): pass
14 14
15 15 def filterfiles(filters, files):
16 16 l = [ x for x in files if x in filters ]
17 17
18 18 for t in filters:
19 19 if t and t[-1] != os.sep: t += os.sep
20 20 l += [ x for x in files if x.startswith(t) ]
21 21 return l
22 22
23 23 def relfilter(repo, files):
24 24 if os.getcwd() != repo.root:
25 25 p = os.getcwd()[len(repo.root) + 1: ]
26 26 return filterfiles([p], files)
27 27 return files
28 28
29 29 def relpath(repo, args):
30 30 if os.getcwd() != repo.root:
31 31 p = os.getcwd()[len(repo.root) + 1: ]
32 32 return [ os.path.normpath(os.path.join(p, x)) for x in args ]
33 33 return args
34 34
35 35 def dodiff(repo, files = None, node1 = None, node2 = None):
36 36 def date(c):
37 37 return time.asctime(time.gmtime(float(c[2].split(' ')[0])))
38 38
39 39 if node2:
40 40 change = repo.changelog.read(node2)
41 41 mmap2 = repo.manifest.read(change[0])
42 42 (c, a, d) = repo.diffrevs(node1, node2)
43 43 def read(f): return repo.file(f).read(mmap2[f])
44 44 date2 = date(change)
45 45 else:
46 46 date2 = time.asctime()
47 47 (c, a, d, u) = repo.diffdir(repo.root, node1)
48 48 if not node1:
49 49 node1 = repo.dirstate.parents()[0]
50 50 def read(f): return file(os.path.join(repo.root, f)).read()
51 51
52 52 change = repo.changelog.read(node1)
53 53 mmap = repo.manifest.read(change[0])
54 54 date1 = date(change)
55 55
56 56 if files:
57 57 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
58 58
59 59 for f in c:
60 60 to = None
61 61 if f in mmap:
62 62 to = repo.file(f).read(mmap[f])
63 63 tn = read(f)
64 64 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
65 65 for f in a:
66 66 to = None
67 67 tn = read(f)
68 68 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
69 69 for f in d:
70 70 to = repo.file(f).read(mmap[f])
71 71 tn = None
72 72 sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
73 73
74 74 def help(ui, cmd=None):
75 75 '''show help for a given command or all commands'''
76 76 if cmd:
77 77 try:
78 78 i = find(cmd)
79 79 ui.write("%s\n\n" % i[2])
80 80 ui.write(i[0].__doc__, "\n")
81 81 except UnknownCommand:
82 82 ui.warn("hg: unknown command %s\n" % cmd)
83 83 sys.exit(0)
84 84 else:
85 85 ui.status('hg commands:\n\n')
86 86
87 87 h = {}
88 88 for e in table.values():
89 89 f = e[0]
90 90 if f.__name__.startswith("debug"): continue
91 91 d = ""
92 92 if f.__doc__:
93 93 d = f.__doc__.splitlines(0)[0].rstrip()
94 94 h[f.__name__] = d
95 95
96 96 fns = h.keys()
97 97 fns.sort()
98 98 m = max(map(len, fns))
99 99 for f in fns:
100 100 ui.status(' %-*s %s\n' % (m, f, h[f]))
101 101
102 102 # Commands start here, listed alphabetically
103 103
104 104 def add(ui, repo, file, *files):
105 105 '''add the specified files on the next commit'''
106 106 repo.add(relpath(repo, (file,) + files))
107 107
108 108 def addremove(ui, repo):
109 109 """add all new files, delete all missing files"""
110 110 (c, a, d, u) = repo.diffdir(repo.root)
111 111 repo.add(u)
112 112 repo.remove(d)
113 113
114 114 def annotate(u, repo, file, *files, **ops):
115 115 """show changeset information per file line"""
116 116 def getnode(rev):
117 117 return hg.short(repo.changelog.node(rev))
118 118
119 119 def getname(rev):
120 120 try:
121 121 return bcache[rev]
122 122 except KeyError:
123 123 cl = repo.changelog.read(repo.changelog.node(rev))
124 124 name = cl[1]
125 125 f = name.find('@')
126 126 if f >= 0:
127 127 name = name[:f]
128 128 bcache[rev] = name
129 129 return name
130 130
131 131 bcache = {}
132 132 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
133 133 if not ops['user'] and not ops['changeset']:
134 134 ops['number'] = 1
135 135
136 136 node = repo.dirstate.parents()[0]
137 137 if ops['revision']:
138 138 node = repo.changelog.lookup(ops['revision'])
139 139 change = repo.changelog.read(node)
140 140 mmap = repo.manifest.read(change[0])
141 141 maxuserlen = 0
142 142 maxchangelen = 0
143 143 for f in relpath(repo, (file,) + files):
144 144 lines = repo.file(f).annotate(mmap[f])
145 145 pieces = []
146 146
147 147 for o, f in opmap:
148 148 if ops[o]:
149 149 l = [ f(n) for n,t in lines ]
150 150 m = max(map(len, l))
151 151 pieces.append([ "%*s" % (m, x) for x in l])
152 152
153 153 for p,l in zip(zip(*pieces), lines):
154 154 u.write(" ".join(p) + ": " + l[1])
155 155
156 156 def branch(ui, path):
157 157 '''branch from a local repository'''
158 158 # this should eventually support remote repos
159 159 os.system("cp -al %s/.hg .hg" % path)
160 160
161 161 def cat(ui, repo, file, rev = []):
162 162 """output the latest or given revision of a file"""
163 163 r = repo.file(relpath(repo, [file])[0])
164 164 n = r.tip()
165 165 if rev: n = r.lookup(rev)
166 166 sys.stdout.write(r.read(n))
167 167
168 def commit(ui, repo, *files):
168 def commit(ui, repo, *files, **opts):
169 169 """commit the specified files or all outstanding changes"""
170 repo.commit(relpath(repo, files))
170 text = opts['text']
171 if not text and opts['logfile']:
172 try: text = open(opts['logfile']).read()
173 except IOError: pass
174
175 repo.commit(relpath(repo, files), text)
171 176
172 177 def debugaddchangegroup(ui, repo):
173 178 data = sys.stdin.read()
174 179 repo.addchangegroup(data)
175 180
176 181 def debugchangegroup(ui, repo, roots):
177 182 newer = repo.newer(map(repo.lookup, roots))
178 183 for chunk in repo.changegroup(newer):
179 184 sys.stdout.write(chunk)
180 185
181 186 def debugindex(ui, file):
182 187 r = hg.revlog(open, file, "")
183 188 print " rev offset length base linkrev"+\
184 189 " p1 p2 nodeid"
185 190 for i in range(r.count()):
186 191 e = r.index[i]
187 192 print "% 6d % 9d % 7d % 6d % 7d %s.. %s.. %s.." % (
188 193 i, e[0], e[1], e[2], e[3],
189 194 hg.hex(e[4][:5]), hg.hex(e[5][:5]), hg.hex(e[6][:5]))
190 195
191 196 def debugindexdot(ui, file):
192 197 r = hg.revlog(open, file, "")
193 198 print "digraph G {"
194 199 for i in range(r.count()):
195 200 e = r.index[i]
196 201 print "\t%d -> %d" % (r.rev(e[4]), i)
197 202 if e[5] != hg.nullid:
198 203 print "\t%d -> %d" % (r.rev(e[5]), i)
199 204 print "}"
200 205
201 206 def diff(ui, repo, *files, **opts):
202 207 """diff working directory (or selected files)"""
203 208 revs = []
204 209 if opts['rev']:
205 210 revs = map(lambda x: repo.lookup(x), opts['rev'])
206 211
207 212 if len(revs) > 2:
208 213 self.ui.warn("too many revisions to diff\n")
209 214 sys.exit(1)
210 215
211 216 if files:
212 217 files = relpath(repo, files)
213 218 else:
214 219 files = relpath(repo, [""])
215 220
216 221 dodiff(repo, files, *revs)
217 222
218 223 def export(ui, repo, changeset):
219 224 """dump the changeset header and diffs for a revision"""
220 225 node = repo.lookup(changeset)
221 226 prev, other = repo.changelog.parents(node)
222 227 change = repo.changelog.read(node)
223 228 print "# HG changeset patch"
224 229 print "# User %s" % change[1]
225 230 print "# Node ID %s" % hg.hex(node)
226 231 print "# Parent %s" % hg.hex(prev)
227 232 print
228 233 if other != hg.nullid:
229 234 print "# Parent %s" % hg.hex(other)
230 235 print change[4].rstrip()
231 236 print
232 237
233 238 dodiff(repo, None, prev, node)
234 239
235 240 def forget(ui, repo, file, *files):
236 241 """don't add the specified files on the next commit"""
237 242 repo.forget(relpath(repo, (file,) + files))
238 243
239 244 def heads(ui, repo):
240 245 '''show current repository heads'''
241 246 for n in repo.changelog.heads():
242 247 i = repo.changelog.rev(n)
243 248 changes = repo.changelog.read(n)
244 249 (p1, p2) = repo.changelog.parents(n)
245 250 (h, h1, h2) = map(hg.hex, (n, p1, p2))
246 251 (i1, i2) = map(repo.changelog.rev, (p1, p2))
247 252 print "rev: %4d:%s" % (i, h)
248 253 print "parents: %4d:%s" % (i1, h1)
249 254 if i2: print " %4d:%s" % (i2, h2)
250 255 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
251 256 hg.hex(changes[0]))
252 257 print "user:", changes[1]
253 258 print "date:", time.asctime(
254 259 time.localtime(float(changes[2].split(' ')[0])))
255 260 if ui.verbose: print "files:", " ".join(changes[3])
256 261 print "description:"
257 262 print changes[4]
258 263
259 264 def history(ui, repo):
260 265 """show the changelog history"""
261 266 for i in range(repo.changelog.count() - 1, -1, -1):
262 267 n = repo.changelog.node(i)
263 268 changes = repo.changelog.read(n)
264 269 (p1, p2) = repo.changelog.parents(n)
265 270 (h, h1, h2) = map(hg.hex, (n, p1, p2))
266 271 (i1, i2) = map(repo.changelog.rev, (p1, p2))
267 272 print "rev: %4d:%s" % (i, h)
268 273 print "parents: %4d:%s" % (i1, h1)
269 274 if i2: print " %4d:%s" % (i2, h2)
270 275 print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
271 276 hg.hex(changes[0]))
272 277 print "user:", changes[1]
273 278 print "date:", time.asctime(
274 279 time.localtime(float(changes[2].split(' ')[0])))
275 280 if ui.verbose: print "files:", " ".join(changes[3])
276 281 print "description:"
277 282 print changes[4]
278 283
279 284 def init(ui):
280 285 """create a repository"""
281 286 hg.repository(ui, ".", create=1)
282 287
283 288 def log(ui, repo, f):
284 289 """show the revision history of a single file"""
285 290 f = relpath(repo, [f])[0]
286 291
287 292 r = repo.file(f)
288 293 for i in range(r.count() - 1, -1, -1):
289 294 n = r.node(i)
290 295 (p1, p2) = r.parents(n)
291 296 (h, h1, h2) = map(hg.hex, (n, p1, p2))
292 297 (i1, i2) = map(r.rev, (p1, p2))
293 298 cr = r.linkrev(n)
294 299 cn = hg.hex(repo.changelog.node(cr))
295 300 print "rev: %4d:%s" % (i, h)
296 301 print "changeset: %4d:%s" % (cr, cn)
297 302 print "parents: %4d:%s" % (i1, h1)
298 303 if i2: print " %4d:%s" % (i2, h2)
299 304 changes = repo.changelog.read(repo.changelog.node(cr))
300 305 print "user: %s" % changes[1]
301 306 print "date: %s" % time.asctime(
302 307 time.localtime(float(changes[2].split(' ')[0])))
303 308 print "description:"
304 309 print changes[4].rstrip()
305 310 print
306 311
307 312 def manifest(ui, repo, rev = []):
308 313 """output the latest or given revision of the project manifest"""
309 314 n = repo.manifest.tip()
310 315 if rev:
311 316 n = repo.manifest.lookup(rev)
312 317 m = repo.manifest.read(n)
313 318 mf = repo.manifest.readflags(n)
314 319 files = m.keys()
315 320 files.sort()
316 321
317 322 for f in files:
318 323 ui.write("%40s %3s %s\n" % (hg.hex(m[f]), mf[f] and "755" or "644", f))
319 324
320 325 def parents(ui, repo, node = None):
321 326 '''show the parents of the current working dir'''
322 327 if node:
323 328 p = repo.changelog.parents(repo.lookup(hg.bin(node)))
324 329 else:
325 330 p = repo.dirstate.parents()
326 331
327 332 for n in p:
328 333 if n != hg.nullid:
329 334 ui.write("%d:%s\n" % (repo.changelog.rev(n), hg.hex(n)))
330 335
331 336 def patch(ui, repo, patches, **opts):
332 337 """import an ordered set of patches"""
333 338 try:
334 339 import psyco
335 340 psyco.full()
336 341 except:
337 342 pass
338 343
339 344 d = opts["base"]
340 345 strip = opts["strip"]
341 346 quiet = opts["quiet"] and "> /dev/null" or ""
342 347
343 348 for patch in patches:
344 349 ui.status("applying %s\n" % patch)
345 350 pf = os.path.join(d, patch)
346 351
347 352 text = ""
348 353 for l in file(pf):
349 354 if l[:4] == "--- ": break
350 355 text += l
351 356
352 357 f = os.popen("lsdiff --strip %d %s" % (strip, pf))
353 358 files = filter(None, map(lambda x: x.rstrip(), f.read().splitlines()))
354 359 f.close()
355 360
356 361 if files:
357 362 if os.system("patch -p%d < %s %s" % (strip, pf, quiet)):
358 363 raise "patch failed!"
359 364 repo.commit(files, text)
360 365
361 366 def pull(ui, repo, source):
362 367 """pull changes from the specified source"""
363 368 paths = {}
364 369 for name, path in ui.configitems("paths"):
365 370 paths[name] = path
366 371
367 372 if source in paths: source = paths[source]
368 373
369 374 other = hg.repository(ui, source)
370 375 cg = repo.getchangegroup(other)
371 376 repo.addchangegroup(cg)
372 377
373 378 def rawcommit(ui, repo, files, **rc):
374 379 "raw commit interface"
375 380
376 381 text = rc['text']
377 382 if not text and rc['logfile']:
378 383 try: text = open(rc['logfile']).read()
379 384 except IOError: pass
380 385 if not text and not rc['logfile']:
381 386 print "missing commit text"
382 387 return 1
383 388
384 389 files = relpath(repo, files)
385 390 if rc['files']:
386 391 files += open(rc['files']).read().splitlines()
387 392
388 393 repo.rawcommit(files, text, rc['user'], rc['date'], *rc['parent'])
389 394
390 395 def recover(ui, repo):
391 396 """roll back an interrupted transaction"""
392 397 repo.recover()
393 398
394 399 def remove(ui, repo, file, *files):
395 400 """remove the specified files on the next commit"""
396 401 repo.remove(relpath(repo, (file,) + files))
397 402
398 403 def serve(ui, repo, **opts):
399 404 """export the repository via HTTP"""
400 405 hgweb.server(repo.root, opts["name"], opts["templates"],
401 406 opts["address"], opts["port"])
402 407
403 408 def status(ui, repo):
404 409 '''show changed files in the working directory
405 410
406 411 C = changed
407 412 A = added
408 413 R = removed
409 414 ? = not tracked'''
410 415
411 416 (c, a, d, u) = repo.diffdir(repo.root)
412 417 (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
413 418
414 419 for f in c: print "C", f
415 420 for f in a: print "A", f
416 421 for f in d: print "R", f
417 422 for f in u: print "?", f
418 423
419 424 def tags(ui, repo):
420 425 """list repository tags"""
421 426 repo.lookup(0) # prime the cache
422 427 i = repo.tags.items()
423 428 n = []
424 429 for e in i:
425 430 try:
426 431 l = repo.changelog.rev(e[1])
427 432 except KeyError:
428 433 l = -2
429 434 n.append((l, e))
430 435
431 436 n.sort()
432 437 n.reverse()
433 438 i = [ e[1] for e in n ]
434 439 for k, n in i:
435 440 try:
436 441 r = repo.changelog.rev(n)
437 442 except KeyError:
438 443 r = "?"
439 444 print "%-30s %5d:%s" % (k, repo.changelog.rev(n), hg.hex(n))
440 445
441 446 def tip(ui, repo):
442 447 """show the tip revision"""
443 448 n = repo.changelog.tip()
444 449 t = repo.changelog.rev(n)
445 450 ui.status("%d:%s\n" % (t, hg.hex(n)))
446 451
447 452 def undo(ui, repo):
448 453 """undo the last transaction"""
449 454 repo.undo()
450 455
451 456 def update(ui, repo, node=None, merge=False, clean=False):
452 457 '''update or merge working directory
453 458
454 459 If there are no outstanding changes in the working directory and
455 460 there is a linear relationship between the current version and the
456 461 requested version, the result is the requested version.
457 462
458 463 Otherwise the result is a merge between the contents of the
459 464 current working directory and the requested version. Files that
460 465 changed between either parent are marked as changed for the next
461 466 commit and a commit must be performed before any further updates
462 467 are allowed.
463 468 '''
464 469 node = node and repo.lookup(node) or repo.changelog.tip()
465 470 return repo.update(node, allow=merge, force=clean)
466 471
467 472 def verify(ui, repo):
468 473 """verify the integrity of the repository"""
469 474 return repo.verify()
470 475
471 476 # Command options and aliases are listed here, alphabetically
472 477
473 478 table = {
474 479 "add": (add, [], "hg add [files]"),
475 480 "addremove": (addremove, [], "hg addremove"),
476 481 "ann|annotate": (annotate,
477 482 [('r', 'revision', '', 'revision'),
478 483 ('u', 'user', None, 'show user'),
479 484 ('n', 'number', None, 'show revision number'),
480 485 ('c', 'changeset', None, 'show changeset')],
481 486 'hg annotate [-u] [-c] [-n] [-r id] [files]'),
482 487 "branch|clone": (branch, [], 'hg branch [path]'),
483 488 "cat|dump": (cat, [], 'hg cat <file> [rev]'),
484 "commit|ci": (commit, [], 'hg commit [files]'),
489 "commit|ci": (commit,
490 [('t', 'text', "", 'commit text'),
491 ('l', 'logfile', "", 'commit text file')],
492 'hg commit [files]'),
485 493 "debugaddchangegroup": (debugaddchangegroup, [], 'debugaddchangegroup'),
486 494 "debugchangegroup": (debugchangegroup, [], 'debugchangegroup [roots]'),
487 495 "debugindex": (debugindex, [], 'debugindex <file>'),
488 496 "debugindexdot": (debugindexdot, [], 'debugindexdot <file>'),
489 497 "diff": (diff, [('r', 'rev', [], 'revision')],
490 498 'hg diff [-r A] [-r B] [files]'),
491 499 "export": (export, [], "hg export <changeset>"),
492 500 "forget": (forget, [], "hg forget [files]"),
493 501 "heads": (heads, [], 'hg heads'),
494 502 "history": (history, [], 'hg history'),
495 503 "help": (help, [], 'hg help [command]'),
496 504 "init": (init, [], 'hg init'),
497 505 "log": (log, [], 'hg log <file>'),
498 506 "manifest|dumpmanifest": (manifest, [], 'hg manifest [rev]'),
499 507 "parents": (parents, [], 'hg parents [node]'),
500 508 "patch|import": (patch,
501 509 [('p', 'strip', 1, 'path strip'),
502 510 ('b', 'base', "", 'base path'),
503 511 ('q', 'quiet', "", 'silence diff')],
504 512 "hg import [options] patches"),
505 513 "pull|merge": (pull, [], 'hg pull [source]'),
506 514 "rawcommit": (rawcommit,
507 515 [('p', 'parent', [], 'parent'),
508 516 ('d', 'date', "", 'data'),
509 517 ('u', 'user', "", 'user'),
510 518 ('F', 'files', "", 'file list'),
511 519 ('t', 'text', "", 'commit text'),
512 520 ('l', 'logfile', "", 'commit text file')],
513 521 'hg rawcommit [options] [files]'),
514 522 "recover": (recover, [], "hg recover"),
515 523 "remove": (remove, [], "hg remove [files]"),
516 524 "serve": (serve, [('p', 'port', 8000, 'listen port'),
517 525 ('a', 'address', '', 'interface address'),
518 526 ('n', 'name', os.getcwd(), 'repository name'),
519 527 ('t', 'templates', "", 'template map')],
520 528 "hg serve [options]"),
521 529 "status": (status, [], 'hg status'),
522 530 "tags": (tags, [], 'hg tags'),
523 531 "tip": (tip, [], 'hg tip'),
524 532 "undo": (undo, [], 'hg undo'),
525 533 "update|up|checkout|co|resolve": (update,
526 534 [('m', 'merge', None,
527 535 'allow merging of conflicts'),
528 536 ('C', 'clean', None,
529 537 'overwrite locally modified files')],
530 538 'hg update [options] [node]'),
531 539 "verify": (verify, [], 'hg verify'),
532 540 }
533 541
534 542 norepo = "init branch help debugindex debugindexdot"
535 543
536 544 def find(cmd):
537 545 i = None
538 546 for e in table.keys():
539 547 if re.match(e + "$", cmd):
540 548 return table[e]
541 549
542 550 raise UnknownCommand(cmd)
543 551
544 552 class SignalInterrupt(Exception): pass
545 553
546 554 def catchterm(*args):
547 555 raise SignalInterrupt
548 556
549 557 def run():
550 558 sys.exit(dispatch(sys.argv[1:]))
551 559
552 560 def dispatch(args):
553 561 options = {}
554 562 opts = [('v', 'verbose', None, 'verbose'),
555 563 ('d', 'debug', None, 'debug'),
556 564 ('q', 'quiet', None, 'quiet'),
557 565 ('y', 'noninteractive', None, 'run non-interactively'),
558 566 ]
559 567
560 568 args = fancyopts.fancyopts(args, opts, options,
561 569 'hg [options] <command> [options] [files]')
562 570
563 571 if not args:
564 572 cmd = "help"
565 573 else:
566 574 cmd, args = args[0], args[1:]
567 575
568 576 u = ui.ui(options["verbose"], options["debug"], options["quiet"],
569 577 not options["noninteractive"])
570 578
571 579 try:
572 580 i = find(cmd)
573 581 except UnknownCommand:
574 582 u.warn("hg: unknown command '%s'\n" % cmd)
575 583 help(u)
576 584 sys.exit(1)
577 585
578 586 signal.signal(signal.SIGTERM, catchterm)
579 587
580 588 cmdoptions = {}
581 589 args = fancyopts.fancyopts(args, i[1], cmdoptions, i[2])
582 590
583 591 if cmd not in norepo.split():
584 592 repo = hg.repository(ui = u)
585 593 d = lambda: i[0](u, repo, *args, **cmdoptions)
586 594 else:
587 595 d = lambda: i[0](u, *args, **cmdoptions)
588 596
589 597 try:
590 598 return d()
591 599 except SignalInterrupt:
592 600 u.warn("killed!\n")
593 601 except KeyboardInterrupt:
594 602 u.warn("interrupted!\n")
595 603 except IOError, inst:
596 604 if inst.errno == 32:
597 605 u.warn("broken pipe\n")
598 606 else:
599 607 raise
600 608 except TypeError, inst:
601 609 # was this an argument error?
602 610 tb = traceback.extract_tb(sys.exc_info()[2])
603 611 if len(tb) > 2: # no
604 612 raise
605 613 raise
606 614 u.warn("%s: invalid arguments\n" % i[0].__name__)
607 615 u.warn("syntax: %s\n" % i[2])
608 616 sys.exit(-1)
General Comments 0
You need to be logged in to leave comments. Login now