##// END OF EJS Templates
rename stream hgrc option to compressed.
Vadim Gelfer -
r2622:064aef91 default
parent child Browse files
Show More
@@ -1,464 +1,464 b''
1 1 HGRC(5)
2 2 =======
3 3 Bryan O'Sullivan <bos@serpentine.com>
4 4
5 5 NAME
6 6 ----
7 7 hgrc - configuration files for Mercurial
8 8
9 9 SYNOPSIS
10 10 --------
11 11
12 12 The Mercurial system uses a set of configuration files to control
13 13 aspects of its behaviour.
14 14
15 15 FILES
16 16 -----
17 17
18 18 Mercurial reads configuration data from several files, if they exist.
19 19 The names of these files depend on the system on which Mercurial is
20 20 installed.
21 21
22 22 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
23 23 (Unix) <install-root>/etc/mercurial/hgrc::
24 24 Per-installation configuration files, searched for in the
25 25 directory where Mercurial is installed. For example, if installed
26 26 in /shared/tools, Mercurial will look in
27 27 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
28 28 all Mercurial commands executed by any user in any directory.
29 29
30 30 (Unix) /etc/mercurial/hgrc.d/*.rc::
31 31 (Unix) /etc/mercurial/hgrc::
32 32 (Windows) C:\Mercurial\Mercurial.ini::
33 33 Per-system configuration files, for the system on which Mercurial
34 34 is running. Options in these files apply to all Mercurial
35 35 commands executed by any user in any directory. Options in these
36 36 files override per-installation options.
37 37
38 38 (Unix) $HOME/.hgrc::
39 39 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
40 40 (Windows) $HOME\Mercurial.ini::
41 41 Per-user configuration file, for the user running Mercurial.
42 42 Options in this file apply to all Mercurial commands executed by
43 43 any user in any directory. Options in this file override
44 44 per-installation and per-system options.
45 45 On Windows system, one of these is chosen exclusively according
46 46 to definition of HOME environment variable.
47 47
48 48 (Unix, Windows) <repo>/.hg/hgrc::
49 49 Per-repository configuration options that only apply in a
50 50 particular repository. This file is not version-controlled, and
51 51 will not get transferred during a "clone" operation. Options in
52 52 this file override options in all other configuration files.
53 53
54 54 SYNTAX
55 55 ------
56 56
57 57 A configuration file consists of sections, led by a "[section]" header
58 58 and followed by "name: value" entries; "name=value" is also accepted.
59 59
60 60 [spam]
61 61 eggs=ham
62 62 green=
63 63 eggs
64 64
65 65 Each line contains one entry. If the lines that follow are indented,
66 66 they are treated as continuations of that entry.
67 67
68 68 Leading whitespace is removed from values. Empty lines are skipped.
69 69
70 70 The optional values can contain format strings which refer to other
71 71 values in the same section, or values in a special DEFAULT section.
72 72
73 73 Lines beginning with "#" or ";" are ignored and may be used to provide
74 74 comments.
75 75
76 76 SECTIONS
77 77 --------
78 78
79 79 This section describes the different sections that may appear in a
80 80 Mercurial "hgrc" file, the purpose of each section, its possible
81 81 keys, and their possible values.
82 82
83 83 decode/encode::
84 84 Filters for transforming files on checkout/checkin. This would
85 85 typically be used for newline processing or other
86 86 localization/canonicalization of files.
87 87
88 88 Filters consist of a filter pattern followed by a filter command.
89 89 Filter patterns are globs by default, rooted at the repository
90 90 root. For example, to match any file ending in ".txt" in the root
91 91 directory only, use the pattern "*.txt". To match any file ending
92 92 in ".c" anywhere in the repository, use the pattern "**.c".
93 93
94 94 The filter command can start with a specifier, either "pipe:" or
95 95 "tempfile:". If no specifier is given, "pipe:" is used by default.
96 96
97 97 A "pipe:" command must accept data on stdin and return the
98 98 transformed data on stdout.
99 99
100 100 Pipe example:
101 101
102 102 [encode]
103 103 # uncompress gzip files on checkin to improve delta compression
104 104 # note: not necessarily a good idea, just an example
105 105 *.gz = pipe: gunzip
106 106
107 107 [decode]
108 108 # recompress gzip files when writing them to the working dir (we
109 109 # can safely omit "pipe:", because it's the default)
110 110 *.gz = gzip
111 111
112 112 A "tempfile:" command is a template. The string INFILE is replaced
113 113 with the name of a temporary file that contains the data to be
114 114 filtered by the command. The string OUTFILE is replaced with the
115 115 name of an empty temporary file, where the filtered data must be
116 116 written by the command.
117 117
118 118 NOTE: the tempfile mechanism is recommended for Windows systems,
119 119 where the standard shell I/O redirection operators often have
120 120 strange effects. In particular, if you are doing line ending
121 121 conversion on Windows using the popular dos2unix and unix2dos
122 122 programs, you *must* use the tempfile mechanism, as using pipes will
123 123 corrupt the contents of your files.
124 124
125 125 Tempfile example:
126 126
127 127 [encode]
128 128 # convert files to unix line ending conventions on checkin
129 129 **.txt = tempfile: dos2unix -n INFILE OUTFILE
130 130
131 131 [decode]
132 132 # convert files to windows line ending conventions when writing
133 133 # them to the working dir
134 134 **.txt = tempfile: unix2dos -n INFILE OUTFILE
135 135
136 136 email::
137 137 Settings for extensions that send email messages.
138 138 from;;
139 139 Optional. Email address to use in "From" header and SMTP envelope
140 140 of outgoing messages.
141 141 method;;
142 142 Optional. Method to use to send email messages. If value is
143 143 "smtp" (default), use SMTP (see section "[mail]" for
144 144 configuration). Otherwise, use as name of program to run that
145 145 acts like sendmail (takes "-f" option for sender, list of
146 146 recipients on command line, message on stdin). Normally, setting
147 147 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
148 148 sendmail to send messages.
149 149
150 150 Email example:
151 151
152 152 [email]
153 153 from = Joseph User <joe.user@example.com>
154 154 method = /usr/sbin/sendmail
155 155
156 156 extensions::
157 157 Mercurial has an extension mechanism for adding new features. To
158 158 enable an extension, create an entry for it in this section.
159 159
160 160 If you know that the extension is already in Python's search path,
161 161 you can give the name of the module, followed by "=", with nothing
162 162 after the "=".
163 163
164 164 Otherwise, give a name that you choose, followed by "=", followed by
165 165 the path to the ".py" file (including the file name extension) that
166 166 defines the extension.
167 167
168 168 Example for ~/.hgrc:
169 169
170 170 [extensions]
171 171 # (the mq extension will get loaded from mercurial's path)
172 172 hgext.mq =
173 173 # (this extension will get loaded from the file specified)
174 174 myfeature = ~/.hgext/myfeature.py
175 175
176 176 hooks::
177 177 Commands or Python functions that get automatically executed by
178 178 various actions such as starting or finishing a commit. Multiple
179 179 hooks can be run for the same action by appending a suffix to the
180 180 action. Overriding a site-wide hook can be done by changing its
181 181 value or setting it to an empty string.
182 182
183 183 Example .hg/hgrc:
184 184
185 185 [hooks]
186 186 # do not use the site-wide hook
187 187 incoming =
188 188 incoming.email = /my/email/hook
189 189 incoming.autobuild = /my/build/hook
190 190
191 191 Most hooks are run with environment variables set that give added
192 192 useful information. For each hook below, the environment variables
193 193 it is passed are listed with names of the form "$HG_foo".
194 194
195 195 changegroup;;
196 196 Run after a changegroup has been added via push, pull or
197 197 unbundle. ID of the first new changeset is in $HG_NODE.
198 198 commit;;
199 199 Run after a changeset has been created in the local repository.
200 200 ID of the newly created changeset is in $HG_NODE. Parent
201 201 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
202 202 incoming;;
203 203 Run after a changeset has been pulled, pushed, or unbundled into
204 204 the local repository. The ID of the newly arrived changeset is in
205 205 $HG_NODE.
206 206 outgoing;;
207 207 Run after sending changes from local repository to another. ID of
208 208 first changeset sent is in $HG_NODE. Source of operation is in
209 209 $HG_SOURCE; see "preoutgoing" hook for description.
210 210 prechangegroup;;
211 211 Run before a changegroup is added via push, pull or unbundle.
212 212 Exit status 0 allows the changegroup to proceed. Non-zero status
213 213 will cause the push, pull or unbundle to fail.
214 214 precommit;;
215 215 Run before starting a local commit. Exit status 0 allows the
216 216 commit to proceed. Non-zero status will cause the commit to fail.
217 217 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
218 218 preoutgoing;;
219 219 Run before computing changes to send from the local repository to
220 220 another. Non-zero status will cause failure. This lets you
221 221 prevent pull over http or ssh. Also prevents against local pull,
222 222 push (outbound) or bundle commands, but not effective, since you
223 223 can just copy files instead then. Source of operation is in
224 224 $HG_SOURCE. If "serve", operation is happening on behalf of
225 225 remote ssh or http repository. If "push", "pull" or "bundle",
226 226 operation is happening on behalf of repository on same system.
227 227 pretag;;
228 228 Run before creating a tag. Exit status 0 allows the tag to be
229 229 created. Non-zero status will cause the tag to fail. ID of
230 230 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
231 231 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
232 232 pretxnchangegroup;;
233 233 Run after a changegroup has been added via push, pull or unbundle,
234 234 but before the transaction has been committed. Changegroup is
235 235 visible to hook program. This lets you validate incoming changes
236 236 before accepting them. Passed the ID of the first new changeset
237 237 in $HG_NODE. Exit status 0 allows the transaction to commit.
238 238 Non-zero status will cause the transaction to be rolled back and
239 239 the push, pull or unbundle will fail.
240 240 pretxncommit;;
241 241 Run after a changeset has been created but the transaction not yet
242 242 committed. Changeset is visible to hook program. This lets you
243 243 validate commit message and changes. Exit status 0 allows the
244 244 commit to proceed. Non-zero status will cause the transaction to
245 245 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
246 246 IDs are in $HG_PARENT1 and $HG_PARENT2.
247 247 preupdate;;
248 248 Run before updating the working directory. Exit status 0 allows
249 249 the update to proceed. Non-zero status will prevent the update.
250 250 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
251 251 of second new parent is in $HG_PARENT2.
252 252 tag;;
253 253 Run after a tag is created. ID of tagged changeset is in
254 254 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
255 255 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
256 256 update;;
257 257 Run after updating the working directory. Changeset ID of first
258 258 new parent is in $HG_PARENT1. If merge, ID of second new parent
259 259 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
260 260 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
261 261
262 262 Note: In earlier releases, the names of hook environment variables
263 263 did not have a "HG_" prefix. The old unprefixed names are no longer
264 264 provided in the environment.
265 265
266 266 The syntax for Python hooks is as follows:
267 267
268 268 hookname = python:modulename.submodule.callable
269 269
270 270 Python hooks are run within the Mercurial process. Each hook is
271 271 called with at least three keyword arguments: a ui object (keyword
272 272 "ui"), a repository object (keyword "repo"), and a "hooktype"
273 273 keyword that tells what kind of hook is used. Arguments listed as
274 274 environment variables above are passed as keyword arguments, with no
275 275 "HG_" prefix, and names in lower case.
276 276
277 277 A Python hook must return a "true" value to succeed. Returning a
278 278 "false" value or raising an exception is treated as failure of the
279 279 hook.
280 280
281 281 http_proxy::
282 282 Used to access web-based Mercurial repositories through a HTTP
283 283 proxy.
284 284 host;;
285 285 Host name and (optional) port of the proxy server, for example
286 286 "myproxy:8000".
287 287 no;;
288 288 Optional. Comma-separated list of host names that should bypass
289 289 the proxy.
290 290 passwd;;
291 291 Optional. Password to authenticate with at the proxy server.
292 292 user;;
293 293 Optional. User name to authenticate with at the proxy server.
294 294
295 295 smtp::
296 296 Configuration for extensions that need to send email messages.
297 297 host;;
298 298 Optional. Host name of mail server. Default: "mail".
299 299 port;;
300 300 Optional. Port to connect to on mail server. Default: 25.
301 301 tls;;
302 302 Optional. Whether to connect to mail server using TLS. True or
303 303 False. Default: False.
304 304 username;;
305 305 Optional. User name to authenticate to SMTP server with.
306 306 If username is specified, password must also be specified.
307 307 Default: none.
308 308 password;;
309 309 Optional. Password to authenticate to SMTP server with.
310 310 If username is specified, password must also be specified.
311 311 Default: none.
312 312 local_hostname;;
313 313 Optional. It's the hostname that the sender can use to identify itself
314 314 to the MTA.
315 315
316 316 paths::
317 317 Assigns symbolic names to repositories. The left side is the
318 318 symbolic name, and the right gives the directory or URL that is the
319 319 location of the repository. Default paths can be declared by
320 320 setting the following entries.
321 321 default;;
322 322 Directory or URL to use when pulling if no source is specified.
323 323 Default is set to repository from which the current repository
324 324 was cloned.
325 325 default-push;;
326 326 Optional. Directory or URL to use when pushing if no destination
327 327 is specified.
328 328
329 329 server::
330 330 Controls generic server settings.
331 stream;;
331 uncompressed;;
332 332 Whether to allow clients to clone a repo using the uncompressed
333 333 streaming protocol. This transfers about 40% more data than a
334 334 regular clone, but uses less memory and CPU on both server and
335 335 client. Over a LAN (100Mbps or better) or a very fast WAN, an
336 336 uncompressed streaming clone is a lot faster (~10x) than a regular
337 337 clone. Over most WAN connections (anything slower than about
338 338 6Mbps), uncompressed streaming is slower, because of the extra
339 339 data transfer overhead. Default is False.
340 340
341 341 ui::
342 342 User interface controls.
343 343 debug;;
344 344 Print debugging information. True or False. Default is False.
345 345 editor;;
346 346 The editor to use during a commit. Default is $EDITOR or "vi".
347 347 ignore;;
348 348 A file to read per-user ignore patterns from. This file should be in
349 349 the same format as a repository-wide .hgignore file. This option
350 350 supports hook syntax, so if you want to specify multiple ignore
351 351 files, you can do so by setting something like
352 352 "ignore.other = ~/.hgignore2". For details of the ignore file
353 353 format, see the hgignore(5) man page.
354 354 interactive;;
355 355 Allow to prompt the user. True or False. Default is True.
356 356 logtemplate;;
357 357 Template string for commands that print changesets.
358 358 style;;
359 359 Name of style to use for command output.
360 360 merge;;
361 361 The conflict resolution program to use during a manual merge.
362 362 Default is "hgmerge".
363 363 quiet;;
364 364 Reduce the amount of output printed. True or False. Default is False.
365 365 remotecmd;;
366 366 remote command to use for clone/push/pull operations. Default is 'hg'.
367 367 ssh;;
368 368 command to use for SSH connections. Default is 'ssh'.
369 369 timeout;;
370 370 The timeout used when a lock is held (in seconds), a negative value
371 371 means no timeout. Default is 600.
372 372 username;;
373 373 The committer of a changeset created when running "commit".
374 374 Typically a person's name and email address, e.g. "Fred Widget
375 375 <fred@example.com>". Default is $EMAIL or username@hostname, unless
376 376 username is set to an empty string, which enforces specifying the
377 377 username manually.
378 378 verbose;;
379 379 Increase the amount of output printed. True or False. Default is False.
380 380
381 381
382 382 web::
383 383 Web interface configuration.
384 384 accesslog;;
385 385 Where to output the access log. Default is stdout.
386 386 address;;
387 387 Interface address to bind to. Default is all.
388 388 allow_archive;;
389 389 List of archive format (bz2, gz, zip) allowed for downloading.
390 390 Default is empty.
391 391 allowbz2;;
392 392 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
393 393 Default is false.
394 394 allowgz;;
395 395 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
396 396 Default is false.
397 397 allowpull;;
398 398 Whether to allow pulling from the repository. Default is true.
399 399 allow_push;;
400 400 Whether to allow pushing to the repository. If empty or not set,
401 401 push is not allowed. If the special value "*", any remote user
402 402 can push, including unauthenticated users. Otherwise, the remote
403 403 user must have been authenticated, and the authenticated user name
404 404 must be present in this list (separated by whitespace or ",").
405 405 The contents of the allow_push list are examined after the
406 406 deny_push list.
407 407 allowzip;;
408 408 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
409 409 Default is false. This feature creates temporary files.
410 410 baseurl;;
411 411 Base URL to use when publishing URLs in other locations, so
412 412 third-party tools like email notification hooks can construct URLs.
413 413 Example: "http://hgserver/repos/"
414 414 contact;;
415 415 Name or email address of the person in charge of the repository.
416 416 Default is "unknown".
417 417 deny_push;;
418 418 Whether to deny pushing to the repository. If empty or not set,
419 419 push is not denied. If the special value "*", all remote users
420 420 are denied push. Otherwise, unauthenticated users are all denied,
421 421 and any authenticated user name present in this list (separated by
422 422 whitespace or ",") is also denied. The contents of the deny_push
423 423 list are examined before the allow_push list.
424 424 description;;
425 425 Textual description of the repository's purpose or contents.
426 426 Default is "unknown".
427 427 errorlog;;
428 428 Where to output the error log. Default is stderr.
429 429 ipv6;;
430 430 Whether to use IPv6. Default is false.
431 431 name;;
432 432 Repository name to use in the web interface. Default is current
433 433 working directory.
434 434 maxchanges;;
435 435 Maximum number of changes to list on the changelog. Default is 10.
436 436 maxfiles;;
437 437 Maximum number of files to list per changeset. Default is 10.
438 438 port;;
439 439 Port to listen on. Default is 8000.
440 440 push_ssl;;
441 441 Whether to require that inbound pushes be transported over SSL to
442 442 prevent password sniffing. Default is true.
443 443 style;;
444 444 Which template map style to use.
445 445 templates;;
446 446 Where to find the HTML templates. Default is install path.
447 447
448 448
449 449 AUTHOR
450 450 ------
451 451 Bryan O'Sullivan <bos@serpentine.com>.
452 452
453 453 Mercurial was written by Matt Mackall <mpm@selenic.com>.
454 454
455 455 SEE ALSO
456 456 --------
457 457 hg(1), hgignore(5)
458 458
459 459 COPYING
460 460 -------
461 461 This manual page is copyright 2005 Bryan O'Sullivan.
462 462 Mercurial is copyright 2005, 2006 Matt Mackall.
463 463 Free use of this software is granted under the terms of the GNU General
464 464 Public License (GPL).
@@ -1,960 +1,960 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import os
10 10 import os.path
11 11 import mimetypes
12 12 from mercurial.demandload import demandload
13 13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone")
15 15 demandload(globals(), "mercurial:templater")
16 16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
17 17 from mercurial.node import *
18 18 from mercurial.i18n import gettext as _
19 19
20 20 def _up(p):
21 21 if p[0] != "/":
22 22 p = "/" + p
23 23 if p[-1] == "/":
24 24 p = p[:-1]
25 25 up = os.path.dirname(p)
26 26 if up == "/":
27 27 return "/"
28 28 return up + "/"
29 29
30 30 class hgweb(object):
31 31 def __init__(self, repo, name=None):
32 32 if type(repo) == type(""):
33 33 self.repo = hg.repository(ui.ui(), repo)
34 34 else:
35 35 self.repo = repo
36 36
37 37 self.mtime = -1
38 38 self.reponame = name
39 39 self.archives = 'zip', 'gz', 'bz2'
40 40 self.templatepath = self.repo.ui.config("web", "templates",
41 41 templater.templatepath())
42 42
43 43 def refresh(self):
44 44 mtime = get_mtime(self.repo.root)
45 45 if mtime != self.mtime:
46 46 self.mtime = mtime
47 47 self.repo = hg.repository(self.repo.ui, self.repo.root)
48 48 self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
49 49 self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
50 50 self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
51 51
52 52 def archivelist(self, nodeid):
53 53 allowed = self.repo.ui.configlist("web", "allow_archive")
54 54 for i in self.archives:
55 55 if i in allowed or self.repo.ui.configbool("web", "allow" + i):
56 56 yield {"type" : i, "node" : nodeid, "url": ""}
57 57
58 58 def listfiles(self, files, mf):
59 59 for f in files[:self.maxfiles]:
60 60 yield self.t("filenodelink", node=hex(mf[f]), file=f)
61 61 if len(files) > self.maxfiles:
62 62 yield self.t("fileellipses")
63 63
64 64 def listfilediffs(self, files, changeset):
65 65 for f in files[:self.maxfiles]:
66 66 yield self.t("filedifflink", node=hex(changeset), file=f)
67 67 if len(files) > self.maxfiles:
68 68 yield self.t("fileellipses")
69 69
70 70 def siblings(self, siblings=[], rev=None, hiderev=None, **args):
71 71 if not rev:
72 72 rev = lambda x: ""
73 73 siblings = [s for s in siblings if s != nullid]
74 74 if len(siblings) == 1 and rev(siblings[0]) == hiderev:
75 75 return
76 76 for s in siblings:
77 77 yield dict(node=hex(s), rev=rev(s), **args)
78 78
79 79 def renamelink(self, fl, node):
80 80 r = fl.renamed(node)
81 81 if r:
82 82 return [dict(file=r[0], node=hex(r[1]))]
83 83 return []
84 84
85 85 def showtag(self, t1, node=nullid, **args):
86 86 for t in self.repo.nodetags(node):
87 87 yield self.t(t1, tag=t, **args)
88 88
89 89 def diff(self, node1, node2, files):
90 90 def filterfiles(filters, files):
91 91 l = [x for x in files if x in filters]
92 92
93 93 for t in filters:
94 94 if t and t[-1] != os.sep:
95 95 t += os.sep
96 96 l += [x for x in files if x.startswith(t)]
97 97 return l
98 98
99 99 parity = [0]
100 100 def diffblock(diff, f, fn):
101 101 yield self.t("diffblock",
102 102 lines=prettyprintlines(diff),
103 103 parity=parity[0],
104 104 file=f,
105 105 filenode=hex(fn or nullid))
106 106 parity[0] = 1 - parity[0]
107 107
108 108 def prettyprintlines(diff):
109 109 for l in diff.splitlines(1):
110 110 if l.startswith('+'):
111 111 yield self.t("difflineplus", line=l)
112 112 elif l.startswith('-'):
113 113 yield self.t("difflineminus", line=l)
114 114 elif l.startswith('@'):
115 115 yield self.t("difflineat", line=l)
116 116 else:
117 117 yield self.t("diffline", line=l)
118 118
119 119 r = self.repo
120 120 cl = r.changelog
121 121 mf = r.manifest
122 122 change1 = cl.read(node1)
123 123 change2 = cl.read(node2)
124 124 mmap1 = mf.read(change1[0])
125 125 mmap2 = mf.read(change2[0])
126 126 date1 = util.datestr(change1[2])
127 127 date2 = util.datestr(change2[2])
128 128
129 129 modified, added, removed, deleted, unknown = r.changes(node1, node2)
130 130 if files:
131 131 modified, added, removed = map(lambda x: filterfiles(files, x),
132 132 (modified, added, removed))
133 133
134 134 diffopts = self.repo.ui.diffopts()
135 135 showfunc = diffopts['showfunc']
136 136 ignorews = diffopts['ignorews']
137 137 ignorewsamount = diffopts['ignorewsamount']
138 138 ignoreblanklines = diffopts['ignoreblanklines']
139 139 for f in modified:
140 140 to = r.file(f).read(mmap1[f])
141 141 tn = r.file(f).read(mmap2[f])
142 142 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
143 143 showfunc=showfunc, ignorews=ignorews,
144 144 ignorewsamount=ignorewsamount,
145 145 ignoreblanklines=ignoreblanklines), f, tn)
146 146 for f in added:
147 147 to = None
148 148 tn = r.file(f).read(mmap2[f])
149 149 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
150 150 showfunc=showfunc, ignorews=ignorews,
151 151 ignorewsamount=ignorewsamount,
152 152 ignoreblanklines=ignoreblanklines), f, tn)
153 153 for f in removed:
154 154 to = r.file(f).read(mmap1[f])
155 155 tn = None
156 156 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
157 157 showfunc=showfunc, ignorews=ignorews,
158 158 ignorewsamount=ignorewsamount,
159 159 ignoreblanklines=ignoreblanklines), f, tn)
160 160
161 161 def changelog(self, pos):
162 162 def changenav(**map):
163 163 def seq(factor, maxchanges=None):
164 164 if maxchanges:
165 165 yield maxchanges
166 166 if maxchanges >= 20 and maxchanges <= 40:
167 167 yield 50
168 168 else:
169 169 yield 1 * factor
170 170 yield 3 * factor
171 171 for f in seq(factor * 10):
172 172 yield f
173 173
174 174 l = []
175 175 last = 0
176 176 for f in seq(1, self.maxchanges):
177 177 if f < self.maxchanges or f <= last:
178 178 continue
179 179 if f > count:
180 180 break
181 181 last = f
182 182 r = "%d" % f
183 183 if pos + f < count:
184 184 l.append(("+" + r, pos + f))
185 185 if pos - f >= 0:
186 186 l.insert(0, ("-" + r, pos - f))
187 187
188 188 yield {"rev": 0, "label": "(0)"}
189 189
190 190 for label, rev in l:
191 191 yield {"label": label, "rev": rev}
192 192
193 193 yield {"label": "tip", "rev": "tip"}
194 194
195 195 def changelist(**map):
196 196 parity = (start - end) & 1
197 197 cl = self.repo.changelog
198 198 l = [] # build a list in forward order for efficiency
199 199 for i in range(start, end):
200 200 n = cl.node(i)
201 201 changes = cl.read(n)
202 202 hn = hex(n)
203 203
204 204 l.insert(0, {"parity": parity,
205 205 "author": changes[1],
206 206 "parent": self.siblings(cl.parents(n), cl.rev,
207 207 cl.rev(n) - 1),
208 208 "child": self.siblings(cl.children(n), cl.rev,
209 209 cl.rev(n) + 1),
210 210 "changelogtag": self.showtag("changelogtag",n),
211 211 "manifest": hex(changes[0]),
212 212 "desc": changes[4],
213 213 "date": changes[2],
214 214 "files": self.listfilediffs(changes[3], n),
215 215 "rev": i,
216 216 "node": hn})
217 217 parity = 1 - parity
218 218
219 219 for e in l:
220 220 yield e
221 221
222 222 cl = self.repo.changelog
223 223 mf = cl.read(cl.tip())[0]
224 224 count = cl.count()
225 225 start = max(0, pos - self.maxchanges + 1)
226 226 end = min(count, start + self.maxchanges)
227 227 pos = end - 1
228 228
229 229 yield self.t('changelog',
230 230 changenav=changenav,
231 231 manifest=hex(mf),
232 232 rev=pos, changesets=count, entries=changelist,
233 233 archives=self.archivelist("tip"))
234 234
235 235 def search(self, query):
236 236
237 237 def changelist(**map):
238 238 cl = self.repo.changelog
239 239 count = 0
240 240 qw = query.lower().split()
241 241
242 242 def revgen():
243 243 for i in range(cl.count() - 1, 0, -100):
244 244 l = []
245 245 for j in range(max(0, i - 100), i):
246 246 n = cl.node(j)
247 247 changes = cl.read(n)
248 248 l.append((n, j, changes))
249 249 l.reverse()
250 250 for e in l:
251 251 yield e
252 252
253 253 for n, i, changes in revgen():
254 254 miss = 0
255 255 for q in qw:
256 256 if not (q in changes[1].lower() or
257 257 q in changes[4].lower() or
258 258 q in " ".join(changes[3][:20]).lower()):
259 259 miss = 1
260 260 break
261 261 if miss:
262 262 continue
263 263
264 264 count += 1
265 265 hn = hex(n)
266 266
267 267 yield self.t('searchentry',
268 268 parity=count & 1,
269 269 author=changes[1],
270 270 parent=self.siblings(cl.parents(n), cl.rev),
271 271 child=self.siblings(cl.children(n), cl.rev),
272 272 changelogtag=self.showtag("changelogtag",n),
273 273 manifest=hex(changes[0]),
274 274 desc=changes[4],
275 275 date=changes[2],
276 276 files=self.listfilediffs(changes[3], n),
277 277 rev=i,
278 278 node=hn)
279 279
280 280 if count >= self.maxchanges:
281 281 break
282 282
283 283 cl = self.repo.changelog
284 284 mf = cl.read(cl.tip())[0]
285 285
286 286 yield self.t('search',
287 287 query=query,
288 288 manifest=hex(mf),
289 289 entries=changelist)
290 290
291 291 def changeset(self, nodeid):
292 292 cl = self.repo.changelog
293 293 n = self.repo.lookup(nodeid)
294 294 nodeid = hex(n)
295 295 changes = cl.read(n)
296 296 p1 = cl.parents(n)[0]
297 297
298 298 files = []
299 299 mf = self.repo.manifest.read(changes[0])
300 300 for f in changes[3]:
301 301 files.append(self.t("filenodelink",
302 302 filenode=hex(mf.get(f, nullid)), file=f))
303 303
304 304 def diff(**map):
305 305 yield self.diff(p1, n, None)
306 306
307 307 yield self.t('changeset',
308 308 diff=diff,
309 309 rev=cl.rev(n),
310 310 node=nodeid,
311 311 parent=self.siblings(cl.parents(n), cl.rev),
312 312 child=self.siblings(cl.children(n), cl.rev),
313 313 changesettag=self.showtag("changesettag",n),
314 314 manifest=hex(changes[0]),
315 315 author=changes[1],
316 316 desc=changes[4],
317 317 date=changes[2],
318 318 files=files,
319 319 archives=self.archivelist(nodeid))
320 320
321 321 def filelog(self, f, filenode):
322 322 cl = self.repo.changelog
323 323 fl = self.repo.file(f)
324 324 filenode = hex(fl.lookup(filenode))
325 325 count = fl.count()
326 326
327 327 def entries(**map):
328 328 l = []
329 329 parity = (count - 1) & 1
330 330
331 331 for i in range(count):
332 332 n = fl.node(i)
333 333 lr = fl.linkrev(n)
334 334 cn = cl.node(lr)
335 335 cs = cl.read(cl.node(lr))
336 336
337 337 l.insert(0, {"parity": parity,
338 338 "filenode": hex(n),
339 339 "filerev": i,
340 340 "file": f,
341 341 "node": hex(cn),
342 342 "author": cs[1],
343 343 "date": cs[2],
344 344 "rename": self.renamelink(fl, n),
345 345 "parent": self.siblings(fl.parents(n),
346 346 fl.rev, file=f),
347 347 "child": self.siblings(fl.children(n),
348 348 fl.rev, file=f),
349 349 "desc": cs[4]})
350 350 parity = 1 - parity
351 351
352 352 for e in l:
353 353 yield e
354 354
355 355 yield self.t("filelog", file=f, filenode=filenode, entries=entries)
356 356
357 357 def filerevision(self, f, node):
358 358 fl = self.repo.file(f)
359 359 n = fl.lookup(node)
360 360 node = hex(n)
361 361 text = fl.read(n)
362 362 changerev = fl.linkrev(n)
363 363 cl = self.repo.changelog
364 364 cn = cl.node(changerev)
365 365 cs = cl.read(cn)
366 366 mfn = cs[0]
367 367
368 368 mt = mimetypes.guess_type(f)[0]
369 369 rawtext = text
370 370 if util.binary(text):
371 371 mt = mt or 'application/octet-stream'
372 372 text = "(binary:%s)" % mt
373 373 mt = mt or 'text/plain'
374 374
375 375 def lines():
376 376 for l, t in enumerate(text.splitlines(1)):
377 377 yield {"line": t,
378 378 "linenumber": "% 6d" % (l + 1),
379 379 "parity": l & 1}
380 380
381 381 yield self.t("filerevision",
382 382 file=f,
383 383 filenode=node,
384 384 path=_up(f),
385 385 text=lines(),
386 386 raw=rawtext,
387 387 mimetype=mt,
388 388 rev=changerev,
389 389 node=hex(cn),
390 390 manifest=hex(mfn),
391 391 author=cs[1],
392 392 date=cs[2],
393 393 parent=self.siblings(fl.parents(n), fl.rev, file=f),
394 394 child=self.siblings(fl.children(n), fl.rev, file=f),
395 395 rename=self.renamelink(fl, n),
396 396 permissions=self.repo.manifest.readflags(mfn)[f])
397 397
398 398 def fileannotate(self, f, node):
399 399 bcache = {}
400 400 ncache = {}
401 401 fl = self.repo.file(f)
402 402 n = fl.lookup(node)
403 403 node = hex(n)
404 404 changerev = fl.linkrev(n)
405 405
406 406 cl = self.repo.changelog
407 407 cn = cl.node(changerev)
408 408 cs = cl.read(cn)
409 409 mfn = cs[0]
410 410
411 411 def annotate(**map):
412 412 parity = 1
413 413 last = None
414 414 for r, l in fl.annotate(n):
415 415 try:
416 416 cnode = ncache[r]
417 417 except KeyError:
418 418 cnode = ncache[r] = self.repo.changelog.node(r)
419 419
420 420 try:
421 421 name = bcache[r]
422 422 except KeyError:
423 423 cl = self.repo.changelog.read(cnode)
424 424 bcache[r] = name = self.repo.ui.shortuser(cl[1])
425 425
426 426 if last != cnode:
427 427 parity = 1 - parity
428 428 last = cnode
429 429
430 430 yield {"parity": parity,
431 431 "node": hex(cnode),
432 432 "rev": r,
433 433 "author": name,
434 434 "file": f,
435 435 "line": l}
436 436
437 437 yield self.t("fileannotate",
438 438 file=f,
439 439 filenode=node,
440 440 annotate=annotate,
441 441 path=_up(f),
442 442 rev=changerev,
443 443 node=hex(cn),
444 444 manifest=hex(mfn),
445 445 author=cs[1],
446 446 date=cs[2],
447 447 rename=self.renamelink(fl, n),
448 448 parent=self.siblings(fl.parents(n), fl.rev, file=f),
449 449 child=self.siblings(fl.children(n), fl.rev, file=f),
450 450 permissions=self.repo.manifest.readflags(mfn)[f])
451 451
452 452 def manifest(self, mnode, path):
453 453 man = self.repo.manifest
454 454 mn = man.lookup(mnode)
455 455 mnode = hex(mn)
456 456 mf = man.read(mn)
457 457 rev = man.rev(mn)
458 458 changerev = man.linkrev(mn)
459 459 node = self.repo.changelog.node(changerev)
460 460 mff = man.readflags(mn)
461 461
462 462 files = {}
463 463
464 464 p = path[1:]
465 465 if p and p[-1] != "/":
466 466 p += "/"
467 467 l = len(p)
468 468
469 469 for f,n in mf.items():
470 470 if f[:l] != p:
471 471 continue
472 472 remain = f[l:]
473 473 if "/" in remain:
474 474 short = remain[:remain.index("/") + 1] # bleah
475 475 files[short] = (f, None)
476 476 else:
477 477 short = os.path.basename(remain)
478 478 files[short] = (f, n)
479 479
480 480 def filelist(**map):
481 481 parity = 0
482 482 fl = files.keys()
483 483 fl.sort()
484 484 for f in fl:
485 485 full, fnode = files[f]
486 486 if not fnode:
487 487 continue
488 488
489 489 yield {"file": full,
490 490 "manifest": mnode,
491 491 "filenode": hex(fnode),
492 492 "parity": parity,
493 493 "basename": f,
494 494 "permissions": mff[full]}
495 495 parity = 1 - parity
496 496
497 497 def dirlist(**map):
498 498 parity = 0
499 499 fl = files.keys()
500 500 fl.sort()
501 501 for f in fl:
502 502 full, fnode = files[f]
503 503 if fnode:
504 504 continue
505 505
506 506 yield {"parity": parity,
507 507 "path": os.path.join(path, f),
508 508 "manifest": mnode,
509 509 "basename": f[:-1]}
510 510 parity = 1 - parity
511 511
512 512 yield self.t("manifest",
513 513 manifest=mnode,
514 514 rev=rev,
515 515 node=hex(node),
516 516 path=path,
517 517 up=_up(path),
518 518 fentries=filelist,
519 519 dentries=dirlist,
520 520 archives=self.archivelist(hex(node)))
521 521
522 522 def tags(self):
523 523 cl = self.repo.changelog
524 524 mf = cl.read(cl.tip())[0]
525 525
526 526 i = self.repo.tagslist()
527 527 i.reverse()
528 528
529 529 def entries(notip=False, **map):
530 530 parity = 0
531 531 for k,n in i:
532 532 if notip and k == "tip": continue
533 533 yield {"parity": parity,
534 534 "tag": k,
535 535 "tagmanifest": hex(cl.read(n)[0]),
536 536 "date": cl.read(n)[2],
537 537 "node": hex(n)}
538 538 parity = 1 - parity
539 539
540 540 yield self.t("tags",
541 541 manifest=hex(mf),
542 542 entries=lambda **x: entries(False, **x),
543 543 entriesnotip=lambda **x: entries(True, **x))
544 544
545 545 def summary(self):
546 546 cl = self.repo.changelog
547 547 mf = cl.read(cl.tip())[0]
548 548
549 549 i = self.repo.tagslist()
550 550 i.reverse()
551 551
552 552 def tagentries(**map):
553 553 parity = 0
554 554 count = 0
555 555 for k,n in i:
556 556 if k == "tip": # skip tip
557 557 continue;
558 558
559 559 count += 1
560 560 if count > 10: # limit to 10 tags
561 561 break;
562 562
563 563 c = cl.read(n)
564 564 m = c[0]
565 565 t = c[2]
566 566
567 567 yield self.t("tagentry",
568 568 parity = parity,
569 569 tag = k,
570 570 node = hex(n),
571 571 date = t,
572 572 tagmanifest = hex(m))
573 573 parity = 1 - parity
574 574
575 575 def changelist(**map):
576 576 parity = 0
577 577 cl = self.repo.changelog
578 578 l = [] # build a list in forward order for efficiency
579 579 for i in range(start, end):
580 580 n = cl.node(i)
581 581 changes = cl.read(n)
582 582 hn = hex(n)
583 583 t = changes[2]
584 584
585 585 l.insert(0, self.t(
586 586 'shortlogentry',
587 587 parity = parity,
588 588 author = changes[1],
589 589 manifest = hex(changes[0]),
590 590 desc = changes[4],
591 591 date = t,
592 592 rev = i,
593 593 node = hn))
594 594 parity = 1 - parity
595 595
596 596 yield l
597 597
598 598 cl = self.repo.changelog
599 599 mf = cl.read(cl.tip())[0]
600 600 count = cl.count()
601 601 start = max(0, count - self.maxchanges)
602 602 end = min(count, start + self.maxchanges)
603 603
604 604 yield self.t("summary",
605 605 desc = self.repo.ui.config("web", "description", "unknown"),
606 606 owner = (self.repo.ui.config("ui", "username") or # preferred
607 607 self.repo.ui.config("web", "contact") or # deprecated
608 608 self.repo.ui.config("web", "author", "unknown")), # also
609 609 lastchange = (0, 0), # FIXME
610 610 manifest = hex(mf),
611 611 tags = tagentries,
612 612 shortlog = changelist)
613 613
614 614 def filediff(self, file, changeset):
615 615 cl = self.repo.changelog
616 616 n = self.repo.lookup(changeset)
617 617 changeset = hex(n)
618 618 p1 = cl.parents(n)[0]
619 619 cs = cl.read(n)
620 620 mf = self.repo.manifest.read(cs[0])
621 621
622 622 def diff(**map):
623 623 yield self.diff(p1, n, [file])
624 624
625 625 yield self.t("filediff",
626 626 file=file,
627 627 filenode=hex(mf.get(file, nullid)),
628 628 node=changeset,
629 629 rev=self.repo.changelog.rev(n),
630 630 parent=self.siblings(cl.parents(n), cl.rev),
631 631 child=self.siblings(cl.children(n), cl.rev),
632 632 diff=diff)
633 633
634 634 archive_specs = {
635 635 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
636 636 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
637 637 'zip': ('application/zip', 'zip', '.zip', None),
638 638 }
639 639
640 640 def archive(self, req, cnode, type_):
641 641 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
642 642 name = "%s-%s" % (reponame, short(cnode))
643 643 mimetype, artype, extension, encoding = self.archive_specs[type_]
644 644 headers = [('Content-type', mimetype),
645 645 ('Content-disposition', 'attachment; filename=%s%s' %
646 646 (name, extension))]
647 647 if encoding:
648 648 headers.append(('Content-encoding', encoding))
649 649 req.header(headers)
650 650 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
651 651
652 652 # add tags to things
653 653 # tags -> list of changesets corresponding to tags
654 654 # find tag, changeset, file
655 655
656 656 def cleanpath(self, path):
657 657 p = util.normpath(path)
658 658 if p[:2] == "..":
659 659 raise Exception("suspicious path")
660 660 return p
661 661
662 662 def run(self):
663 663 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
664 664 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
665 665 import mercurial.hgweb.wsgicgi as wsgicgi
666 666 from request import wsgiapplication
667 667 def make_web_app():
668 668 return self
669 669 wsgicgi.launch(wsgiapplication(make_web_app))
670 670
671 671 def run_wsgi(self, req):
672 672 def header(**map):
673 673 header_file = cStringIO.StringIO(''.join(self.t("header", **map)))
674 674 msg = mimetools.Message(header_file, 0)
675 675 req.header(msg.items())
676 676 yield header_file.read()
677 677
678 678 def rawfileheader(**map):
679 679 req.header([('Content-type', map['mimetype']),
680 680 ('Content-disposition', 'filename=%s' % map['file']),
681 681 ('Content-length', str(len(map['raw'])))])
682 682 yield ''
683 683
684 684 def footer(**map):
685 685 yield self.t("footer",
686 686 motd=self.repo.ui.config("web", "motd", ""),
687 687 **map)
688 688
689 689 def expand_form(form):
690 690 shortcuts = {
691 691 'cl': [('cmd', ['changelog']), ('rev', None)],
692 692 'cs': [('cmd', ['changeset']), ('node', None)],
693 693 'f': [('cmd', ['file']), ('filenode', None)],
694 694 'fl': [('cmd', ['filelog']), ('filenode', None)],
695 695 'fd': [('cmd', ['filediff']), ('node', None)],
696 696 'fa': [('cmd', ['annotate']), ('filenode', None)],
697 697 'mf': [('cmd', ['manifest']), ('manifest', None)],
698 698 'ca': [('cmd', ['archive']), ('node', None)],
699 699 'tags': [('cmd', ['tags'])],
700 700 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
701 701 'static': [('cmd', ['static']), ('file', None)]
702 702 }
703 703
704 704 for k in shortcuts.iterkeys():
705 705 if form.has_key(k):
706 706 for name, value in shortcuts[k]:
707 707 if value is None:
708 708 value = form[k]
709 709 form[name] = value
710 710 del form[k]
711 711
712 712 self.refresh()
713 713
714 714 expand_form(req.form)
715 715
716 716 m = os.path.join(self.templatepath, "map")
717 717 style = self.repo.ui.config("web", "style", "")
718 718 if req.form.has_key('style'):
719 719 style = req.form['style'][0]
720 720 if style:
721 721 b = os.path.basename("map-" + style)
722 722 p = os.path.join(self.templatepath, b)
723 723 if os.path.isfile(p):
724 724 m = p
725 725
726 726 port = req.env["SERVER_PORT"]
727 727 port = port != "80" and (":" + port) or ""
728 728 uri = req.env["REQUEST_URI"]
729 729 if "?" in uri:
730 730 uri = uri.split("?")[0]
731 731 url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
732 732 if not self.reponame:
733 733 self.reponame = (self.repo.ui.config("web", "name")
734 734 or uri.strip('/') or self.repo.root)
735 735
736 736 self.t = templater.templater(m, templater.common_filters,
737 737 defaults={"url": url,
738 738 "repo": self.reponame,
739 739 "header": header,
740 740 "footer": footer,
741 741 "rawfileheader": rawfileheader,
742 742 })
743 743
744 744 if not req.form.has_key('cmd'):
745 745 req.form['cmd'] = [self.t.cache['default'],]
746 746
747 747 cmd = req.form['cmd'][0]
748 748
749 749 method = getattr(self, 'do_' + cmd, None)
750 750 if method:
751 751 method(req)
752 752 else:
753 753 req.write(self.t("error"))
754 754
755 755 def do_changelog(self, req):
756 756 hi = self.repo.changelog.count() - 1
757 757 if req.form.has_key('rev'):
758 758 hi = req.form['rev'][0]
759 759 try:
760 760 hi = self.repo.changelog.rev(self.repo.lookup(hi))
761 761 except hg.RepoError:
762 762 req.write(self.search(hi)) # XXX redirect to 404 page?
763 763 return
764 764
765 765 req.write(self.changelog(hi))
766 766
767 767 def do_changeset(self, req):
768 768 req.write(self.changeset(req.form['node'][0]))
769 769
770 770 def do_manifest(self, req):
771 771 req.write(self.manifest(req.form['manifest'][0],
772 772 self.cleanpath(req.form['path'][0])))
773 773
774 774 def do_tags(self, req):
775 775 req.write(self.tags())
776 776
777 777 def do_summary(self, req):
778 778 req.write(self.summary())
779 779
780 780 def do_filediff(self, req):
781 781 req.write(self.filediff(self.cleanpath(req.form['file'][0]),
782 782 req.form['node'][0]))
783 783
784 784 def do_file(self, req):
785 785 req.write(self.filerevision(self.cleanpath(req.form['file'][0]),
786 786 req.form['filenode'][0]))
787 787
788 788 def do_annotate(self, req):
789 789 req.write(self.fileannotate(self.cleanpath(req.form['file'][0]),
790 790 req.form['filenode'][0]))
791 791
792 792 def do_filelog(self, req):
793 793 req.write(self.filelog(self.cleanpath(req.form['file'][0]),
794 794 req.form['filenode'][0]))
795 795
796 796 def do_heads(self, req):
797 797 resp = " ".join(map(hex, self.repo.heads())) + "\n"
798 798 req.httphdr("application/mercurial-0.1", length=len(resp))
799 799 req.write(resp)
800 800
801 801 def do_branches(self, req):
802 802 nodes = []
803 803 if req.form.has_key('nodes'):
804 804 nodes = map(bin, req.form['nodes'][0].split(" "))
805 805 resp = cStringIO.StringIO()
806 806 for b in self.repo.branches(nodes):
807 807 resp.write(" ".join(map(hex, b)) + "\n")
808 808 resp = resp.getvalue()
809 809 req.httphdr("application/mercurial-0.1", length=len(resp))
810 810 req.write(resp)
811 811
812 812 def do_between(self, req):
813 813 nodes = []
814 814 if req.form.has_key('pairs'):
815 815 pairs = [map(bin, p.split("-"))
816 816 for p in req.form['pairs'][0].split(" ")]
817 817 resp = cStringIO.StringIO()
818 818 for b in self.repo.between(pairs):
819 819 resp.write(" ".join(map(hex, b)) + "\n")
820 820 resp = resp.getvalue()
821 821 req.httphdr("application/mercurial-0.1", length=len(resp))
822 822 req.write(resp)
823 823
824 824 def do_changegroup(self, req):
825 825 req.httphdr("application/mercurial-0.1")
826 826 nodes = []
827 827 if not self.allowpull:
828 828 return
829 829
830 830 if req.form.has_key('roots'):
831 831 nodes = map(bin, req.form['roots'][0].split(" "))
832 832
833 833 z = zlib.compressobj()
834 834 f = self.repo.changegroup(nodes, 'serve')
835 835 while 1:
836 836 chunk = f.read(4096)
837 837 if not chunk:
838 838 break
839 839 req.write(z.compress(chunk))
840 840
841 841 req.write(z.flush())
842 842
843 843 def do_archive(self, req):
844 844 changeset = self.repo.lookup(req.form['node'][0])
845 845 type_ = req.form['type'][0]
846 846 allowed = self.repo.ui.configlist("web", "allow_archive")
847 847 if (type_ in self.archives and (type_ in allowed or
848 848 self.repo.ui.configbool("web", "allow" + type_, False))):
849 849 self.archive(req, changeset, type_)
850 850 return
851 851
852 852 req.write(self.t("error"))
853 853
854 854 def do_static(self, req):
855 855 fname = req.form['file'][0]
856 856 static = self.repo.ui.config("web", "static",
857 857 os.path.join(self.templatepath,
858 858 "static"))
859 859 req.write(staticfile(static, fname, req)
860 860 or self.t("error", error="%r not found" % fname))
861 861
862 862 def do_capabilities(self, req):
863 863 caps = ['unbundle']
864 if self.repo.ui.configbool('server', 'stream'):
864 if self.repo.ui.configbool('server', 'uncompressed'):
865 865 caps.append('stream=%d' % self.repo.revlogversion)
866 866 resp = ' '.join(caps)
867 867 req.httphdr("application/mercurial-0.1", length=len(resp))
868 868 req.write(resp)
869 869
870 870 def check_perm(self, req, op, default):
871 871 '''check permission for operation based on user auth.
872 872 return true if op allowed, else false.
873 873 default is policy to use if no config given.'''
874 874
875 875 user = req.env.get('REMOTE_USER')
876 876
877 877 deny = self.repo.ui.configlist('web', 'deny_' + op)
878 878 if deny and (not user or deny == ['*'] or user in deny):
879 879 return False
880 880
881 881 allow = self.repo.ui.configlist('web', 'allow_' + op)
882 882 return (allow and (allow == ['*'] or user in allow)) or default
883 883
884 884 def do_unbundle(self, req):
885 885 def bail(response, headers={}):
886 886 length = int(req.env['CONTENT_LENGTH'])
887 887 for s in util.filechunkiter(req, limit=length):
888 888 # drain incoming bundle, else client will not see
889 889 # response when run outside cgi script
890 890 pass
891 891 req.httphdr("application/mercurial-0.1", headers=headers)
892 892 req.write('0\n')
893 893 req.write(response)
894 894
895 895 # require ssl by default, auth info cannot be sniffed and
896 896 # replayed
897 897 ssl_req = self.repo.ui.configbool('web', 'push_ssl', True)
898 898 if ssl_req and not req.env.get('HTTPS'):
899 899 bail(_('ssl required\n'))
900 900 return
901 901
902 902 # do not allow push unless explicitly allowed
903 903 if not self.check_perm(req, 'push', False):
904 904 bail(_('push not authorized\n'),
905 905 headers={'status': '401 Unauthorized'})
906 906 return
907 907
908 908 req.httphdr("application/mercurial-0.1")
909 909
910 910 their_heads = req.form['heads'][0].split(' ')
911 911
912 912 def check_heads():
913 913 heads = map(hex, self.repo.heads())
914 914 return their_heads == [hex('force')] or their_heads == heads
915 915
916 916 # fail early if possible
917 917 if not check_heads():
918 918 bail(_('unsynced changes\n'))
919 919 return
920 920
921 921 # do not lock repo until all changegroup data is
922 922 # streamed. save to temporary file.
923 923
924 924 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
925 925 fp = os.fdopen(fd, 'wb+')
926 926 try:
927 927 length = int(req.env['CONTENT_LENGTH'])
928 928 for s in util.filechunkiter(req, limit=length):
929 929 fp.write(s)
930 930
931 931 lock = self.repo.lock()
932 932 try:
933 933 if not check_heads():
934 934 req.write('0\n')
935 935 req.write(_('unsynced changes\n'))
936 936 return
937 937
938 938 fp.seek(0)
939 939
940 940 # send addchangegroup output to client
941 941
942 942 old_stdout = sys.stdout
943 943 sys.stdout = cStringIO.StringIO()
944 944
945 945 try:
946 946 ret = self.repo.addchangegroup(fp, 'serve')
947 947 finally:
948 948 val = sys.stdout.getvalue()
949 949 sys.stdout = old_stdout
950 950 req.write('%d\n' % ret)
951 951 req.write(val)
952 952 finally:
953 953 lock.release()
954 954 finally:
955 955 fp.close()
956 956 os.unlink(tempname)
957 957
958 958 def do_stream_out(self, req):
959 959 req.httphdr("application/mercurial-0.1")
960 960 streamclone.stream_out(self.repo, req)
@@ -1,173 +1,173 b''
1 1 # sshserver.py - ssh protocol server support for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from i18n import gettext as _
10 10 from node import *
11 11 demandload(globals(), "os streamclone sys tempfile util")
12 12
13 13 class sshserver(object):
14 14 def __init__(self, ui, repo):
15 15 self.ui = ui
16 16 self.repo = repo
17 17 self.lock = None
18 18 self.fin = sys.stdin
19 19 self.fout = sys.stdout
20 20
21 21 sys.stdout = sys.stderr
22 22
23 23 # Prevent insertion/deletion of CRs
24 24 util.set_binary(self.fin)
25 25 util.set_binary(self.fout)
26 26
27 27 def getarg(self):
28 28 argline = self.fin.readline()[:-1]
29 29 arg, l = argline.split()
30 30 val = self.fin.read(int(l))
31 31 return arg, val
32 32
33 33 def respond(self, v):
34 34 self.fout.write("%d\n" % len(v))
35 35 self.fout.write(v)
36 36 self.fout.flush()
37 37
38 38 def serve_forever(self):
39 39 while self.serve_one(): pass
40 40 sys.exit(0)
41 41
42 42 def serve_one(self):
43 43 cmd = self.fin.readline()[:-1]
44 44 if cmd:
45 45 impl = getattr(self, 'do_' + cmd, None)
46 46 if impl: impl()
47 47 else: self.respond("")
48 48 return cmd != ''
49 49
50 50 def do_heads(self):
51 51 h = self.repo.heads()
52 52 self.respond(" ".join(map(hex, h)) + "\n")
53 53
54 54 def do_hello(self):
55 55 '''the hello command returns a set of lines describing various
56 56 interesting things about the server, in an RFC822-like format.
57 57 Currently the only one defined is "capabilities", which
58 58 consists of a line in the form:
59 59
60 60 capabilities: space separated list of tokens
61 61 '''
62 62
63 63 caps = ['unbundle']
64 if self.ui.configbool('server', 'stream'):
64 if self.ui.configbool('server', 'uncompressed'):
65 65 caps.append('stream=%d' % self.repo.revlogversion)
66 66 self.respond("capabilities: %s\n" % (' '.join(caps),))
67 67
68 68 def do_lock(self):
69 69 '''DEPRECATED - allowing remote client to lock repo is not safe'''
70 70
71 71 self.lock = self.repo.lock()
72 72 self.respond("")
73 73
74 74 def do_unlock(self):
75 75 '''DEPRECATED'''
76 76
77 77 if self.lock:
78 78 self.lock.release()
79 79 self.lock = None
80 80 self.respond("")
81 81
82 82 def do_branches(self):
83 83 arg, nodes = self.getarg()
84 84 nodes = map(bin, nodes.split(" "))
85 85 r = []
86 86 for b in self.repo.branches(nodes):
87 87 r.append(" ".join(map(hex, b)) + "\n")
88 88 self.respond("".join(r))
89 89
90 90 def do_between(self):
91 91 arg, pairs = self.getarg()
92 92 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
93 93 r = []
94 94 for b in self.repo.between(pairs):
95 95 r.append(" ".join(map(hex, b)) + "\n")
96 96 self.respond("".join(r))
97 97
98 98 def do_changegroup(self):
99 99 nodes = []
100 100 arg, roots = self.getarg()
101 101 nodes = map(bin, roots.split(" "))
102 102
103 103 cg = self.repo.changegroup(nodes, 'serve')
104 104 while True:
105 105 d = cg.read(4096)
106 106 if not d:
107 107 break
108 108 self.fout.write(d)
109 109
110 110 self.fout.flush()
111 111
112 112 def do_addchangegroup(self):
113 113 '''DEPRECATED'''
114 114
115 115 if not self.lock:
116 116 self.respond("not locked")
117 117 return
118 118
119 119 self.respond("")
120 120 r = self.repo.addchangegroup(self.fin, 'serve')
121 121 self.respond(str(r))
122 122
123 123 def do_unbundle(self):
124 124 their_heads = self.getarg()[1].split()
125 125
126 126 def check_heads():
127 127 heads = map(hex, self.repo.heads())
128 128 return their_heads == [hex('force')] or their_heads == heads
129 129
130 130 # fail early if possible
131 131 if not check_heads():
132 132 self.respond(_('unsynced changes'))
133 133 return
134 134
135 135 self.respond('')
136 136
137 137 # write bundle data to temporary file because it can be big
138 138
139 139 try:
140 140 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
141 141 fp = os.fdopen(fd, 'wb+')
142 142
143 143 count = int(self.fin.readline())
144 144 while count:
145 145 fp.write(self.fin.read(count))
146 146 count = int(self.fin.readline())
147 147
148 148 was_locked = self.lock is not None
149 149 if not was_locked:
150 150 self.lock = self.repo.lock()
151 151 try:
152 152 if not check_heads():
153 153 # someone else committed/pushed/unbundled while we
154 154 # were transferring data
155 155 self.respond(_('unsynced changes'))
156 156 return
157 157 self.respond('')
158 158
159 159 # push can proceed
160 160
161 161 fp.seek(0)
162 162 r = self.repo.addchangegroup(fp, 'serve')
163 163 self.respond(str(r))
164 164 finally:
165 165 if not was_locked:
166 166 self.lock.release()
167 167 self.lock = None
168 168 finally:
169 169 fp.close()
170 170 os.unlink(tempname)
171 171
172 172 def do_stream_out(self):
173 173 streamclone.stream_out(self.repo, self.fout)
@@ -1,89 +1,89 b''
1 1 # streamclone.py - streaming clone server support for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from i18n import gettext as _
10 10 demandload(globals(), "os stat util")
11 11
12 12 # if server supports streaming clone, it advertises "stream"
13 13 # capability with value that is version+flags of repo it is serving.
14 14 # client only streams if it can read that repo format.
15 15
16 16 def walkrepo(root):
17 17 '''iterate over metadata files in repository.
18 18 walk in natural (sorted) order.
19 19 yields 2-tuples: name of .d or .i file, size of file.'''
20 20
21 21 strip_count = len(root) + len(os.sep)
22 22 def walk(path, recurse):
23 23 ents = os.listdir(path)
24 24 ents.sort()
25 25 for e in ents:
26 26 pe = os.path.join(path, e)
27 27 st = os.lstat(pe)
28 28 if stat.S_ISDIR(st.st_mode):
29 29 if recurse:
30 30 for x in walk(pe, True):
31 31 yield x
32 32 else:
33 33 if not stat.S_ISREG(st.st_mode) or len(e) < 2:
34 34 continue
35 35 sfx = e[-2:]
36 36 if sfx in ('.d', '.i'):
37 37 yield pe[strip_count:], st.st_size
38 38 # write file data first
39 39 for x in walk(os.path.join(root, 'data'), True):
40 40 yield x
41 41 # write manifest before changelog
42 42 meta = list(walk(root, False))
43 43 meta.sort(reverse=True)
44 44 for x in meta:
45 45 yield x
46 46
47 47 # stream file format is simple.
48 48 #
49 49 # server writes out line that says how many files, how many total
50 50 # bytes. separator is ascii space, byte counts are strings.
51 51 #
52 52 # then for each file:
53 53 #
54 54 # server writes out line that says file name, how many bytes in
55 55 # file. separator is ascii nul, byte count is string.
56 56 #
57 57 # server writes out raw file data.
58 58
59 59 def stream_out(repo, fileobj):
60 60 '''stream out all metadata files in repository.
61 61 writes to file-like object, must support write() and optional flush().'''
62 62
63 if not repo.ui.configbool('server', 'stream'):
63 if not repo.ui.configbool('server', 'uncompressed'):
64 64 fileobj.write('1\n')
65 65 return
66 66
67 67 fileobj.write('0\n')
68 68
69 69 # get consistent snapshot of repo. lock during scan so lock not
70 70 # needed while we stream, and commits can happen.
71 71 lock = repo.lock()
72 72 repo.ui.debug('scanning\n')
73 73 entries = []
74 74 total_bytes = 0
75 75 for name, size in walkrepo(repo.path):
76 76 entries.append((name, size))
77 77 total_bytes += size
78 78 lock.release()
79 79
80 80 repo.ui.debug('%d files, %d bytes to transfer\n' %
81 81 (len(entries), total_bytes))
82 82 fileobj.write('%d %d\n' % (len(entries), total_bytes))
83 83 for name, size in entries:
84 84 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
85 85 fileobj.write('%s\0%d\n' % (name, size))
86 86 for chunk in util.filechunkiter(repo.opener(name), limit=size):
87 87 fileobj.write(chunk)
88 88 flush = getattr(fileobj, 'flush', None)
89 89 if flush: flush()
@@ -1,25 +1,25 b''
1 1 #!/bin/sh
2 2
3 3 hg init test
4 4 cd test
5 5 echo foo>foo
6 6 hg commit -A -d '0 0' -m 1
7 hg --config server.stream=True serve -p 20059 -d --pid-file=hg1.pid
7 hg --config server.uncompressed=True serve -p 20059 -d --pid-file=hg1.pid
8 8 cat hg1.pid >> $DAEMON_PIDS
9 9 hg serve -p 20060 -d --pid-file=hg2.pid
10 10 cat hg2.pid >> $DAEMON_PIDS
11 11 cd ..
12 12
13 13 echo % clone via stream
14 14 http_proxy= hg clone --uncompressed http://localhost:20059/ copy 2>&1 | \
15 15 sed -e 's/[0-9][0-9.]*/XXX/g'
16 16 cd copy
17 17 hg verify
18 18
19 19 echo % try to clone via stream, should use pull instead
20 20 http_proxy= hg clone --uncompressed http://localhost:20060/ copy2
21 21
22 22 echo % clone via pull
23 23 http_proxy= hg clone http://localhost:20059/ copy-pull
24 24 cd copy-pull
25 25 hg verify
@@ -1,41 +1,41 b''
1 1 #!/bin/sh
2 2
3 3 hg init a
4 4 cd a
5 5 echo a > a
6 6 hg ci -Ama -d '1123456789 0'
7 hg --config server.stream=True serve -p 20059 -d --pid-file=hg.pid
7 hg --config server.uncompressed=True serve -p 20059 -d --pid-file=hg.pid
8 8 cat hg.pid >> $DAEMON_PIDS
9 9
10 10 cd ..
11 11 ("$TESTDIR/tinyproxy.py" 20060 localhost >proxy.log 2>&1 </dev/null &
12 12 echo $! > proxy.pid)
13 13 cat proxy.pid >> $DAEMON_PIDS
14 14 sleep 2
15 15
16 16 echo %% url for proxy, stream
17 17 http_proxy=http://localhost:20060/ hg --config http_proxy.always=True clone --uncompressed http://localhost:20059/ b | \
18 18 sed -e 's/[0-9][0-9.]*/XXX/g'
19 19 cd b
20 20 hg verify
21 21 cd ..
22 22
23 23 echo %% url for proxy, pull
24 24 http_proxy=http://localhost:20060/ hg --config http_proxy.always=True clone http://localhost:20059/ b-pull
25 25 cd b-pull
26 26 hg verify
27 27 cd ..
28 28
29 29 echo %% host:port for proxy
30 30 http_proxy=localhost:20060 hg clone --config http_proxy.always=True http://localhost:20059/ c
31 31
32 32 echo %% proxy url with user name and password
33 33 http_proxy=http://user:passwd@localhost:20060 hg clone --config http_proxy.always=True http://localhost:20059/ d
34 34
35 35 echo %% url with user name and password
36 36 http_proxy=http://user:passwd@localhost:20060 hg clone --config http_proxy.always=True http://user:passwd@localhost:20059/ e
37 37
38 38 echo %% bad host:port for proxy
39 39 http_proxy=localhost:20061 hg clone --config http_proxy.always=True http://localhost:20059/ f
40 40
41 41 exit 0
@@ -1,92 +1,92 b''
1 1 #!/bin/sh
2 2
3 3 # This test tries to exercise the ssh functionality with a dummy script
4 4
5 5 cat <<'EOF' > dummyssh
6 6 #!/bin/sh
7 7 # this attempts to deal with relative pathnames
8 8 cd `dirname $0`
9 9
10 10 # check for proper args
11 11 if [ $1 != "user@dummy" ] ; then
12 12 exit -1
13 13 fi
14 14
15 15 # check that we're in the right directory
16 16 if [ ! -x dummyssh ] ; then
17 17 exit -1
18 18 fi
19 19
20 20 echo Got arguments 1:$1 2:$2 3:$3 4:$4 5:$5 >> dummylog
21 21 $2
22 22 EOF
23 23 chmod +x dummyssh
24 24
25 25 echo "# creating 'remote'"
26 26 hg init remote
27 27 cd remote
28 28 echo this > foo
29 29 hg ci -A -m "init" -d "1000000 0" foo
30 30 echo '[server]' > .hg/hgrc
31 echo 'stream = True' >> .hg/hgrc
31 echo 'uncompressed = True' >> .hg/hgrc
32 32
33 33 cd ..
34 34
35 35 echo "# clone remote via stream"
36 36 hg clone -e ./dummyssh --uncompressed ssh://user@dummy/remote local-stream 2>&1 | \
37 37 sed -e 's/[0-9][0-9.]*/XXX/g'
38 38 cd local-stream
39 39 hg verify
40 40 cd ..
41 41
42 42 echo "# clone remote via pull"
43 43 hg clone -e ./dummyssh ssh://user@dummy/remote local
44 44
45 45 echo "# verify"
46 46 cd local
47 47 hg verify
48 48
49 49 echo "# empty default pull"
50 50 hg paths
51 51 hg pull -e ../dummyssh
52 52
53 53 echo "# local change"
54 54 echo bleah > foo
55 55 hg ci -m "add" -d "1000000 0"
56 56
57 57 echo "# updating rc"
58 58 echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
59 59 echo "[ui]" >> .hg/hgrc
60 60 echo "ssh = ../dummyssh" >> .hg/hgrc
61 61
62 62 echo "# find outgoing"
63 63 hg out ssh://user@dummy/remote
64 64
65 65 echo "# find incoming on the remote side"
66 66 hg incoming -R ../remote -e ../dummyssh ssh://user@dummy/local
67 67
68 68 echo "# push"
69 69 hg push
70 70
71 71 cd ../remote
72 72
73 73 echo "# check remote tip"
74 74 hg tip
75 75 hg verify
76 76 hg cat foo
77 77
78 78 echo z > z
79 79 hg ci -A -m z -d '1000001 0' z
80 80
81 81 cd ../local
82 82 echo r > r
83 83 hg ci -A -m z -d '1000002 0' r
84 84
85 85 echo "# push should fail"
86 86 hg push
87 87
88 88 echo "# push should succeed"
89 89 hg push -f
90 90
91 91 cd ..
92 92 cat dummylog
General Comments 0
You need to be logged in to leave comments. Login now