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