##// END OF EJS Templates
Clean up date and timezone handling....
Bryan O'Sullivan -
r1321:b47f96a1 default
parent child Browse files
Show More
@@ -1,53 +1,56 b''
1 1 # changelog.py - changelog class 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 import os, time
9 8 from revlog import *
9 from demandload import demandload
10 demandload(globals(), "os time util")
10 11
11 12 class changelog(revlog):
12 13 def __init__(self, opener):
13 14 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
14 15
15 16 def extract(self, text):
16 17 if not text:
17 18 return (nullid, "", "0", [], "")
18 19 last = text.index("\n\n")
19 20 desc = text[last + 2:]
20 21 l = text[:last].splitlines()
21 22 manifest = bin(l[0])
22 23 user = l[1]
23 date = l[2]
24 if " " not in date:
25 date += " 0" # some tools used -d without a timezone
24 date = l[2].split(' ')
25 time = int(date.pop(0))
26 try:
27 # various tools did silly things with the time zone field.
28 timezone = int(date[0])
29 except:
30 timezone = 0
26 31 files = l[3:]
27 return (manifest, user, date, files, desc)
32 return (manifest, user, (time, timezone), files, desc)
28 33
29 34 def read(self, node):
30 35 return self.extract(self.revision(node))
31 36
32 37 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
33 38 user=None, date=None):
34 39 if date:
35 40 # validate explicit (probably user-specified) date and
36 41 # time zone offset. values must fit in signed 32 bits for
37 42 # current 32-bit linux runtimes.
38 43 try:
39 44 when, offset = map(int, date.split(' '))
40 45 except ValueError:
41 46 raise ValueError('invalid date: %r' % date)
42 47 if abs(when) > 0x7fffffff:
43 48 raise ValueError('date exceeds 32 bits: %d' % when)
44 49 if abs(offset) >= 43200:
45 50 raise ValueError('impossible time zone offset: %d' % offset)
46 51 else:
47 if time.daylight: offset = time.altzone
48 else: offset = time.timezone
49 date = "%d %d" % (time.time(), offset)
52 date = "%d %d" % util.makedate()
50 53 list.sort()
51 54 l = [hex(manifest), user, date] + list + ["", desc]
52 55 text = "\n".join(l)
53 56 return self.addrevision(text, transaction, self.count(), p1, p2)
@@ -1,2195 +1,2195 b''
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 from demandload import demandload
9 9 from node import *
10 10 demandload(globals(), "os re sys signal shutil imp urllib pdb")
11 11 demandload(globals(), "fancyopts ui hg util lock revlog")
12 12 demandload(globals(), "fnmatch hgweb mdiff random signal time traceback")
13 13 demandload(globals(), "errno socket version struct atexit sets bz2")
14 14
15 15 class UnknownCommand(Exception):
16 16 """Exception raised if command is not in the command table."""
17 17
18 18 def filterfiles(filters, files):
19 19 l = [x for x in files if x in filters]
20 20
21 21 for t in filters:
22 22 if t and t[-1] != "/":
23 23 t += "/"
24 24 l += [x for x in files if x.startswith(t)]
25 25 return l
26 26
27 27 def relpath(repo, args):
28 28 cwd = repo.getcwd()
29 29 if cwd:
30 30 return [util.normpath(os.path.join(cwd, x)) for x in args]
31 31 return args
32 32
33 33 def matchpats(repo, cwd, pats=[], opts={}, head=''):
34 34 return util.matcher(repo.root, cwd, pats or ['.'], opts.get('include'),
35 35 opts.get('exclude'), head)
36 36
37 37 def makewalk(repo, pats, opts, head=''):
38 38 cwd = repo.getcwd()
39 39 files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head)
40 40 exact = dict(zip(files, files))
41 41 def walk():
42 42 for src, fn in repo.walk(files=files, match=matchfn):
43 43 yield src, fn, util.pathto(cwd, fn), fn in exact
44 44 return files, matchfn, walk()
45 45
46 46 def walk(repo, pats, opts, head=''):
47 47 files, matchfn, results = makewalk(repo, pats, opts, head)
48 48 for r in results:
49 49 yield r
50 50
51 51 def walkchangerevs(ui, repo, cwd, pats, opts):
52 52 '''Iterate over files and the revs they changed in.
53 53
54 54 Callers most commonly need to iterate backwards over the history
55 55 it is interested in. Doing so has awful (quadratic-looking)
56 56 performance, so we use iterators in a "windowed" way.
57 57
58 58 We walk a window of revisions in the desired order. Within the
59 59 window, we first walk forwards to gather data, then in the desired
60 60 order (usually backwards) to display it.
61 61
62 62 This function returns an (iterator, getchange) pair. The
63 63 getchange function returns the changelog entry for a numeric
64 64 revision. The iterator yields 3-tuples. They will be of one of
65 65 the following forms:
66 66
67 67 "window", incrementing, lastrev: stepping through a window,
68 68 positive if walking forwards through revs, last rev in the
69 69 sequence iterated over - use to reset state for the current window
70 70
71 71 "add", rev, fns: out-of-order traversal of the given file names
72 72 fns, which changed during revision rev - use to gather data for
73 73 possible display
74 74
75 75 "iter", rev, None: in-order traversal of the revs earlier iterated
76 76 over with "add" - use to display data'''
77 77 cwd = repo.getcwd()
78 78 if not pats and cwd:
79 79 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
80 80 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
81 81 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
82 82 pats, opts)
83 83 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
84 84 wanted = {}
85 85 slowpath = anypats
86 86 window = 300
87 87 fncache = {}
88 88
89 89 chcache = {}
90 90 def getchange(rev):
91 91 ch = chcache.get(rev)
92 92 if ch is None:
93 93 chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev)))
94 94 return ch
95 95
96 96 if not slowpath and not files:
97 97 # No files, no patterns. Display all revs.
98 98 wanted = dict(zip(revs, revs))
99 99 if not slowpath:
100 100 # Only files, no patterns. Check the history of each file.
101 101 def filerevgen(filelog):
102 102 for i in xrange(filelog.count() - 1, -1, -window):
103 103 revs = []
104 104 for j in xrange(max(0, i - window), i + 1):
105 105 revs.append(filelog.linkrev(filelog.node(j)))
106 106 revs.reverse()
107 107 for rev in revs:
108 108 yield rev
109 109
110 110 minrev, maxrev = min(revs), max(revs)
111 111 for file in files:
112 112 filelog = repo.file(file)
113 113 # A zero count may be a directory or deleted file, so
114 114 # try to find matching entries on the slow path.
115 115 if filelog.count() == 0:
116 116 slowpath = True
117 117 break
118 118 for rev in filerevgen(filelog):
119 119 if rev <= maxrev:
120 120 if rev < minrev:
121 121 break
122 122 fncache.setdefault(rev, [])
123 123 fncache[rev].append(file)
124 124 wanted[rev] = 1
125 125 if slowpath:
126 126 # The slow path checks files modified in every changeset.
127 127 def changerevgen():
128 128 for i in xrange(repo.changelog.count() - 1, -1, -window):
129 129 for j in xrange(max(0, i - window), i + 1):
130 130 yield j, getchange(j)[3]
131 131
132 132 for rev, changefiles in changerevgen():
133 133 matches = filter(matchfn, changefiles)
134 134 if matches:
135 135 fncache[rev] = matches
136 136 wanted[rev] = 1
137 137
138 138 def iterate():
139 139 for i in xrange(0, len(revs), window):
140 140 yield 'window', revs[0] < revs[-1], revs[-1]
141 141 nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
142 142 if rev in wanted]
143 143 srevs = list(nrevs)
144 144 srevs.sort()
145 145 for rev in srevs:
146 146 fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3])
147 147 yield 'add', rev, fns
148 148 for rev in nrevs:
149 149 yield 'iter', rev, None
150 150 return iterate(), getchange
151 151
152 152 revrangesep = ':'
153 153
154 154 def revrange(ui, repo, revs, revlog=None):
155 155 """Yield revision as strings from a list of revision specifications."""
156 156 if revlog is None:
157 157 revlog = repo.changelog
158 158 revcount = revlog.count()
159 159 def fix(val, defval):
160 160 if not val:
161 161 return defval
162 162 try:
163 163 num = int(val)
164 164 if str(num) != val:
165 165 raise ValueError
166 166 if num < 0: num += revcount
167 167 if num < 0: num = 0
168 168 elif num >= revcount:
169 169 raise ValueError
170 170 except ValueError:
171 171 try:
172 172 num = repo.changelog.rev(repo.lookup(val))
173 173 except KeyError:
174 174 try:
175 175 num = revlog.rev(revlog.lookup(val))
176 176 except KeyError:
177 177 raise util.Abort('invalid revision identifier %s', val)
178 178 return num
179 179 seen = {}
180 180 for spec in revs:
181 181 if spec.find(revrangesep) >= 0:
182 182 start, end = spec.split(revrangesep, 1)
183 183 start = fix(start, 0)
184 184 end = fix(end, revcount - 1)
185 185 step = start > end and -1 or 1
186 186 for rev in xrange(start, end+step, step):
187 187 if rev in seen: continue
188 188 seen[rev] = 1
189 189 yield str(rev)
190 190 else:
191 191 rev = fix(spec, None)
192 192 if rev in seen: continue
193 193 seen[rev] = 1
194 194 yield str(rev)
195 195
196 196 def make_filename(repo, r, pat, node=None,
197 197 total=None, seqno=None, revwidth=None, pathname=None):
198 198 node_expander = {
199 199 'H': lambda: hex(node),
200 200 'R': lambda: str(r.rev(node)),
201 201 'h': lambda: short(node),
202 202 }
203 203 expander = {
204 204 '%': lambda: '%',
205 205 'b': lambda: os.path.basename(repo.root),
206 206 }
207 207
208 208 try:
209 209 if node:
210 210 expander.update(node_expander)
211 211 if node and revwidth is not None:
212 212 expander['r'] = lambda: str(r.rev(node)).zfill(revwidth)
213 213 if total is not None:
214 214 expander['N'] = lambda: str(total)
215 215 if seqno is not None:
216 216 expander['n'] = lambda: str(seqno)
217 217 if total is not None and seqno is not None:
218 218 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
219 219 if pathname is not None:
220 220 expander['s'] = lambda: os.path.basename(pathname)
221 221 expander['d'] = lambda: os.path.dirname(pathname) or '.'
222 222 expander['p'] = lambda: pathname
223 223
224 224 newname = []
225 225 patlen = len(pat)
226 226 i = 0
227 227 while i < patlen:
228 228 c = pat[i]
229 229 if c == '%':
230 230 i += 1
231 231 c = pat[i]
232 232 c = expander[c]()
233 233 newname.append(c)
234 234 i += 1
235 235 return ''.join(newname)
236 236 except KeyError, inst:
237 237 raise util.Abort("invalid format spec '%%%s' in output file name",
238 238 inst.args[0])
239 239
240 240 def make_file(repo, r, pat, node=None,
241 241 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
242 242 if not pat or pat == '-':
243 243 return 'w' in mode and sys.stdout or sys.stdin
244 244 if hasattr(pat, 'write') and 'w' in mode:
245 245 return pat
246 246 if hasattr(pat, 'read') and 'r' in mode:
247 247 return pat
248 248 return open(make_filename(repo, r, pat, node, total, seqno, revwidth,
249 249 pathname),
250 250 mode)
251 251
252 252 def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
253 253 changes=None, text=False):
254 254 if not changes:
255 255 (c, a, d, u) = repo.changes(node1, node2, files, match=match)
256 256 else:
257 257 (c, a, d, u) = changes
258 258 if files:
259 259 c, a, d = map(lambda x: filterfiles(files, x), (c, a, d))
260 260
261 261 if not c and not a and not d:
262 262 return
263 263
264 264 if node2:
265 265 change = repo.changelog.read(node2)
266 266 mmap2 = repo.manifest.read(change[0])
267 date2 = util.datestr(change)
267 date2 = util.datestr(change[2])
268 268 def read(f):
269 269 return repo.file(f).read(mmap2[f])
270 270 else:
271 271 date2 = util.datestr()
272 272 if not node1:
273 273 node1 = repo.dirstate.parents()[0]
274 274 def read(f):
275 275 return repo.wfile(f).read()
276 276
277 277 if ui.quiet:
278 278 r = None
279 279 else:
280 280 hexfunc = ui.verbose and hex or short
281 281 r = [hexfunc(node) for node in [node1, node2] if node]
282 282
283 283 change = repo.changelog.read(node1)
284 284 mmap = repo.manifest.read(change[0])
285 date1 = util.datestr(change)
285 date1 = util.datestr(change[2])
286 286
287 287 for f in c:
288 288 to = None
289 289 if f in mmap:
290 290 to = repo.file(f).read(mmap[f])
291 291 tn = read(f)
292 292 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
293 293 for f in a:
294 294 to = None
295 295 tn = read(f)
296 296 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
297 297 for f in d:
298 298 to = repo.file(f).read(mmap[f])
299 299 tn = None
300 300 fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text))
301 301
302 302 def trimuser(ui, name, rev, revcache):
303 303 """trim the name of the user who committed a change"""
304 304 user = revcache.get(rev)
305 305 if user is None:
306 306 user = revcache[rev] = ui.shortuser(name)
307 307 return user
308 308
309 309 def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None):
310 310 """show a single changeset or file revision"""
311 311 log = repo.changelog
312 312 if changenode is None:
313 313 changenode = log.node(rev)
314 314 elif not rev:
315 315 rev = log.rev(changenode)
316 316
317 317 if ui.quiet:
318 318 ui.write("%d:%s\n" % (rev, short(changenode)))
319 319 return
320 320
321 321 changes = log.read(changenode)
322 date = util.datestr(changes)
322 date = util.datestr(changes[2])
323 323
324 324 parents = [(log.rev(p), ui.verbose and hex(p) or short(p))
325 325 for p in log.parents(changenode)
326 326 if ui.debugflag or p != nullid]
327 327 if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1:
328 328 parents = []
329 329
330 330 if ui.verbose:
331 331 ui.write("changeset: %d:%s\n" % (rev, hex(changenode)))
332 332 else:
333 333 ui.write("changeset: %d:%s\n" % (rev, short(changenode)))
334 334
335 335 for tag in repo.nodetags(changenode):
336 336 ui.status("tag: %s\n" % tag)
337 337 for parent in parents:
338 338 ui.write("parent: %d:%s\n" % parent)
339 339
340 340 if brinfo and changenode in brinfo:
341 341 br = brinfo[changenode]
342 342 ui.write("branch: %s\n" % " ".join(br))
343 343
344 344 ui.debug("manifest: %d:%s\n" % (repo.manifest.rev(changes[0]),
345 345 hex(changes[0])))
346 346 ui.status("user: %s\n" % changes[1])
347 347 ui.status("date: %s\n" % date)
348 348
349 349 if ui.debugflag:
350 350 files = repo.changes(log.parents(changenode)[0], changenode)
351 351 for key, value in zip(["files:", "files+:", "files-:"], files):
352 352 if value:
353 353 ui.note("%-12s %s\n" % (key, " ".join(value)))
354 354 else:
355 355 ui.note("files: %s\n" % " ".join(changes[3]))
356 356
357 357 description = changes[4].strip()
358 358 if description:
359 359 if ui.verbose:
360 360 ui.status("description:\n")
361 361 ui.status(description)
362 362 ui.status("\n\n")
363 363 else:
364 364 ui.status("summary: %s\n" % description.splitlines()[0])
365 365 ui.status("\n")
366 366
367 367 def show_version(ui):
368 368 """output version and copyright information"""
369 369 ui.write("Mercurial Distributed SCM (version %s)\n"
370 370 % version.get_version())
371 371 ui.status(
372 372 "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n"
373 373 "This is free software; see the source for copying conditions. "
374 374 "There is NO\nwarranty; "
375 375 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
376 376 )
377 377
378 378 def help_(ui, cmd=None, with_version=False):
379 379 """show help for a given command or all commands"""
380 380 option_lists = []
381 381 if cmd and cmd != 'shortlist':
382 382 if with_version:
383 383 show_version(ui)
384 384 ui.write('\n')
385 385 key, i = find(cmd)
386 386 # synopsis
387 387 ui.write("%s\n\n" % i[2])
388 388
389 389 # description
390 390 doc = i[0].__doc__
391 391 if ui.quiet:
392 392 doc = doc.splitlines(0)[0]
393 393 ui.write("%s\n" % doc.rstrip())
394 394
395 395 if not ui.quiet:
396 396 # aliases
397 397 aliases = ', '.join(key.split('|')[1:])
398 398 if aliases:
399 399 ui.write("\naliases: %s\n" % aliases)
400 400
401 401 # options
402 402 if i[1]:
403 403 option_lists.append(("options", i[1]))
404 404
405 405 else:
406 406 # program name
407 407 if ui.verbose or with_version:
408 408 show_version(ui)
409 409 else:
410 410 ui.status("Mercurial Distributed SCM\n")
411 411 ui.status('\n')
412 412
413 413 # list of commands
414 414 if cmd == "shortlist":
415 415 ui.status('basic commands (use "hg help" '
416 416 'for the full list or option "-v" for details):\n\n')
417 417 elif ui.verbose:
418 418 ui.status('list of commands:\n\n')
419 419 else:
420 420 ui.status('list of commands (use "hg help -v" '
421 421 'to show aliases and global options):\n\n')
422 422
423 423 h = {}
424 424 cmds = {}
425 425 for c, e in table.items():
426 426 f = c.split("|")[0]
427 427 if cmd == "shortlist" and not f.startswith("^"):
428 428 continue
429 429 f = f.lstrip("^")
430 430 if not ui.debugflag and f.startswith("debug"):
431 431 continue
432 432 d = ""
433 433 if e[0].__doc__:
434 434 d = e[0].__doc__.splitlines(0)[0].rstrip()
435 435 h[f] = d
436 436 cmds[f]=c.lstrip("^")
437 437
438 438 fns = h.keys()
439 439 fns.sort()
440 440 m = max(map(len, fns))
441 441 for f in fns:
442 442 if ui.verbose:
443 443 commands = cmds[f].replace("|",", ")
444 444 ui.write(" %s:\n %s\n"%(commands,h[f]))
445 445 else:
446 446 ui.write(' %-*s %s\n' % (m, f, h[f]))
447 447
448 448 # global options
449 449 if ui.verbose:
450 450 option_lists.append(("global options", globalopts))
451 451
452 452 # list all option lists
453 453 opt_output = []
454 454 for title, options in option_lists:
455 455 opt_output.append(("\n%s:\n" % title, None))
456 456 for shortopt, longopt, default, desc in options:
457 457 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
458 458 longopt and " --%s" % longopt),
459 459 "%s%s" % (desc,
460 460 default and " (default: %s)" % default
461 461 or "")))
462 462
463 463 if opt_output:
464 464 opts_len = max([len(line[0]) for line in opt_output if line[1]])
465 465 for first, second in opt_output:
466 466 if second:
467 467 ui.write(" %-*s %s\n" % (opts_len, first, second))
468 468 else:
469 469 ui.write("%s\n" % first)
470 470
471 471 # Commands start here, listed alphabetically
472 472
473 473 def add(ui, repo, *pats, **opts):
474 474 '''add the specified files on the next commit'''
475 475 names = []
476 476 for src, abs, rel, exact in walk(repo, pats, opts):
477 477 if exact:
478 478 if ui.verbose: ui.status('adding %s\n' % rel)
479 479 names.append(abs)
480 480 elif repo.dirstate.state(abs) == '?':
481 481 ui.status('adding %s\n' % rel)
482 482 names.append(abs)
483 483 repo.add(names)
484 484
485 485 def addremove(ui, repo, *pats, **opts):
486 486 """add all new files, delete all missing files"""
487 487 add, remove = [], []
488 488 for src, abs, rel, exact in walk(repo, pats, opts):
489 489 if src == 'f' and repo.dirstate.state(abs) == '?':
490 490 add.append(abs)
491 491 if ui.verbose or not exact:
492 492 ui.status('adding ', rel, '\n')
493 493 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
494 494 remove.append(abs)
495 495 if ui.verbose or not exact:
496 496 ui.status('removing ', rel, '\n')
497 497 repo.add(add)
498 498 repo.remove(remove)
499 499
500 500 def annotate(ui, repo, *pats, **opts):
501 501 """show changeset information per file line"""
502 502 def getnode(rev):
503 503 return short(repo.changelog.node(rev))
504 504
505 505 ucache = {}
506 506 def getname(rev):
507 507 cl = repo.changelog.read(repo.changelog.node(rev))
508 508 return trimuser(ui, cl[1], rev, ucache)
509 509
510 510 if not pats:
511 511 raise util.Abort('at least one file name or pattern required')
512 512
513 513 opmap = [['user', getname], ['number', str], ['changeset', getnode]]
514 514 if not opts['user'] and not opts['changeset']:
515 515 opts['number'] = 1
516 516
517 517 if opts['rev']:
518 518 node = repo.changelog.lookup(opts['rev'])
519 519 else:
520 520 node = repo.dirstate.parents()[0]
521 521 change = repo.changelog.read(node)
522 522 mmap = repo.manifest.read(change[0])
523 523
524 524 for src, abs, rel, exact in walk(repo, pats, opts):
525 525 if abs not in mmap:
526 526 ui.warn("warning: %s is not in the repository!\n" % rel)
527 527 continue
528 528
529 529 f = repo.file(abs)
530 530 if not opts['text'] and util.binary(f.read(mmap[abs])):
531 531 ui.write("%s: binary file\n" % rel)
532 532 continue
533 533
534 534 lines = f.annotate(mmap[abs])
535 535 pieces = []
536 536
537 537 for o, f in opmap:
538 538 if opts[o]:
539 539 l = [f(n) for n, dummy in lines]
540 540 if l:
541 541 m = max(map(len, l))
542 542 pieces.append(["%*s" % (m, x) for x in l])
543 543
544 544 if pieces:
545 545 for p, l in zip(zip(*pieces), lines):
546 546 ui.write("%s: %s" % (" ".join(p), l[1]))
547 547
548 548 def bundle(ui, repo, fname, dest="default-push", **opts):
549 549 """create a changegroup file"""
550 550 f = open(fname, "wb")
551 551 dest = ui.expandpath(dest)
552 552 other = hg.repository(ui, dest)
553 553 o = repo.findoutgoing(other)
554 554 cg = repo.changegroup(o)
555 555
556 556 try:
557 557 f.write("HG10")
558 558 z = bz2.BZ2Compressor(9)
559 559 while 1:
560 560 chunk = cg.read(4096)
561 561 if not chunk:
562 562 break
563 563 f.write(z.compress(chunk))
564 564 f.write(z.flush())
565 565 except:
566 566 os.unlink(fname)
567 567
568 568 def cat(ui, repo, file1, *pats, **opts):
569 569 """output the latest or given revisions of files"""
570 570 mf = {}
571 571 if opts['rev']:
572 572 change = repo.changelog.read(repo.lookup(opts['rev']))
573 573 mf = repo.manifest.read(change[0])
574 574 for src, abs, rel, exact in walk(repo, (file1,) + pats, opts):
575 575 r = repo.file(abs)
576 576 if opts['rev']:
577 577 try:
578 578 n = mf[abs]
579 579 except (hg.RepoError, KeyError):
580 580 try:
581 581 n = r.lookup(rev)
582 582 except KeyError, inst:
583 583 raise util.Abort('cannot find file %s in rev %s', rel, rev)
584 584 else:
585 585 n = r.tip()
586 586 fp = make_file(repo, r, opts['output'], node=n, pathname=abs)
587 587 fp.write(r.read(n))
588 588
589 589 def clone(ui, source, dest=None, **opts):
590 590 """make a copy of an existing repository"""
591 591 if dest is None:
592 592 dest = os.path.basename(os.path.normpath(source))
593 593
594 594 if os.path.exists(dest):
595 595 raise util.Abort("destination '%s' already exists", dest)
596 596
597 597 dest = os.path.realpath(dest)
598 598
599 599 class Dircleanup:
600 600 def __init__(self, dir_):
601 601 self.rmtree = shutil.rmtree
602 602 self.dir_ = dir_
603 603 os.mkdir(dir_)
604 604 def close(self):
605 605 self.dir_ = None
606 606 def __del__(self):
607 607 if self.dir_:
608 608 self.rmtree(self.dir_, True)
609 609
610 610 if opts['ssh']:
611 611 ui.setconfig("ui", "ssh", opts['ssh'])
612 612 if opts['remotecmd']:
613 613 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
614 614
615 615 if not os.path.exists(source):
616 616 source = ui.expandpath(source)
617 617
618 618 d = Dircleanup(dest)
619 619 abspath = source
620 620 other = hg.repository(ui, source)
621 621
622 622 copy = False
623 623 if other.dev() != -1:
624 624 abspath = os.path.abspath(source)
625 625 if not opts['pull']:
626 626 copy = True
627 627
628 628 if copy:
629 629 try:
630 630 # we use a lock here because if we race with commit, we
631 631 # can end up with extra data in the cloned revlogs that's
632 632 # not pointed to by changesets, thus causing verify to
633 633 # fail
634 634 l1 = lock.lock(os.path.join(source, ".hg", "lock"))
635 635 except OSError:
636 636 copy = False
637 637
638 638 if copy:
639 639 # we lock here to avoid premature writing to the target
640 640 os.mkdir(os.path.join(dest, ".hg"))
641 641 l2 = lock.lock(os.path.join(dest, ".hg", "lock"))
642 642
643 643 files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i"
644 644 for f in files.split():
645 645 src = os.path.join(source, ".hg", f)
646 646 dst = os.path.join(dest, ".hg", f)
647 647 util.copyfiles(src, dst)
648 648
649 649 repo = hg.repository(ui, dest)
650 650
651 651 else:
652 652 repo = hg.repository(ui, dest, create=1)
653 653 repo.pull(other)
654 654
655 655 f = repo.opener("hgrc", "w")
656 656 f.write("[paths]\n")
657 657 f.write("default = %s\n" % abspath)
658 658
659 659 if not opts['noupdate']:
660 660 update(ui, repo)
661 661
662 662 d.close()
663 663
664 664 def commit(ui, repo, *pats, **opts):
665 665 """commit the specified files or all outstanding changes"""
666 666 if opts['text']:
667 667 ui.warn("Warning: -t and --text is deprecated,"
668 668 " please use -m or --message instead.\n")
669 669 message = opts['message'] or opts['text']
670 670 logfile = opts['logfile']
671 671
672 672 if message and logfile:
673 673 raise util.Abort('options --message and --logfile are mutually '
674 674 'exclusive')
675 675 if not message and logfile:
676 676 try:
677 677 if logfile == '-':
678 678 message = sys.stdin.read()
679 679 else:
680 680 message = open(logfile).read()
681 681 except IOError, inst:
682 682 raise util.Abort("can't read commit message '%s': %s" %
683 683 (logfile, inst.strerror))
684 684
685 685 if opts['addremove']:
686 686 addremove(ui, repo, *pats, **opts)
687 687 cwd = repo.getcwd()
688 688 if not pats and cwd:
689 689 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
690 690 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
691 691 fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '',
692 692 pats, opts)
693 693 if pats:
694 694 c, a, d, u = repo.changes(files=fns, match=match)
695 695 files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r']
696 696 else:
697 697 files = []
698 698 try:
699 699 repo.commit(files, message, opts['user'], opts['date'], match)
700 700 except ValueError, inst:
701 701 raise util.Abort(str(inst))
702 702
703 703 def docopy(ui, repo, pats, opts):
704 704 if not pats:
705 705 raise util.Abort('no source or destination specified')
706 706 elif len(pats) == 1:
707 707 raise util.Abort('no destination specified')
708 708 pats = list(pats)
709 709 dest = pats.pop()
710 710 sources = []
711 711
712 712 def okaytocopy(abs, rel, exact):
713 713 reasons = {'?': 'is not managed',
714 714 'a': 'has been marked for add'}
715 715 reason = reasons.get(repo.dirstate.state(abs))
716 716 if reason:
717 717 if exact: ui.warn('%s: not copying - file %s\n' % (rel, reason))
718 718 else:
719 719 return True
720 720
721 721 for src, abs, rel, exact in walk(repo, pats, opts):
722 722 if okaytocopy(abs, rel, exact):
723 723 sources.append((abs, rel, exact))
724 724 if not sources:
725 725 raise util.Abort('no files to copy')
726 726
727 727 cwd = repo.getcwd()
728 728 absdest = util.canonpath(repo.root, cwd, dest)
729 729 reldest = util.pathto(cwd, absdest)
730 730 if os.path.exists(reldest):
731 731 destisfile = not os.path.isdir(reldest)
732 732 else:
733 733 destisfile = len(sources) == 1 or repo.dirstate.state(absdest) != '?'
734 734
735 735 if destisfile:
736 736 if opts['parents']:
737 737 raise util.Abort('with --parents, destination must be a directory')
738 738 elif len(sources) > 1:
739 739 raise util.Abort('with multiple sources, destination must be a '
740 740 'directory')
741 741 errs, copied = 0, []
742 742 for abs, rel, exact in sources:
743 743 if opts['parents']:
744 744 mydest = os.path.join(dest, rel)
745 745 elif destisfile:
746 746 mydest = reldest
747 747 else:
748 748 mydest = os.path.join(dest, os.path.basename(rel))
749 749 myabsdest = util.canonpath(repo.root, cwd, mydest)
750 750 myreldest = util.pathto(cwd, myabsdest)
751 751 if not opts['force'] and repo.dirstate.state(myabsdest) not in 'a?':
752 752 ui.warn('%s: not overwriting - file already managed\n' % myreldest)
753 753 continue
754 754 mydestdir = os.path.dirname(myreldest) or '.'
755 755 if not opts['after']:
756 756 try:
757 757 if opts['parents']: os.makedirs(mydestdir)
758 758 elif not destisfile: os.mkdir(mydestdir)
759 759 except OSError, inst:
760 760 if inst.errno != errno.EEXIST: raise
761 761 if ui.verbose or not exact:
762 762 ui.status('copying %s to %s\n' % (rel, myreldest))
763 763 if not opts['after']:
764 764 try:
765 765 shutil.copyfile(rel, myreldest)
766 766 n = repo.manifest.tip()
767 767 mf = repo.manifest.readflags(n)
768 768 util.set_exec(myreldest, util.is_exec(rel, mf[abs]))
769 769 except shutil.Error, inst:
770 770 raise util.Abort(str(inst))
771 771 except IOError, inst:
772 772 if inst.errno == errno.ENOENT:
773 773 ui.warn('%s: deleted in working copy\n' % rel)
774 774 else:
775 775 ui.warn('%s: cannot copy - %s\n' % (rel, inst.strerror))
776 776 errs += 1
777 777 continue
778 778 repo.copy(abs, myabsdest)
779 779 copied.append((abs, rel, exact))
780 780 if errs:
781 781 ui.warn('(consider using --after)\n')
782 782 return errs, copied
783 783
784 784 def copy(ui, repo, *pats, **opts):
785 785 """mark files as copied for the next commit"""
786 786 errs, copied = docopy(ui, repo, pats, opts)
787 787 return errs
788 788
789 789 def debugancestor(ui, index, rev1, rev2):
790 790 """find the ancestor revision of two revisions in a given index"""
791 791 r = revlog.revlog(file, index, "")
792 792 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
793 793 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
794 794
795 795 def debugcheckstate(ui, repo):
796 796 """validate the correctness of the current dirstate"""
797 797 parent1, parent2 = repo.dirstate.parents()
798 798 repo.dirstate.read()
799 799 dc = repo.dirstate.map
800 800 keys = dc.keys()
801 801 keys.sort()
802 802 m1n = repo.changelog.read(parent1)[0]
803 803 m2n = repo.changelog.read(parent2)[0]
804 804 m1 = repo.manifest.read(m1n)
805 805 m2 = repo.manifest.read(m2n)
806 806 errors = 0
807 807 for f in dc:
808 808 state = repo.dirstate.state(f)
809 809 if state in "nr" and f not in m1:
810 810 ui.warn("%s in state %s, but not in manifest1\n" % (f, state))
811 811 errors += 1
812 812 if state in "a" and f in m1:
813 813 ui.warn("%s in state %s, but also in manifest1\n" % (f, state))
814 814 errors += 1
815 815 if state in "m" and f not in m1 and f not in m2:
816 816 ui.warn("%s in state %s, but not in either manifest\n" %
817 817 (f, state))
818 818 errors += 1
819 819 for f in m1:
820 820 state = repo.dirstate.state(f)
821 821 if state not in "nrm":
822 822 ui.warn("%s in manifest1, but listed as state %s" % (f, state))
823 823 errors += 1
824 824 if errors:
825 825 raise util.Abort(".hg/dirstate inconsistent with current parent's manifest")
826 826
827 827 def debugconfig(ui):
828 828 """show combined config settings from all hgrc files"""
829 829 try:
830 830 repo = hg.repository(ui)
831 831 except hg.RepoError:
832 832 pass
833 833 for section, name, value in ui.walkconfig():
834 834 ui.write('%s.%s=%s\n' % (section, name, value))
835 835
836 836 def debugstate(ui, repo):
837 837 """show the contents of the current dirstate"""
838 838 repo.dirstate.read()
839 839 dc = repo.dirstate.map
840 840 keys = dc.keys()
841 841 keys.sort()
842 842 for file_ in keys:
843 843 ui.write("%c %3o %10d %s %s\n"
844 844 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
845 845 time.strftime("%x %X",
846 846 time.localtime(dc[file_][3])), file_))
847 847 for f in repo.dirstate.copies:
848 848 ui.write("copy: %s -> %s\n" % (repo.dirstate.copies[f], f))
849 849
850 850 def debugdata(ui, file_, rev):
851 851 """dump the contents of an data file revision"""
852 852 r = revlog.revlog(file, file_[:-2] + ".i", file_)
853 853 try:
854 854 ui.write(r.revision(r.lookup(rev)))
855 855 except KeyError:
856 856 raise util.Abort('invalid revision identifier %s', rev)
857 857
858 858 def debugindex(ui, file_):
859 859 """dump the contents of an index file"""
860 860 r = revlog.revlog(file, file_, "")
861 861 ui.write(" rev offset length base linkrev" +
862 862 " nodeid p1 p2\n")
863 863 for i in range(r.count()):
864 864 e = r.index[i]
865 865 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
866 866 i, e[0], e[1], e[2], e[3],
867 867 short(e[6]), short(e[4]), short(e[5])))
868 868
869 869 def debugindexdot(ui, file_):
870 870 """dump an index DAG as a .dot file"""
871 871 r = revlog.revlog(file, file_, "")
872 872 ui.write("digraph G {\n")
873 873 for i in range(r.count()):
874 874 e = r.index[i]
875 875 ui.write("\t%d -> %d\n" % (r.rev(e[4]), i))
876 876 if e[5] != nullid:
877 877 ui.write("\t%d -> %d\n" % (r.rev(e[5]), i))
878 878 ui.write("}\n")
879 879
880 880 def debugrename(ui, repo, file, rev=None):
881 881 """dump rename information"""
882 882 r = repo.file(relpath(repo, [file])[0])
883 883 if rev:
884 884 try:
885 885 # assume all revision numbers are for changesets
886 886 n = repo.lookup(rev)
887 887 change = repo.changelog.read(n)
888 888 m = repo.manifest.read(change[0])
889 889 n = m[relpath(repo, [file])[0]]
890 890 except hg.RepoError, KeyError:
891 891 n = r.lookup(rev)
892 892 else:
893 893 n = r.tip()
894 894 m = r.renamed(n)
895 895 if m:
896 896 ui.write("renamed from %s:%s\n" % (m[0], hex(m[1])))
897 897 else:
898 898 ui.write("not renamed\n")
899 899
900 900 def debugwalk(ui, repo, *pats, **opts):
901 901 """show how files match on given patterns"""
902 902 items = list(walk(repo, pats, opts))
903 903 if not items:
904 904 return
905 905 fmt = '%%s %%-%ds %%-%ds %%s' % (
906 906 max([len(abs) for (src, abs, rel, exact) in items]),
907 907 max([len(rel) for (src, abs, rel, exact) in items]))
908 908 for src, abs, rel, exact in items:
909 909 line = fmt % (src, abs, rel, exact and 'exact' or '')
910 910 ui.write("%s\n" % line.rstrip())
911 911
912 912 def diff(ui, repo, *pats, **opts):
913 913 """diff working directory (or selected files)"""
914 914 node1, node2 = None, None
915 915 revs = [repo.lookup(x) for x in opts['rev']]
916 916
917 917 if len(revs) > 0:
918 918 node1 = revs[0]
919 919 if len(revs) > 1:
920 920 node2 = revs[1]
921 921 if len(revs) > 2:
922 922 raise util.Abort("too many revisions to diff")
923 923
924 924 fns, matchfn, anypats = matchpats(repo, repo.getcwd(), pats, opts)
925 925
926 926 dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn,
927 927 text=opts['text'])
928 928
929 929 def doexport(ui, repo, changeset, seqno, total, revwidth, opts):
930 930 node = repo.lookup(changeset)
931 931 prev, other = repo.changelog.parents(node)
932 932 change = repo.changelog.read(node)
933 933
934 934 fp = make_file(repo, repo.changelog, opts['output'],
935 935 node=node, total=total, seqno=seqno,
936 936 revwidth=revwidth)
937 937 if fp != sys.stdout:
938 938 ui.note("%s\n" % fp.name)
939 939
940 940 fp.write("# HG changeset patch\n")
941 941 fp.write("# User %s\n" % change[1])
942 942 fp.write("# Node ID %s\n" % hex(node))
943 943 fp.write("# Parent %s\n" % hex(prev))
944 944 if other != nullid:
945 945 fp.write("# Parent %s\n" % hex(other))
946 946 fp.write(change[4].rstrip())
947 947 fp.write("\n\n")
948 948
949 949 dodiff(fp, ui, repo, prev, node, text=opts['text'])
950 950 if fp != sys.stdout:
951 951 fp.close()
952 952
953 953 def export(ui, repo, *changesets, **opts):
954 954 """dump the header and diffs for one or more changesets"""
955 955 if not changesets:
956 956 raise util.Abort("export requires at least one changeset")
957 957 seqno = 0
958 958 revs = list(revrange(ui, repo, changesets))
959 959 total = len(revs)
960 960 revwidth = max(map(len, revs))
961 961 ui.note(len(revs) > 1 and "Exporting patches:\n" or "Exporting patch:\n")
962 962 for cset in revs:
963 963 seqno += 1
964 964 doexport(ui, repo, cset, seqno, total, revwidth, opts)
965 965
966 966 def forget(ui, repo, *pats, **opts):
967 967 """don't add the specified files on the next commit"""
968 968 forget = []
969 969 for src, abs, rel, exact in walk(repo, pats, opts):
970 970 if repo.dirstate.state(abs) == 'a':
971 971 forget.append(abs)
972 972 if ui.verbose or not exact:
973 973 ui.status('forgetting ', rel, '\n')
974 974 repo.forget(forget)
975 975
976 976 def grep(ui, repo, pattern, *pats, **opts):
977 977 """search for a pattern in specified files and revisions"""
978 978 reflags = 0
979 979 if opts['ignore_case']:
980 980 reflags |= re.I
981 981 regexp = re.compile(pattern, reflags)
982 982 sep, eol = ':', '\n'
983 983 if opts['print0']:
984 984 sep = eol = '\0'
985 985
986 986 fcache = {}
987 987 def getfile(fn):
988 988 if fn not in fcache:
989 989 fcache[fn] = repo.file(fn)
990 990 return fcache[fn]
991 991
992 992 def matchlines(body):
993 993 begin = 0
994 994 linenum = 0
995 995 while True:
996 996 match = regexp.search(body, begin)
997 997 if not match:
998 998 break
999 999 mstart, mend = match.span()
1000 1000 linenum += body.count('\n', begin, mstart) + 1
1001 1001 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1002 1002 lend = body.find('\n', mend)
1003 1003 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1004 1004 begin = lend + 1
1005 1005
1006 1006 class linestate:
1007 1007 def __init__(self, line, linenum, colstart, colend):
1008 1008 self.line = line
1009 1009 self.linenum = linenum
1010 1010 self.colstart = colstart
1011 1011 self.colend = colend
1012 1012 def __eq__(self, other):
1013 1013 return self.line == other.line
1014 1014 def __hash__(self):
1015 1015 return hash(self.line)
1016 1016
1017 1017 matches = {}
1018 1018 def grepbody(fn, rev, body):
1019 1019 matches[rev].setdefault(fn, {})
1020 1020 m = matches[rev][fn]
1021 1021 for lnum, cstart, cend, line in matchlines(body):
1022 1022 s = linestate(line, lnum, cstart, cend)
1023 1023 m[s] = s
1024 1024
1025 1025 prev = {}
1026 1026 ucache = {}
1027 1027 def display(fn, rev, states, prevstates):
1028 1028 diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates)))
1029 1029 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
1030 1030 counts = {'-': 0, '+': 0}
1031 1031 filerevmatches = {}
1032 1032 for l in diff:
1033 1033 if incrementing or not opts['all']:
1034 1034 change = ((l in prevstates) and '-') or '+'
1035 1035 r = rev
1036 1036 else:
1037 1037 change = ((l in states) and '-') or '+'
1038 1038 r = prev[fn]
1039 1039 cols = [fn, str(rev)]
1040 1040 if opts['line_number']: cols.append(str(l.linenum))
1041 1041 if opts['all']: cols.append(change)
1042 1042 if opts['user']: cols.append(trimuser(ui, getchange(rev)[1], rev,
1043 1043 ucache))
1044 1044 if opts['files_with_matches']:
1045 1045 c = (fn, rev)
1046 1046 if c in filerevmatches: continue
1047 1047 filerevmatches[c] = 1
1048 1048 else:
1049 1049 cols.append(l.line)
1050 1050 ui.write(sep.join(cols), eol)
1051 1051 counts[change] += 1
1052 1052 return counts['+'], counts['-']
1053 1053
1054 1054 fstate = {}
1055 1055 skip = {}
1056 1056 changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts)
1057 1057 count = 0
1058 1058 for st, rev, fns in changeiter:
1059 1059 if st == 'window':
1060 1060 incrementing = rev
1061 1061 matches.clear()
1062 1062 elif st == 'add':
1063 1063 change = repo.changelog.read(repo.lookup(str(rev)))
1064 1064 mf = repo.manifest.read(change[0])
1065 1065 matches[rev] = {}
1066 1066 for fn in fns:
1067 1067 if fn in skip: continue
1068 1068 fstate.setdefault(fn, {})
1069 1069 try:
1070 1070 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1071 1071 except KeyError:
1072 1072 pass
1073 1073 elif st == 'iter':
1074 1074 states = matches[rev].items()
1075 1075 states.sort()
1076 1076 for fn, m in states:
1077 1077 if fn in skip: continue
1078 1078 if incrementing or not opts['all'] or fstate[fn]:
1079 1079 pos, neg = display(fn, rev, m, fstate[fn])
1080 1080 count += pos + neg
1081 1081 if pos and not opts['all']:
1082 1082 skip[fn] = True
1083 1083 fstate[fn] = m
1084 1084 prev[fn] = rev
1085 1085
1086 1086 if not incrementing:
1087 1087 fstate = fstate.items()
1088 1088 fstate.sort()
1089 1089 for fn, state in fstate:
1090 1090 if fn in skip: continue
1091 1091 display(fn, rev, {}, state)
1092 1092 return (count == 0 and 1) or 0
1093 1093
1094 1094 def heads(ui, repo, **opts):
1095 1095 """show current repository heads"""
1096 1096 heads = repo.changelog.heads()
1097 1097 br = None
1098 1098 if opts['branches']:
1099 1099 br = repo.branchlookup(heads)
1100 1100 for n in repo.changelog.heads():
1101 1101 show_changeset(ui, repo, changenode=n, brinfo=br)
1102 1102
1103 1103 def identify(ui, repo):
1104 1104 """print information about the working copy"""
1105 1105 parents = [p for p in repo.dirstate.parents() if p != nullid]
1106 1106 if not parents:
1107 1107 ui.write("unknown\n")
1108 1108 return
1109 1109
1110 1110 hexfunc = ui.verbose and hex or short
1111 1111 (c, a, d, u) = repo.changes()
1112 1112 output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]),
1113 1113 (c or a or d) and "+" or "")]
1114 1114
1115 1115 if not ui.quiet:
1116 1116 # multiple tags for a single parent separated by '/'
1117 1117 parenttags = ['/'.join(tags)
1118 1118 for tags in map(repo.nodetags, parents) if tags]
1119 1119 # tags for multiple parents separated by ' + '
1120 1120 if parenttags:
1121 1121 output.append(' + '.join(parenttags))
1122 1122
1123 1123 ui.write("%s\n" % ' '.join(output))
1124 1124
1125 1125 def import_(ui, repo, patch1, *patches, **opts):
1126 1126 """import an ordered set of patches"""
1127 1127 patches = (patch1,) + patches
1128 1128
1129 1129 if not opts['force']:
1130 1130 (c, a, d, u) = repo.changes()
1131 1131 if c or a or d:
1132 1132 raise util.Abort("outstanding uncommitted changes")
1133 1133
1134 1134 d = opts["base"]
1135 1135 strip = opts["strip"]
1136 1136
1137 1137 mailre = re.compile(r'(?:From |[\w-]+:)')
1138 1138 diffre = re.compile(r'(?:diff -|--- .*\s+\w+ \w+ +\d+ \d+:\d+:\d+ \d+)')
1139 1139
1140 1140 for patch in patches:
1141 1141 ui.status("applying %s\n" % patch)
1142 1142 pf = os.path.join(d, patch)
1143 1143
1144 1144 message = []
1145 1145 user = None
1146 1146 hgpatch = False
1147 1147 for line in file(pf):
1148 1148 line = line.rstrip()
1149 1149 if (not message and not hgpatch and
1150 1150 mailre.match(line) and not opts['force']):
1151 1151 if len(line) > 35: line = line[:32] + '...'
1152 1152 raise util.Abort('first line looks like a '
1153 1153 'mail header: ' + line)
1154 1154 if diffre.match(line):
1155 1155 break
1156 1156 elif hgpatch:
1157 1157 # parse values when importing the result of an hg export
1158 1158 if line.startswith("# User "):
1159 1159 user = line[7:]
1160 1160 ui.debug('User: %s\n' % user)
1161 1161 elif not line.startswith("# ") and line:
1162 1162 message.append(line)
1163 1163 hgpatch = False
1164 1164 elif line == '# HG changeset patch':
1165 1165 hgpatch = True
1166 1166 message = [] # We may have collected garbage
1167 1167 else:
1168 1168 message.append(line)
1169 1169
1170 1170 # make sure message isn't empty
1171 1171 if not message:
1172 1172 message = "imported patch %s\n" % patch
1173 1173 else:
1174 1174 message = "%s\n" % '\n'.join(message)
1175 1175 ui.debug('message:\n%s\n' % message)
1176 1176
1177 1177 files = util.patch(strip, pf, ui)
1178 1178
1179 1179 if len(files) > 0:
1180 1180 addremove(ui, repo, *files)
1181 1181 repo.commit(files, message, user)
1182 1182
1183 1183 def incoming(ui, repo, source="default", **opts):
1184 1184 """show new changesets found in source"""
1185 1185 source = ui.expandpath(source)
1186 1186 other = hg.repository(ui, source)
1187 1187 if not other.local():
1188 1188 raise util.Abort("incoming doesn't work for remote repositories yet")
1189 1189 o = repo.findincoming(other)
1190 1190 if not o:
1191 1191 return
1192 1192 o = other.newer(o)
1193 1193 for n in o:
1194 1194 show_changeset(ui, other, changenode=n)
1195 1195 if opts['patch']:
1196 1196 prev = other.changelog.parents(n)[0]
1197 1197 dodiff(ui, ui, other, prev, n)
1198 1198 ui.write("\n")
1199 1199
1200 1200 def init(ui, dest="."):
1201 1201 """create a new repository in the given directory"""
1202 1202 if not os.path.exists(dest):
1203 1203 os.mkdir(dest)
1204 1204 hg.repository(ui, dest, create=1)
1205 1205
1206 1206 def locate(ui, repo, *pats, **opts):
1207 1207 """locate files matching specific patterns"""
1208 1208 end = opts['print0'] and '\0' or '\n'
1209 1209
1210 1210 for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'):
1211 1211 if repo.dirstate.state(abs) == '?':
1212 1212 continue
1213 1213 if opts['fullpath']:
1214 1214 ui.write(os.path.join(repo.root, abs), end)
1215 1215 else:
1216 1216 ui.write(rel, end)
1217 1217
1218 1218 def log(ui, repo, *pats, **opts):
1219 1219 """show revision history of entire repository or files"""
1220 1220 class dui:
1221 1221 # Implement and delegate some ui protocol. Save hunks of
1222 1222 # output for later display in the desired order.
1223 1223 def __init__(self, ui):
1224 1224 self.ui = ui
1225 1225 self.hunk = {}
1226 1226 def bump(self, rev):
1227 1227 self.rev = rev
1228 1228 self.hunk[rev] = []
1229 1229 def note(self, *args):
1230 1230 if self.verbose:
1231 1231 self.write(*args)
1232 1232 def status(self, *args):
1233 1233 if not self.quiet:
1234 1234 self.write(*args)
1235 1235 def write(self, *args):
1236 1236 self.hunk[self.rev].append(args)
1237 1237 def __getattr__(self, key):
1238 1238 return getattr(self.ui, key)
1239 1239 cwd = repo.getcwd()
1240 1240 if not pats and cwd:
1241 1241 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1242 1242 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1243 1243 changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '',
1244 1244 pats, opts)
1245 1245 for st, rev, fns in changeiter:
1246 1246 if st == 'window':
1247 1247 du = dui(ui)
1248 1248 elif st == 'add':
1249 1249 du.bump(rev)
1250 1250 br = None
1251 1251 if opts['branch']:
1252 1252 br = repo.branchlookup([repo.changelog.node(rev)])
1253 1253 show_changeset(du, repo, rev, brinfo=br)
1254 1254 if opts['patch']:
1255 1255 changenode = repo.changelog.node(rev)
1256 1256 prev, other = repo.changelog.parents(changenode)
1257 1257 dodiff(du, du, repo, prev, changenode, fns)
1258 1258 du.write("\n\n")
1259 1259 elif st == 'iter':
1260 1260 for args in du.hunk[rev]:
1261 1261 ui.write(*args)
1262 1262
1263 1263 def manifest(ui, repo, rev=None):
1264 1264 """output the latest or given revision of the project manifest"""
1265 1265 if rev:
1266 1266 try:
1267 1267 # assume all revision numbers are for changesets
1268 1268 n = repo.lookup(rev)
1269 1269 change = repo.changelog.read(n)
1270 1270 n = change[0]
1271 1271 except hg.RepoError:
1272 1272 n = repo.manifest.lookup(rev)
1273 1273 else:
1274 1274 n = repo.manifest.tip()
1275 1275 m = repo.manifest.read(n)
1276 1276 mf = repo.manifest.readflags(n)
1277 1277 files = m.keys()
1278 1278 files.sort()
1279 1279
1280 1280 for f in files:
1281 1281 ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f))
1282 1282
1283 1283 def outgoing(ui, repo, dest="default-push", **opts):
1284 1284 """show changesets not found in destination"""
1285 1285 dest = ui.expandpath(dest)
1286 1286 other = hg.repository(ui, dest)
1287 1287 o = repo.findoutgoing(other)
1288 1288 o = repo.newer(o)
1289 1289 for n in o:
1290 1290 show_changeset(ui, repo, changenode=n)
1291 1291 if opts['patch']:
1292 1292 prev = repo.changelog.parents(n)[0]
1293 1293 dodiff(ui, ui, repo, prev, n)
1294 1294 ui.write("\n")
1295 1295
1296 1296 def parents(ui, repo, rev=None):
1297 1297 """show the parents of the working dir or revision"""
1298 1298 if rev:
1299 1299 p = repo.changelog.parents(repo.lookup(rev))
1300 1300 else:
1301 1301 p = repo.dirstate.parents()
1302 1302
1303 1303 for n in p:
1304 1304 if n != nullid:
1305 1305 show_changeset(ui, repo, changenode=n)
1306 1306
1307 1307 def paths(ui, search=None):
1308 1308 """show definition of symbolic path names"""
1309 1309 try:
1310 1310 repo = hg.repository(ui=ui)
1311 1311 except hg.RepoError:
1312 1312 pass
1313 1313
1314 1314 if search:
1315 1315 for name, path in ui.configitems("paths"):
1316 1316 if name == search:
1317 1317 ui.write("%s\n" % path)
1318 1318 return
1319 1319 ui.warn("not found!\n")
1320 1320 return 1
1321 1321 else:
1322 1322 for name, path in ui.configitems("paths"):
1323 1323 ui.write("%s = %s\n" % (name, path))
1324 1324
1325 1325 def pull(ui, repo, source="default", **opts):
1326 1326 """pull changes from the specified source"""
1327 1327 source = ui.expandpath(source)
1328 1328 ui.status('pulling from %s\n' % (source))
1329 1329
1330 1330 if opts['ssh']:
1331 1331 ui.setconfig("ui", "ssh", opts['ssh'])
1332 1332 if opts['remotecmd']:
1333 1333 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
1334 1334
1335 1335 other = hg.repository(ui, source)
1336 1336 r = repo.pull(other)
1337 1337 if not r:
1338 1338 if opts['update']:
1339 1339 return update(ui, repo)
1340 1340 else:
1341 1341 ui.status("(run 'hg update' to get a working copy)\n")
1342 1342
1343 1343 return r
1344 1344
1345 1345 def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
1346 1346 """push changes to the specified destination"""
1347 1347 dest = ui.expandpath(dest)
1348 1348 ui.status('pushing to %s\n' % (dest))
1349 1349
1350 1350 if ssh:
1351 1351 ui.setconfig("ui", "ssh", ssh)
1352 1352 if remotecmd:
1353 1353 ui.setconfig("ui", "remotecmd", remotecmd)
1354 1354
1355 1355 other = hg.repository(ui, dest)
1356 1356 r = repo.push(other, force)
1357 1357 return r
1358 1358
1359 1359 def rawcommit(ui, repo, *flist, **rc):
1360 1360 "raw commit interface"
1361 1361 if rc['text']:
1362 1362 ui.warn("Warning: -t and --text is deprecated,"
1363 1363 " please use -m or --message instead.\n")
1364 1364 message = rc['message'] or rc['text']
1365 1365 if not message and rc['logfile']:
1366 1366 try:
1367 1367 message = open(rc['logfile']).read()
1368 1368 except IOError:
1369 1369 pass
1370 1370 if not message and not rc['logfile']:
1371 1371 raise util.Abort("missing commit message")
1372 1372
1373 1373 files = relpath(repo, list(flist))
1374 1374 if rc['files']:
1375 1375 files += open(rc['files']).read().splitlines()
1376 1376
1377 1377 rc['parent'] = map(repo.lookup, rc['parent'])
1378 1378
1379 1379 try:
1380 1380 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
1381 1381 except ValueError, inst:
1382 1382 raise util.Abort(str(inst))
1383 1383
1384 1384 def recover(ui, repo):
1385 1385 """roll back an interrupted transaction"""
1386 1386 repo.recover()
1387 1387
1388 1388 def remove(ui, repo, pat, *pats, **opts):
1389 1389 """remove the specified files on the next commit"""
1390 1390 names = []
1391 1391 def okaytoremove(abs, rel, exact):
1392 1392 c, a, d, u = repo.changes(files = [abs])
1393 1393 reason = None
1394 1394 if c: reason = 'is modified'
1395 1395 elif a: reason = 'has been marked for add'
1396 1396 elif u: reason = 'is not managed'
1397 1397 if reason:
1398 1398 if exact: ui.warn('not removing %s: file %s\n' % (rel, reason))
1399 1399 else:
1400 1400 return True
1401 1401 for src, abs, rel, exact in walk(repo, (pat,) + pats, opts):
1402 1402 if okaytoremove(abs, rel, exact):
1403 1403 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1404 1404 names.append(abs)
1405 1405 for name in names:
1406 1406 try:
1407 1407 os.unlink(name)
1408 1408 except OSError, inst:
1409 1409 if inst.errno != errno.ENOENT: raise
1410 1410 repo.remove(names)
1411 1411
1412 1412 def rename(ui, repo, *pats, **opts):
1413 1413 """rename files; equivalent of copy + remove"""
1414 1414 errs, copied = docopy(ui, repo, pats, opts)
1415 1415 names = []
1416 1416 for abs, rel, exact in copied:
1417 1417 if ui.verbose or not exact: ui.status('removing %s\n' % rel)
1418 1418 try:
1419 1419 os.unlink(rel)
1420 1420 except OSError, inst:
1421 1421 if inst.errno != errno.ENOENT: raise
1422 1422 names.append(abs)
1423 1423 repo.remove(names)
1424 1424 return errs
1425 1425
1426 1426 def revert(ui, repo, *names, **opts):
1427 1427 """revert modified files or dirs back to their unmodified states"""
1428 1428 node = opts['rev'] and repo.lookup(opts['rev']) or \
1429 1429 repo.dirstate.parents()[0]
1430 1430 root = os.path.realpath(repo.root)
1431 1431
1432 1432 def trimpath(p):
1433 1433 p = os.path.realpath(p)
1434 1434 if p.startswith(root):
1435 1435 rest = p[len(root):]
1436 1436 if not rest:
1437 1437 return rest
1438 1438 if p.startswith(os.sep):
1439 1439 return rest[1:]
1440 1440 return p
1441 1441
1442 1442 relnames = map(trimpath, names or [os.getcwd()])
1443 1443 chosen = {}
1444 1444
1445 1445 def choose(name):
1446 1446 def body(name):
1447 1447 for r in relnames:
1448 1448 if not name.startswith(r):
1449 1449 continue
1450 1450 rest = name[len(r):]
1451 1451 if not rest:
1452 1452 return r, True
1453 1453 depth = rest.count(os.sep)
1454 1454 if not r:
1455 1455 if depth == 0 or not opts['nonrecursive']:
1456 1456 return r, True
1457 1457 elif rest[0] == os.sep:
1458 1458 if depth == 1 or not opts['nonrecursive']:
1459 1459 return r, True
1460 1460 return None, False
1461 1461 relname, ret = body(name)
1462 1462 if ret:
1463 1463 chosen[relname] = 1
1464 1464 return ret
1465 1465
1466 1466 r = repo.update(node, False, True, choose, False)
1467 1467 for n in relnames:
1468 1468 if n not in chosen:
1469 1469 ui.warn('error: no matches for %s\n' % n)
1470 1470 r = 1
1471 1471 sys.stdout.flush()
1472 1472 return r
1473 1473
1474 1474 def root(ui, repo):
1475 1475 """print the root (top) of the current working dir"""
1476 1476 ui.write(repo.root + "\n")
1477 1477
1478 1478 def serve(ui, repo, **opts):
1479 1479 """export the repository via HTTP"""
1480 1480
1481 1481 if opts["stdio"]:
1482 1482 fin, fout = sys.stdin, sys.stdout
1483 1483 sys.stdout = sys.stderr
1484 1484
1485 1485 def getarg():
1486 1486 argline = fin.readline()[:-1]
1487 1487 arg, l = argline.split()
1488 1488 val = fin.read(int(l))
1489 1489 return arg, val
1490 1490 def respond(v):
1491 1491 fout.write("%d\n" % len(v))
1492 1492 fout.write(v)
1493 1493 fout.flush()
1494 1494
1495 1495 lock = None
1496 1496
1497 1497 while 1:
1498 1498 cmd = fin.readline()[:-1]
1499 1499 if cmd == '':
1500 1500 return
1501 1501 if cmd == "heads":
1502 1502 h = repo.heads()
1503 1503 respond(" ".join(map(hex, h)) + "\n")
1504 1504 if cmd == "lock":
1505 1505 lock = repo.lock()
1506 1506 respond("")
1507 1507 if cmd == "unlock":
1508 1508 if lock:
1509 1509 lock.release()
1510 1510 lock = None
1511 1511 respond("")
1512 1512 elif cmd == "branches":
1513 1513 arg, nodes = getarg()
1514 1514 nodes = map(bin, nodes.split(" "))
1515 1515 r = []
1516 1516 for b in repo.branches(nodes):
1517 1517 r.append(" ".join(map(hex, b)) + "\n")
1518 1518 respond("".join(r))
1519 1519 elif cmd == "between":
1520 1520 arg, pairs = getarg()
1521 1521 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
1522 1522 r = []
1523 1523 for b in repo.between(pairs):
1524 1524 r.append(" ".join(map(hex, b)) + "\n")
1525 1525 respond("".join(r))
1526 1526 elif cmd == "changegroup":
1527 1527 nodes = []
1528 1528 arg, roots = getarg()
1529 1529 nodes = map(bin, roots.split(" "))
1530 1530
1531 1531 cg = repo.changegroup(nodes)
1532 1532 while 1:
1533 1533 d = cg.read(4096)
1534 1534 if not d:
1535 1535 break
1536 1536 fout.write(d)
1537 1537
1538 1538 fout.flush()
1539 1539
1540 1540 elif cmd == "addchangegroup":
1541 1541 if not lock:
1542 1542 respond("not locked")
1543 1543 continue
1544 1544 respond("")
1545 1545
1546 1546 r = repo.addchangegroup(fin)
1547 1547 respond("")
1548 1548
1549 1549 optlist = "name templates style address port ipv6 accesslog errorlog"
1550 1550 for o in optlist.split():
1551 1551 if opts[o]:
1552 1552 ui.setconfig("web", o, opts[o])
1553 1553
1554 1554 try:
1555 1555 httpd = hgweb.create_server(repo)
1556 1556 except socket.error, inst:
1557 1557 raise util.Abort('cannot start server: ' + inst.args[1])
1558 1558
1559 1559 if ui.verbose:
1560 1560 addr, port = httpd.socket.getsockname()
1561 1561 if addr == '0.0.0.0':
1562 1562 addr = socket.gethostname()
1563 1563 else:
1564 1564 try:
1565 1565 addr = socket.gethostbyaddr(addr)[0]
1566 1566 except socket.error:
1567 1567 pass
1568 1568 if port != 80:
1569 1569 ui.status('listening at http://%s:%d/\n' % (addr, port))
1570 1570 else:
1571 1571 ui.status('listening at http://%s/\n' % addr)
1572 1572 httpd.serve_forever()
1573 1573
1574 1574 def status(ui, repo, *pats, **opts):
1575 1575 '''show changed files in the working directory
1576 1576
1577 1577 M = modified
1578 1578 A = added
1579 1579 R = removed
1580 1580 ? = not tracked
1581 1581 '''
1582 1582
1583 1583 cwd = repo.getcwd()
1584 1584 files, matchfn, anypats = matchpats(repo, cwd, pats, opts)
1585 1585 (c, a, d, u) = [[util.pathto(cwd, x) for x in n]
1586 1586 for n in repo.changes(files=files, match=matchfn)]
1587 1587
1588 1588 changetypes = [('modified', 'M', c),
1589 1589 ('added', 'A', a),
1590 1590 ('removed', 'R', d),
1591 1591 ('unknown', '?', u)]
1592 1592
1593 1593 end = opts['print0'] and '\0' or '\n'
1594 1594
1595 1595 for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]]
1596 1596 or changetypes):
1597 1597 if opts['no_status']:
1598 1598 format = "%%s%s" % end
1599 1599 else:
1600 1600 format = "%s %%s%s" % (char, end);
1601 1601
1602 1602 for f in changes:
1603 1603 ui.write(format % f)
1604 1604
1605 1605 def tag(ui, repo, name, rev=None, **opts):
1606 1606 """add a tag for the current tip or a given revision"""
1607 1607 if opts['text']:
1608 1608 ui.warn("Warning: -t and --text is deprecated,"
1609 1609 " please use -m or --message instead.\n")
1610 1610 if name == "tip":
1611 1611 raise util.Abort("the name 'tip' is reserved")
1612 1612 if rev:
1613 1613 r = hex(repo.lookup(rev))
1614 1614 else:
1615 1615 r = hex(repo.changelog.tip())
1616 1616
1617 1617 if name.find(revrangesep) >= 0:
1618 1618 raise util.Abort("'%s' cannot be used in a tag name" % revrangesep)
1619 1619
1620 1620 if opts['local']:
1621 1621 repo.opener("localtags", "a").write("%s %s\n" % (r, name))
1622 1622 return
1623 1623
1624 1624 (c, a, d, u) = repo.changes()
1625 1625 for x in (c, a, d, u):
1626 1626 if ".hgtags" in x:
1627 1627 raise util.Abort("working copy of .hgtags is changed "
1628 1628 "(please commit .hgtags manually)")
1629 1629
1630 1630 repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name))
1631 1631 if repo.dirstate.state(".hgtags") == '?':
1632 1632 repo.add([".hgtags"])
1633 1633
1634 1634 message = (opts['message'] or opts['text'] or
1635 1635 "Added tag %s for changeset %s" % (name, r))
1636 1636 try:
1637 1637 repo.commit([".hgtags"], message, opts['user'], opts['date'])
1638 1638 except ValueError, inst:
1639 1639 raise util.Abort(str(inst))
1640 1640
1641 1641 def tags(ui, repo):
1642 1642 """list repository tags"""
1643 1643
1644 1644 l = repo.tagslist()
1645 1645 l.reverse()
1646 1646 for t, n in l:
1647 1647 try:
1648 1648 r = "%5d:%s" % (repo.changelog.rev(n), hex(n))
1649 1649 except KeyError:
1650 1650 r = " ?:?"
1651 1651 ui.write("%-30s %s\n" % (t, r))
1652 1652
1653 1653 def tip(ui, repo):
1654 1654 """show the tip revision"""
1655 1655 n = repo.changelog.tip()
1656 1656 show_changeset(ui, repo, changenode=n)
1657 1657
1658 1658 def unbundle(ui, repo, fname):
1659 1659 """apply a changegroup file"""
1660 1660 f = urllib.urlopen(fname)
1661 1661
1662 1662 if f.read(4) != "HG10":
1663 1663 raise util.Abort("%s: not a Mercurial bundle file" % fname)
1664 1664
1665 1665 class bzread:
1666 1666 def __init__(self, f):
1667 1667 self.zd = bz2.BZ2Decompressor()
1668 1668 self.f = f
1669 1669 self.buf = ""
1670 1670 def read(self, l):
1671 1671 while l > len(self.buf):
1672 1672 r = self.f.read(4096)
1673 1673 if r:
1674 1674 self.buf += self.zd.decompress(r)
1675 1675 else:
1676 1676 break
1677 1677 d, self.buf = self.buf[:l], self.buf[l:]
1678 1678 return d
1679 1679
1680 1680 repo.addchangegroup(bzread(f))
1681 1681
1682 1682 def undo(ui, repo):
1683 1683 """undo the last commit or pull
1684 1684
1685 1685 Roll back the last pull or commit transaction on the
1686 1686 repository, restoring the project to its earlier state.
1687 1687
1688 1688 This command should be used with care. There is only one level of
1689 1689 undo and there is no redo.
1690 1690
1691 1691 This command is not intended for use on public repositories. Once
1692 1692 a change is visible for pull by other users, undoing it locally is
1693 1693 ineffective.
1694 1694 """
1695 1695 repo.undo()
1696 1696
1697 1697 def update(ui, repo, node=None, merge=False, clean=False, branch=None):
1698 1698 '''update or merge working directory
1699 1699
1700 1700 If there are no outstanding changes in the working directory and
1701 1701 there is a linear relationship between the current version and the
1702 1702 requested version, the result is the requested version.
1703 1703
1704 1704 Otherwise the result is a merge between the contents of the
1705 1705 current working directory and the requested version. Files that
1706 1706 changed between either parent are marked as changed for the next
1707 1707 commit and a commit must be performed before any further updates
1708 1708 are allowed.
1709 1709 '''
1710 1710 if branch:
1711 1711 br = repo.branchlookup(branch=branch)
1712 1712 found = []
1713 1713 for x in br:
1714 1714 if branch in br[x]:
1715 1715 found.append(x)
1716 1716 if len(found) > 1:
1717 1717 ui.warn("Found multiple heads for %s\n" % branch)
1718 1718 for x in found:
1719 1719 show_changeset(ui, repo, changenode=x, brinfo=br)
1720 1720 return 1
1721 1721 if len(found) == 1:
1722 1722 node = found[0]
1723 1723 ui.warn("Using head %s for branch %s\n" % (short(node), branch))
1724 1724 else:
1725 1725 ui.warn("branch %s not found\n" % (branch))
1726 1726 return 1
1727 1727 else:
1728 1728 node = node and repo.lookup(node) or repo.changelog.tip()
1729 1729 return repo.update(node, allow=merge, force=clean)
1730 1730
1731 1731 def verify(ui, repo):
1732 1732 """verify the integrity of the repository"""
1733 1733 return repo.verify()
1734 1734
1735 1735 # Command options and aliases are listed here, alphabetically
1736 1736
1737 1737 table = {
1738 1738 "^add":
1739 1739 (add,
1740 1740 [('I', 'include', [], 'include path in search'),
1741 1741 ('X', 'exclude', [], 'exclude path from search')],
1742 1742 "hg add [OPTION]... [FILE]..."),
1743 1743 "addremove":
1744 1744 (addremove,
1745 1745 [('I', 'include', [], 'include path in search'),
1746 1746 ('X', 'exclude', [], 'exclude path from search')],
1747 1747 "hg addremove [OPTION]... [FILE]..."),
1748 1748 "^annotate":
1749 1749 (annotate,
1750 1750 [('r', 'rev', '', 'revision'),
1751 1751 ('a', 'text', None, 'treat all files as text'),
1752 1752 ('u', 'user', None, 'show user'),
1753 1753 ('n', 'number', None, 'show revision number'),
1754 1754 ('c', 'changeset', None, 'show changeset'),
1755 1755 ('I', 'include', [], 'include path in search'),
1756 1756 ('X', 'exclude', [], 'exclude path from search')],
1757 1757 'hg annotate [OPTION]... FILE...'),
1758 1758 "bundle":
1759 1759 (bundle,
1760 1760 [],
1761 1761 'hg bundle FILE DEST'),
1762 1762 "cat":
1763 1763 (cat,
1764 1764 [('I', 'include', [], 'include path in search'),
1765 1765 ('X', 'exclude', [], 'exclude path from search'),
1766 1766 ('o', 'output', "", 'output to file'),
1767 1767 ('r', 'rev', '', 'revision')],
1768 1768 'hg cat [OPTION]... FILE...'),
1769 1769 "^clone":
1770 1770 (clone,
1771 1771 [('U', 'noupdate', None, 'skip update after cloning'),
1772 1772 ('e', 'ssh', "", 'ssh command'),
1773 1773 ('', 'pull', None, 'use pull protocol to copy metadata'),
1774 1774 ('', 'remotecmd', "", 'remote hg command')],
1775 1775 'hg clone [OPTION]... SOURCE [DEST]'),
1776 1776 "^commit|ci":
1777 1777 (commit,
1778 1778 [('A', 'addremove', None, 'run add/remove during commit'),
1779 1779 ('I', 'include', [], 'include path in search'),
1780 1780 ('X', 'exclude', [], 'exclude path from search'),
1781 1781 ('m', 'message', "", 'commit message'),
1782 1782 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1783 1783 ('l', 'logfile', "", 'commit message file'),
1784 1784 ('d', 'date', "", 'date code'),
1785 1785 ('u', 'user', "", 'user')],
1786 1786 'hg commit [OPTION]... [FILE]...'),
1787 1787 "copy|cp": (copy,
1788 1788 [('I', 'include', [], 'include path in search'),
1789 1789 ('X', 'exclude', [], 'exclude path from search'),
1790 1790 ('A', 'after', None, 'record a copy after it has happened'),
1791 1791 ('f', 'force', None, 'replace destination if it exists'),
1792 1792 ('p', 'parents', None, 'append source path to dest')],
1793 1793 'hg copy [OPTION]... [SOURCE]... DEST'),
1794 1794 "debugancestor": (debugancestor, [], 'debugancestor INDEX REV1 REV2'),
1795 1795 "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'),
1796 1796 "debugconfig": (debugconfig, [], 'debugconfig'),
1797 1797 "debugstate": (debugstate, [], 'debugstate'),
1798 1798 "debugdata": (debugdata, [], 'debugdata FILE REV'),
1799 1799 "debugindex": (debugindex, [], 'debugindex FILE'),
1800 1800 "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'),
1801 1801 "debugrename": (debugrename, [], 'debugrename FILE [REV]'),
1802 1802 "debugwalk":
1803 1803 (debugwalk,
1804 1804 [('I', 'include', [], 'include path in search'),
1805 1805 ('X', 'exclude', [], 'exclude path from search')],
1806 1806 'debugwalk [OPTION]... [FILE]...'),
1807 1807 "^diff":
1808 1808 (diff,
1809 1809 [('r', 'rev', [], 'revision'),
1810 1810 ('a', 'text', None, 'treat all files as text'),
1811 1811 ('I', 'include', [], 'include path in search'),
1812 1812 ('X', 'exclude', [], 'exclude path from search')],
1813 1813 'hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...'),
1814 1814 "^export":
1815 1815 (export,
1816 1816 [('o', 'output', "", 'output to file'),
1817 1817 ('a', 'text', None, 'treat all files as text')],
1818 1818 "hg export [-a] [-o OUTFILE] REV..."),
1819 1819 "forget":
1820 1820 (forget,
1821 1821 [('I', 'include', [], 'include path in search'),
1822 1822 ('X', 'exclude', [], 'exclude path from search')],
1823 1823 "hg forget [OPTION]... FILE..."),
1824 1824 "grep":
1825 1825 (grep,
1826 1826 [('0', 'print0', None, 'end fields with NUL'),
1827 1827 ('I', 'include', [], 'include path in search'),
1828 1828 ('X', 'exclude', [], 'include path in search'),
1829 1829 ('', 'all', None, 'print all revisions with matches'),
1830 1830 ('i', 'ignore-case', None, 'ignore case when matching'),
1831 1831 ('l', 'files-with-matches', None, 'print names of files and revs with matches'),
1832 1832 ('n', 'line-number', None, 'print line numbers'),
1833 1833 ('r', 'rev', [], 'search in revision rev'),
1834 1834 ('u', 'user', None, 'print user who made change')],
1835 1835 "hg grep [OPTION]... PATTERN [FILE]..."),
1836 1836 "heads":
1837 1837 (heads,
1838 1838 [('b', 'branches', None, 'find branch info')],
1839 1839 'hg heads [-b]'),
1840 1840 "help": (help_, [], 'hg help [COMMAND]'),
1841 1841 "identify|id": (identify, [], 'hg identify'),
1842 1842 "import|patch":
1843 1843 (import_,
1844 1844 [('p', 'strip', 1, 'path strip'),
1845 1845 ('f', 'force', None, 'skip check for outstanding changes'),
1846 1846 ('b', 'base', "", 'base path')],
1847 1847 "hg import [-f] [-p NUM] [-b BASE] PATCH..."),
1848 1848 "incoming|in": (incoming,
1849 1849 [('p', 'patch', None, 'show patch')],
1850 1850 'hg incoming [-p] [SOURCE]'),
1851 1851 "^init": (init, [], 'hg init [DEST]'),
1852 1852 "locate":
1853 1853 (locate,
1854 1854 [('r', 'rev', '', 'revision'),
1855 1855 ('0', 'print0', None, 'end filenames with NUL'),
1856 1856 ('f', 'fullpath', None, 'print complete paths'),
1857 1857 ('I', 'include', [], 'include path in search'),
1858 1858 ('X', 'exclude', [], 'exclude path from search')],
1859 1859 'hg locate [OPTION]... [PATTERN]...'),
1860 1860 "^log|history":
1861 1861 (log,
1862 1862 [('I', 'include', [], 'include path in search'),
1863 1863 ('X', 'exclude', [], 'exclude path from search'),
1864 1864 ('b', 'branch', None, 'show branches'),
1865 1865 ('r', 'rev', [], 'revision'),
1866 1866 ('p', 'patch', None, 'show patch')],
1867 1867 'hg log [-I] [-X] [-r REV]... [-p] [FILE]'),
1868 1868 "manifest": (manifest, [], 'hg manifest [REV]'),
1869 1869 "outgoing|out": (outgoing,
1870 1870 [('p', 'patch', None, 'show patch')],
1871 1871 'hg outgoing [-p] [DEST]'),
1872 1872 "parents": (parents, [], 'hg parents [REV]'),
1873 1873 "paths": (paths, [], 'hg paths [NAME]'),
1874 1874 "^pull":
1875 1875 (pull,
1876 1876 [('u', 'update', None, 'update working directory'),
1877 1877 ('e', 'ssh', "", 'ssh command'),
1878 1878 ('', 'remotecmd', "", 'remote hg command')],
1879 1879 'hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]'),
1880 1880 "^push":
1881 1881 (push,
1882 1882 [('f', 'force', None, 'force push'),
1883 1883 ('e', 'ssh', "", 'ssh command'),
1884 1884 ('', 'remotecmd', "", 'remote hg command')],
1885 1885 'hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]'),
1886 1886 "rawcommit":
1887 1887 (rawcommit,
1888 1888 [('p', 'parent', [], 'parent'),
1889 1889 ('d', 'date', "", 'date code'),
1890 1890 ('u', 'user', "", 'user'),
1891 1891 ('F', 'files', "", 'file list'),
1892 1892 ('m', 'message', "", 'commit message'),
1893 1893 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1894 1894 ('l', 'logfile', "", 'commit message file')],
1895 1895 'hg rawcommit [OPTION]... [FILE]...'),
1896 1896 "recover": (recover, [], "hg recover"),
1897 1897 "^remove|rm": (remove,
1898 1898 [('I', 'include', [], 'include path in search'),
1899 1899 ('X', 'exclude', [], 'exclude path from search')],
1900 1900 "hg remove [OPTION]... FILE..."),
1901 1901 "rename|mv": (rename,
1902 1902 [('I', 'include', [], 'include path in search'),
1903 1903 ('X', 'exclude', [], 'exclude path from search'),
1904 1904 ('A', 'after', None, 'record a copy after it has happened'),
1905 1905 ('f', 'force', None, 'replace destination if it exists'),
1906 1906 ('p', 'parents', None, 'append source path to dest')],
1907 1907 'hg rename [OPTION]... [SOURCE]... DEST'),
1908 1908 "^revert":
1909 1909 (revert,
1910 1910 [("n", "nonrecursive", None, "don't recurse into subdirs"),
1911 1911 ("r", "rev", "", "revision")],
1912 1912 "hg revert [-n] [-r REV] [NAME]..."),
1913 1913 "root": (root, [], "hg root"),
1914 1914 "^serve":
1915 1915 (serve,
1916 1916 [('A', 'accesslog', '', 'access log file'),
1917 1917 ('E', 'errorlog', '', 'error log file'),
1918 1918 ('p', 'port', 0, 'listen port'),
1919 1919 ('a', 'address', '', 'interface address'),
1920 1920 ('n', 'name', "", 'repository name'),
1921 1921 ('', 'stdio', None, 'for remote clients'),
1922 1922 ('t', 'templates', "", 'template directory'),
1923 1923 ('', 'style', "", 'template style'),
1924 1924 ('6', 'ipv6', None, 'use IPv6 in addition to IPv4')],
1925 1925 "hg serve [OPTION]..."),
1926 1926 "^status":
1927 1927 (status,
1928 1928 [('m', 'modified', None, 'show only modified files'),
1929 1929 ('a', 'added', None, 'show only added files'),
1930 1930 ('r', 'removed', None, 'show only removed files'),
1931 1931 ('u', 'unknown', None, 'show only unknown (not tracked) files'),
1932 1932 ('n', 'no-status', None, 'hide status prefix'),
1933 1933 ('0', 'print0', None, 'end filenames with NUL'),
1934 1934 ('I', 'include', [], 'include path in search'),
1935 1935 ('X', 'exclude', [], 'exclude path from search')],
1936 1936 "hg status [OPTION]... [FILE]..."),
1937 1937 "tag":
1938 1938 (tag,
1939 1939 [('l', 'local', None, 'make the tag local'),
1940 1940 ('m', 'message', "", 'commit message'),
1941 1941 ('t', 'text', "", 'commit message (deprecated: use -m)'),
1942 1942 ('d', 'date', "", 'date code'),
1943 1943 ('u', 'user', "", 'user')],
1944 1944 'hg tag [OPTION]... NAME [REV]'),
1945 1945 "tags": (tags, [], 'hg tags'),
1946 1946 "tip": (tip, [], 'hg tip'),
1947 1947 "unbundle":
1948 1948 (unbundle,
1949 1949 [],
1950 1950 'hg unbundle FILE'),
1951 1951 "undo": (undo, [], 'hg undo'),
1952 1952 "^update|up|checkout|co":
1953 1953 (update,
1954 1954 [('b', 'branch', "", 'checkout the head of a specific branch'),
1955 1955 ('m', 'merge', None, 'allow merging of conflicts'),
1956 1956 ('C', 'clean', None, 'overwrite locally modified files')],
1957 1957 'hg update [-b TAG] [-m] [-C] [REV]'),
1958 1958 "verify": (verify, [], 'hg verify'),
1959 1959 "version": (show_version, [], 'hg version'),
1960 1960 }
1961 1961
1962 1962 globalopts = [
1963 1963 ('R', 'repository', "", 'repository root directory'),
1964 1964 ('', 'cwd', '', 'change working directory'),
1965 1965 ('y', 'noninteractive', None, 'run non-interactively'),
1966 1966 ('q', 'quiet', None, 'quiet mode'),
1967 1967 ('v', 'verbose', None, 'verbose mode'),
1968 1968 ('', 'debug', None, 'debug mode'),
1969 1969 ('', 'debugger', None, 'start debugger'),
1970 1970 ('', 'traceback', None, 'print traceback on exception'),
1971 1971 ('', 'time', None, 'time how long the command takes'),
1972 1972 ('', 'profile', None, 'profile'),
1973 1973 ('', 'version', None, 'output version information and exit'),
1974 1974 ('h', 'help', None, 'display help and exit'),
1975 1975 ]
1976 1976
1977 1977 norepo = ("clone init version help debugancestor debugconfig debugdata"
1978 1978 " debugindex debugindexdot paths")
1979 1979
1980 1980 def find(cmd):
1981 1981 for e in table.keys():
1982 1982 if re.match("(%s)$" % e, cmd):
1983 1983 return e, table[e]
1984 1984
1985 1985 raise UnknownCommand(cmd)
1986 1986
1987 1987 class SignalInterrupt(Exception):
1988 1988 """Exception raised on SIGTERM and SIGHUP."""
1989 1989
1990 1990 def catchterm(*args):
1991 1991 raise SignalInterrupt
1992 1992
1993 1993 def run():
1994 1994 sys.exit(dispatch(sys.argv[1:]))
1995 1995
1996 1996 class ParseError(Exception):
1997 1997 """Exception raised on errors in parsing the command line."""
1998 1998
1999 1999 def parse(args):
2000 2000 options = {}
2001 2001 cmdoptions = {}
2002 2002
2003 2003 try:
2004 2004 args = fancyopts.fancyopts(args, globalopts, options)
2005 2005 except fancyopts.getopt.GetoptError, inst:
2006 2006 raise ParseError(None, inst)
2007 2007
2008 2008 if args:
2009 2009 cmd, args = args[0], args[1:]
2010 2010 i = find(cmd)[1]
2011 2011 c = list(i[1])
2012 2012 else:
2013 2013 cmd = None
2014 2014 c = []
2015 2015
2016 2016 # combine global options into local
2017 2017 for o in globalopts:
2018 2018 c.append((o[0], o[1], options[o[1]], o[3]))
2019 2019
2020 2020 try:
2021 2021 args = fancyopts.fancyopts(args, c, cmdoptions)
2022 2022 except fancyopts.getopt.GetoptError, inst:
2023 2023 raise ParseError(cmd, inst)
2024 2024
2025 2025 # separate global options back out
2026 2026 for o in globalopts:
2027 2027 n = o[1]
2028 2028 options[n] = cmdoptions[n]
2029 2029 del cmdoptions[n]
2030 2030
2031 2031 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
2032 2032
2033 2033 def dispatch(args):
2034 2034 signal.signal(signal.SIGTERM, catchterm)
2035 2035 try:
2036 2036 signal.signal(signal.SIGHUP, catchterm)
2037 2037 except AttributeError:
2038 2038 pass
2039 2039
2040 2040 u = ui.ui()
2041 2041 external = []
2042 2042 for x in u.extensions():
2043 2043 if x[1]:
2044 2044 mod = imp.load_source(x[0], x[1])
2045 2045 else:
2046 2046 def importh(name):
2047 2047 mod = __import__(name)
2048 2048 components = name.split('.')
2049 2049 for comp in components[1:]:
2050 2050 mod = getattr(mod, comp)
2051 2051 return mod
2052 2052 mod = importh(x[0])
2053 2053 external.append(mod)
2054 2054 for x in external:
2055 2055 cmdtable = getattr(x, 'cmdtable', {})
2056 2056 for t in cmdtable:
2057 2057 if t in table:
2058 2058 u.warn("module %s overrides %s\n" % (x.__name__, t))
2059 2059 table.update(cmdtable)
2060 2060
2061 2061 try:
2062 2062 cmd, func, args, options, cmdoptions = parse(args)
2063 2063 except ParseError, inst:
2064 2064 if inst.args[0]:
2065 2065 u.warn("hg %s: %s\n" % (inst.args[0], inst.args[1]))
2066 2066 help_(u, inst.args[0])
2067 2067 else:
2068 2068 u.warn("hg: %s\n" % inst.args[1])
2069 2069 help_(u, 'shortlist')
2070 2070 sys.exit(-1)
2071 2071 except UnknownCommand, inst:
2072 2072 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2073 2073 help_(u, 'shortlist')
2074 2074 sys.exit(1)
2075 2075
2076 2076 if options["time"]:
2077 2077 def get_times():
2078 2078 t = os.times()
2079 2079 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
2080 2080 t = (t[0], t[1], t[2], t[3], time.clock())
2081 2081 return t
2082 2082 s = get_times()
2083 2083 def print_time():
2084 2084 t = get_times()
2085 2085 u.warn("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n" %
2086 2086 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
2087 2087 atexit.register(print_time)
2088 2088
2089 2089 u.updateopts(options["verbose"], options["debug"], options["quiet"],
2090 2090 not options["noninteractive"])
2091 2091
2092 2092 # enter the debugger before command execution
2093 2093 if options['debugger']:
2094 2094 pdb.set_trace()
2095 2095
2096 2096 try:
2097 2097 try:
2098 2098 if options['help']:
2099 2099 help_(u, cmd, options['version'])
2100 2100 sys.exit(0)
2101 2101 elif options['version']:
2102 2102 show_version(u)
2103 2103 sys.exit(0)
2104 2104 elif not cmd:
2105 2105 help_(u, 'shortlist')
2106 2106 sys.exit(0)
2107 2107
2108 2108 if options['cwd']:
2109 2109 try:
2110 2110 os.chdir(options['cwd'])
2111 2111 except OSError, inst:
2112 2112 raise util.Abort('%s: %s' %
2113 2113 (options['cwd'], inst.strerror))
2114 2114
2115 2115 if cmd not in norepo.split():
2116 2116 path = options["repository"] or ""
2117 2117 repo = hg.repository(ui=u, path=path)
2118 2118 for x in external:
2119 2119 if hasattr(x, 'reposetup'): x.reposetup(u, repo)
2120 2120 d = lambda: func(u, repo, *args, **cmdoptions)
2121 2121 else:
2122 2122 d = lambda: func(u, *args, **cmdoptions)
2123 2123
2124 2124 if options['profile']:
2125 2125 import hotshot, hotshot.stats
2126 2126 prof = hotshot.Profile("hg.prof")
2127 2127 r = prof.runcall(d)
2128 2128 prof.close()
2129 2129 stats = hotshot.stats.load("hg.prof")
2130 2130 stats.strip_dirs()
2131 2131 stats.sort_stats('time', 'calls')
2132 2132 stats.print_stats(40)
2133 2133 return r
2134 2134 else:
2135 2135 return d()
2136 2136 except:
2137 2137 # enter the debugger when we hit an exception
2138 2138 if options['debugger']:
2139 2139 pdb.post_mortem(sys.exc_info()[2])
2140 2140 if options['traceback']:
2141 2141 traceback.print_exc()
2142 2142 raise
2143 2143 except hg.RepoError, inst:
2144 2144 u.warn("abort: ", inst, "!\n")
2145 2145 except revlog.RevlogError, inst:
2146 2146 u.warn("abort: ", inst, "!\n")
2147 2147 except SignalInterrupt:
2148 2148 u.warn("killed!\n")
2149 2149 except KeyboardInterrupt:
2150 2150 try:
2151 2151 u.warn("interrupted!\n")
2152 2152 except IOError, inst:
2153 2153 if inst.errno == errno.EPIPE:
2154 2154 if u.debugflag:
2155 2155 u.warn("\nbroken pipe\n")
2156 2156 else:
2157 2157 raise
2158 2158 except IOError, inst:
2159 2159 if hasattr(inst, "code"):
2160 2160 u.warn("abort: %s\n" % inst)
2161 2161 elif hasattr(inst, "reason"):
2162 2162 u.warn("abort: error: %s\n" % inst.reason[1])
2163 2163 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
2164 2164 if u.debugflag:
2165 2165 u.warn("broken pipe\n")
2166 2166 else:
2167 2167 raise
2168 2168 except OSError, inst:
2169 2169 if hasattr(inst, "filename"):
2170 2170 u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename))
2171 2171 else:
2172 2172 u.warn("abort: %s\n" % inst.strerror)
2173 2173 except util.Abort, inst:
2174 2174 u.warn('abort: ', inst.args[0] % inst.args[1:], '\n')
2175 2175 sys.exit(1)
2176 2176 except TypeError, inst:
2177 2177 # was this an argument error?
2178 2178 tb = traceback.extract_tb(sys.exc_info()[2])
2179 2179 if len(tb) > 2: # no
2180 2180 raise
2181 2181 u.debug(inst, "\n")
2182 2182 u.warn("%s: invalid arguments\n" % cmd)
2183 2183 help_(u, cmd)
2184 2184 except UnknownCommand, inst:
2185 2185 u.warn("hg: unknown command '%s'\n" % inst.args[0])
2186 2186 help_(u, 'shortlist')
2187 2187 except SystemExit:
2188 2188 # don't catch this in the catch-all below
2189 2189 raise
2190 2190 except:
2191 2191 u.warn("** unknown exception encountered, details follow\n")
2192 2192 u.warn("** report bug details to mercurial@selenic.com\n")
2193 2193 raise
2194 2194
2195 2195 sys.exit(-1)
@@ -1,977 +1,977 b''
1 1 # hgweb.py - web interface to a mercurial repository
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os, cgi, sys
10 10 from demandload import demandload
11 11 demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
12 12 demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
13 13 from node import *
14 14
15 15 def templatepath():
16 16 for f in "templates", "../templates":
17 17 p = os.path.join(os.path.dirname(__file__), f)
18 18 if os.path.isdir(p):
19 19 return p
20 20
21 21 def age(x):
22 22 def plural(t, c):
23 23 if c == 1:
24 24 return t
25 25 return t + "s"
26 26 def fmt(t, c):
27 27 return "%d %s" % (c, plural(t, c))
28 28
29 29 now = time.time()
30 then = int(x[2].split(' ')[0])
30 then = x[2][0]
31 31 delta = max(1, int(now - then))
32 32
33 33 scales = [["second", 1],
34 34 ["minute", 60],
35 35 ["hour", 3600],
36 36 ["day", 3600 * 24],
37 37 ["week", 3600 * 24 * 7],
38 38 ["month", 3600 * 24 * 30],
39 39 ["year", 3600 * 24 * 365]]
40 40
41 41 scales.reverse()
42 42
43 43 for t, s in scales:
44 44 n = delta / s
45 45 if n >= 2 or s == 1:
46 46 return fmt(t, n)
47 47
48 48 def nl2br(text):
49 49 return text.replace('\n', '<br/>\n')
50 50
51 51 def obfuscate(text):
52 52 return ''.join(['&#%d;' % ord(c) for c in text])
53 53
54 54 def up(p):
55 55 if p[0] != "/":
56 56 p = "/" + p
57 57 if p[-1] == "/":
58 58 p = p[:-1]
59 59 up = os.path.dirname(p)
60 60 if up == "/":
61 61 return "/"
62 62 return up + "/"
63 63
64 64 class hgrequest:
65 65 def __init__(self, inp=None, out=None, env=None):
66 66 self.inp = inp or sys.stdin
67 67 self.out = out or sys.stdout
68 68 self.env = env or os.environ
69 69 self.form = cgi.parse(self.inp, self.env)
70 70
71 71 def write(self, *things):
72 72 for thing in things:
73 73 if hasattr(thing, "__iter__"):
74 74 for part in thing:
75 75 self.write(part)
76 76 else:
77 77 try:
78 78 self.out.write(str(thing))
79 79 except socket.error, inst:
80 80 if inst[0] != errno.ECONNRESET:
81 81 raise
82 82
83 83 def header(self, headers=[('Content-type','text/html')]):
84 84 for header in headers:
85 85 self.out.write("%s: %s\r\n" % header)
86 86 self.out.write("\r\n")
87 87
88 88 def httphdr(self, type, file="", size=0):
89 89
90 90 headers = [('Content-type', type)]
91 91 if file:
92 92 headers.append(('Content-disposition', 'attachment; filename=%s' % file))
93 93 if size > 0:
94 94 headers.append(('Content-length', str(size)))
95 95 self.header(headers)
96 96
97 97 class templater:
98 98 def __init__(self, mapfile, filters={}, defaults={}):
99 99 self.cache = {}
100 100 self.map = {}
101 101 self.base = os.path.dirname(mapfile)
102 102 self.filters = filters
103 103 self.defaults = defaults
104 104
105 105 for l in file(mapfile):
106 106 m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
107 107 if m:
108 108 self.cache[m.group(1)] = m.group(2)
109 109 else:
110 110 m = re.match(r'(\S+)\s*=\s*(\S+)', l)
111 111 if m:
112 112 self.map[m.group(1)] = os.path.join(self.base, m.group(2))
113 113 else:
114 114 raise LookupError("unknown map entry '%s'" % l)
115 115
116 116 def __call__(self, t, **map):
117 117 m = self.defaults.copy()
118 118 m.update(map)
119 119 try:
120 120 tmpl = self.cache[t]
121 121 except KeyError:
122 122 tmpl = self.cache[t] = file(self.map[t]).read()
123 123 return self.template(tmpl, self.filters, **m)
124 124
125 125 def template(self, tmpl, filters={}, **map):
126 126 while tmpl:
127 127 m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
128 128 if m:
129 129 yield tmpl[:m.start(0)]
130 130 v = map.get(m.group(1), "")
131 131 v = callable(v) and v(**map) or v
132 132
133 133 format = m.group(2)
134 134 fl = m.group(4)
135 135
136 136 if format:
137 137 q = v.__iter__
138 138 for i in q():
139 139 lm = map.copy()
140 140 lm.update(i)
141 141 yield self(format[1:], **lm)
142 142
143 143 v = ""
144 144
145 145 elif fl:
146 146 for f in fl.split("|")[1:]:
147 147 v = filters[f](v)
148 148
149 149 yield v
150 150 tmpl = tmpl[m.end(0):]
151 151 else:
152 152 yield tmpl
153 153 return
154 154
155 155 common_filters = {
156 156 "escape": cgi.escape,
157 157 "age": age,
158 "date": util.datestr,
158 "date": lambda x: util.datestr(x[2]),
159 159 "addbreaks": nl2br,
160 160 "obfuscate": obfuscate,
161 161 "short": (lambda x: x[:12]),
162 162 "firstline": (lambda x: x.splitlines(1)[0]),
163 163 "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
164 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
164 "rfc822date": lambda x: util.datestr(x[2], "%a, %d %b %Y %H:%M:%S"),
165 165 }
166 166
167 167 class hgweb:
168 168 def __init__(self, repo, name=None):
169 169 if type(repo) == type(""):
170 170 self.repo = hg.repository(ui.ui(), repo)
171 171 else:
172 172 self.repo = repo
173 173
174 174 self.mtime = -1
175 175 self.reponame = name
176 176 self.archives = 'zip', 'gz', 'bz2'
177 177
178 178 def refresh(self):
179 179 s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
180 180 if s.st_mtime != self.mtime:
181 181 self.mtime = s.st_mtime
182 182 self.repo = hg.repository(self.repo.ui, self.repo.root)
183 183 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
184 184 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
185 185 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
186 186
187 187 def date(self, cs):
188 return util.datestr(cs)
188 return util.datestr(cs[2])
189 189
190 190 def listfiles(self, files, mf):
191 191 for f in files[:self.maxfiles]:
192 192 yield self.t("filenodelink", node=hex(mf[f]), file=f)
193 193 if len(files) > self.maxfiles:
194 194 yield self.t("fileellipses")
195 195
196 196 def listfilediffs(self, files, changeset):
197 197 for f in files[:self.maxfiles]:
198 198 yield self.t("filedifflink", node=hex(changeset), file=f)
199 199 if len(files) > self.maxfiles:
200 200 yield self.t("fileellipses")
201 201
202 202 def parents(self, t1, nodes=[], rev=None,**args):
203 203 if not rev:
204 204 rev = lambda x: ""
205 205 for node in nodes:
206 206 if node != nullid:
207 207 yield self.t(t1, node=hex(node), rev=rev(node), **args)
208 208
209 209 def showtag(self, t1, node=nullid, **args):
210 210 for t in self.repo.nodetags(node):
211 211 yield self.t(t1, tag=t, **args)
212 212
213 213 def diff(self, node1, node2, files):
214 214 def filterfiles(list, files):
215 215 l = [x for x in list if x in files]
216 216
217 217 for f in files:
218 218 if f[-1] != os.sep:
219 219 f += os.sep
220 220 l += [x for x in list if x.startswith(f)]
221 221 return l
222 222
223 223 parity = [0]
224 224 def diffblock(diff, f, fn):
225 225 yield self.t("diffblock",
226 226 lines=prettyprintlines(diff),
227 227 parity=parity[0],
228 228 file=f,
229 229 filenode=hex(fn or nullid))
230 230 parity[0] = 1 - parity[0]
231 231
232 232 def prettyprintlines(diff):
233 233 for l in diff.splitlines(1):
234 234 if l.startswith('+'):
235 235 yield self.t("difflineplus", line=l)
236 236 elif l.startswith('-'):
237 237 yield self.t("difflineminus", line=l)
238 238 elif l.startswith('@'):
239 239 yield self.t("difflineat", line=l)
240 240 else:
241 241 yield self.t("diffline", line=l)
242 242
243 243 r = self.repo
244 244 cl = r.changelog
245 245 mf = r.manifest
246 246 change1 = cl.read(node1)
247 247 change2 = cl.read(node2)
248 248 mmap1 = mf.read(change1[0])
249 249 mmap2 = mf.read(change2[0])
250 250 date1 = self.date(change1)
251 251 date2 = self.date(change2)
252 252
253 253 c, a, d, u = r.changes(node1, node2)
254 254 if files:
255 255 c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
256 256
257 257 for f in c:
258 258 to = r.file(f).read(mmap1[f])
259 259 tn = r.file(f).read(mmap2[f])
260 260 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
261 261 for f in a:
262 262 to = None
263 263 tn = r.file(f).read(mmap2[f])
264 264 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
265 265 for f in d:
266 266 to = r.file(f).read(mmap1[f])
267 267 tn = None
268 268 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
269 269
270 270 def changelog(self, pos):
271 271 def changenav(**map):
272 272 def seq(factor=1):
273 273 yield 1 * factor
274 274 yield 3 * factor
275 275 #yield 5 * factor
276 276 for f in seq(factor * 10):
277 277 yield f
278 278
279 279 l = []
280 280 for f in seq():
281 281 if f < self.maxchanges / 2:
282 282 continue
283 283 if f > count:
284 284 break
285 285 r = "%d" % f
286 286 if pos + f < count:
287 287 l.append(("+" + r, pos + f))
288 288 if pos - f >= 0:
289 289 l.insert(0, ("-" + r, pos - f))
290 290
291 291 yield {"rev": 0, "label": "(0)"}
292 292
293 293 for label, rev in l:
294 294 yield {"label": label, "rev": rev}
295 295
296 296 yield {"label": "tip", "rev": ""}
297 297
298 298 def changelist(**map):
299 299 parity = (start - end) & 1
300 300 cl = self.repo.changelog
301 301 l = [] # build a list in forward order for efficiency
302 302 for i in range(start, end):
303 303 n = cl.node(i)
304 304 changes = cl.read(n)
305 305 hn = hex(n)
306 306
307 307 l.insert(0, {"parity": parity,
308 308 "author": changes[1],
309 309 "parent": self.parents("changelogparent",
310 310 cl.parents(n), cl.rev),
311 311 "changelogtag": self.showtag("changelogtag",n),
312 312 "manifest": hex(changes[0]),
313 313 "desc": changes[4],
314 314 "date": changes,
315 315 "files": self.listfilediffs(changes[3], n),
316 316 "rev": i,
317 317 "node": hn})
318 318 parity = 1 - parity
319 319
320 320 for e in l:
321 321 yield e
322 322
323 323 cl = self.repo.changelog
324 324 mf = cl.read(cl.tip())[0]
325 325 count = cl.count()
326 326 start = max(0, pos - self.maxchanges + 1)
327 327 end = min(count, start + self.maxchanges)
328 328 pos = end - 1
329 329
330 330 yield self.t('changelog',
331 331 changenav=changenav,
332 332 manifest=hex(mf),
333 333 rev=pos, changesets=count, entries=changelist)
334 334
335 335 def search(self, query):
336 336
337 337 def changelist(**map):
338 338 cl = self.repo.changelog
339 339 count = 0
340 340 qw = query.lower().split()
341 341
342 342 def revgen():
343 343 for i in range(cl.count() - 1, 0, -100):
344 344 l = []
345 345 for j in range(max(0, i - 100), i):
346 346 n = cl.node(j)
347 347 changes = cl.read(n)
348 348 l.append((n, j, changes))
349 349 l.reverse()
350 350 for e in l:
351 351 yield e
352 352
353 353 for n, i, changes in revgen():
354 354 miss = 0
355 355 for q in qw:
356 356 if not (q in changes[1].lower() or
357 357 q in changes[4].lower() or
358 358 q in " ".join(changes[3][:20]).lower()):
359 359 miss = 1
360 360 break
361 361 if miss:
362 362 continue
363 363
364 364 count += 1
365 365 hn = hex(n)
366 366
367 367 yield self.t('searchentry',
368 368 parity=count & 1,
369 369 author=changes[1],
370 370 parent=self.parents("changelogparent",
371 371 cl.parents(n), cl.rev),
372 372 changelogtag=self.showtag("changelogtag",n),
373 373 manifest=hex(changes[0]),
374 374 desc=changes[4],
375 375 date=changes,
376 376 files=self.listfilediffs(changes[3], n),
377 377 rev=i,
378 378 node=hn)
379 379
380 380 if count >= self.maxchanges:
381 381 break
382 382
383 383 cl = self.repo.changelog
384 384 mf = cl.read(cl.tip())[0]
385 385
386 386 yield self.t('search',
387 387 query=query,
388 388 manifest=hex(mf),
389 389 entries=changelist)
390 390
391 391 def changeset(self, nodeid):
392 392 n = bin(nodeid)
393 393 cl = self.repo.changelog
394 394 changes = cl.read(n)
395 395 p1 = cl.parents(n)[0]
396 396
397 397 files = []
398 398 mf = self.repo.manifest.read(changes[0])
399 399 for f in changes[3]:
400 400 files.append(self.t("filenodelink",
401 401 filenode=hex(mf.get(f, nullid)), file=f))
402 402
403 403 def diff(**map):
404 404 yield self.diff(p1, n, None)
405 405
406 406 def archivelist():
407 407 for i in self.archives:
408 408 if self.repo.ui.configbool("web", "allow" + i, False):
409 409 yield {"type" : i, "node" : nodeid}
410 410
411 411 yield self.t('changeset',
412 412 diff=diff,
413 413 rev=cl.rev(n),
414 414 node=nodeid,
415 415 parent=self.parents("changesetparent",
416 416 cl.parents(n), cl.rev),
417 417 changesettag=self.showtag("changesettag",n),
418 418 manifest=hex(changes[0]),
419 419 author=changes[1],
420 420 desc=changes[4],
421 421 date=changes,
422 422 files=files,
423 423 archives=archivelist())
424 424
425 425 def filelog(self, f, filenode):
426 426 cl = self.repo.changelog
427 427 fl = self.repo.file(f)
428 428 count = fl.count()
429 429
430 430 def entries(**map):
431 431 l = []
432 432 parity = (count - 1) & 1
433 433
434 434 for i in range(count):
435 435 n = fl.node(i)
436 436 lr = fl.linkrev(n)
437 437 cn = cl.node(lr)
438 438 cs = cl.read(cl.node(lr))
439 439
440 440 l.insert(0, {"parity": parity,
441 441 "filenode": hex(n),
442 442 "filerev": i,
443 443 "file": f,
444 444 "node": hex(cn),
445 445 "author": cs[1],
446 446 "date": cs,
447 447 "parent": self.parents("filelogparent",
448 448 fl.parents(n),
449 449 fl.rev, file=f),
450 450 "desc": cs[4]})
451 451 parity = 1 - parity
452 452
453 453 for e in l:
454 454 yield e
455 455
456 456 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
457 457
458 458 def filerevision(self, f, node):
459 459 fl = self.repo.file(f)
460 460 n = bin(node)
461 461 text = fl.read(n)
462 462 changerev = fl.linkrev(n)
463 463 cl = self.repo.changelog
464 464 cn = cl.node(changerev)
465 465 cs = cl.read(cn)
466 466 mfn = cs[0]
467 467
468 468 def lines():
469 469 for l, t in enumerate(text.splitlines(1)):
470 470 yield {"line": t,
471 471 "linenumber": "% 6d" % (l + 1),
472 472 "parity": l & 1}
473 473
474 474 yield self.t("filerevision",
475 475 file=f,
476 476 filenode=node,
477 477 path=up(f),
478 478 text=lines(),
479 479 rev=changerev,
480 480 node=hex(cn),
481 481 manifest=hex(mfn),
482 482 author=cs[1],
483 483 date=cs,
484 484 parent=self.parents("filerevparent",
485 485 fl.parents(n), fl.rev, file=f),
486 486 permissions=self.repo.manifest.readflags(mfn)[f])
487 487
488 488 def fileannotate(self, f, node):
489 489 bcache = {}
490 490 ncache = {}
491 491 fl = self.repo.file(f)
492 492 n = bin(node)
493 493 changerev = fl.linkrev(n)
494 494
495 495 cl = self.repo.changelog
496 496 cn = cl.node(changerev)
497 497 cs = cl.read(cn)
498 498 mfn = cs[0]
499 499
500 500 def annotate(**map):
501 501 parity = 1
502 502 last = None
503 503 for r, l in fl.annotate(n):
504 504 try:
505 505 cnode = ncache[r]
506 506 except KeyError:
507 507 cnode = ncache[r] = self.repo.changelog.node(r)
508 508
509 509 try:
510 510 name = bcache[r]
511 511 except KeyError:
512 512 cl = self.repo.changelog.read(cnode)
513 513 bcache[r] = name = self.repo.ui.shortuser(cl[1])
514 514
515 515 if last != cnode:
516 516 parity = 1 - parity
517 517 last = cnode
518 518
519 519 yield {"parity": parity,
520 520 "node": hex(cnode),
521 521 "rev": r,
522 522 "author": name,
523 523 "file": f,
524 524 "line": l}
525 525
526 526 yield self.t("fileannotate",
527 527 file=f,
528 528 filenode=node,
529 529 annotate=annotate,
530 530 path=up(f),
531 531 rev=changerev,
532 532 node=hex(cn),
533 533 manifest=hex(mfn),
534 534 author=cs[1],
535 535 date=cs,
536 536 parent=self.parents("fileannotateparent",
537 537 fl.parents(n), fl.rev, file=f),
538 538 permissions=self.repo.manifest.readflags(mfn)[f])
539 539
540 540 def manifest(self, mnode, path):
541 541 mf = self.repo.manifest.read(bin(mnode))
542 542 rev = self.repo.manifest.rev(bin(mnode))
543 543 node = self.repo.changelog.node(rev)
544 544 mff=self.repo.manifest.readflags(bin(mnode))
545 545
546 546 files = {}
547 547
548 548 p = path[1:]
549 549 l = len(p)
550 550
551 551 for f,n in mf.items():
552 552 if f[:l] != p:
553 553 continue
554 554 remain = f[l:]
555 555 if "/" in remain:
556 556 short = remain[:remain.find("/") + 1] # bleah
557 557 files[short] = (f, None)
558 558 else:
559 559 short = os.path.basename(remain)
560 560 files[short] = (f, n)
561 561
562 562 def filelist(**map):
563 563 parity = 0
564 564 fl = files.keys()
565 565 fl.sort()
566 566 for f in fl:
567 567 full, fnode = files[f]
568 568 if not fnode:
569 569 continue
570 570
571 571 yield {"file": full,
572 572 "manifest": mnode,
573 573 "filenode": hex(fnode),
574 574 "parity": parity,
575 575 "basename": f,
576 576 "permissions": mff[full]}
577 577 parity = 1 - parity
578 578
579 579 def dirlist(**map):
580 580 parity = 0
581 581 fl = files.keys()
582 582 fl.sort()
583 583 for f in fl:
584 584 full, fnode = files[f]
585 585 if fnode:
586 586 continue
587 587
588 588 yield {"parity": parity,
589 589 "path": os.path.join(path, f),
590 590 "manifest": mnode,
591 591 "basename": f[:-1]}
592 592 parity = 1 - parity
593 593
594 594 yield self.t("manifest",
595 595 manifest=mnode,
596 596 rev=rev,
597 597 node=hex(node),
598 598 path=path,
599 599 up=up(path),
600 600 fentries=filelist,
601 601 dentries=dirlist)
602 602
603 603 def tags(self):
604 604 cl = self.repo.changelog
605 605 mf = cl.read(cl.tip())[0]
606 606
607 607 i = self.repo.tagslist()
608 608 i.reverse()
609 609
610 610 def entries(**map):
611 611 parity = 0
612 612 for k,n in i:
613 613 yield {"parity": parity,
614 614 "tag": k,
615 615 "node": hex(n)}
616 616 parity = 1 - parity
617 617
618 618 yield self.t("tags",
619 619 manifest=hex(mf),
620 620 entries=entries)
621 621
622 622 def filediff(self, file, changeset):
623 623 n = bin(changeset)
624 624 cl = self.repo.changelog
625 625 p1 = cl.parents(n)[0]
626 626 cs = cl.read(n)
627 627 mf = self.repo.manifest.read(cs[0])
628 628
629 629 def diff(**map):
630 630 yield self.diff(p1, n, file)
631 631
632 632 yield self.t("filediff",
633 633 file=file,
634 634 filenode=hex(mf.get(file, nullid)),
635 635 node=changeset,
636 636 rev=self.repo.changelog.rev(n),
637 637 parent=self.parents("filediffparent",
638 638 cl.parents(n), cl.rev),
639 639 diff=diff)
640 640
641 641 def archive(self, req, cnode, type):
642 642 cs = self.repo.changelog.read(cnode)
643 643 mnode = cs[0]
644 644 mf = self.repo.manifest.read(mnode)
645 645 rev = self.repo.manifest.rev(mnode)
646 646 reponame = re.sub(r"\W+", "-", self.reponame)
647 647 name = "%s-%s/" % (reponame, short(cnode))
648 648
649 649 files = mf.keys()
650 650 files.sort()
651 651
652 652 if type == 'zip':
653 653 tmp = tempfile.mkstemp()[1]
654 654 try:
655 655 zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
656 656
657 657 for f in files:
658 658 zf.writestr(name + f, self.repo.file(f).read(mf[f]))
659 659 zf.close()
660 660
661 661 f = open(tmp, 'r')
662 662 req.httphdr('application/zip', name[:-1] + '.zip',
663 663 os.path.getsize(tmp))
664 664 req.write(f.read())
665 665 f.close()
666 666 finally:
667 667 os.unlink(tmp)
668 668
669 669 else:
670 670 tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out)
671 671 mff = self.repo.manifest.readflags(mnode)
672 672 mtime = int(time.time())
673 673
674 674 if type == "gz":
675 675 encoding = "gzip"
676 676 else:
677 677 encoding = "x-bzip2"
678 678 req.header([('Content-type', 'application/x-tar'),
679 679 ('Content-disposition', 'attachment; filename=%s%s%s' %
680 680 (name[:-1], '.tar.', type)),
681 681 ('Content-encoding', encoding)])
682 682 for fname in files:
683 683 rcont = self.repo.file(fname).read(mf[fname])
684 684 finfo = tarfile.TarInfo(name + fname)
685 685 finfo.mtime = mtime
686 686 finfo.size = len(rcont)
687 687 finfo.mode = mff[fname] and 0755 or 0644
688 688 tf.addfile(finfo, StringIO.StringIO(rcont))
689 689 tf.close()
690 690
691 691 # add tags to things
692 692 # tags -> list of changesets corresponding to tags
693 693 # find tag, changeset, file
694 694
695 695 def run(self, req=hgrequest()):
696 696 def header(**map):
697 697 yield self.t("header", **map)
698 698
699 699 def footer(**map):
700 700 yield self.t("footer", **map)
701 701
702 702 self.refresh()
703 703
704 704 t = self.repo.ui.config("web", "templates", templatepath())
705 705 m = os.path.join(t, "map")
706 706 style = self.repo.ui.config("web", "style", "")
707 707 if req.form.has_key('style'):
708 708 style = req.form['style'][0]
709 709 if style:
710 710 b = os.path.basename("map-" + style)
711 711 p = os.path.join(t, b)
712 712 if os.path.isfile(p):
713 713 m = p
714 714
715 715 port = req.env["SERVER_PORT"]
716 716 port = port != "80" and (":" + port) or ""
717 717 uri = req.env["REQUEST_URI"]
718 718 if "?" in uri:
719 719 uri = uri.split("?")[0]
720 720 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
721 721 if not self.reponame:
722 722 self.reponame = (self.repo.ui.config("web", "name")
723 723 or uri.strip('/') or self.repo.root)
724 724
725 725 self.t = templater(m, common_filters,
726 726 {"url": url,
727 727 "repo": self.reponame,
728 728 "header": header,
729 729 "footer": footer,
730 730 })
731 731
732 732 if not req.form.has_key('cmd'):
733 733 req.form['cmd'] = [self.t.cache['default'],]
734 734
735 735 if req.form['cmd'][0] == 'changelog':
736 736 c = self.repo.changelog.count() - 1
737 737 hi = c
738 738 if req.form.has_key('rev'):
739 739 hi = req.form['rev'][0]
740 740 try:
741 741 hi = self.repo.changelog.rev(self.repo.lookup(hi))
742 742 except hg.RepoError:
743 743 req.write(self.search(hi))
744 744 return
745 745
746 746 req.write(self.changelog(hi))
747 747
748 748 elif req.form['cmd'][0] == 'changeset':
749 749 req.write(self.changeset(req.form['node'][0]))
750 750
751 751 elif req.form['cmd'][0] == 'manifest':
752 752 req.write(self.manifest(req.form['manifest'][0], req.form['path'][0]))
753 753
754 754 elif req.form['cmd'][0] == 'tags':
755 755 req.write(self.tags())
756 756
757 757 elif req.form['cmd'][0] == 'filediff':
758 758 req.write(self.filediff(req.form['file'][0], req.form['node'][0]))
759 759
760 760 elif req.form['cmd'][0] == 'file':
761 761 req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0]))
762 762
763 763 elif req.form['cmd'][0] == 'annotate':
764 764 req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0]))
765 765
766 766 elif req.form['cmd'][0] == 'filelog':
767 767 req.write(self.filelog(req.form['file'][0], req.form['filenode'][0]))
768 768
769 769 elif req.form['cmd'][0] == 'heads':
770 770 req.httphdr("application/mercurial-0.1")
771 771 h = self.repo.heads()
772 772 req.write(" ".join(map(hex, h)) + "\n")
773 773
774 774 elif req.form['cmd'][0] == 'branches':
775 775 req.httphdr("application/mercurial-0.1")
776 776 nodes = []
777 777 if req.form.has_key('nodes'):
778 778 nodes = map(bin, req.form['nodes'][0].split(" "))
779 779 for b in self.repo.branches(nodes):
780 780 req.write(" ".join(map(hex, b)) + "\n")
781 781
782 782 elif req.form['cmd'][0] == 'between':
783 783 req.httphdr("application/mercurial-0.1")
784 784 nodes = []
785 785 if req.form.has_key('pairs'):
786 786 pairs = [map(bin, p.split("-"))
787 787 for p in req.form['pairs'][0].split(" ")]
788 788 for b in self.repo.between(pairs):
789 789 req.write(" ".join(map(hex, b)) + "\n")
790 790
791 791 elif req.form['cmd'][0] == 'changegroup':
792 792 req.httphdr("application/mercurial-0.1")
793 793 nodes = []
794 794 if not self.allowpull:
795 795 return
796 796
797 797 if req.form.has_key('roots'):
798 798 nodes = map(bin, req.form['roots'][0].split(" "))
799 799
800 800 z = zlib.compressobj()
801 801 f = self.repo.changegroup(nodes)
802 802 while 1:
803 803 chunk = f.read(4096)
804 804 if not chunk:
805 805 break
806 806 req.write(z.compress(chunk))
807 807
808 808 req.write(z.flush())
809 809
810 810 elif req.form['cmd'][0] == 'archive':
811 811 changeset = bin(req.form['node'][0])
812 812 type = req.form['type'][0]
813 813 if (type in self.archives and
814 814 self.repo.ui.configbool("web", "allow" + type, False)):
815 815 self.archive(req, changeset, type)
816 816 return
817 817
818 818 req.write(self.t("error"))
819 819
820 820 else:
821 821 req.write(self.t("error"))
822 822
823 823 def create_server(repo):
824 824
825 825 def openlog(opt, default):
826 826 if opt and opt != '-':
827 827 return open(opt, 'w')
828 828 return default
829 829
830 830 address = repo.ui.config("web", "address", "")
831 831 port = int(repo.ui.config("web", "port", 8000))
832 832 use_ipv6 = repo.ui.configbool("web", "ipv6")
833 833 accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
834 834 errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)
835 835
836 836 class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
837 837 address_family = getattr(socket, 'AF_INET6', None)
838 838
839 839 def __init__(self, *args, **kwargs):
840 840 if self.address_family is None:
841 841 raise hg.RepoError('IPv6 not available on this system')
842 842 BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)
843 843
844 844 class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
845 845 def log_error(self, format, *args):
846 846 errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
847 847 self.log_date_time_string(),
848 848 format % args))
849 849
850 850 def log_message(self, format, *args):
851 851 accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
852 852 self.log_date_time_string(),
853 853 format % args))
854 854
855 855 def do_POST(self):
856 856 try:
857 857 self.do_hgweb()
858 858 except socket.error, inst:
859 859 if inst[0] != errno.EPIPE:
860 860 raise
861 861
862 862 def do_GET(self):
863 863 self.do_POST()
864 864
865 865 def do_hgweb(self):
866 866 query = ""
867 867 p = self.path.find("?")
868 868 if p:
869 869 query = self.path[p + 1:]
870 870 query = query.replace('+', ' ')
871 871
872 872 env = {}
873 873 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
874 874 env['REQUEST_METHOD'] = self.command
875 875 env['SERVER_NAME'] = self.server.server_name
876 876 env['SERVER_PORT'] = str(self.server.server_port)
877 877 env['REQUEST_URI'] = "/"
878 878 if query:
879 879 env['QUERY_STRING'] = query
880 880 host = self.address_string()
881 881 if host != self.client_address[0]:
882 882 env['REMOTE_HOST'] = host
883 883 env['REMOTE_ADDR'] = self.client_address[0]
884 884
885 885 if self.headers.typeheader is None:
886 886 env['CONTENT_TYPE'] = self.headers.type
887 887 else:
888 888 env['CONTENT_TYPE'] = self.headers.typeheader
889 889 length = self.headers.getheader('content-length')
890 890 if length:
891 891 env['CONTENT_LENGTH'] = length
892 892 accept = []
893 893 for line in self.headers.getallmatchingheaders('accept'):
894 894 if line[:1] in "\t\n\r ":
895 895 accept.append(line.strip())
896 896 else:
897 897 accept = accept + line[7:].split(',')
898 898 env['HTTP_ACCEPT'] = ','.join(accept)
899 899
900 900 req = hgrequest(self.rfile, self.wfile, env)
901 901 self.send_response(200, "Script output follows")
902 902 hg.run(req)
903 903
904 904 hg = hgweb(repo)
905 905 if use_ipv6:
906 906 return IPv6HTTPServer((address, port), hgwebhandler)
907 907 else:
908 908 return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)
909 909
910 910 def server(path, name, templates, address, port, use_ipv6=False,
911 911 accesslog=sys.stdout, errorlog=sys.stderr):
912 912 httpd = create_server(path, name, templates, address, port, use_ipv6,
913 913 accesslog, errorlog)
914 914 httpd.serve_forever()
915 915
916 916 # This is a stopgap
917 917 class hgwebdir:
918 918 def __init__(self, config):
919 919 def cleannames(items):
920 920 return [(name.strip('/'), path) for name, path in items]
921 921
922 922 if type(config) == type([]):
923 923 self.repos = cleannames(config)
924 924 elif type(config) == type({}):
925 925 self.repos = cleannames(config.items())
926 926 self.repos.sort()
927 927 else:
928 928 cp = ConfigParser.SafeConfigParser()
929 929 cp.read(config)
930 930 self.repos = cleannames(cp.items("paths"))
931 931 self.repos.sort()
932 932
933 933 def run(self, req=hgrequest()):
934 934 def header(**map):
935 935 yield tmpl("header", **map)
936 936
937 937 def footer(**map):
938 938 yield tmpl("footer", **map)
939 939
940 940 m = os.path.join(templatepath(), "map")
941 941 tmpl = templater(m, common_filters,
942 942 {"header": header, "footer": footer})
943 943
944 944 def entries(**map):
945 945 parity = 0
946 946 for name, path in self.repos:
947 947 u = ui.ui()
948 948 try:
949 949 u.readconfig(file(os.path.join(path, '.hg', 'hgrc')))
950 950 except IOError:
951 951 pass
952 952 get = u.config
953 953
954 954 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
955 955 .replace("//", "/"))
956 956
957 957 yield dict(contact=(get("ui", "username") or # preferred
958 958 get("web", "contact") or # deprecated
959 959 get("web", "author", "unknown")), # also
960 960 name=get("web", "name", name),
961 961 url=url,
962 962 parity=parity,
963 963 shortdesc=get("web", "description", "unknown"),
964 964 lastupdate=os.stat(os.path.join(path, ".hg",
965 965 "00changelog.d")).st_mtime)
966 966
967 967 parity = 1 - parity
968 968
969 969 virtual = req.env.get("PATH_INFO", "").strip('/')
970 970 if virtual:
971 971 real = dict(self.repos).get(virtual)
972 972 if real:
973 973 hgweb(real).run(req)
974 974 else:
975 975 req.write(tmpl("notfound", repo=virtual))
976 976 else:
977 977 req.write(tmpl("index", entries=entries))
@@ -1,565 +1,561 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5
6 6 This software may be used and distributed according to the terms
7 7 of the GNU General Public License, incorporated herein by reference.
8 8
9 9 This contains helper routines that are independent of the SCM core and hide
10 10 platform-specific details from the core.
11 11 """
12 12
13 13 import os, errno
14 14 from demandload import *
15 15 demandload(globals(), "re cStringIO shutil popen2 tempfile threading time")
16 16
17 17 def pipefilter(s, cmd):
18 18 '''filter string S through command CMD, returning its output'''
19 19 (pout, pin) = popen2.popen2(cmd, -1, 'b')
20 20 def writer():
21 21 pin.write(s)
22 22 pin.close()
23 23
24 24 # we should use select instead on UNIX, but this will work on most
25 25 # systems, including Windows
26 26 w = threading.Thread(target=writer)
27 27 w.start()
28 28 f = pout.read()
29 29 pout.close()
30 30 w.join()
31 31 return f
32 32
33 33 def tempfilter(s, cmd):
34 34 '''filter string S through a pair of temporary files with CMD.
35 35 CMD is used as a template to create the real command to be run,
36 36 with the strings INFILE and OUTFILE replaced by the real names of
37 37 the temporary files generated.'''
38 38 inname, outname = None, None
39 39 try:
40 40 infd, inname = tempfile.mkstemp(prefix='hgfin')
41 41 fp = os.fdopen(infd, 'wb')
42 42 fp.write(s)
43 43 fp.close()
44 44 outfd, outname = tempfile.mkstemp(prefix='hgfout')
45 45 os.close(outfd)
46 46 cmd = cmd.replace('INFILE', inname)
47 47 cmd = cmd.replace('OUTFILE', outname)
48 48 code = os.system(cmd)
49 49 if code: raise Abort("command '%s' failed: %s" %
50 50 (cmd, explain_exit(code)))
51 51 return open(outname, 'rb').read()
52 52 finally:
53 53 try:
54 54 if inname: os.unlink(inname)
55 55 except: pass
56 56 try:
57 57 if outname: os.unlink(outname)
58 58 except: pass
59 59
60 60 filtertable = {
61 61 'tempfile:': tempfilter,
62 62 'pipe:': pipefilter,
63 63 }
64 64
65 65 def filter(s, cmd):
66 66 "filter a string through a command that transforms its input to its output"
67 67 for name, fn in filtertable.iteritems():
68 68 if cmd.startswith(name):
69 69 return fn(s, cmd[len(name):].lstrip())
70 70 return pipefilter(s, cmd)
71 71
72 72 def patch(strip, patchname, ui):
73 73 """apply the patch <patchname> to the working directory.
74 74 a list of patched files is returned"""
75 75 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
76 76 files = {}
77 77 for line in fp:
78 78 line = line.rstrip()
79 79 ui.status("%s\n" % line)
80 80 if line.startswith('patching file '):
81 81 pf = parse_patch_output(line)
82 82 files.setdefault(pf, 1)
83 83 code = fp.close()
84 84 if code:
85 85 raise Abort("patch command failed: %s" % explain_exit(code))
86 86 return files.keys()
87 87
88 88 def binary(s):
89 89 """return true if a string is binary data using diff's heuristic"""
90 90 if s and '\0' in s[:4096]:
91 91 return True
92 92 return False
93 93
94 94 def unique(g):
95 95 """return the uniq elements of iterable g"""
96 96 seen = {}
97 97 for f in g:
98 98 if f not in seen:
99 99 seen[f] = 1
100 100 yield f
101 101
102 102 class Abort(Exception):
103 103 """Raised if a command needs to print an error and exit."""
104 104
105 105 def always(fn): return True
106 106 def never(fn): return False
107 107
108 108 def globre(pat, head='^', tail='$'):
109 109 "convert a glob pattern into a regexp"
110 110 i, n = 0, len(pat)
111 111 res = ''
112 112 group = False
113 113 def peek(): return i < n and pat[i]
114 114 while i < n:
115 115 c = pat[i]
116 116 i = i+1
117 117 if c == '*':
118 118 if peek() == '*':
119 119 i += 1
120 120 res += '.*'
121 121 else:
122 122 res += '[^/]*'
123 123 elif c == '?':
124 124 res += '.'
125 125 elif c == '[':
126 126 j = i
127 127 if j < n and pat[j] in '!]':
128 128 j += 1
129 129 while j < n and pat[j] != ']':
130 130 j += 1
131 131 if j >= n:
132 132 res += '\\['
133 133 else:
134 134 stuff = pat[i:j].replace('\\','\\\\')
135 135 i = j + 1
136 136 if stuff[0] == '!':
137 137 stuff = '^' + stuff[1:]
138 138 elif stuff[0] == '^':
139 139 stuff = '\\' + stuff
140 140 res = '%s[%s]' % (res, stuff)
141 141 elif c == '{':
142 142 group = True
143 143 res += '(?:'
144 144 elif c == '}' and group:
145 145 res += ')'
146 146 group = False
147 147 elif c == ',' and group:
148 148 res += '|'
149 149 else:
150 150 res += re.escape(c)
151 151 return head + res + tail
152 152
153 153 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
154 154
155 155 def pathto(n1, n2):
156 156 '''return the relative path from one place to another.
157 157 this returns a path in the form used by the local filesystem, not hg.'''
158 158 if not n1: return localpath(n2)
159 159 a, b = n1.split('/'), n2.split('/')
160 160 a.reverse(), b.reverse()
161 161 while a and b and a[-1] == b[-1]:
162 162 a.pop(), b.pop()
163 163 b.reverse()
164 164 return os.sep.join((['..'] * len(a)) + b)
165 165
166 166 def canonpath(root, cwd, myname):
167 167 """return the canonical path of myname, given cwd and root"""
168 168 rootsep = root + os.sep
169 169 name = myname
170 170 if not name.startswith(os.sep):
171 171 name = os.path.join(root, cwd, name)
172 172 name = os.path.normpath(name)
173 173 if name.startswith(rootsep):
174 174 return pconvert(name[len(rootsep):])
175 175 elif name == root:
176 176 return ''
177 177 else:
178 178 raise Abort('%s not under root' % myname)
179 179
180 180 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
181 181 """build a function to match a set of file patterns
182 182
183 183 arguments:
184 184 canonroot - the canonical root of the tree you're matching against
185 185 cwd - the current working directory, if relevant
186 186 names - patterns to find
187 187 inc - patterns to include
188 188 exc - patterns to exclude
189 189 head - a regex to prepend to patterns to control whether a match is rooted
190 190
191 191 a pattern is one of:
192 192 'glob:<rooted glob>'
193 193 're:<rooted regexp>'
194 194 'path:<rooted path>'
195 195 'relglob:<relative glob>'
196 196 'relpath:<relative path>'
197 197 'relre:<relative regexp>'
198 198 '<rooted path or regexp>'
199 199
200 200 returns:
201 201 a 3-tuple containing
202 202 - list of explicit non-pattern names passed in
203 203 - a bool match(filename) function
204 204 - a bool indicating if any patterns were passed in
205 205
206 206 todo:
207 207 make head regex a rooted bool
208 208 """
209 209
210 210 def patkind(name):
211 211 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
212 212 if name.startswith(prefix + ':'): return name.split(':', 1)
213 213 for c in name:
214 214 if c in _globchars: return 'glob', name
215 215 return 'relpath', name
216 216
217 217 def regex(kind, name, tail):
218 218 '''convert a pattern into a regular expression'''
219 219 if kind == 're':
220 220 return name
221 221 elif kind == 'path':
222 222 return '^' + re.escape(name) + '(?:/|$)'
223 223 elif kind == 'relglob':
224 224 return head + globre(name, '(?:|.*/)', tail)
225 225 elif kind == 'relpath':
226 226 return head + re.escape(name) + tail
227 227 elif kind == 'relre':
228 228 if name.startswith('^'):
229 229 return name
230 230 return '.*' + name
231 231 return head + globre(name, '', tail)
232 232
233 233 def matchfn(pats, tail):
234 234 """build a matching function from a set of patterns"""
235 235 if pats:
236 236 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
237 237 return re.compile(pat).match
238 238
239 239 def globprefix(pat):
240 240 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
241 241 root = []
242 242 for p in pat.split(os.sep):
243 243 if patkind(p)[0] == 'glob': break
244 244 root.append(p)
245 245 return '/'.join(root)
246 246
247 247 pats = []
248 248 files = []
249 249 roots = []
250 250 for kind, name in map(patkind, names):
251 251 if kind in ('glob', 'relpath'):
252 252 name = canonpath(canonroot, cwd, name)
253 253 if name == '':
254 254 kind, name = 'glob', '**'
255 255 if kind in ('glob', 'path', 're'):
256 256 pats.append((kind, name))
257 257 if kind == 'glob':
258 258 root = globprefix(name)
259 259 if root: roots.append(root)
260 260 elif kind == 'relpath':
261 261 files.append((kind, name))
262 262 roots.append(name)
263 263
264 264 patmatch = matchfn(pats, '$') or always
265 265 filematch = matchfn(files, '(?:/|$)') or always
266 266 incmatch = always
267 267 if inc:
268 268 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
269 269 excmatch = lambda fn: False
270 270 if exc:
271 271 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
272 272
273 273 return (roots,
274 274 lambda fn: (incmatch(fn) and not excmatch(fn) and
275 275 (fn.endswith('/') or
276 276 (not pats and not files) or
277 277 (pats and patmatch(fn)) or
278 278 (files and filematch(fn)))),
279 279 (inc or exc or (pats and pats != [('glob', '**')])) and True)
280 280
281 281 def system(cmd, errprefix=None):
282 282 """execute a shell command that must succeed"""
283 283 rc = os.system(cmd)
284 284 if rc:
285 285 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
286 286 explain_exit(rc)[0])
287 287 if errprefix:
288 288 errmsg = "%s: %s" % (errprefix, errmsg)
289 289 raise Abort(errmsg)
290 290
291 291 def rename(src, dst):
292 292 """forcibly rename a file"""
293 293 try:
294 294 os.rename(src, dst)
295 295 except:
296 296 os.unlink(dst)
297 297 os.rename(src, dst)
298 298
299 299 def copyfiles(src, dst, hardlink=None):
300 300 """Copy a directory tree using hardlinks if possible"""
301 301
302 302 if hardlink is None:
303 303 hardlink = (os.stat(src).st_dev ==
304 304 os.stat(os.path.dirname(dst)).st_dev)
305 305
306 306 if os.path.isdir(src):
307 307 os.mkdir(dst)
308 308 for name in os.listdir(src):
309 309 srcname = os.path.join(src, name)
310 310 dstname = os.path.join(dst, name)
311 311 copyfiles(srcname, dstname, hardlink)
312 312 else:
313 313 if hardlink:
314 314 try:
315 315 os_link(src, dst)
316 316 except:
317 317 hardlink = False
318 318 shutil.copy2(src, dst)
319 319 else:
320 320 shutil.copy2(src, dst)
321 321
322 322 def opener(base):
323 323 """
324 324 return a function that opens files relative to base
325 325
326 326 this function is used to hide the details of COW semantics and
327 327 remote file access from higher level code.
328 328 """
329 329 p = base
330 330 def o(path, mode="r"):
331 331 f = os.path.join(p, path)
332 332
333 333 mode += "b" # for that other OS
334 334
335 335 if mode[0] != "r":
336 336 try:
337 337 nlink = nlinks(f)
338 338 except OSError:
339 339 d = os.path.dirname(f)
340 340 if not os.path.isdir(d):
341 341 os.makedirs(d)
342 342 else:
343 343 if nlink > 1:
344 344 file(f + ".tmp", "wb").write(file(f, "rb").read())
345 345 rename(f+".tmp", f)
346 346
347 347 return file(f, mode)
348 348
349 349 return o
350 350
351 351 def _makelock_file(info, pathname):
352 352 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
353 353 os.write(ld, info)
354 354 os.close(ld)
355 355
356 356 def _readlock_file(pathname):
357 357 return file(pathname).read()
358 358
359 359 def nlinks(pathname):
360 360 """Return number of hardlinks for the given file."""
361 361 return os.stat(pathname).st_nlink
362 362
363 363 if hasattr(os, 'link'):
364 364 os_link = os.link
365 365 else:
366 366 def os_link(src, dst):
367 367 raise OSError(0, "Hardlinks not supported")
368 368
369 369 # Platform specific variants
370 370 if os.name == 'nt':
371 371 nulldev = 'NUL:'
372 372
373 373 rcpath = (r'c:\mercurial\mercurial.ini',
374 374 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
375 375
376 376 def parse_patch_output(output_line):
377 377 """parses the output produced by patch and returns the file name"""
378 378 pf = output_line[14:]
379 379 if pf[0] == '`':
380 380 pf = pf[1:-1] # Remove the quotes
381 381 return pf
382 382
383 383 try: # ActivePython can create hard links using win32file module
384 384 import win32file
385 385
386 386 def os_link(src, dst): # NB will only succeed on NTFS
387 387 win32file.CreateHardLink(dst, src)
388 388
389 389 def nlinks(pathname):
390 390 """Return number of hardlinks for the given file."""
391 391 try:
392 392 fh = win32file.CreateFile(pathname,
393 393 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
394 394 None, win32file.OPEN_EXISTING, 0, None)
395 395 res = win32file.GetFileInformationByHandle(fh)
396 396 fh.Close()
397 397 return res[7]
398 398 except:
399 399 return os.stat(pathname).st_nlink
400 400
401 401 except ImportError:
402 402 pass
403 403
404 404 def is_exec(f, last):
405 405 return last
406 406
407 407 def set_exec(f, mode):
408 408 pass
409 409
410 410 def pconvert(path):
411 411 return path.replace("\\", "/")
412 412
413 413 def localpath(path):
414 414 return path.replace('/', '\\')
415 415
416 416 def normpath(path):
417 417 return pconvert(os.path.normpath(path))
418 418
419 419 makelock = _makelock_file
420 420 readlock = _readlock_file
421 421
422 422 def explain_exit(code):
423 423 return "exited with status %d" % code, code
424 424
425 425 else:
426 426 nulldev = '/dev/null'
427 427
428 428 rcpath = map(os.path.normpath,
429 429 ('/etc/mercurial/hgrc', os.path.expanduser('~/.hgrc')))
430 430
431 431 def parse_patch_output(output_line):
432 432 """parses the output produced by patch and returns the file name"""
433 433 return output_line[14:]
434 434
435 435 def is_exec(f, last):
436 436 """check whether a file is executable"""
437 437 return (os.stat(f).st_mode & 0100 != 0)
438 438
439 439 def set_exec(f, mode):
440 440 s = os.stat(f).st_mode
441 441 if (s & 0100 != 0) == mode:
442 442 return
443 443 if mode:
444 444 # Turn on +x for every +r bit when making a file executable
445 445 # and obey umask.
446 446 umask = os.umask(0)
447 447 os.umask(umask)
448 448 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
449 449 else:
450 450 os.chmod(f, s & 0666)
451 451
452 452 def pconvert(path):
453 453 return path
454 454
455 455 def localpath(path):
456 456 return path
457 457
458 458 normpath = os.path.normpath
459 459
460 460 def makelock(info, pathname):
461 461 try:
462 462 os.symlink(info, pathname)
463 463 except OSError, why:
464 464 if why.errno == errno.EEXIST:
465 465 raise
466 466 else:
467 467 _makelock_file(info, pathname)
468 468
469 469 def readlock(pathname):
470 470 try:
471 471 return os.readlink(pathname)
472 472 except OSError, why:
473 473 if why.errno == errno.EINVAL:
474 474 return _readlock_file(pathname)
475 475 else:
476 476 raise
477 477
478 478 def explain_exit(code):
479 479 """return a 2-tuple (desc, code) describing a process's status"""
480 480 if os.WIFEXITED(code):
481 481 val = os.WEXITSTATUS(code)
482 482 return "exited with status %d" % val, val
483 483 elif os.WIFSIGNALED(code):
484 484 val = os.WTERMSIG(code)
485 485 return "killed by signal %d" % val, val
486 486 elif os.WIFSTOPPED(code):
487 487 val = os.WSTOPSIG(code)
488 488 return "stopped by signal %d" % val, val
489 489 raise ValueError("invalid exit code")
490 490
491 491 class chunkbuffer(object):
492 492 """Allow arbitrary sized chunks of data to be efficiently read from an
493 493 iterator over chunks of arbitrary size."""
494 494
495 495 def __init__(self, in_iter, targetsize = 2**16):
496 496 """in_iter is the iterator that's iterating over the input chunks.
497 497 targetsize is how big a buffer to try to maintain."""
498 498 self.in_iter = iter(in_iter)
499 499 self.buf = ''
500 500 self.targetsize = int(targetsize)
501 501 if self.targetsize <= 0:
502 502 raise ValueError("targetsize must be greater than 0, was %d" %
503 503 targetsize)
504 504 self.iterempty = False
505 505
506 506 def fillbuf(self):
507 507 """Ignore target size; read every chunk from iterator until empty."""
508 508 if not self.iterempty:
509 509 collector = cStringIO.StringIO()
510 510 collector.write(self.buf)
511 511 for ch in self.in_iter:
512 512 collector.write(ch)
513 513 self.buf = collector.getvalue()
514 514 self.iterempty = True
515 515
516 516 def read(self, l):
517 517 """Read L bytes of data from the iterator of chunks of data.
518 518 Returns less than L bytes if the iterator runs dry."""
519 519 if l > len(self.buf) and not self.iterempty:
520 520 # Clamp to a multiple of self.targetsize
521 521 targetsize = self.targetsize * ((l // self.targetsize) + 1)
522 522 collector = cStringIO.StringIO()
523 523 collector.write(self.buf)
524 524 collected = len(self.buf)
525 525 for chunk in self.in_iter:
526 526 collector.write(chunk)
527 527 collected += len(chunk)
528 528 if collected >= targetsize:
529 529 break
530 530 if collected < targetsize:
531 531 self.iterempty = True
532 532 self.buf = collector.getvalue()
533 533 s, self.buf = self.buf[:l], buffer(self.buf, l)
534 534 return s
535 535
536 536 def filechunkiter(f, size = 65536):
537 537 """Create a generator that produces all the data in the file size
538 538 (default 65536) bytes at a time. Chunks may be less than size
539 539 bytes if the chunk is the last chunk in the file, or the file is a
540 540 socket or some other type of file that sometimes reads less data
541 541 than is requested."""
542 542 s = f.read(size)
543 543 while len(s) >= 0:
544 544 yield s
545 545 s = f.read(size)
546 546
547 def datestr(change=None, format='%c'):
548 """represent a change date as a localized time.
549 a change date is a 'unixtime offset' string, where unixtime is
550 seconds since the epoch, and offset is seconds away from UTC."""
551 if change is None:
552 t = time.time()
553 if time.daylight: tz = time.altzone
554 else: tz = time.timezone
555 else:
556 t, tz = change[2].split(' ')
557 try:
558 # a conversion tool was sticking non-integer offsets into repos
559 tz = int(tz)
560 except ValueError:
561 tz = 0
547 def makedate():
548 t = time.time()
549 if time.daylight: tz = time.altzone
550 else: tz = time.timezone
551 return t, tz
552
553 def datestr(date=None, format='%c'):
554 """represent a (unixtime, offset) tuple as a localized time.
555 unixtime is seconds since the epoch, and offset is the time zone's
556 number of seconds away from UTC."""
557 t, tz = date or makedate()
562 558 return ("%s %+03d%02d" %
563 559 (time.strftime(format, time.gmtime(float(t) - tz)),
564 560 -tz / 3600,
565 561 ((-tz % 3600) / 60)))
General Comments 0
You need to be logged in to leave comments. Login now