##// END OF EJS Templates
hgweb: fix a crash when using web.archivesubrepos...
Matt Harbison -
r23232:a0ccb66f stable
parent child Browse files
Show More
@@ -1,1107 +1,1107 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import os, mimetypes, re, cgi, copy
9 9 import webutil
10 10 from mercurial import error, encoding, archival, templater, templatefilters
11 11 from mercurial.node import short, hex
12 12 from mercurial import util
13 13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 15 from mercurial import graphmod, patch
16 16 from mercurial import help as helpmod
17 17 from mercurial import scmutil
18 18 from mercurial.i18n import _
19 19 from mercurial.error import ParseError, RepoLookupError, Abort
20 20 from mercurial import revset
21 21
22 22 # __all__ is populated with the allowed commands. Be sure to add to it if
23 23 # you're adding a new command, or the new command won't work.
24 24
25 25 __all__ = [
26 26 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
27 27 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
28 28 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
29 29 ]
30 30
31 31 def log(web, req, tmpl):
32 32 if 'file' in req.form and req.form['file'][0]:
33 33 return filelog(web, req, tmpl)
34 34 else:
35 35 return changelog(web, req, tmpl)
36 36
37 37 def rawfile(web, req, tmpl):
38 38 guessmime = web.configbool('web', 'guessmime', False)
39 39
40 40 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
41 41 if not path:
42 42 content = manifest(web, req, tmpl)
43 43 req.respond(HTTP_OK, web.ctype)
44 44 return content
45 45
46 46 try:
47 47 fctx = webutil.filectx(web.repo, req)
48 48 except error.LookupError, inst:
49 49 try:
50 50 content = manifest(web, req, tmpl)
51 51 req.respond(HTTP_OK, web.ctype)
52 52 return content
53 53 except ErrorResponse:
54 54 raise inst
55 55
56 56 path = fctx.path()
57 57 text = fctx.data()
58 58 mt = 'application/binary'
59 59 if guessmime:
60 60 mt = mimetypes.guess_type(path)[0]
61 61 if mt is None:
62 62 mt = util.binary(text) and 'application/binary' or 'text/plain'
63 63 if mt.startswith('text/'):
64 64 mt += '; charset="%s"' % encoding.encoding
65 65
66 66 req.respond(HTTP_OK, mt, path, body=text)
67 67 return []
68 68
69 69 def _filerevision(web, tmpl, fctx):
70 70 f = fctx.path()
71 71 text = fctx.data()
72 72 parity = paritygen(web.stripecount)
73 73
74 74 if util.binary(text):
75 75 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
76 76 text = '(binary:%s)' % mt
77 77
78 78 def lines():
79 79 for lineno, t in enumerate(text.splitlines(True)):
80 80 yield {"line": t,
81 81 "lineid": "l%d" % (lineno + 1),
82 82 "linenumber": "% 6d" % (lineno + 1),
83 83 "parity": parity.next()}
84 84
85 85 return tmpl("filerevision",
86 86 file=f,
87 87 path=webutil.up(f),
88 88 text=lines(),
89 89 rev=fctx.rev(),
90 90 node=fctx.hex(),
91 91 author=fctx.user(),
92 92 date=fctx.date(),
93 93 desc=fctx.description(),
94 94 extra=fctx.extra(),
95 95 branch=webutil.nodebranchnodefault(fctx),
96 96 parent=webutil.parents(fctx),
97 97 child=webutil.children(fctx),
98 98 rename=webutil.renamelink(fctx),
99 99 permissions=fctx.manifest().flags(f))
100 100
101 101 def file(web, req, tmpl):
102 102 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
103 103 if not path:
104 104 return manifest(web, req, tmpl)
105 105 try:
106 106 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
107 107 except error.LookupError, inst:
108 108 try:
109 109 return manifest(web, req, tmpl)
110 110 except ErrorResponse:
111 111 raise inst
112 112
113 113 def _search(web, req, tmpl):
114 114 MODE_REVISION = 'rev'
115 115 MODE_KEYWORD = 'keyword'
116 116 MODE_REVSET = 'revset'
117 117
118 118 def revsearch(ctx):
119 119 yield ctx
120 120
121 121 def keywordsearch(query):
122 122 lower = encoding.lower
123 123 qw = lower(query).split()
124 124
125 125 def revgen():
126 126 cl = web.repo.changelog
127 127 for i in xrange(len(web.repo) - 1, 0, -100):
128 128 l = []
129 129 for j in cl.revs(max(0, i - 99), i):
130 130 ctx = web.repo[j]
131 131 l.append(ctx)
132 132 l.reverse()
133 133 for e in l:
134 134 yield e
135 135
136 136 for ctx in revgen():
137 137 miss = 0
138 138 for q in qw:
139 139 if not (q in lower(ctx.user()) or
140 140 q in lower(ctx.description()) or
141 141 q in lower(" ".join(ctx.files()))):
142 142 miss = 1
143 143 break
144 144 if miss:
145 145 continue
146 146
147 147 yield ctx
148 148
149 149 def revsetsearch(revs):
150 150 for r in revs:
151 151 yield web.repo[r]
152 152
153 153 searchfuncs = {
154 154 MODE_REVISION: (revsearch, 'exact revision search'),
155 155 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
156 156 MODE_REVSET: (revsetsearch, 'revset expression search'),
157 157 }
158 158
159 159 def getsearchmode(query):
160 160 try:
161 161 ctx = web.repo[query]
162 162 except (error.RepoError, error.LookupError):
163 163 # query is not an exact revision pointer, need to
164 164 # decide if it's a revset expression or keywords
165 165 pass
166 166 else:
167 167 return MODE_REVISION, ctx
168 168
169 169 revdef = 'reverse(%s)' % query
170 170 try:
171 171 tree, pos = revset.parse(revdef)
172 172 except ParseError:
173 173 # can't parse to a revset tree
174 174 return MODE_KEYWORD, query
175 175
176 176 if revset.depth(tree) <= 2:
177 177 # no revset syntax used
178 178 return MODE_KEYWORD, query
179 179
180 180 if util.any((token, (value or '')[:3]) == ('string', 're:')
181 181 for token, value, pos in revset.tokenize(revdef)):
182 182 return MODE_KEYWORD, query
183 183
184 184 funcsused = revset.funcsused(tree)
185 185 if not funcsused.issubset(revset.safesymbols):
186 186 return MODE_KEYWORD, query
187 187
188 188 mfunc = revset.match(web.repo.ui, revdef)
189 189 try:
190 190 revs = mfunc(web.repo, revset.baseset(web.repo))
191 191 return MODE_REVSET, revs
192 192 # ParseError: wrongly placed tokens, wrongs arguments, etc
193 193 # RepoLookupError: no such revision, e.g. in 'revision:'
194 194 # Abort: bookmark/tag not exists
195 195 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
196 196 except (ParseError, RepoLookupError, Abort, LookupError):
197 197 return MODE_KEYWORD, query
198 198
199 199 def changelist(**map):
200 200 count = 0
201 201
202 202 for ctx in searchfunc[0](funcarg):
203 203 count += 1
204 204 n = ctx.node()
205 205 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
206 206 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
207 207
208 208 yield tmpl('searchentry',
209 209 parity=parity.next(),
210 210 author=ctx.user(),
211 211 parent=webutil.parents(ctx),
212 212 child=webutil.children(ctx),
213 213 changelogtag=showtags,
214 214 desc=ctx.description(),
215 215 extra=ctx.extra(),
216 216 date=ctx.date(),
217 217 files=files,
218 218 rev=ctx.rev(),
219 219 node=hex(n),
220 220 tags=webutil.nodetagsdict(web.repo, n),
221 221 bookmarks=webutil.nodebookmarksdict(web.repo, n),
222 222 inbranch=webutil.nodeinbranch(web.repo, ctx),
223 223 branches=webutil.nodebranchdict(web.repo, ctx))
224 224
225 225 if count >= revcount:
226 226 break
227 227
228 228 query = req.form['rev'][0]
229 229 revcount = web.maxchanges
230 230 if 'revcount' in req.form:
231 231 try:
232 232 revcount = int(req.form.get('revcount', [revcount])[0])
233 233 revcount = max(revcount, 1)
234 234 tmpl.defaults['sessionvars']['revcount'] = revcount
235 235 except ValueError:
236 236 pass
237 237
238 238 lessvars = copy.copy(tmpl.defaults['sessionvars'])
239 239 lessvars['revcount'] = max(revcount / 2, 1)
240 240 lessvars['rev'] = query
241 241 morevars = copy.copy(tmpl.defaults['sessionvars'])
242 242 morevars['revcount'] = revcount * 2
243 243 morevars['rev'] = query
244 244
245 245 mode, funcarg = getsearchmode(query)
246 246
247 247 if 'forcekw' in req.form:
248 248 showforcekw = ''
249 249 showunforcekw = searchfuncs[mode][1]
250 250 mode = MODE_KEYWORD
251 251 funcarg = query
252 252 else:
253 253 if mode != MODE_KEYWORD:
254 254 showforcekw = searchfuncs[MODE_KEYWORD][1]
255 255 else:
256 256 showforcekw = ''
257 257 showunforcekw = ''
258 258
259 259 searchfunc = searchfuncs[mode]
260 260
261 261 tip = web.repo['tip']
262 262 parity = paritygen(web.stripecount)
263 263
264 264 return tmpl('search', query=query, node=tip.hex(),
265 265 entries=changelist, archives=web.archivelist("tip"),
266 266 morevars=morevars, lessvars=lessvars,
267 267 modedesc=searchfunc[1],
268 268 showforcekw=showforcekw, showunforcekw=showunforcekw)
269 269
270 270 def changelog(web, req, tmpl, shortlog=False):
271 271
272 272 query = ''
273 273 if 'node' in req.form:
274 274 ctx = webutil.changectx(web.repo, req)
275 275 elif 'rev' in req.form:
276 276 return _search(web, req, tmpl)
277 277 else:
278 278 ctx = web.repo['tip']
279 279
280 280 def changelist():
281 281 revs = []
282 282 if pos != -1:
283 283 revs = web.repo.changelog.revs(pos, 0)
284 284 curcount = 0
285 285 for i in revs:
286 286 ctx = web.repo[i]
287 287 n = ctx.node()
288 288 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
289 289 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
290 290
291 291 curcount += 1
292 292 if curcount > revcount + 1:
293 293 break
294 294 yield {"parity": parity.next(),
295 295 "author": ctx.user(),
296 296 "parent": webutil.parents(ctx, i - 1),
297 297 "child": webutil.children(ctx, i + 1),
298 298 "changelogtag": showtags,
299 299 "desc": ctx.description(),
300 300 "extra": ctx.extra(),
301 301 "date": ctx.date(),
302 302 "files": files,
303 303 "rev": i,
304 304 "node": hex(n),
305 305 "tags": webutil.nodetagsdict(web.repo, n),
306 306 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
307 307 "inbranch": webutil.nodeinbranch(web.repo, ctx),
308 308 "branches": webutil.nodebranchdict(web.repo, ctx)
309 309 }
310 310
311 311 revcount = shortlog and web.maxshortchanges or web.maxchanges
312 312 if 'revcount' in req.form:
313 313 try:
314 314 revcount = int(req.form.get('revcount', [revcount])[0])
315 315 revcount = max(revcount, 1)
316 316 tmpl.defaults['sessionvars']['revcount'] = revcount
317 317 except ValueError:
318 318 pass
319 319
320 320 lessvars = copy.copy(tmpl.defaults['sessionvars'])
321 321 lessvars['revcount'] = max(revcount / 2, 1)
322 322 morevars = copy.copy(tmpl.defaults['sessionvars'])
323 323 morevars['revcount'] = revcount * 2
324 324
325 325 count = len(web.repo)
326 326 pos = ctx.rev()
327 327 parity = paritygen(web.stripecount)
328 328
329 329 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
330 330
331 331 entries = list(changelist())
332 332 latestentry = entries[:1]
333 333 if len(entries) > revcount:
334 334 nextentry = entries[-1:]
335 335 entries = entries[:-1]
336 336 else:
337 337 nextentry = []
338 338
339 339 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
340 340 node=ctx.hex(), rev=pos, changesets=count,
341 341 entries=entries,
342 342 latestentry=latestentry, nextentry=nextentry,
343 343 archives=web.archivelist("tip"), revcount=revcount,
344 344 morevars=morevars, lessvars=lessvars, query=query)
345 345
346 346 def shortlog(web, req, tmpl):
347 347 return changelog(web, req, tmpl, shortlog=True)
348 348
349 349 def changeset(web, req, tmpl):
350 350 ctx = webutil.changectx(web.repo, req)
351 351 basectx = webutil.basechangectx(web.repo, req)
352 352 if basectx is None:
353 353 basectx = ctx.p1()
354 354 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
355 355 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
356 356 ctx.node())
357 357 showbranch = webutil.nodebranchnodefault(ctx)
358 358
359 359 files = []
360 360 parity = paritygen(web.stripecount)
361 361 for blockno, f in enumerate(ctx.files()):
362 362 template = f in ctx and 'filenodelink' or 'filenolink'
363 363 files.append(tmpl(template,
364 364 node=ctx.hex(), file=f, blockno=blockno + 1,
365 365 parity=parity.next()))
366 366
367 367 style = web.config('web', 'style', 'paper')
368 368 if 'style' in req.form:
369 369 style = req.form['style'][0]
370 370
371 371 parity = paritygen(web.stripecount)
372 372 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
373 373
374 374 parity = paritygen(web.stripecount)
375 375 diffstatgen = webutil.diffstatgen(ctx, basectx)
376 376 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
377 377
378 378 return tmpl('changeset',
379 379 diff=diffs,
380 380 rev=ctx.rev(),
381 381 node=ctx.hex(),
382 382 parent=webutil.parents(ctx),
383 383 child=webutil.children(ctx),
384 384 basenode=basectx.hex(),
385 385 changesettag=showtags,
386 386 changesetbookmark=showbookmarks,
387 387 changesetbranch=showbranch,
388 388 author=ctx.user(),
389 389 desc=ctx.description(),
390 390 extra=ctx.extra(),
391 391 date=ctx.date(),
392 392 files=files,
393 393 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
394 394 diffstat=diffstat,
395 395 archives=web.archivelist(ctx.hex()),
396 396 tags=webutil.nodetagsdict(web.repo, ctx.node()),
397 397 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
398 398 branch=webutil.nodebranchnodefault(ctx),
399 399 inbranch=webutil.nodeinbranch(web.repo, ctx),
400 400 branches=webutil.nodebranchdict(web.repo, ctx))
401 401
402 402 rev = changeset
403 403
404 404 def decodepath(path):
405 405 """Hook for mapping a path in the repository to a path in the
406 406 working copy.
407 407
408 408 Extensions (e.g., largefiles) can override this to remap files in
409 409 the virtual file system presented by the manifest command below."""
410 410 return path
411 411
412 412 def manifest(web, req, tmpl):
413 413 ctx = webutil.changectx(web.repo, req)
414 414 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
415 415 mf = ctx.manifest()
416 416 node = ctx.node()
417 417
418 418 files = {}
419 419 dirs = {}
420 420 parity = paritygen(web.stripecount)
421 421
422 422 if path and path[-1] != "/":
423 423 path += "/"
424 424 l = len(path)
425 425 abspath = "/" + path
426 426
427 427 for full, n in mf.iteritems():
428 428 # the virtual path (working copy path) used for the full
429 429 # (repository) path
430 430 f = decodepath(full)
431 431
432 432 if f[:l] != path:
433 433 continue
434 434 remain = f[l:]
435 435 elements = remain.split('/')
436 436 if len(elements) == 1:
437 437 files[remain] = full
438 438 else:
439 439 h = dirs # need to retain ref to dirs (root)
440 440 for elem in elements[0:-1]:
441 441 if elem not in h:
442 442 h[elem] = {}
443 443 h = h[elem]
444 444 if len(h) > 1:
445 445 break
446 446 h[None] = None # denotes files present
447 447
448 448 if mf and not files and not dirs:
449 449 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
450 450
451 451 def filelist(**map):
452 452 for f in sorted(files):
453 453 full = files[f]
454 454
455 455 fctx = ctx.filectx(full)
456 456 yield {"file": full,
457 457 "parity": parity.next(),
458 458 "basename": f,
459 459 "date": fctx.date(),
460 460 "size": fctx.size(),
461 461 "permissions": mf.flags(full)}
462 462
463 463 def dirlist(**map):
464 464 for d in sorted(dirs):
465 465
466 466 emptydirs = []
467 467 h = dirs[d]
468 468 while isinstance(h, dict) and len(h) == 1:
469 469 k, v = h.items()[0]
470 470 if v:
471 471 emptydirs.append(k)
472 472 h = v
473 473
474 474 path = "%s%s" % (abspath, d)
475 475 yield {"parity": parity.next(),
476 476 "path": path,
477 477 "emptydirs": "/".join(emptydirs),
478 478 "basename": d}
479 479
480 480 return tmpl("manifest",
481 481 rev=ctx.rev(),
482 482 node=hex(node),
483 483 path=abspath,
484 484 up=webutil.up(abspath),
485 485 upparity=parity.next(),
486 486 fentries=filelist,
487 487 dentries=dirlist,
488 488 archives=web.archivelist(hex(node)),
489 489 tags=webutil.nodetagsdict(web.repo, node),
490 490 bookmarks=webutil.nodebookmarksdict(web.repo, node),
491 491 inbranch=webutil.nodeinbranch(web.repo, ctx),
492 492 branches=webutil.nodebranchdict(web.repo, ctx))
493 493
494 494 def tags(web, req, tmpl):
495 495 i = list(reversed(web.repo.tagslist()))
496 496 parity = paritygen(web.stripecount)
497 497
498 498 def entries(notip, latestonly, **map):
499 499 t = i
500 500 if notip:
501 501 t = [(k, n) for k, n in i if k != "tip"]
502 502 if latestonly:
503 503 t = t[:1]
504 504 for k, n in t:
505 505 yield {"parity": parity.next(),
506 506 "tag": k,
507 507 "date": web.repo[n].date(),
508 508 "node": hex(n)}
509 509
510 510 return tmpl("tags",
511 511 node=hex(web.repo.changelog.tip()),
512 512 entries=lambda **x: entries(False, False, **x),
513 513 entriesnotip=lambda **x: entries(True, False, **x),
514 514 latestentry=lambda **x: entries(True, True, **x))
515 515
516 516 def bookmarks(web, req, tmpl):
517 517 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
518 518 parity = paritygen(web.stripecount)
519 519
520 520 def entries(latestonly, **map):
521 521 if latestonly:
522 522 t = [min(i)]
523 523 else:
524 524 t = sorted(i)
525 525 for k, n in t:
526 526 yield {"parity": parity.next(),
527 527 "bookmark": k,
528 528 "date": web.repo[n].date(),
529 529 "node": hex(n)}
530 530
531 531 return tmpl("bookmarks",
532 532 node=hex(web.repo.changelog.tip()),
533 533 entries=lambda **x: entries(latestonly=False, **x),
534 534 latestentry=lambda **x: entries(latestonly=True, **x))
535 535
536 536 def branches(web, req, tmpl):
537 537 tips = []
538 538 heads = web.repo.heads()
539 539 parity = paritygen(web.stripecount)
540 540 sortkey = lambda item: (not item[1], item[0].rev())
541 541
542 542 def entries(limit, **map):
543 543 count = 0
544 544 if not tips:
545 545 for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
546 546 tips.append((web.repo[tip], closed))
547 547 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
548 548 if limit > 0 and count >= limit:
549 549 return
550 550 count += 1
551 551 if closed:
552 552 status = 'closed'
553 553 elif ctx.node() not in heads:
554 554 status = 'inactive'
555 555 else:
556 556 status = 'open'
557 557 yield {'parity': parity.next(),
558 558 'branch': ctx.branch(),
559 559 'status': status,
560 560 'node': ctx.hex(),
561 561 'date': ctx.date()}
562 562
563 563 return tmpl('branches', node=hex(web.repo.changelog.tip()),
564 564 entries=lambda **x: entries(0, **x),
565 565 latestentry=lambda **x: entries(1, **x))
566 566
567 567 def summary(web, req, tmpl):
568 568 i = reversed(web.repo.tagslist())
569 569
570 570 def tagentries(**map):
571 571 parity = paritygen(web.stripecount)
572 572 count = 0
573 573 for k, n in i:
574 574 if k == "tip": # skip tip
575 575 continue
576 576
577 577 count += 1
578 578 if count > 10: # limit to 10 tags
579 579 break
580 580
581 581 yield tmpl("tagentry",
582 582 parity=parity.next(),
583 583 tag=k,
584 584 node=hex(n),
585 585 date=web.repo[n].date())
586 586
587 587 def bookmarks(**map):
588 588 parity = paritygen(web.stripecount)
589 589 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
590 590 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
591 591 yield {'parity': parity.next(),
592 592 'bookmark': k,
593 593 'date': web.repo[n].date(),
594 594 'node': hex(n)}
595 595
596 596 def branches(**map):
597 597 parity = paritygen(web.stripecount)
598 598
599 599 b = web.repo.branchmap()
600 600 l = [(-web.repo.changelog.rev(tip), tip, tag)
601 601 for tag, heads, tip, closed in b.iterbranches()]
602 602 for r, n, t in sorted(l):
603 603 yield {'parity': parity.next(),
604 604 'branch': t,
605 605 'node': hex(n),
606 606 'date': web.repo[n].date()}
607 607
608 608 def changelist(**map):
609 609 parity = paritygen(web.stripecount, offset=start - end)
610 610 l = [] # build a list in forward order for efficiency
611 611 revs = []
612 612 if start < end:
613 613 revs = web.repo.changelog.revs(start, end - 1)
614 614 for i in revs:
615 615 ctx = web.repo[i]
616 616 n = ctx.node()
617 617 hn = hex(n)
618 618
619 619 l.append(tmpl(
620 620 'shortlogentry',
621 621 parity=parity.next(),
622 622 author=ctx.user(),
623 623 desc=ctx.description(),
624 624 extra=ctx.extra(),
625 625 date=ctx.date(),
626 626 rev=i,
627 627 node=hn,
628 628 tags=webutil.nodetagsdict(web.repo, n),
629 629 bookmarks=webutil.nodebookmarksdict(web.repo, n),
630 630 inbranch=webutil.nodeinbranch(web.repo, ctx),
631 631 branches=webutil.nodebranchdict(web.repo, ctx)))
632 632
633 633 l.reverse()
634 634 yield l
635 635
636 636 tip = web.repo['tip']
637 637 count = len(web.repo)
638 638 start = max(0, count - web.maxchanges)
639 639 end = min(count, start + web.maxchanges)
640 640
641 641 return tmpl("summary",
642 642 desc=web.config("web", "description", "unknown"),
643 643 owner=get_contact(web.config) or "unknown",
644 644 lastchange=tip.date(),
645 645 tags=tagentries,
646 646 bookmarks=bookmarks,
647 647 branches=branches,
648 648 shortlog=changelist,
649 649 node=tip.hex(),
650 650 archives=web.archivelist("tip"))
651 651
652 652 def filediff(web, req, tmpl):
653 653 fctx, ctx = None, None
654 654 try:
655 655 fctx = webutil.filectx(web.repo, req)
656 656 except LookupError:
657 657 ctx = webutil.changectx(web.repo, req)
658 658 path = webutil.cleanpath(web.repo, req.form['file'][0])
659 659 if path not in ctx.files():
660 660 raise
661 661
662 662 if fctx is not None:
663 663 n = fctx.node()
664 664 path = fctx.path()
665 665 ctx = fctx.changectx()
666 666 else:
667 667 n = ctx.node()
668 668 # path already defined in except clause
669 669
670 670 parity = paritygen(web.stripecount)
671 671 style = web.config('web', 'style', 'paper')
672 672 if 'style' in req.form:
673 673 style = req.form['style'][0]
674 674
675 675 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
676 676 rename = fctx and webutil.renamelink(fctx) or []
677 677 ctx = fctx and fctx or ctx
678 678 return tmpl("filediff",
679 679 file=path,
680 680 node=hex(n),
681 681 rev=ctx.rev(),
682 682 date=ctx.date(),
683 683 desc=ctx.description(),
684 684 extra=ctx.extra(),
685 685 author=ctx.user(),
686 686 rename=rename,
687 687 branch=webutil.nodebranchnodefault(ctx),
688 688 parent=webutil.parents(ctx),
689 689 child=webutil.children(ctx),
690 690 diff=diffs)
691 691
692 692 diff = filediff
693 693
694 694 def comparison(web, req, tmpl):
695 695 ctx = webutil.changectx(web.repo, req)
696 696 if 'file' not in req.form:
697 697 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
698 698 path = webutil.cleanpath(web.repo, req.form['file'][0])
699 699 rename = path in ctx and webutil.renamelink(ctx[path]) or []
700 700
701 701 parsecontext = lambda v: v == 'full' and -1 or int(v)
702 702 if 'context' in req.form:
703 703 context = parsecontext(req.form['context'][0])
704 704 else:
705 705 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
706 706
707 707 def filelines(f):
708 708 if util.binary(f.data()):
709 709 mt = mimetypes.guess_type(f.path())[0]
710 710 if not mt:
711 711 mt = 'application/octet-stream'
712 712 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
713 713 return f.data().splitlines()
714 714
715 715 parent = ctx.p1()
716 716 leftrev = parent.rev()
717 717 leftnode = parent.node()
718 718 rightrev = ctx.rev()
719 719 rightnode = ctx.node()
720 720 if path in ctx:
721 721 fctx = ctx[path]
722 722 rightlines = filelines(fctx)
723 723 if path not in parent:
724 724 leftlines = ()
725 725 else:
726 726 pfctx = parent[path]
727 727 leftlines = filelines(pfctx)
728 728 else:
729 729 rightlines = ()
730 730 fctx = ctx.parents()[0][path]
731 731 leftlines = filelines(fctx)
732 732
733 733 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
734 734 return tmpl('filecomparison',
735 735 file=path,
736 736 node=hex(ctx.node()),
737 737 rev=ctx.rev(),
738 738 date=ctx.date(),
739 739 desc=ctx.description(),
740 740 extra=ctx.extra(),
741 741 author=ctx.user(),
742 742 rename=rename,
743 743 branch=webutil.nodebranchnodefault(ctx),
744 744 parent=webutil.parents(fctx),
745 745 child=webutil.children(fctx),
746 746 leftrev=leftrev,
747 747 leftnode=hex(leftnode),
748 748 rightrev=rightrev,
749 749 rightnode=hex(rightnode),
750 750 comparison=comparison)
751 751
752 752 def annotate(web, req, tmpl):
753 753 fctx = webutil.filectx(web.repo, req)
754 754 f = fctx.path()
755 755 parity = paritygen(web.stripecount)
756 756 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
757 757
758 758 def annotate(**map):
759 759 last = None
760 760 if util.binary(fctx.data()):
761 761 mt = (mimetypes.guess_type(fctx.path())[0]
762 762 or 'application/octet-stream')
763 763 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
764 764 '(binary:%s)' % mt)])
765 765 else:
766 766 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
767 767 diffopts=diffopts))
768 768 for lineno, ((f, targetline), l) in lines:
769 769 fnode = f.filenode()
770 770
771 771 if last != fnode:
772 772 last = fnode
773 773
774 774 yield {"parity": parity.next(),
775 775 "node": f.hex(),
776 776 "rev": f.rev(),
777 777 "author": f.user(),
778 778 "desc": f.description(),
779 779 "extra": f.extra(),
780 780 "file": f.path(),
781 781 "targetline": targetline,
782 782 "line": l,
783 783 "lineid": "l%d" % (lineno + 1),
784 784 "linenumber": "% 6d" % (lineno + 1),
785 785 "revdate": f.date()}
786 786
787 787 return tmpl("fileannotate",
788 788 file=f,
789 789 annotate=annotate,
790 790 path=webutil.up(f),
791 791 rev=fctx.rev(),
792 792 node=fctx.hex(),
793 793 author=fctx.user(),
794 794 date=fctx.date(),
795 795 desc=fctx.description(),
796 796 extra=fctx.extra(),
797 797 rename=webutil.renamelink(fctx),
798 798 branch=webutil.nodebranchnodefault(fctx),
799 799 parent=webutil.parents(fctx),
800 800 child=webutil.children(fctx),
801 801 permissions=fctx.manifest().flags(f))
802 802
803 803 def filelog(web, req, tmpl):
804 804
805 805 try:
806 806 fctx = webutil.filectx(web.repo, req)
807 807 f = fctx.path()
808 808 fl = fctx.filelog()
809 809 except error.LookupError:
810 810 f = webutil.cleanpath(web.repo, req.form['file'][0])
811 811 fl = web.repo.file(f)
812 812 numrevs = len(fl)
813 813 if not numrevs: # file doesn't exist at all
814 814 raise
815 815 rev = webutil.changectx(web.repo, req).rev()
816 816 first = fl.linkrev(0)
817 817 if rev < first: # current rev is from before file existed
818 818 raise
819 819 frev = numrevs - 1
820 820 while fl.linkrev(frev) > rev:
821 821 frev -= 1
822 822 fctx = web.repo.filectx(f, fl.linkrev(frev))
823 823
824 824 revcount = web.maxshortchanges
825 825 if 'revcount' in req.form:
826 826 try:
827 827 revcount = int(req.form.get('revcount', [revcount])[0])
828 828 revcount = max(revcount, 1)
829 829 tmpl.defaults['sessionvars']['revcount'] = revcount
830 830 except ValueError:
831 831 pass
832 832
833 833 lessvars = copy.copy(tmpl.defaults['sessionvars'])
834 834 lessvars['revcount'] = max(revcount / 2, 1)
835 835 morevars = copy.copy(tmpl.defaults['sessionvars'])
836 836 morevars['revcount'] = revcount * 2
837 837
838 838 count = fctx.filerev() + 1
839 839 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
840 840 end = min(count, start + revcount) # last rev on this page
841 841 parity = paritygen(web.stripecount, offset=start - end)
842 842
843 843 def entries():
844 844 l = []
845 845
846 846 repo = web.repo
847 847 revs = fctx.filelog().revs(start, end - 1)
848 848 for i in revs:
849 849 iterfctx = fctx.filectx(i)
850 850
851 851 l.append({"parity": parity.next(),
852 852 "filerev": i,
853 853 "file": f,
854 854 "node": iterfctx.hex(),
855 855 "author": iterfctx.user(),
856 856 "date": iterfctx.date(),
857 857 "rename": webutil.renamelink(iterfctx),
858 858 "parent": webutil.parents(iterfctx),
859 859 "child": webutil.children(iterfctx),
860 860 "desc": iterfctx.description(),
861 861 "extra": iterfctx.extra(),
862 862 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
863 863 "bookmarks": webutil.nodebookmarksdict(
864 864 repo, iterfctx.node()),
865 865 "branch": webutil.nodebranchnodefault(iterfctx),
866 866 "inbranch": webutil.nodeinbranch(repo, iterfctx),
867 867 "branches": webutil.nodebranchdict(repo, iterfctx)})
868 868 for e in reversed(l):
869 869 yield e
870 870
871 871 entries = list(entries())
872 872 latestentry = entries[:1]
873 873
874 874 revnav = webutil.filerevnav(web.repo, fctx.path())
875 875 nav = revnav.gen(end - 1, revcount, count)
876 876 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
877 877 entries=entries,
878 878 latestentry=latestentry,
879 879 revcount=revcount, morevars=morevars, lessvars=lessvars)
880 880
881 881 def archive(web, req, tmpl):
882 882 type_ = req.form.get('type', [None])[0]
883 883 allowed = web.configlist("web", "allow_archive")
884 884 key = req.form['node'][0]
885 885
886 886 if type_ not in web.archives:
887 887 msg = 'Unsupported archive type: %s' % type_
888 888 raise ErrorResponse(HTTP_NOT_FOUND, msg)
889 889
890 890 if not ((type_ in allowed or
891 891 web.configbool("web", "allow" + type_, False))):
892 892 msg = 'Archive type not allowed: %s' % type_
893 893 raise ErrorResponse(HTTP_FORBIDDEN, msg)
894 894
895 895 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
896 896 cnode = web.repo.lookup(key)
897 897 arch_version = key
898 898 if cnode == key or key == 'tip':
899 899 arch_version = short(cnode)
900 900 name = "%s-%s" % (reponame, arch_version)
901 901
902 902 ctx = webutil.changectx(web.repo, req)
903 903 pats = []
904 matchfn = None
904 matchfn = scmutil.match(ctx, [])
905 905 file = req.form.get('file', None)
906 906 if file:
907 907 pats = ['path:' + file[0]]
908 908 matchfn = scmutil.match(ctx, pats, default='path')
909 909 if pats:
910 910 files = [f for f in ctx.manifest().keys() if matchfn(f)]
911 911 if not files:
912 912 raise ErrorResponse(HTTP_NOT_FOUND,
913 913 'file(s) not found: %s' % file[0])
914 914
915 915 mimetype, artype, extension, encoding = web.archive_specs[type_]
916 916 headers = [
917 917 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
918 918 ]
919 919 if encoding:
920 920 headers.append(('Content-Encoding', encoding))
921 921 req.headers.extend(headers)
922 922 req.respond(HTTP_OK, mimetype)
923 923
924 924 archival.archive(web.repo, req, cnode, artype, prefix=name,
925 925 matchfn=matchfn,
926 926 subrepos=web.configbool("web", "archivesubrepos"))
927 927 return []
928 928
929 929
930 930 def static(web, req, tmpl):
931 931 fname = req.form['file'][0]
932 932 # a repo owner may set web.static in .hg/hgrc to get any file
933 933 # readable by the user running the CGI script
934 934 static = web.config("web", "static", None, untrusted=False)
935 935 if not static:
936 936 tp = web.templatepath or templater.templatepaths()
937 937 if isinstance(tp, str):
938 938 tp = [tp]
939 939 static = [os.path.join(p, 'static') for p in tp]
940 940 staticfile(static, fname, req)
941 941 return []
942 942
943 943 def graph(web, req, tmpl):
944 944
945 945 ctx = webutil.changectx(web.repo, req)
946 946 rev = ctx.rev()
947 947
948 948 bg_height = 39
949 949 revcount = web.maxshortchanges
950 950 if 'revcount' in req.form:
951 951 try:
952 952 revcount = int(req.form.get('revcount', [revcount])[0])
953 953 revcount = max(revcount, 1)
954 954 tmpl.defaults['sessionvars']['revcount'] = revcount
955 955 except ValueError:
956 956 pass
957 957
958 958 lessvars = copy.copy(tmpl.defaults['sessionvars'])
959 959 lessvars['revcount'] = max(revcount / 2, 1)
960 960 morevars = copy.copy(tmpl.defaults['sessionvars'])
961 961 morevars['revcount'] = revcount * 2
962 962
963 963 count = len(web.repo)
964 964 pos = rev
965 965
966 966 uprev = min(max(0, count - 1), rev + revcount)
967 967 downrev = max(0, rev - revcount)
968 968 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
969 969
970 970 tree = []
971 971 if pos != -1:
972 972 allrevs = web.repo.changelog.revs(pos, 0)
973 973 revs = []
974 974 for i in allrevs:
975 975 revs.append(i)
976 976 if len(revs) >= revcount:
977 977 break
978 978
979 979 # We have to feed a baseset to dagwalker as it is expecting smartset
980 980 # object. This does not have a big impact on hgweb performance itself
981 981 # since hgweb graphing code is not itself lazy yet.
982 982 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
983 983 # As we said one line above... not lazy.
984 984 tree = list(graphmod.colored(dag, web.repo))
985 985
986 986 def getcolumns(tree):
987 987 cols = 0
988 988 for (id, type, ctx, vtx, edges) in tree:
989 989 if type != graphmod.CHANGESET:
990 990 continue
991 991 cols = max(cols, max([edge[0] for edge in edges] or [0]),
992 992 max([edge[1] for edge in edges] or [0]))
993 993 return cols
994 994
995 995 def graphdata(usetuples, **map):
996 996 data = []
997 997
998 998 row = 0
999 999 for (id, type, ctx, vtx, edges) in tree:
1000 1000 if type != graphmod.CHANGESET:
1001 1001 continue
1002 1002 node = str(ctx)
1003 1003 age = templatefilters.age(ctx.date())
1004 1004 desc = templatefilters.firstline(ctx.description())
1005 1005 desc = cgi.escape(templatefilters.nonempty(desc))
1006 1006 user = cgi.escape(templatefilters.person(ctx.user()))
1007 1007 branch = cgi.escape(ctx.branch())
1008 1008 try:
1009 1009 branchnode = web.repo.branchtip(branch)
1010 1010 except error.RepoLookupError:
1011 1011 branchnode = None
1012 1012 branch = branch, branchnode == ctx.node()
1013 1013
1014 1014 if usetuples:
1015 1015 data.append((node, vtx, edges, desc, user, age, branch,
1016 1016 [cgi.escape(x) for x in ctx.tags()],
1017 1017 [cgi.escape(x) for x in ctx.bookmarks()]))
1018 1018 else:
1019 1019 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1020 1020 'color': (edge[2] - 1) % 6 + 1,
1021 1021 'width': edge[3], 'bcolor': edge[4]}
1022 1022 for edge in edges]
1023 1023
1024 1024 data.append(
1025 1025 {'node': node,
1026 1026 'col': vtx[0],
1027 1027 'color': (vtx[1] - 1) % 6 + 1,
1028 1028 'edges': edgedata,
1029 1029 'row': row,
1030 1030 'nextrow': row + 1,
1031 1031 'desc': desc,
1032 1032 'user': user,
1033 1033 'age': age,
1034 1034 'bookmarks': webutil.nodebookmarksdict(
1035 1035 web.repo, ctx.node()),
1036 1036 'branches': webutil.nodebranchdict(web.repo, ctx),
1037 1037 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1038 1038 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1039 1039
1040 1040 row += 1
1041 1041
1042 1042 return data
1043 1043
1044 1044 cols = getcolumns(tree)
1045 1045 rows = len(tree)
1046 1046 canvasheight = (rows + 1) * bg_height - 27
1047 1047
1048 1048 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1049 1049 lessvars=lessvars, morevars=morevars, downrev=downrev,
1050 1050 cols=cols, rows=rows,
1051 1051 canvaswidth=(cols + 1) * bg_height,
1052 1052 truecanvasheight=rows * bg_height,
1053 1053 canvasheight=canvasheight, bg_height=bg_height,
1054 1054 jsdata=lambda **x: graphdata(True, **x),
1055 1055 nodes=lambda **x: graphdata(False, **x),
1056 1056 node=ctx.hex(), changenav=changenav)
1057 1057
1058 1058 def _getdoc(e):
1059 1059 doc = e[0].__doc__
1060 1060 if doc:
1061 1061 doc = _(doc).split('\n')[0]
1062 1062 else:
1063 1063 doc = _('(no help text available)')
1064 1064 return doc
1065 1065
1066 1066 def help(web, req, tmpl):
1067 1067 from mercurial import commands # avoid cycle
1068 1068
1069 1069 topicname = req.form.get('node', [None])[0]
1070 1070 if not topicname:
1071 1071 def topics(**map):
1072 1072 for entries, summary, _doc in helpmod.helptable:
1073 1073 yield {'topic': entries[0], 'summary': summary}
1074 1074
1075 1075 early, other = [], []
1076 1076 primary = lambda s: s.split('|')[0]
1077 1077 for c, e in commands.table.iteritems():
1078 1078 doc = _getdoc(e)
1079 1079 if 'DEPRECATED' in doc or c.startswith('debug'):
1080 1080 continue
1081 1081 cmd = primary(c)
1082 1082 if cmd.startswith('^'):
1083 1083 early.append((cmd[1:], doc))
1084 1084 else:
1085 1085 other.append((cmd, doc))
1086 1086
1087 1087 early.sort()
1088 1088 other.sort()
1089 1089
1090 1090 def earlycommands(**map):
1091 1091 for c, doc in early:
1092 1092 yield {'topic': c, 'summary': doc}
1093 1093
1094 1094 def othercommands(**map):
1095 1095 for c, doc in other:
1096 1096 yield {'topic': c, 'summary': doc}
1097 1097
1098 1098 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1099 1099 othercommands=othercommands, title='Index')
1100 1100
1101 1101 u = webutil.wsgiui()
1102 1102 u.verbose = True
1103 1103 try:
1104 1104 doc = helpmod.help_(u, topicname)
1105 1105 except error.UnknownCommand:
1106 1106 raise ErrorResponse(HTTP_NOT_FOUND)
1107 1107 return tmpl('help', topic=topicname, doc=doc)
@@ -1,364 +1,368 b''
1 1 #require serve
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo foo>foo
6 6 $ hg commit -Am 1 -d '1 0'
7 7 adding foo
8 8 $ echo bar>bar
9 9 $ hg commit -Am 2 -d '2 0'
10 10 adding bar
11 11 $ mkdir baz
12 12 $ echo bletch>baz/bletch
13 13 $ hg commit -Am 3 -d '1000000000 0'
14 14 adding baz/bletch
15 15 $ hg init subrepo
16 16 $ touch subrepo/sub
17 17 $ hg -q -R subrepo ci -Am "init subrepo"
18 18 $ echo "subrepo = subrepo" > .hgsub
19 19 $ hg add .hgsub
20 20 $ hg ci -m "add subrepo"
21 21 $ echo "[web]" >> .hg/hgrc
22 22 $ echo "name = test-archive" >> .hg/hgrc
23 $ echo "archivesubrepos = True" >> .hg/hgrc
23 24 $ cp .hg/hgrc .hg/hgrc-base
24 25 > test_archtype() {
25 26 > echo "allow_archive = $1" >> .hg/hgrc
26 27 > hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
27 28 > cat hg.pid >> $DAEMON_PIDS
28 29 > echo % $1 allowed should give 200
29 30 > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.$2" | head -n 1
30 31 > echo % $3 and $4 disallowed should both give 403
31 32 > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.$3" | head -n 1
32 33 > "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.$4" | head -n 1
33 34 > "$TESTDIR/killdaemons.py" $DAEMON_PIDS
34 35 > cat errors.log
35 36 > cp .hg/hgrc-base .hg/hgrc
36 37 > }
37 38
38 39 check http return codes
39 40
40 41 $ test_archtype gz tar.gz tar.bz2 zip
41 42 % gz allowed should give 200
42 43 200 Script output follows
43 44 % tar.bz2 and zip disallowed should both give 403
44 45 403 Archive type not allowed: bz2
45 46 403 Archive type not allowed: zip
46 47 $ test_archtype bz2 tar.bz2 zip tar.gz
47 48 % bz2 allowed should give 200
48 49 200 Script output follows
49 50 % zip and tar.gz disallowed should both give 403
50 51 403 Archive type not allowed: zip
51 52 403 Archive type not allowed: gz
52 53 $ test_archtype zip zip tar.gz tar.bz2
53 54 % zip allowed should give 200
54 55 200 Script output follows
55 56 % tar.gz and tar.bz2 disallowed should both give 403
56 57 403 Archive type not allowed: gz
57 58 403 Archive type not allowed: bz2
58 59
59 60 $ echo "allow_archive = gz bz2 zip" >> .hg/hgrc
60 61 $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
61 62 $ cat hg.pid >> $DAEMON_PIDS
62 63
63 64 invalid arch type should give 404
64 65
65 66 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT "archive/tip.invalid" | head -n 1
66 67 404 Unsupported archive type: None
67 68
68 69 $ TIP=`hg id -v | cut -f1 -d' '`
69 70 $ QTIP=`hg id -q`
70 71 $ cat > getarchive.py <<EOF
71 72 > import os, sys, urllib2
72 73 > try:
73 74 > # Set stdout to binary mode for win32 platforms
74 75 > import msvcrt
75 76 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
76 77 > except ImportError:
77 78 > pass
78 79 > if len(sys.argv) <= 3:
79 80 > node, archive = sys.argv[1:]
80 81 > requeststr = 'cmd=archive;node=%s;type=%s' % (node, archive)
81 82 > else:
82 83 > node, archive, file = sys.argv[1:]
83 84 > requeststr = 'cmd=archive;node=%s;type=%s;file=%s' % (node, archive, file)
84 85 > try:
85 86 > f = urllib2.urlopen('http://127.0.0.1:%s/?%s'
86 87 > % (os.environ['HGPORT'], requeststr))
87 88 > sys.stdout.write(f.read())
88 89 > except urllib2.HTTPError, e:
89 90 > sys.stderr.write(str(e) + '\n')
90 91 > EOF
91 92 $ python getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
92 93 test-archive-1701ef1f1510/.hg_archival.txt
93 94 test-archive-1701ef1f1510/.hgsub
94 95 test-archive-1701ef1f1510/.hgsubstate
95 96 test-archive-1701ef1f1510/bar
96 97 test-archive-1701ef1f1510/baz/bletch
97 98 test-archive-1701ef1f1510/foo
99 test-archive-1701ef1f1510/subrepo/sub
98 100 $ python getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
99 101 test-archive-1701ef1f1510/.hg_archival.txt
100 102 test-archive-1701ef1f1510/.hgsub
101 103 test-archive-1701ef1f1510/.hgsubstate
102 104 test-archive-1701ef1f1510/bar
103 105 test-archive-1701ef1f1510/baz/bletch
104 106 test-archive-1701ef1f1510/foo
107 test-archive-1701ef1f1510/subrepo/sub
105 108 $ python getarchive.py "$TIP" zip > archive.zip
106 109 $ unzip -t archive.zip
107 110 Archive: archive.zip
108 111 testing: test-archive-1701ef1f1510/.hg_archival.txt OK
109 112 testing: test-archive-1701ef1f1510/.hgsub OK
110 113 testing: test-archive-1701ef1f1510/.hgsubstate OK
111 114 testing: test-archive-1701ef1f1510/bar OK
112 115 testing: test-archive-1701ef1f1510/baz/bletch OK
113 116 testing: test-archive-1701ef1f1510/foo OK
117 testing: test-archive-1701ef1f1510/subrepo/sub OK
114 118 No errors detected in compressed data of archive.zip.
115 119
116 120 test that we can download single directories and files
117 121
118 122 $ python getarchive.py "$TIP" gz baz | gunzip | tar tf - 2>/dev/null
119 123 test-archive-1701ef1f1510/baz/bletch
120 124 $ python getarchive.py "$TIP" gz foo | gunzip | tar tf - 2>/dev/null
121 125 test-archive-1701ef1f1510/foo
122 126
123 127 test that we detect file patterns that match no files
124 128
125 129 $ python getarchive.py "$TIP" gz foobar
126 130 HTTP Error 404: file(s) not found: foobar
127 131
128 132 test that we reject unsafe patterns
129 133
130 134 $ python getarchive.py "$TIP" gz relre:baz
131 135 HTTP Error 404: file(s) not found: relre:baz
132 136
133 137 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
134 138
135 139 $ hg archive -t tar test.tar
136 140 $ tar tf test.tar
137 141 test/.hg_archival.txt
138 142 test/.hgsub
139 143 test/.hgsubstate
140 144 test/bar
141 145 test/baz/bletch
142 146 test/foo
143 147
144 148 $ hg archive --debug -t tbz2 -X baz test.tar.bz2
145 149 archiving: 0/4 files (0.00%)
146 150 archiving: .hgsub 1/4 files (25.00%)
147 151 archiving: .hgsubstate 2/4 files (50.00%)
148 152 archiving: bar 3/4 files (75.00%)
149 153 archiving: foo 4/4 files (100.00%)
150 154 $ bunzip2 -dc test.tar.bz2 | tar tf - 2>/dev/null
151 155 test/.hg_archival.txt
152 156 test/.hgsub
153 157 test/.hgsubstate
154 158 test/bar
155 159 test/foo
156 160
157 161 $ hg archive -t tgz -p %b-%h test-%h.tar.gz
158 162 $ gzip -dc test-$QTIP.tar.gz | tar tf - 2>/dev/null
159 163 test-1701ef1f1510/.hg_archival.txt
160 164 test-1701ef1f1510/.hgsub
161 165 test-1701ef1f1510/.hgsubstate
162 166 test-1701ef1f1510/bar
163 167 test-1701ef1f1510/baz/bletch
164 168 test-1701ef1f1510/foo
165 169
166 170 $ hg archive autodetected_test.tar
167 171 $ tar tf autodetected_test.tar
168 172 autodetected_test/.hg_archival.txt
169 173 autodetected_test/.hgsub
170 174 autodetected_test/.hgsubstate
171 175 autodetected_test/bar
172 176 autodetected_test/baz/bletch
173 177 autodetected_test/foo
174 178
175 179 The '-t' should override autodetection
176 180
177 181 $ hg archive -t tar autodetect_override_test.zip
178 182 $ tar tf autodetect_override_test.zip
179 183 autodetect_override_test.zip/.hg_archival.txt
180 184 autodetect_override_test.zip/.hgsub
181 185 autodetect_override_test.zip/.hgsubstate
182 186 autodetect_override_test.zip/bar
183 187 autodetect_override_test.zip/baz/bletch
184 188 autodetect_override_test.zip/foo
185 189
186 190 $ for ext in tar tar.gz tgz tar.bz2 tbz2 zip; do
187 191 > hg archive auto_test.$ext
188 192 > if [ -d auto_test.$ext ]; then
189 193 > echo "extension $ext was not autodetected."
190 194 > fi
191 195 > done
192 196
193 197 $ cat > md5comp.py <<EOF
194 198 > try:
195 199 > from hashlib import md5
196 200 > except ImportError:
197 201 > from md5 import md5
198 202 > import sys
199 203 > f1, f2 = sys.argv[1:3]
200 204 > h1 = md5(file(f1, 'rb').read()).hexdigest()
201 205 > h2 = md5(file(f2, 'rb').read()).hexdigest()
202 206 > print h1 == h2 or "md5 differ: " + repr((h1, h2))
203 207 > EOF
204 208
205 209 archive name is stored in the archive, so create similar archives and
206 210 rename them afterwards.
207 211
208 212 $ hg archive -t tgz tip.tar.gz
209 213 $ mv tip.tar.gz tip1.tar.gz
210 214 $ sleep 1
211 215 $ hg archive -t tgz tip.tar.gz
212 216 $ mv tip.tar.gz tip2.tar.gz
213 217 $ python md5comp.py tip1.tar.gz tip2.tar.gz
214 218 True
215 219
216 220 $ hg archive -t zip -p /illegal test.zip
217 221 abort: archive prefix contains illegal components
218 222 [255]
219 223 $ hg archive -t zip -p very/../bad test.zip
220 224
221 225 $ hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
222 226 $ unzip -t test.zip
223 227 Archive: test.zip
224 228 testing: test/bar OK
225 229 testing: test/baz/bletch OK
226 230 testing: test/foo OK
227 231 No errors detected in compressed data of test.zip.
228 232
229 233 $ hg archive -t tar - | tar tf - 2>/dev/null
230 234 test-1701ef1f1510/.hg_archival.txt
231 235 test-1701ef1f1510/.hgsub
232 236 test-1701ef1f1510/.hgsubstate
233 237 test-1701ef1f1510/bar
234 238 test-1701ef1f1510/baz/bletch
235 239 test-1701ef1f1510/foo
236 240
237 241 $ hg archive -r 0 -t tar rev-%r.tar
238 242 $ [ -f rev-0.tar ]
239 243
240 244 test .hg_archival.txt
241 245
242 246 $ hg archive ../test-tags
243 247 $ cat ../test-tags/.hg_archival.txt
244 248 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
245 249 node: 1701ef1f151069b8747038e93b5186bb43a47504
246 250 branch: default
247 251 latesttag: null
248 252 latesttagdistance: 4
249 253 $ hg tag -r 2 mytag
250 254 $ hg tag -r 2 anothertag
251 255 $ hg archive -r 2 ../test-lasttag
252 256 $ cat ../test-lasttag/.hg_archival.txt
253 257 repo: daa7f7c60e0a224faa4ff77ca41b2760562af264
254 258 node: 2c0277f05ed49d1c8328fb9ba92fba7a5ebcb33e
255 259 branch: default
256 260 tag: anothertag
257 261 tag: mytag
258 262
259 263 $ hg archive -t bogus test.bogus
260 264 abort: unknown archive type 'bogus'
261 265 [255]
262 266
263 267 enable progress extension:
264 268
265 269 $ cp $HGRCPATH $HGRCPATH.no-progress
266 270 $ cat >> $HGRCPATH <<EOF
267 271 > [extensions]
268 272 > progress =
269 273 > [progress]
270 274 > assume-tty = 1
271 275 > format = topic bar number
272 276 > delay = 0
273 277 > refresh = 0
274 278 > width = 60
275 279 > EOF
276 280
277 281 $ hg archive ../with-progress
278 282 \r (no-eol) (esc)
279 283 archiving [ ] 0/6\r (no-eol) (esc)
280 284 archiving [ ] 0/6\r (no-eol) (esc)
281 285 archiving [======> ] 1/6\r (no-eol) (esc)
282 286 archiving [======> ] 1/6\r (no-eol) (esc)
283 287 archiving [=============> ] 2/6\r (no-eol) (esc)
284 288 archiving [=============> ] 2/6\r (no-eol) (esc)
285 289 archiving [====================> ] 3/6\r (no-eol) (esc)
286 290 archiving [====================> ] 3/6\r (no-eol) (esc)
287 291 archiving [===========================> ] 4/6\r (no-eol) (esc)
288 292 archiving [===========================> ] 4/6\r (no-eol) (esc)
289 293 archiving [==================================> ] 5/6\r (no-eol) (esc)
290 294 archiving [==================================> ] 5/6\r (no-eol) (esc)
291 295 archiving [==========================================>] 6/6\r (no-eol) (esc)
292 296 archiving [==========================================>] 6/6\r (no-eol) (esc)
293 297 \r (no-eol) (esc)
294 298
295 299 cleanup after progress extension test:
296 300
297 301 $ cp $HGRCPATH.no-progress $HGRCPATH
298 302
299 303 server errors
300 304
301 305 $ cat errors.log
302 306
303 307 empty repo
304 308
305 309 $ hg init ../empty
306 310 $ cd ../empty
307 311 $ hg archive ../test-empty
308 312 abort: no working directory: please specify a revision
309 313 [255]
310 314
311 315 old file -- date clamped to 1980
312 316
313 317 $ touch -t 197501010000 old
314 318 $ hg add old
315 319 $ hg commit -m old
316 320 $ hg archive ../old.zip
317 321 $ unzip -l ../old.zip
318 322 Archive: ../old.zip
319 323 \s*Length.* (re)
320 324 *-----* (glob)
321 325 *147*80*00:00*old/.hg_archival.txt (glob)
322 326 *0*80*00:00*old/old (glob)
323 327 *-----* (glob)
324 328 \s*147\s+2 files (re)
325 329
326 330 show an error when a provided pattern matches no files
327 331
328 332 $ hg archive -I file_that_does_not_exist.foo ../empty.zip
329 333 abort: no files match the archive pattern
330 334 [255]
331 335
332 336 $ hg archive -X * ../empty.zip
333 337 abort: no files match the archive pattern
334 338 [255]
335 339
336 340 $ cd ..
337 341
338 342 issue3600: check whether "hg archive" can create archive files which
339 343 are extracted with expected timestamp, even though TZ is not
340 344 configured as GMT.
341 345
342 346 $ mkdir issue3600
343 347 $ cd issue3600
344 348
345 349 $ hg init repo
346 350 $ echo a > repo/a
347 351 $ hg -R repo add repo/a
348 352 $ hg -R repo commit -m '#0' -d '456789012 21600'
349 353 $ cat > show_mtime.py <<EOF
350 354 > import sys, os
351 355 > print int(os.stat(sys.argv[1]).st_mtime)
352 356 > EOF
353 357
354 358 $ hg -R repo archive --prefix tar-extracted archive.tar
355 359 $ (TZ=UTC-3; export TZ; tar xf archive.tar)
356 360 $ python show_mtime.py tar-extracted/a
357 361 456789012
358 362
359 363 $ hg -R repo archive --prefix zip-extracted archive.zip
360 364 $ (TZ=UTC-3; export TZ; unzip -q archive.zip)
361 365 $ python show_mtime.py zip-extracted/a
362 366 456789012
363 367
364 368 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now