##// END OF EJS Templates
Only read .hg/hgrc files from trusted users/groups...
Alexis S. L. Carvalho -
r3551:3b07e223 default
parent child Browse files
Show More
@@ -0,0 +1,113
1 #!/usr/bin/env python
2 # Since it's not easy to write a test that portably deals
3 # with files from different users/groups, we cheat a bit by
4 # monkey-patching some functions in the util module
5
6 import os
7 from mercurial import ui, util
8
9 hgrc = os.environ['HGRCPATH']
10
11 def testui(user='foo', group='bar', tusers=(), tgroups=(),
12 cuser='foo', cgroup='bar', debug=False):
13 # user, group => owners of the file
14 # tusers, tgroups => trusted users/groups
15 # cuser, cgroup => user/group of the current process
16
17 # write a global hgrc with the list of trusted users/groups and
18 # some setting so that we can be sure it was read
19 f = open(hgrc, 'w')
20 f.write('[paths]\n')
21 f.write('global = /some/path\n\n')
22
23 if tusers or tgroups:
24 f.write('[trusted]\n')
25 if tusers:
26 f.write('users = %s\n' % ', '.join(tusers))
27 if tgroups:
28 f.write('groups = %s\n' % ', '.join(tgroups))
29 f.close()
30
31 # override the functions that give names to uids and gids
32 def username(uid=None):
33 if uid is None:
34 return cuser
35 return user
36 util.username = username
37
38 def groupname(gid=None):
39 if gid is None:
40 return 'bar'
41 return group
42 util.groupname = groupname
43
44 # try to read everything
45 #print '# File belongs to user %s, group %s' % (user, group)
46 #print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups)
47 kind = ('different', 'same')
48 who = ('', 'user', 'group', 'user and the group')
49 trusted = who[(user in tusers) + 2*(group in tgroups)]
50 if trusted:
51 trusted = ', but we trust the ' + trusted
52 print '# %s user, %s group%s' % (kind[user == cuser], kind[group == cgroup],
53 trusted)
54
55 parentui = ui.ui()
56 parentui.updateopts(debug=debug)
57 u = ui.ui(parentui=parentui)
58 u.readconfig('.hg/hgrc')
59 for name, path in u.configitems('paths'):
60 print ' ', name, '=', path
61 print
62
63 return u
64
65 os.mkdir('repo')
66 os.chdir('repo')
67 os.mkdir('.hg')
68 f = open('.hg/hgrc', 'w')
69 f.write('[paths]\n')
70 f.write('local = /another/path\n\n')
71 f.close()
72
73 #print '# Everything is run by user foo, group bar\n'
74
75 # same user, same group
76 testui()
77 # same user, different group
78 testui(group='def')
79 # different user, same group
80 testui(user='abc')
81 # ... but we trust the group
82 testui(user='abc', tgroups=['bar'])
83 # different user, different group
84 testui(user='abc', group='def')
85 # ... but we trust the user
86 testui(user='abc', group='def', tusers=['abc'])
87 # ... but we trust the group
88 testui(user='abc', group='def', tgroups=['def'])
89 # ... but we trust the user and the group
90 testui(user='abc', group='def', tusers=['abc'], tgroups=['def'])
91 # ... but we trust all users
92 print '# we trust all users'
93 testui(user='abc', group='def', tusers=['*'])
94 # ... but we trust all groups
95 print '# we trust all groups'
96 testui(user='abc', group='def', tgroups=['*'])
97 # ... but we trust the whole universe
98 print '# we trust all users and groups'
99 testui(user='abc', group='def', tusers=['*'], tgroups=['*'])
100 # ... check that users and groups are in different namespaces
101 print "# we don't get confused by users and groups with the same name"
102 testui(user='abc', group='def', tusers=['def'], tgroups=['abc'])
103 # ... lists of user names work
104 print "# list of user names"
105 testui(user='abc', group='def', tusers=['foo', 'xyz', 'abc', 'bleh'],
106 tgroups=['bar', 'baz', 'qux'])
107 # ... lists of group names work
108 print "# list of group names"
109 testui(user='abc', group='def', tusers=['foo', 'xyz', 'bleh'],
110 tgroups=['bar', 'def', 'baz', 'qux'])
111
112 print "# Can't figure out the name of the user running this process"
113 testui(user='abc', group='def', cuser=None)
@@ -0,0 +1,67
1 # same user, same group
2 global = /some/path
3 local = /another/path
4
5 # same user, different group
6 global = /some/path
7 local = /another/path
8
9 # different user, same group
10 Not reading file .hg/hgrc from untrusted user abc, group bar
11 global = /some/path
12
13 # different user, same group, but we trust the group
14 global = /some/path
15 local = /another/path
16
17 # different user, different group
18 Not reading file .hg/hgrc from untrusted user abc, group def
19 global = /some/path
20
21 # different user, different group, but we trust the user
22 global = /some/path
23 local = /another/path
24
25 # different user, different group, but we trust the group
26 global = /some/path
27 local = /another/path
28
29 # different user, different group, but we trust the user and the group
30 global = /some/path
31 local = /another/path
32
33 # we trust all users
34 # different user, different group
35 global = /some/path
36 local = /another/path
37
38 # we trust all groups
39 # different user, different group
40 global = /some/path
41 local = /another/path
42
43 # we trust all users and groups
44 # different user, different group
45 global = /some/path
46 local = /another/path
47
48 # we don't get confused by users and groups with the same name
49 # different user, different group
50 Not reading file .hg/hgrc from untrusted user abc, group def
51 global = /some/path
52
53 # list of user names
54 # different user, different group, but we trust the user
55 global = /some/path
56 local = /another/path
57
58 # list of group names
59 # different user, different group, but we trust the group
60 global = /some/path
61 local = /another/path
62
63 # Can't figure out the name of the user running this process
64 # different user, different group
65 global = /some/path
66 local = /another/path
67
@@ -1,495 +1,508
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 On Unix, this file is only read if it belongs to a trusted user
54 or to a trusted group.
53 55
54 56 SYNTAX
55 57 ------
56 58
57 59 A configuration file consists of sections, led by a "[section]" header
58 60 and followed by "name: value" entries; "name=value" is also accepted.
59 61
60 62 [spam]
61 63 eggs=ham
62 64 green=
63 65 eggs
64 66
65 67 Each line contains one entry. If the lines that follow are indented,
66 68 they are treated as continuations of that entry.
67 69
68 70 Leading whitespace is removed from values. Empty lines are skipped.
69 71
70 72 The optional values can contain format strings which refer to other
71 73 values in the same section, or values in a special DEFAULT section.
72 74
73 75 Lines beginning with "#" or ";" are ignored and may be used to provide
74 76 comments.
75 77
76 78 SECTIONS
77 79 --------
78 80
79 81 This section describes the different sections that may appear in a
80 82 Mercurial "hgrc" file, the purpose of each section, its possible
81 83 keys, and their possible values.
82 84
83 85 decode/encode::
84 86 Filters for transforming files on checkout/checkin. This would
85 87 typically be used for newline processing or other
86 88 localization/canonicalization of files.
87 89
88 90 Filters consist of a filter pattern followed by a filter command.
89 91 Filter patterns are globs by default, rooted at the repository
90 92 root. For example, to match any file ending in ".txt" in the root
91 93 directory only, use the pattern "*.txt". To match any file ending
92 94 in ".c" anywhere in the repository, use the pattern "**.c".
93 95
94 96 The filter command can start with a specifier, either "pipe:" or
95 97 "tempfile:". If no specifier is given, "pipe:" is used by default.
96 98
97 99 A "pipe:" command must accept data on stdin and return the
98 100 transformed data on stdout.
99 101
100 102 Pipe example:
101 103
102 104 [encode]
103 105 # uncompress gzip files on checkin to improve delta compression
104 106 # note: not necessarily a good idea, just an example
105 107 *.gz = pipe: gunzip
106 108
107 109 [decode]
108 110 # recompress gzip files when writing them to the working dir (we
109 111 # can safely omit "pipe:", because it's the default)
110 112 *.gz = gzip
111 113
112 114 A "tempfile:" command is a template. The string INFILE is replaced
113 115 with the name of a temporary file that contains the data to be
114 116 filtered by the command. The string OUTFILE is replaced with the
115 117 name of an empty temporary file, where the filtered data must be
116 118 written by the command.
117 119
118 120 NOTE: the tempfile mechanism is recommended for Windows systems,
119 121 where the standard shell I/O redirection operators often have
120 122 strange effects. In particular, if you are doing line ending
121 123 conversion on Windows using the popular dos2unix and unix2dos
122 124 programs, you *must* use the tempfile mechanism, as using pipes will
123 125 corrupt the contents of your files.
124 126
125 127 Tempfile example:
126 128
127 129 [encode]
128 130 # convert files to unix line ending conventions on checkin
129 131 **.txt = tempfile: dos2unix -n INFILE OUTFILE
130 132
131 133 [decode]
132 134 # convert files to windows line ending conventions when writing
133 135 # them to the working dir
134 136 **.txt = tempfile: unix2dos -n INFILE OUTFILE
135 137
136 138 defaults::
137 139 Use the [defaults] section to define command defaults, i.e. the
138 140 default options/arguments to pass to the specified commands.
139 141
140 142 The following example makes 'hg log' run in verbose mode, and
141 143 'hg status' show only the modified files, by default.
142 144
143 145 [defaults]
144 146 log = -v
145 147 status = -m
146 148
147 149 The actual commands, instead of their aliases, must be used when
148 150 defining command defaults. The command defaults will also be
149 151 applied to the aliases of the commands defined.
150 152
151 153 email::
152 154 Settings for extensions that send email messages.
153 155 from;;
154 156 Optional. Email address to use in "From" header and SMTP envelope
155 157 of outgoing messages.
156 158 to;;
157 159 Optional. Comma-separated list of recipients' email addresses.
158 160 cc;;
159 161 Optional. Comma-separated list of carbon copy recipients'
160 162 email addresses.
161 163 bcc;;
162 164 Optional. Comma-separated list of blind carbon copy
163 165 recipients' email addresses. Cannot be set interactively.
164 166 method;;
165 167 Optional. Method to use to send email messages. If value is
166 168 "smtp" (default), use SMTP (see section "[smtp]" for
167 169 configuration). Otherwise, use as name of program to run that
168 170 acts like sendmail (takes "-f" option for sender, list of
169 171 recipients on command line, message on stdin). Normally, setting
170 172 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
171 173 sendmail to send messages.
172 174
173 175 Email example:
174 176
175 177 [email]
176 178 from = Joseph User <joe.user@example.com>
177 179 method = /usr/sbin/sendmail
178 180
179 181 extensions::
180 182 Mercurial has an extension mechanism for adding new features. To
181 183 enable an extension, create an entry for it in this section.
182 184
183 185 If you know that the extension is already in Python's search path,
184 186 you can give the name of the module, followed by "=", with nothing
185 187 after the "=".
186 188
187 189 Otherwise, give a name that you choose, followed by "=", followed by
188 190 the path to the ".py" file (including the file name extension) that
189 191 defines the extension.
190 192
191 193 Example for ~/.hgrc:
192 194
193 195 [extensions]
194 196 # (the mq extension will get loaded from mercurial's path)
195 197 hgext.mq =
196 198 # (this extension will get loaded from the file specified)
197 199 myfeature = ~/.hgext/myfeature.py
198 200
199 201 hooks::
200 202 Commands or Python functions that get automatically executed by
201 203 various actions such as starting or finishing a commit. Multiple
202 204 hooks can be run for the same action by appending a suffix to the
203 205 action. Overriding a site-wide hook can be done by changing its
204 206 value or setting it to an empty string.
205 207
206 208 Example .hg/hgrc:
207 209
208 210 [hooks]
209 211 # do not use the site-wide hook
210 212 incoming =
211 213 incoming.email = /my/email/hook
212 214 incoming.autobuild = /my/build/hook
213 215
214 216 Most hooks are run with environment variables set that give added
215 217 useful information. For each hook below, the environment variables
216 218 it is passed are listed with names of the form "$HG_foo".
217 219
218 220 changegroup;;
219 221 Run after a changegroup has been added via push, pull or
220 222 unbundle. ID of the first new changeset is in $HG_NODE. URL from
221 223 which changes came is in $HG_URL.
222 224 commit;;
223 225 Run after a changeset has been created in the local repository.
224 226 ID of the newly created changeset is in $HG_NODE. Parent
225 227 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
226 228 incoming;;
227 229 Run after a changeset has been pulled, pushed, or unbundled into
228 230 the local repository. The ID of the newly arrived changeset is in
229 231 $HG_NODE. URL that was source of changes came is in $HG_URL.
230 232 outgoing;;
231 233 Run after sending changes from local repository to another. ID of
232 234 first changeset sent is in $HG_NODE. Source of operation is in
233 235 $HG_SOURCE; see "preoutgoing" hook for description.
234 236 prechangegroup;;
235 237 Run before a changegroup is added via push, pull or unbundle.
236 238 Exit status 0 allows the changegroup to proceed. Non-zero status
237 239 will cause the push, pull or unbundle to fail. URL from which
238 240 changes will come is in $HG_URL.
239 241 precommit;;
240 242 Run before starting a local commit. Exit status 0 allows the
241 243 commit to proceed. Non-zero status will cause the commit to fail.
242 244 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
243 245 preoutgoing;;
244 246 Run before computing changes to send from the local repository to
245 247 another. Non-zero status will cause failure. This lets you
246 248 prevent pull over http or ssh. Also prevents against local pull,
247 249 push (outbound) or bundle commands, but not effective, since you
248 250 can just copy files instead then. Source of operation is in
249 251 $HG_SOURCE. If "serve", operation is happening on behalf of
250 252 remote ssh or http repository. If "push", "pull" or "bundle",
251 253 operation is happening on behalf of repository on same system.
252 254 pretag;;
253 255 Run before creating a tag. Exit status 0 allows the tag to be
254 256 created. Non-zero status will cause the tag to fail. ID of
255 257 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
256 258 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
257 259 pretxnchangegroup;;
258 260 Run after a changegroup has been added via push, pull or unbundle,
259 261 but before the transaction has been committed. Changegroup is
260 262 visible to hook program. This lets you validate incoming changes
261 263 before accepting them. Passed the ID of the first new changeset
262 264 in $HG_NODE. Exit status 0 allows the transaction to commit.
263 265 Non-zero status will cause the transaction to be rolled back and
264 266 the push, pull or unbundle will fail. URL that was source of
265 267 changes is in $HG_URL.
266 268 pretxncommit;;
267 269 Run after a changeset has been created but the transaction not yet
268 270 committed. Changeset is visible to hook program. This lets you
269 271 validate commit message and changes. Exit status 0 allows the
270 272 commit to proceed. Non-zero status will cause the transaction to
271 273 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
272 274 IDs are in $HG_PARENT1 and $HG_PARENT2.
273 275 preupdate;;
274 276 Run before updating the working directory. Exit status 0 allows
275 277 the update to proceed. Non-zero status will prevent the update.
276 278 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
277 279 of second new parent is in $HG_PARENT2.
278 280 tag;;
279 281 Run after a tag is created. ID of tagged changeset is in
280 282 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
281 283 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
282 284 update;;
283 285 Run after updating the working directory. Changeset ID of first
284 286 new parent is in $HG_PARENT1. If merge, ID of second new parent
285 287 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
286 288 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
287 289
288 290 Note: In earlier releases, the names of hook environment variables
289 291 did not have a "HG_" prefix. The old unprefixed names are no longer
290 292 provided in the environment.
291 293
292 294 The syntax for Python hooks is as follows:
293 295
294 296 hookname = python:modulename.submodule.callable
295 297
296 298 Python hooks are run within the Mercurial process. Each hook is
297 299 called with at least three keyword arguments: a ui object (keyword
298 300 "ui"), a repository object (keyword "repo"), and a "hooktype"
299 301 keyword that tells what kind of hook is used. Arguments listed as
300 302 environment variables above are passed as keyword arguments, with no
301 303 "HG_" prefix, and names in lower case.
302 304
303 305 A Python hook must return a "true" value to succeed. Returning a
304 306 "false" value or raising an exception is treated as failure of the
305 307 hook.
306 308
307 309 http_proxy::
308 310 Used to access web-based Mercurial repositories through a HTTP
309 311 proxy.
310 312 host;;
311 313 Host name and (optional) port of the proxy server, for example
312 314 "myproxy:8000".
313 315 no;;
314 316 Optional. Comma-separated list of host names that should bypass
315 317 the proxy.
316 318 passwd;;
317 319 Optional. Password to authenticate with at the proxy server.
318 320 user;;
319 321 Optional. User name to authenticate with at the proxy server.
320 322
321 323 smtp::
322 324 Configuration for extensions that need to send email messages.
323 325 host;;
324 326 Host name of mail server, e.g. "mail.example.com".
325 327 port;;
326 328 Optional. Port to connect to on mail server. Default: 25.
327 329 tls;;
328 330 Optional. Whether to connect to mail server using TLS. True or
329 331 False. Default: False.
330 332 username;;
331 333 Optional. User name to authenticate to SMTP server with.
332 334 If username is specified, password must also be specified.
333 335 Default: none.
334 336 password;;
335 337 Optional. Password to authenticate to SMTP server with.
336 338 If username is specified, password must also be specified.
337 339 Default: none.
338 340 local_hostname;;
339 341 Optional. It's the hostname that the sender can use to identify itself
340 342 to the MTA.
341 343
342 344 paths::
343 345 Assigns symbolic names to repositories. The left side is the
344 346 symbolic name, and the right gives the directory or URL that is the
345 347 location of the repository. Default paths can be declared by
346 348 setting the following entries.
347 349 default;;
348 350 Directory or URL to use when pulling if no source is specified.
349 351 Default is set to repository from which the current repository
350 352 was cloned.
351 353 default-push;;
352 354 Optional. Directory or URL to use when pushing if no destination
353 355 is specified.
354 356
355 357 server::
356 358 Controls generic server settings.
357 359 uncompressed;;
358 360 Whether to allow clients to clone a repo using the uncompressed
359 361 streaming protocol. This transfers about 40% more data than a
360 362 regular clone, but uses less memory and CPU on both server and
361 363 client. Over a LAN (100Mbps or better) or a very fast WAN, an
362 364 uncompressed streaming clone is a lot faster (~10x) than a regular
363 365 clone. Over most WAN connections (anything slower than about
364 366 6Mbps), uncompressed streaming is slower, because of the extra
365 367 data transfer overhead. Default is False.
366 368
369 trusted::
370 Mercurial will only read the .hg/hgrc file from a repository if
371 it belongs to a trusted user or to a trusted group. This section
372 specifies what users and groups are trusted. The current user is
373 always trusted. To trust everybody, list a user or a group with
374 name "*".
375 users;;
376 Comma-separated list of trusted users.
377 groups;;
378 Comma-separated list of trusted groups.
379
367 380 ui::
368 381 User interface controls.
369 382 debug;;
370 383 Print debugging information. True or False. Default is False.
371 384 editor;;
372 385 The editor to use during a commit. Default is $EDITOR or "vi".
373 386 ignore;;
374 387 A file to read per-user ignore patterns from. This file should be in
375 388 the same format as a repository-wide .hgignore file. This option
376 389 supports hook syntax, so if you want to specify multiple ignore
377 390 files, you can do so by setting something like
378 391 "ignore.other = ~/.hgignore2". For details of the ignore file
379 392 format, see the hgignore(5) man page.
380 393 interactive;;
381 394 Allow to prompt the user. True or False. Default is True.
382 395 logtemplate;;
383 396 Template string for commands that print changesets.
384 397 style;;
385 398 Name of style to use for command output.
386 399 merge;;
387 400 The conflict resolution program to use during a manual merge.
388 401 Default is "hgmerge".
389 402 quiet;;
390 403 Reduce the amount of output printed. True or False. Default is False.
391 404 remotecmd;;
392 405 remote command to use for clone/push/pull operations. Default is 'hg'.
393 406 ssh;;
394 407 command to use for SSH connections. Default is 'ssh'.
395 408 strict;;
396 409 Require exact command names, instead of allowing unambiguous
397 410 abbreviations. True or False. Default is False.
398 411 timeout;;
399 412 The timeout used when a lock is held (in seconds), a negative value
400 413 means no timeout. Default is 600.
401 414 username;;
402 415 The committer of a changeset created when running "commit".
403 416 Typically a person's name and email address, e.g. "Fred Widget
404 417 <fred@example.com>". Default is $EMAIL. If no default is found, or the
405 418 configured username is empty, it has to be specified manually.
406 419 verbose;;
407 420 Increase the amount of output printed. True or False. Default is False.
408 421
409 422
410 423 web::
411 424 Web interface configuration.
412 425 accesslog;;
413 426 Where to output the access log. Default is stdout.
414 427 address;;
415 428 Interface address to bind to. Default is all.
416 429 allow_archive;;
417 430 List of archive format (bz2, gz, zip) allowed for downloading.
418 431 Default is empty.
419 432 allowbz2;;
420 433 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
421 434 Default is false.
422 435 allowgz;;
423 436 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
424 437 Default is false.
425 438 allowpull;;
426 439 Whether to allow pulling from the repository. Default is true.
427 440 allow_push;;
428 441 Whether to allow pushing to the repository. If empty or not set,
429 442 push is not allowed. If the special value "*", any remote user
430 443 can push, including unauthenticated users. Otherwise, the remote
431 444 user must have been authenticated, and the authenticated user name
432 445 must be present in this list (separated by whitespace or ",").
433 446 The contents of the allow_push list are examined after the
434 447 deny_push list.
435 448 allowzip;;
436 449 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
437 450 Default is false. This feature creates temporary files.
438 451 baseurl;;
439 452 Base URL to use when publishing URLs in other locations, so
440 453 third-party tools like email notification hooks can construct URLs.
441 454 Example: "http://hgserver/repos/"
442 455 contact;;
443 456 Name or email address of the person in charge of the repository.
444 457 Default is "unknown".
445 458 deny_push;;
446 459 Whether to deny pushing to the repository. If empty or not set,
447 460 push is not denied. If the special value "*", all remote users
448 461 are denied push. Otherwise, unauthenticated users are all denied,
449 462 and any authenticated user name present in this list (separated by
450 463 whitespace or ",") is also denied. The contents of the deny_push
451 464 list are examined before the allow_push list.
452 465 description;;
453 466 Textual description of the repository's purpose or contents.
454 467 Default is "unknown".
455 468 errorlog;;
456 469 Where to output the error log. Default is stderr.
457 470 ipv6;;
458 471 Whether to use IPv6. Default is false.
459 472 name;;
460 473 Repository name to use in the web interface. Default is current
461 474 working directory.
462 475 maxchanges;;
463 476 Maximum number of changes to list on the changelog. Default is 10.
464 477 maxfiles;;
465 478 Maximum number of files to list per changeset. Default is 10.
466 479 port;;
467 480 Port to listen on. Default is 8000.
468 481 push_ssl;;
469 482 Whether to require that inbound pushes be transported over SSL to
470 483 prevent password sniffing. Default is true.
471 484 stripes;;
472 485 How many lines a "zebra stripe" should span in multiline output.
473 486 Default is 1; set to 0 to disable.
474 487 style;;
475 488 Which template map style to use.
476 489 templates;;
477 490 Where to find the HTML templates. Default is install path.
478 491
479 492
480 493 AUTHOR
481 494 ------
482 495 Bryan O'Sullivan <bos@serpentine.com>.
483 496
484 497 Mercurial was written by Matt Mackall <mpm@selenic.com>.
485 498
486 499 SEE ALSO
487 500 --------
488 501 hg(1), hgignore(5)
489 502
490 503 COPYING
491 504 -------
492 505 This manual page is copyright 2005 Bryan O'Sullivan.
493 506 Mercurial is copyright 2005, 2006 Matt Mackall.
494 507 Free use of this software is granted under the terms of the GNU General
495 508 Public License (GPL).
@@ -1,343 +1,377
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005, 2006 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 i18n import gettext as _
9 9 from demandload import *
10 10 demandload(globals(), "errno getpass os re socket sys tempfile")
11 11 demandload(globals(), "ConfigParser traceback util")
12 12
13 13 def dupconfig(orig):
14 14 new = util.configparser(orig.defaults())
15 15 updateconfig(orig, new)
16 16 return new
17 17
18 18 def updateconfig(source, dest, sections=None):
19 19 if not sections:
20 20 sections = source.sections()
21 21 for section in sections:
22 22 if not dest.has_section(section):
23 23 dest.add_section(section)
24 24 for name, value in source.items(section, raw=True):
25 25 dest.set(section, name, value)
26 26
27 27 class ui(object):
28 28 def __init__(self, verbose=False, debug=False, quiet=False,
29 29 interactive=True, traceback=False, parentui=None):
30 30 self.overlay = None
31 31 self.header = []
32 32 self.prev_header = []
33 33 if parentui is None:
34 34 # this is the parent of all ui children
35 35 self.parentui = None
36 36 self.readhooks = []
37 37 self.quiet = quiet
38 38 self.verbose = verbose
39 39 self.debugflag = debug
40 40 self.interactive = interactive
41 41 self.traceback = traceback
42 self.trusted_users = {}
43 self.trusted_groups = {}
42 44 self.cdata = util.configparser()
43 45 self.readconfig(util.rcpath())
44 46 self.updateopts(verbose, debug, quiet, interactive)
45 47 else:
46 48 # parentui may point to an ui object which is already a child
47 49 self.parentui = parentui.parentui or parentui
48 50 self.readhooks = self.parentui.readhooks[:]
51 self.trusted_users = parentui.trusted_users.copy()
52 self.trusted_groups = parentui.trusted_groups.copy()
49 53 self.cdata = dupconfig(self.parentui.cdata)
50 54 if self.parentui.overlay:
51 55 self.overlay = dupconfig(self.parentui.overlay)
52 56
53 57 def __getattr__(self, key):
54 58 return getattr(self.parentui, key)
55 59
56 60 def updateopts(self, verbose=False, debug=False, quiet=False,
57 61 interactive=True, traceback=False, config=[]):
58 62 for section, name, value in config:
59 63 self.setconfig(section, name, value)
60 64
61 65 if quiet or verbose or debug:
62 66 self.setconfig('ui', 'quiet', str(bool(quiet)))
63 67 self.setconfig('ui', 'verbose', str(bool(verbose)))
64 68 self.setconfig('ui', 'debug', str(bool(debug)))
65 69
66 70 self.verbosity_constraints()
67 71
68 72 if not interactive:
69 73 self.setconfig('ui', 'interactive', 'False')
70 74 self.interactive = False
71 75
72 76 self.traceback = self.traceback or traceback
73 77
74 78 def verbosity_constraints(self):
75 79 self.quiet = self.configbool('ui', 'quiet')
76 80 self.verbose = self.configbool('ui', 'verbose')
77 81 self.debugflag = self.configbool('ui', 'debug')
78 82
79 83 if self.debugflag:
80 84 self.verbose = True
81 85 self.quiet = False
82 86 elif self.verbose and self.quiet:
83 87 self.quiet = self.verbose = False
84 88
89 def _is_trusted(self, fp, f, warn=True):
90 tusers = self.trusted_users
91 tgroups = self.trusted_groups
92 if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups:
93 st = util.fstat(fp)
94 user = util.username(st.st_uid)
95 group = util.groupname(st.st_gid)
96 if user not in tusers and group not in tgroups:
97 if warn:
98 self.warn(_('Not reading file %s from untrusted '
99 'user %s, group %s\n') % (f, user, group))
100 return False
101 return True
102
85 103 def readconfig(self, fn, root=None):
86 104 if isinstance(fn, basestring):
87 105 fn = [fn]
88 106 for f in fn:
89 107 try:
90 self.cdata.read(f)
108 fp = open(f)
109 except IOError:
110 continue
111 if not self._is_trusted(fp, f):
112 continue
113 try:
114 self.cdata.readfp(fp, f)
91 115 except ConfigParser.ParsingError, inst:
92 116 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
93 117 # override data from config files with data set with ui.setconfig
94 118 if self.overlay:
95 119 updateconfig(self.overlay, self.cdata)
96 120 if root is None:
97 121 root = os.path.expanduser('~')
98 122 self.fixconfig(root=root)
99 123 for hook in self.readhooks:
100 124 hook(self)
101 125
102 126 def addreadhook(self, hook):
103 127 self.readhooks.append(hook)
104 128
105 129 def readsections(self, filename, *sections):
106 130 "read filename and add only the specified sections to the config data"
107 131 if not sections:
108 132 return
109 133
110 134 cdata = util.configparser()
111 135 try:
112 136 cdata.read(filename)
113 137 except ConfigParser.ParsingError, inst:
114 138 raise util.Abort(_("failed to parse %s\n%s") % (filename,
115 139 inst))
116 140
117 141 for section in sections:
118 142 if not cdata.has_section(section):
119 143 cdata.add_section(section)
120 144
121 145 updateconfig(cdata, self.cdata, sections)
122 146
123 147 def fixconfig(self, section=None, name=None, value=None, root=None):
124 148 # translate paths relative to root (or home) into absolute paths
125 149 if section is None or section == 'paths':
126 150 if root is None:
127 151 root = os.getcwd()
128 152 items = section and [(name, value)] or []
129 153 for cdata in self.cdata, self.overlay:
130 154 if not cdata: continue
131 155 if not items and cdata.has_section('paths'):
132 156 pathsitems = cdata.items('paths')
133 157 else:
134 158 pathsitems = items
135 159 for n, path in pathsitems:
136 160 if path and "://" not in path and not os.path.isabs(path):
137 161 cdata.set("paths", n, os.path.join(root, path))
138 162
139 163 # update quiet/verbose/debug and interactive status
140 164 if section is None or section == 'ui':
141 165 if name is None or name in ('quiet', 'verbose', 'debug'):
142 166 self.verbosity_constraints()
143 167
144 168 if name is None or name == 'interactive':
145 169 self.interactive = self.configbool("ui", "interactive", True)
146 170
171 # update trust information
172 if section is None or section == 'trusted':
173 user = util.username()
174 if user is not None:
175 self.trusted_users[user] = 1
176 for user in self.configlist('trusted', 'users'):
177 self.trusted_users[user] = 1
178 for group in self.configlist('trusted', 'groups'):
179 self.trusted_groups[group] = 1
180
147 181 def setconfig(self, section, name, value):
148 182 if not self.overlay:
149 183 self.overlay = util.configparser()
150 184 for cdata in (self.overlay, self.cdata):
151 185 if not cdata.has_section(section):
152 186 cdata.add_section(section)
153 187 cdata.set(section, name, value)
154 188 self.fixconfig(section, name, value)
155 189
156 190 def _config(self, section, name, default, funcname):
157 191 if self.cdata.has_option(section, name):
158 192 try:
159 193 func = getattr(self.cdata, funcname)
160 194 return func(section, name)
161 195 except ConfigParser.InterpolationError, inst:
162 196 raise util.Abort(_("Error in configuration section [%s] "
163 197 "parameter '%s':\n%s")
164 198 % (section, name, inst))
165 199 return default
166 200
167 201 def config(self, section, name, default=None):
168 202 return self._config(section, name, default, 'get')
169 203
170 204 def configbool(self, section, name, default=False):
171 205 return self._config(section, name, default, 'getboolean')
172 206
173 207 def configlist(self, section, name, default=None):
174 208 """Return a list of comma/space separated strings"""
175 209 result = self.config(section, name)
176 210 if result is None:
177 211 result = default or []
178 212 if isinstance(result, basestring):
179 213 result = result.replace(",", " ").split()
180 214 return result
181 215
182 216 def has_config(self, section):
183 217 '''tell whether section exists in config.'''
184 218 return self.cdata.has_section(section)
185 219
186 220 def configitems(self, section):
187 221 items = {}
188 222 if self.cdata.has_section(section):
189 223 try:
190 224 items.update(dict(self.cdata.items(section)))
191 225 except ConfigParser.InterpolationError, inst:
192 226 raise util.Abort(_("Error in configuration section [%s]:\n%s")
193 227 % (section, inst))
194 228 x = items.items()
195 229 x.sort()
196 230 return x
197 231
198 232 def walkconfig(self):
199 233 sections = self.cdata.sections()
200 234 sections.sort()
201 235 for section in sections:
202 236 for name, value in self.configitems(section):
203 237 yield section, name, value.replace('\n', '\\n')
204 238
205 239 def extensions(self):
206 240 result = self.configitems("extensions")
207 241 for i, (key, value) in enumerate(result):
208 242 if value:
209 243 result[i] = (key, os.path.expanduser(value))
210 244 return result
211 245
212 246 def hgignorefiles(self):
213 247 result = []
214 248 for key, value in self.configitems("ui"):
215 249 if key == 'ignore' or key.startswith('ignore.'):
216 250 result.append(os.path.expanduser(value))
217 251 return result
218 252
219 253 def configrevlog(self):
220 254 result = {}
221 255 for key, value in self.configitems("revlog"):
222 256 result[key.lower()] = value
223 257 return result
224 258
225 259 def username(self):
226 260 """Return default username to be used in commits.
227 261
228 262 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
229 263 and stop searching if one of these is set.
230 264 Abort if no username is found, to force specifying the commit user
231 265 with line option or repo hgrc.
232 266 """
233 267 user = os.environ.get("HGUSER")
234 268 if user is None:
235 269 user = self.config("ui", "username")
236 270 if user is None:
237 271 user = os.environ.get("EMAIL")
238 272 if not user:
239 273 self.status(_("Please choose a commit username to be recorded "
240 274 "in the changelog via\ncommand line option "
241 275 '(-u "First Last <email@example.com>"), in the\n'
242 276 "configuration files (hgrc), or by setting the "
243 277 "EMAIL environment variable.\n\n"))
244 278 raise util.Abort(_("No commit username specified!"))
245 279 return user
246 280
247 281 def shortuser(self, user):
248 282 """Return a short representation of a user name or email address."""
249 283 if not self.verbose: user = util.shortuser(user)
250 284 return user
251 285
252 286 def expandpath(self, loc, default=None):
253 287 """Return repository location relative to cwd or from [paths]"""
254 288 if "://" in loc or os.path.isdir(loc):
255 289 return loc
256 290
257 291 path = self.config("paths", loc)
258 292 if not path and default is not None:
259 293 path = self.config("paths", default)
260 294 return path or loc
261 295
262 296 def write(self, *args):
263 297 if self.header:
264 298 if self.header != self.prev_header:
265 299 self.prev_header = self.header
266 300 self.write(*self.header)
267 301 self.header = []
268 302 for a in args:
269 303 sys.stdout.write(str(a))
270 304
271 305 def write_header(self, *args):
272 306 for a in args:
273 307 self.header.append(str(a))
274 308
275 309 def write_err(self, *args):
276 310 try:
277 311 if not sys.stdout.closed: sys.stdout.flush()
278 312 for a in args:
279 313 sys.stderr.write(str(a))
280 314 except IOError, inst:
281 315 if inst.errno != errno.EPIPE:
282 316 raise
283 317
284 318 def flush(self):
285 319 try: sys.stdout.flush()
286 320 except: pass
287 321 try: sys.stderr.flush()
288 322 except: pass
289 323
290 324 def readline(self):
291 325 return sys.stdin.readline()[:-1]
292 326 def prompt(self, msg, pat=None, default="y"):
293 327 if not self.interactive: return default
294 328 while 1:
295 329 self.write(msg, " ")
296 330 r = self.readline()
297 331 if not pat or re.match(pat, r):
298 332 return r
299 333 else:
300 334 self.write(_("unrecognized response\n"))
301 335 def getpass(self, prompt=None, default=None):
302 336 if not self.interactive: return default
303 337 return getpass.getpass(prompt or _('password: '))
304 338 def status(self, *msg):
305 339 if not self.quiet: self.write(*msg)
306 340 def warn(self, *msg):
307 341 self.write_err(*msg)
308 342 def note(self, *msg):
309 343 if self.verbose: self.write(*msg)
310 344 def debug(self, *msg):
311 345 if self.debugflag: self.write(*msg)
312 346 def edit(self, text, user):
313 347 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
314 348 text=True)
315 349 try:
316 350 f = os.fdopen(fd, "w")
317 351 f.write(text)
318 352 f.close()
319 353
320 354 editor = (os.environ.get("HGEDITOR") or
321 355 self.config("ui", "editor") or
322 356 os.environ.get("EDITOR", "vi"))
323 357
324 358 util.system("%s \"%s\"" % (editor, name),
325 359 environ={'HGUSER': user},
326 360 onerr=util.Abort, errprefix=_("edit failed"))
327 361
328 362 f = open(name)
329 363 t = f.read()
330 364 f.close()
331 365 t = re.sub("(?m)^HG:.*\n", "", t)
332 366 finally:
333 367 os.unlink(name)
334 368
335 369 return t
336 370
337 371 def print_exc(self):
338 372 '''print exception traceback if traceback printing enabled.
339 373 only to call in exception handler. returns true if traceback
340 374 printed.'''
341 375 if self.traceback:
342 376 traceback.print_exc()
343 377 return self.traceback
@@ -1,1021 +1,1051
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import gettext as _
16 16 from demandload import *
17 17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 18 demandload(globals(), "os threading time calendar ConfigParser")
19 19
20 20 # used by parsedate
21 21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
22 22 '%a %b %d %H:%M:%S %Y')
23 23
24 24 class SignalInterrupt(Exception):
25 25 """Exception raised on SIGTERM and SIGHUP."""
26 26
27 27 # like SafeConfigParser but with case-sensitive keys
28 28 class configparser(ConfigParser.SafeConfigParser):
29 29 def optionxform(self, optionstr):
30 30 return optionstr
31 31
32 32 def cachefunc(func):
33 33 '''cache the result of function calls'''
34 34 # XXX doesn't handle keywords args
35 35 cache = {}
36 36 if func.func_code.co_argcount == 1:
37 37 # we gain a small amount of time because
38 38 # we don't need to pack/unpack the list
39 39 def f(arg):
40 40 if arg not in cache:
41 41 cache[arg] = func(arg)
42 42 return cache[arg]
43 43 else:
44 44 def f(*args):
45 45 if args not in cache:
46 46 cache[args] = func(*args)
47 47 return cache[args]
48 48
49 49 return f
50 50
51 51 def pipefilter(s, cmd):
52 52 '''filter string S through command CMD, returning its output'''
53 53 (pout, pin) = popen2.popen2(cmd, -1, 'b')
54 54 def writer():
55 55 try:
56 56 pin.write(s)
57 57 pin.close()
58 58 except IOError, inst:
59 59 if inst.errno != errno.EPIPE:
60 60 raise
61 61
62 62 # we should use select instead on UNIX, but this will work on most
63 63 # systems, including Windows
64 64 w = threading.Thread(target=writer)
65 65 w.start()
66 66 f = pout.read()
67 67 pout.close()
68 68 w.join()
69 69 return f
70 70
71 71 def tempfilter(s, cmd):
72 72 '''filter string S through a pair of temporary files with CMD.
73 73 CMD is used as a template to create the real command to be run,
74 74 with the strings INFILE and OUTFILE replaced by the real names of
75 75 the temporary files generated.'''
76 76 inname, outname = None, None
77 77 try:
78 78 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
79 79 fp = os.fdopen(infd, 'wb')
80 80 fp.write(s)
81 81 fp.close()
82 82 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
83 83 os.close(outfd)
84 84 cmd = cmd.replace('INFILE', inname)
85 85 cmd = cmd.replace('OUTFILE', outname)
86 86 code = os.system(cmd)
87 87 if code: raise Abort(_("command '%s' failed: %s") %
88 88 (cmd, explain_exit(code)))
89 89 return open(outname, 'rb').read()
90 90 finally:
91 91 try:
92 92 if inname: os.unlink(inname)
93 93 except: pass
94 94 try:
95 95 if outname: os.unlink(outname)
96 96 except: pass
97 97
98 98 filtertable = {
99 99 'tempfile:': tempfilter,
100 100 'pipe:': pipefilter,
101 101 }
102 102
103 103 def filter(s, cmd):
104 104 "filter a string through a command that transforms its input to its output"
105 105 for name, fn in filtertable.iteritems():
106 106 if cmd.startswith(name):
107 107 return fn(s, cmd[len(name):].lstrip())
108 108 return pipefilter(s, cmd)
109 109
110 110 def find_in_path(name, path, default=None):
111 111 '''find name in search path. path can be string (will be split
112 112 with os.pathsep), or iterable thing that returns strings. if name
113 113 found, return path to name. else return default.'''
114 114 if isinstance(path, str):
115 115 path = path.split(os.pathsep)
116 116 for p in path:
117 117 p_name = os.path.join(p, name)
118 118 if os.path.exists(p_name):
119 119 return p_name
120 120 return default
121 121
122 122 def binary(s):
123 123 """return true if a string is binary data using diff's heuristic"""
124 124 if s and '\0' in s[:4096]:
125 125 return True
126 126 return False
127 127
128 128 def unique(g):
129 129 """return the uniq elements of iterable g"""
130 130 seen = {}
131 131 for f in g:
132 132 if f not in seen:
133 133 seen[f] = 1
134 134 yield f
135 135
136 136 class Abort(Exception):
137 137 """Raised if a command needs to print an error and exit."""
138 138
139 139 def always(fn): return True
140 140 def never(fn): return False
141 141
142 142 def patkind(name, dflt_pat='glob'):
143 143 """Split a string into an optional pattern kind prefix and the
144 144 actual pattern."""
145 145 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
146 146 if name.startswith(prefix + ':'): return name.split(':', 1)
147 147 return dflt_pat, name
148 148
149 149 def globre(pat, head='^', tail='$'):
150 150 "convert a glob pattern into a regexp"
151 151 i, n = 0, len(pat)
152 152 res = ''
153 153 group = False
154 154 def peek(): return i < n and pat[i]
155 155 while i < n:
156 156 c = pat[i]
157 157 i = i+1
158 158 if c == '*':
159 159 if peek() == '*':
160 160 i += 1
161 161 res += '.*'
162 162 else:
163 163 res += '[^/]*'
164 164 elif c == '?':
165 165 res += '.'
166 166 elif c == '[':
167 167 j = i
168 168 if j < n and pat[j] in '!]':
169 169 j += 1
170 170 while j < n and pat[j] != ']':
171 171 j += 1
172 172 if j >= n:
173 173 res += '\\['
174 174 else:
175 175 stuff = pat[i:j].replace('\\','\\\\')
176 176 i = j + 1
177 177 if stuff[0] == '!':
178 178 stuff = '^' + stuff[1:]
179 179 elif stuff[0] == '^':
180 180 stuff = '\\' + stuff
181 181 res = '%s[%s]' % (res, stuff)
182 182 elif c == '{':
183 183 group = True
184 184 res += '(?:'
185 185 elif c == '}' and group:
186 186 res += ')'
187 187 group = False
188 188 elif c == ',' and group:
189 189 res += '|'
190 190 elif c == '\\':
191 191 p = peek()
192 192 if p:
193 193 i += 1
194 194 res += re.escape(p)
195 195 else:
196 196 res += re.escape(c)
197 197 else:
198 198 res += re.escape(c)
199 199 return head + res + tail
200 200
201 201 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
202 202
203 203 def pathto(n1, n2):
204 204 '''return the relative path from one place to another.
205 205 this returns a path in the form used by the local filesystem, not hg.'''
206 206 if not n1: return localpath(n2)
207 207 a, b = n1.split('/'), n2.split('/')
208 208 a.reverse()
209 209 b.reverse()
210 210 while a and b and a[-1] == b[-1]:
211 211 a.pop()
212 212 b.pop()
213 213 b.reverse()
214 214 return os.sep.join((['..'] * len(a)) + b)
215 215
216 216 def canonpath(root, cwd, myname):
217 217 """return the canonical path of myname, given cwd and root"""
218 218 if root == os.sep:
219 219 rootsep = os.sep
220 220 elif root.endswith(os.sep):
221 221 rootsep = root
222 222 else:
223 223 rootsep = root + os.sep
224 224 name = myname
225 225 if not os.path.isabs(name):
226 226 name = os.path.join(root, cwd, name)
227 227 name = os.path.normpath(name)
228 228 if name != rootsep and name.startswith(rootsep):
229 229 name = name[len(rootsep):]
230 230 audit_path(name)
231 231 return pconvert(name)
232 232 elif name == root:
233 233 return ''
234 234 else:
235 235 # Determine whether `name' is in the hierarchy at or beneath `root',
236 236 # by iterating name=dirname(name) until that causes no change (can't
237 237 # check name == '/', because that doesn't work on windows). For each
238 238 # `name', compare dev/inode numbers. If they match, the list `rel'
239 239 # holds the reversed list of components making up the relative file
240 240 # name we want.
241 241 root_st = os.stat(root)
242 242 rel = []
243 243 while True:
244 244 try:
245 245 name_st = os.stat(name)
246 246 except OSError:
247 247 break
248 248 if samestat(name_st, root_st):
249 249 rel.reverse()
250 250 name = os.path.join(*rel)
251 251 audit_path(name)
252 252 return pconvert(name)
253 253 dirname, basename = os.path.split(name)
254 254 rel.append(basename)
255 255 if dirname == name:
256 256 break
257 257 name = dirname
258 258
259 259 raise Abort('%s not under root' % myname)
260 260
261 261 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
262 262 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
263 263
264 264 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
265 265 if os.name == 'nt':
266 266 dflt_pat = 'glob'
267 267 else:
268 268 dflt_pat = 'relpath'
269 269 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
270 270
271 271 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
272 272 """build a function to match a set of file patterns
273 273
274 274 arguments:
275 275 canonroot - the canonical root of the tree you're matching against
276 276 cwd - the current working directory, if relevant
277 277 names - patterns to find
278 278 inc - patterns to include
279 279 exc - patterns to exclude
280 280 head - a regex to prepend to patterns to control whether a match is rooted
281 281
282 282 a pattern is one of:
283 283 'glob:<rooted glob>'
284 284 're:<rooted regexp>'
285 285 'path:<rooted path>'
286 286 'relglob:<relative glob>'
287 287 'relpath:<relative path>'
288 288 'relre:<relative regexp>'
289 289 '<rooted path or regexp>'
290 290
291 291 returns:
292 292 a 3-tuple containing
293 293 - list of explicit non-pattern names passed in
294 294 - a bool match(filename) function
295 295 - a bool indicating if any patterns were passed in
296 296
297 297 todo:
298 298 make head regex a rooted bool
299 299 """
300 300
301 301 def contains_glob(name):
302 302 for c in name:
303 303 if c in _globchars: return True
304 304 return False
305 305
306 306 def regex(kind, name, tail):
307 307 '''convert a pattern into a regular expression'''
308 308 if kind == 're':
309 309 return name
310 310 elif kind == 'path':
311 311 return '^' + re.escape(name) + '(?:/|$)'
312 312 elif kind == 'relglob':
313 313 return head + globre(name, '(?:|.*/)', tail)
314 314 elif kind == 'relpath':
315 315 return head + re.escape(name) + tail
316 316 elif kind == 'relre':
317 317 if name.startswith('^'):
318 318 return name
319 319 return '.*' + name
320 320 return head + globre(name, '', tail)
321 321
322 322 def matchfn(pats, tail):
323 323 """build a matching function from a set of patterns"""
324 324 if not pats:
325 325 return
326 326 matches = []
327 327 for k, p in pats:
328 328 try:
329 329 pat = '(?:%s)' % regex(k, p, tail)
330 330 matches.append(re.compile(pat).match)
331 331 except re.error:
332 332 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
333 333 else: raise Abort("invalid pattern (%s): %s" % (k, p))
334 334
335 335 def buildfn(text):
336 336 for m in matches:
337 337 r = m(text)
338 338 if r:
339 339 return r
340 340
341 341 return buildfn
342 342
343 343 def globprefix(pat):
344 344 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
345 345 root = []
346 346 for p in pat.split(os.sep):
347 347 if contains_glob(p): break
348 348 root.append(p)
349 349 return '/'.join(root)
350 350
351 351 pats = []
352 352 files = []
353 353 roots = []
354 354 for kind, name in [patkind(p, dflt_pat) for p in names]:
355 355 if kind in ('glob', 'relpath'):
356 356 name = canonpath(canonroot, cwd, name)
357 357 if name == '':
358 358 kind, name = 'glob', '**'
359 359 if kind in ('glob', 'path', 're'):
360 360 pats.append((kind, name))
361 361 if kind == 'glob':
362 362 root = globprefix(name)
363 363 if root: roots.append(root)
364 364 elif kind == 'relpath':
365 365 files.append((kind, name))
366 366 roots.append(name)
367 367
368 368 patmatch = matchfn(pats, '$') or always
369 369 filematch = matchfn(files, '(?:/|$)') or always
370 370 incmatch = always
371 371 if inc:
372 372 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
373 373 incmatch = matchfn(inckinds, '(?:/|$)')
374 374 excmatch = lambda fn: False
375 375 if exc:
376 376 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
377 377 excmatch = matchfn(exckinds, '(?:/|$)')
378 378
379 379 return (roots,
380 380 lambda fn: (incmatch(fn) and not excmatch(fn) and
381 381 (fn.endswith('/') or
382 382 (not pats and not files) or
383 383 (pats and patmatch(fn)) or
384 384 (files and filematch(fn)))),
385 385 (inc or exc or (pats and pats != [('glob', '**')])) and True)
386 386
387 387 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
388 388 '''enhanced shell command execution.
389 389 run with environment maybe modified, maybe in different dir.
390 390
391 391 if command fails and onerr is None, return status. if ui object,
392 392 print error message and return status, else raise onerr object as
393 393 exception.'''
394 394 def py2shell(val):
395 395 'convert python object into string that is useful to shell'
396 396 if val in (None, False):
397 397 return '0'
398 398 if val == True:
399 399 return '1'
400 400 return str(val)
401 401 oldenv = {}
402 402 for k in environ:
403 403 oldenv[k] = os.environ.get(k)
404 404 if cwd is not None:
405 405 oldcwd = os.getcwd()
406 406 try:
407 407 for k, v in environ.iteritems():
408 408 os.environ[k] = py2shell(v)
409 409 if cwd is not None and oldcwd != cwd:
410 410 os.chdir(cwd)
411 411 rc = os.system(cmd)
412 412 if rc and onerr:
413 413 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
414 414 explain_exit(rc)[0])
415 415 if errprefix:
416 416 errmsg = '%s: %s' % (errprefix, errmsg)
417 417 try:
418 418 onerr.warn(errmsg + '\n')
419 419 except AttributeError:
420 420 raise onerr(errmsg)
421 421 return rc
422 422 finally:
423 423 for k, v in oldenv.iteritems():
424 424 if v is None:
425 425 del os.environ[k]
426 426 else:
427 427 os.environ[k] = v
428 428 if cwd is not None and oldcwd != cwd:
429 429 os.chdir(oldcwd)
430 430
431 431 def rename(src, dst):
432 432 """forcibly rename a file"""
433 433 try:
434 434 os.rename(src, dst)
435 435 except OSError, err:
436 436 # on windows, rename to existing file is not allowed, so we
437 437 # must delete destination first. but if file is open, unlink
438 438 # schedules it for delete but does not delete it. rename
439 439 # happens immediately even for open files, so we create
440 440 # temporary file, delete it, rename destination to that name,
441 441 # then delete that. then rename is safe to do.
442 442 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
443 443 os.close(fd)
444 444 os.unlink(temp)
445 445 os.rename(dst, temp)
446 446 os.unlink(temp)
447 447 os.rename(src, dst)
448 448
449 449 def unlink(f):
450 450 """unlink and remove the directory if it is empty"""
451 451 os.unlink(f)
452 452 # try removing directories that might now be empty
453 453 try:
454 454 os.removedirs(os.path.dirname(f))
455 455 except OSError:
456 456 pass
457 457
458 458 def copyfiles(src, dst, hardlink=None):
459 459 """Copy a directory tree using hardlinks if possible"""
460 460
461 461 if hardlink is None:
462 462 hardlink = (os.stat(src).st_dev ==
463 463 os.stat(os.path.dirname(dst)).st_dev)
464 464
465 465 if os.path.isdir(src):
466 466 os.mkdir(dst)
467 467 for name in os.listdir(src):
468 468 srcname = os.path.join(src, name)
469 469 dstname = os.path.join(dst, name)
470 470 copyfiles(srcname, dstname, hardlink)
471 471 else:
472 472 if hardlink:
473 473 try:
474 474 os_link(src, dst)
475 475 except (IOError, OSError):
476 476 hardlink = False
477 477 shutil.copy(src, dst)
478 478 else:
479 479 shutil.copy(src, dst)
480 480
481 481 def audit_path(path):
482 482 """Abort if path contains dangerous components"""
483 483 parts = os.path.normcase(path).split(os.sep)
484 484 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
485 485 or os.pardir in parts):
486 486 raise Abort(_("path contains illegal component: %s\n") % path)
487 487
488 488 def _makelock_file(info, pathname):
489 489 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
490 490 os.write(ld, info)
491 491 os.close(ld)
492 492
493 493 def _readlock_file(pathname):
494 494 return posixfile(pathname).read()
495 495
496 496 def nlinks(pathname):
497 497 """Return number of hardlinks for the given file."""
498 498 return os.lstat(pathname).st_nlink
499 499
500 500 if hasattr(os, 'link'):
501 501 os_link = os.link
502 502 else:
503 503 def os_link(src, dst):
504 504 raise OSError(0, _("Hardlinks not supported"))
505 505
506 506 def fstat(fp):
507 507 '''stat file object that may not have fileno method.'''
508 508 try:
509 509 return os.fstat(fp.fileno())
510 510 except AttributeError:
511 511 return os.stat(fp.name)
512 512
513 513 posixfile = file
514 514
515 515 def is_win_9x():
516 516 '''return true if run on windows 95, 98 or me.'''
517 517 try:
518 518 return sys.getwindowsversion()[3] == 1
519 519 except AttributeError:
520 520 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
521 521
522 def username(uid=None):
523 """Return the name of the user with the given uid.
524
525 If uid is None, return the name of the current user."""
526 try:
527 import pwd
528 if uid is None:
529 uid = os.getuid()
530 try:
531 return pwd.getpwuid(uid)[0]
532 except KeyError:
533 return str(uid)
534 except ImportError:
535 return None
536
537 def groupname(gid=None):
538 """Return the name of the group with the given gid.
539
540 If gid is None, return the name of the current group."""
541 try:
542 import grp
543 if gid is None:
544 gid = os.getgid()
545 try:
546 return grp.getgrgid(gid)[0]
547 except KeyError:
548 return str(gid)
549 except ImportError:
550 return None
551
522 552 # Platform specific variants
523 553 if os.name == 'nt':
524 554 demandload(globals(), "msvcrt")
525 555 nulldev = 'NUL:'
526 556
527 557 class winstdout:
528 558 '''stdout on windows misbehaves if sent through a pipe'''
529 559
530 560 def __init__(self, fp):
531 561 self.fp = fp
532 562
533 563 def __getattr__(self, key):
534 564 return getattr(self.fp, key)
535 565
536 566 def close(self):
537 567 try:
538 568 self.fp.close()
539 569 except: pass
540 570
541 571 def write(self, s):
542 572 try:
543 573 return self.fp.write(s)
544 574 except IOError, inst:
545 575 if inst.errno != 0: raise
546 576 self.close()
547 577 raise IOError(errno.EPIPE, 'Broken pipe')
548 578
549 579 sys.stdout = winstdout(sys.stdout)
550 580
551 581 def system_rcpath():
552 582 try:
553 583 return system_rcpath_win32()
554 584 except:
555 585 return [r'c:\mercurial\mercurial.ini']
556 586
557 587 def os_rcpath():
558 588 '''return default os-specific hgrc search path'''
559 589 path = system_rcpath()
560 590 path.append(user_rcpath())
561 591 userprofile = os.environ.get('USERPROFILE')
562 592 if userprofile:
563 593 path.append(os.path.join(userprofile, 'mercurial.ini'))
564 594 return path
565 595
566 596 def user_rcpath():
567 597 '''return os-specific hgrc search path to the user dir'''
568 598 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
569 599
570 600 def parse_patch_output(output_line):
571 601 """parses the output produced by patch and returns the file name"""
572 602 pf = output_line[14:]
573 603 if pf[0] == '`':
574 604 pf = pf[1:-1] # Remove the quotes
575 605 return pf
576 606
577 607 def testpid(pid):
578 608 '''return False if pid dead, True if running or not known'''
579 609 return True
580 610
581 611 def is_exec(f, last):
582 612 return last
583 613
584 614 def set_exec(f, mode):
585 615 pass
586 616
587 617 def set_binary(fd):
588 618 msvcrt.setmode(fd.fileno(), os.O_BINARY)
589 619
590 620 def pconvert(path):
591 621 return path.replace("\\", "/")
592 622
593 623 def localpath(path):
594 624 return path.replace('/', '\\')
595 625
596 626 def normpath(path):
597 627 return pconvert(os.path.normpath(path))
598 628
599 629 makelock = _makelock_file
600 630 readlock = _readlock_file
601 631
602 632 def samestat(s1, s2):
603 633 return False
604 634
605 635 def shellquote(s):
606 636 return '"%s"' % s.replace('"', '\\"')
607 637
608 638 def explain_exit(code):
609 639 return _("exited with status %d") % code, code
610 640
611 641 try:
612 642 # override functions with win32 versions if possible
613 643 from util_win32 import *
614 644 if not is_win_9x():
615 645 posixfile = posixfile_nt
616 646 except ImportError:
617 647 pass
618 648
619 649 else:
620 650 nulldev = '/dev/null'
621 651
622 652 def rcfiles(path):
623 653 rcs = [os.path.join(path, 'hgrc')]
624 654 rcdir = os.path.join(path, 'hgrc.d')
625 655 try:
626 656 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
627 657 if f.endswith(".rc")])
628 658 except OSError:
629 659 pass
630 660 return rcs
631 661
632 662 def os_rcpath():
633 663 '''return default os-specific hgrc search path'''
634 664 path = []
635 665 # old mod_python does not set sys.argv
636 666 if len(getattr(sys, 'argv', [])) > 0:
637 667 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
638 668 '/../etc/mercurial'))
639 669 path.extend(rcfiles('/etc/mercurial'))
640 670 path.append(os.path.expanduser('~/.hgrc'))
641 671 path = [os.path.normpath(f) for f in path]
642 672 return path
643 673
644 674 def parse_patch_output(output_line):
645 675 """parses the output produced by patch and returns the file name"""
646 676 pf = output_line[14:]
647 677 if pf.startswith("'") and pf.endswith("'") and " " in pf:
648 678 pf = pf[1:-1] # Remove the quotes
649 679 return pf
650 680
651 681 def is_exec(f, last):
652 682 """check whether a file is executable"""
653 683 return (os.lstat(f).st_mode & 0100 != 0)
654 684
655 685 def set_exec(f, mode):
656 686 s = os.lstat(f).st_mode
657 687 if (s & 0100 != 0) == mode:
658 688 return
659 689 if mode:
660 690 # Turn on +x for every +r bit when making a file executable
661 691 # and obey umask.
662 692 umask = os.umask(0)
663 693 os.umask(umask)
664 694 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
665 695 else:
666 696 os.chmod(f, s & 0666)
667 697
668 698 def set_binary(fd):
669 699 pass
670 700
671 701 def pconvert(path):
672 702 return path
673 703
674 704 def localpath(path):
675 705 return path
676 706
677 707 normpath = os.path.normpath
678 708 samestat = os.path.samestat
679 709
680 710 def makelock(info, pathname):
681 711 try:
682 712 os.symlink(info, pathname)
683 713 except OSError, why:
684 714 if why.errno == errno.EEXIST:
685 715 raise
686 716 else:
687 717 _makelock_file(info, pathname)
688 718
689 719 def readlock(pathname):
690 720 try:
691 721 return os.readlink(pathname)
692 722 except OSError, why:
693 723 if why.errno == errno.EINVAL:
694 724 return _readlock_file(pathname)
695 725 else:
696 726 raise
697 727
698 728 def shellquote(s):
699 729 return "'%s'" % s.replace("'", "'\\''")
700 730
701 731 def testpid(pid):
702 732 '''return False if pid dead, True if running or not sure'''
703 733 try:
704 734 os.kill(pid, 0)
705 735 return True
706 736 except OSError, inst:
707 737 return inst.errno != errno.ESRCH
708 738
709 739 def explain_exit(code):
710 740 """return a 2-tuple (desc, code) describing a process's status"""
711 741 if os.WIFEXITED(code):
712 742 val = os.WEXITSTATUS(code)
713 743 return _("exited with status %d") % val, val
714 744 elif os.WIFSIGNALED(code):
715 745 val = os.WTERMSIG(code)
716 746 return _("killed by signal %d") % val, val
717 747 elif os.WIFSTOPPED(code):
718 748 val = os.WSTOPSIG(code)
719 749 return _("stopped by signal %d") % val, val
720 750 raise ValueError(_("invalid exit code"))
721 751
722 752 def opener(base, audit=True):
723 753 """
724 754 return a function that opens files relative to base
725 755
726 756 this function is used to hide the details of COW semantics and
727 757 remote file access from higher level code.
728 758 """
729 759 p = base
730 760 audit_p = audit
731 761
732 762 def mktempcopy(name):
733 763 d, fn = os.path.split(name)
734 764 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
735 765 os.close(fd)
736 766 ofp = posixfile(temp, "wb")
737 767 try:
738 768 try:
739 769 ifp = posixfile(name, "rb")
740 770 except IOError, inst:
741 771 if not getattr(inst, 'filename', None):
742 772 inst.filename = name
743 773 raise
744 774 for chunk in filechunkiter(ifp):
745 775 ofp.write(chunk)
746 776 ifp.close()
747 777 ofp.close()
748 778 except:
749 779 try: os.unlink(temp)
750 780 except: pass
751 781 raise
752 782 st = os.lstat(name)
753 783 os.chmod(temp, st.st_mode)
754 784 return temp
755 785
756 786 class atomictempfile(posixfile):
757 787 """the file will only be copied when rename is called"""
758 788 def __init__(self, name, mode):
759 789 self.__name = name
760 790 self.temp = mktempcopy(name)
761 791 posixfile.__init__(self, self.temp, mode)
762 792 def rename(self):
763 793 if not self.closed:
764 794 posixfile.close(self)
765 795 rename(self.temp, localpath(self.__name))
766 796 def __del__(self):
767 797 if not self.closed:
768 798 try:
769 799 os.unlink(self.temp)
770 800 except: pass
771 801 posixfile.close(self)
772 802
773 803 class atomicfile(atomictempfile):
774 804 """the file will only be copied on close"""
775 805 def __init__(self, name, mode):
776 806 atomictempfile.__init__(self, name, mode)
777 807 def close(self):
778 808 self.rename()
779 809 def __del__(self):
780 810 self.rename()
781 811
782 812 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
783 813 if audit_p:
784 814 audit_path(path)
785 815 f = os.path.join(p, path)
786 816
787 817 if not text:
788 818 mode += "b" # for that other OS
789 819
790 820 if mode[0] != "r":
791 821 try:
792 822 nlink = nlinks(f)
793 823 except OSError:
794 824 d = os.path.dirname(f)
795 825 if not os.path.isdir(d):
796 826 os.makedirs(d)
797 827 else:
798 828 if atomic:
799 829 return atomicfile(f, mode)
800 830 elif atomictemp:
801 831 return atomictempfile(f, mode)
802 832 if nlink > 1:
803 833 rename(mktempcopy(f), f)
804 834 return posixfile(f, mode)
805 835
806 836 return o
807 837
808 838 class chunkbuffer(object):
809 839 """Allow arbitrary sized chunks of data to be efficiently read from an
810 840 iterator over chunks of arbitrary size."""
811 841
812 842 def __init__(self, in_iter, targetsize = 2**16):
813 843 """in_iter is the iterator that's iterating over the input chunks.
814 844 targetsize is how big a buffer to try to maintain."""
815 845 self.in_iter = iter(in_iter)
816 846 self.buf = ''
817 847 self.targetsize = int(targetsize)
818 848 if self.targetsize <= 0:
819 849 raise ValueError(_("targetsize must be greater than 0, was %d") %
820 850 targetsize)
821 851 self.iterempty = False
822 852
823 853 def fillbuf(self):
824 854 """Ignore target size; read every chunk from iterator until empty."""
825 855 if not self.iterempty:
826 856 collector = cStringIO.StringIO()
827 857 collector.write(self.buf)
828 858 for ch in self.in_iter:
829 859 collector.write(ch)
830 860 self.buf = collector.getvalue()
831 861 self.iterempty = True
832 862
833 863 def read(self, l):
834 864 """Read L bytes of data from the iterator of chunks of data.
835 865 Returns less than L bytes if the iterator runs dry."""
836 866 if l > len(self.buf) and not self.iterempty:
837 867 # Clamp to a multiple of self.targetsize
838 868 targetsize = self.targetsize * ((l // self.targetsize) + 1)
839 869 collector = cStringIO.StringIO()
840 870 collector.write(self.buf)
841 871 collected = len(self.buf)
842 872 for chunk in self.in_iter:
843 873 collector.write(chunk)
844 874 collected += len(chunk)
845 875 if collected >= targetsize:
846 876 break
847 877 if collected < targetsize:
848 878 self.iterempty = True
849 879 self.buf = collector.getvalue()
850 880 s, self.buf = self.buf[:l], buffer(self.buf, l)
851 881 return s
852 882
853 883 def filechunkiter(f, size=65536, limit=None):
854 884 """Create a generator that produces the data in the file size
855 885 (default 65536) bytes at a time, up to optional limit (default is
856 886 to read all data). Chunks may be less than size bytes if the
857 887 chunk is the last chunk in the file, or the file is a socket or
858 888 some other type of file that sometimes reads less data than is
859 889 requested."""
860 890 assert size >= 0
861 891 assert limit is None or limit >= 0
862 892 while True:
863 893 if limit is None: nbytes = size
864 894 else: nbytes = min(limit, size)
865 895 s = nbytes and f.read(nbytes)
866 896 if not s: break
867 897 if limit: limit -= len(s)
868 898 yield s
869 899
870 900 def makedate():
871 901 lt = time.localtime()
872 902 if lt[8] == 1 and time.daylight:
873 903 tz = time.altzone
874 904 else:
875 905 tz = time.timezone
876 906 return time.mktime(lt), tz
877 907
878 908 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
879 909 """represent a (unixtime, offset) tuple as a localized time.
880 910 unixtime is seconds since the epoch, and offset is the time zone's
881 911 number of seconds away from UTC. if timezone is false, do not
882 912 append time zone to string."""
883 913 t, tz = date or makedate()
884 914 s = time.strftime(format, time.gmtime(float(t) - tz))
885 915 if timezone:
886 916 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
887 917 return s
888 918
889 919 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
890 920 """parse a localized time string and return a (unixtime, offset) tuple.
891 921 if the string cannot be parsed, ValueError is raised."""
892 922 def hastimezone(string):
893 923 return (string[-4:].isdigit() and
894 924 (string[-5] == '+' or string[-5] == '-') and
895 925 string[-6].isspace())
896 926
897 927 # NOTE: unixtime = localunixtime + offset
898 928 if hastimezone(string):
899 929 date, tz = string[:-6], string[-5:]
900 930 tz = int(tz)
901 931 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
902 932 else:
903 933 date, offset = string, None
904 934 timetuple = time.strptime(date, format)
905 935 localunixtime = int(calendar.timegm(timetuple))
906 936 if offset is None:
907 937 # local timezone
908 938 unixtime = int(time.mktime(timetuple))
909 939 offset = unixtime - localunixtime
910 940 else:
911 941 unixtime = localunixtime + offset
912 942 return unixtime, offset
913 943
914 944 def parsedate(string, formats=None):
915 945 """parse a localized time string and return a (unixtime, offset) tuple.
916 946 The date may be a "unixtime offset" string or in one of the specified
917 947 formats."""
918 948 if not formats:
919 949 formats = defaultdateformats
920 950 try:
921 951 when, offset = map(int, string.split(' '))
922 952 except ValueError:
923 953 for format in formats:
924 954 try:
925 955 when, offset = strdate(string, format)
926 956 except ValueError:
927 957 pass
928 958 else:
929 959 break
930 960 else:
931 961 raise ValueError(_('invalid date: %r '
932 962 'see hg(1) manual page for details')
933 963 % string)
934 964 # validate explicit (probably user-specified) date and
935 965 # time zone offset. values must fit in signed 32 bits for
936 966 # current 32-bit linux runtimes. timezones go from UTC-12
937 967 # to UTC+14
938 968 if abs(when) > 0x7fffffff:
939 969 raise ValueError(_('date exceeds 32 bits: %d') % when)
940 970 if offset < -50400 or offset > 43200:
941 971 raise ValueError(_('impossible time zone offset: %d') % offset)
942 972 return when, offset
943 973
944 974 def shortuser(user):
945 975 """Return a short representation of a user name or email address."""
946 976 f = user.find('@')
947 977 if f >= 0:
948 978 user = user[:f]
949 979 f = user.find('<')
950 980 if f >= 0:
951 981 user = user[f+1:]
952 982 f = user.find(' ')
953 983 if f >= 0:
954 984 user = user[:f]
955 985 return user
956 986
957 987 def walkrepos(path):
958 988 '''yield every hg repository under path, recursively.'''
959 989 def errhandler(err):
960 990 if err.filename == path:
961 991 raise err
962 992
963 993 for root, dirs, files in os.walk(path, onerror=errhandler):
964 994 for d in dirs:
965 995 if d == '.hg':
966 996 yield root
967 997 dirs[:] = []
968 998 break
969 999
970 1000 _rcpath = None
971 1001
972 1002 def rcpath():
973 1003 '''return hgrc search path. if env var HGRCPATH is set, use it.
974 1004 for each item in path, if directory, use files ending in .rc,
975 1005 else use item.
976 1006 make HGRCPATH empty to only look in .hg/hgrc of current repo.
977 1007 if no HGRCPATH, use default os-specific path.'''
978 1008 global _rcpath
979 1009 if _rcpath is None:
980 1010 if 'HGRCPATH' in os.environ:
981 1011 _rcpath = []
982 1012 for p in os.environ['HGRCPATH'].split(os.pathsep):
983 1013 if not p: continue
984 1014 if os.path.isdir(p):
985 1015 for f in os.listdir(p):
986 1016 if f.endswith('.rc'):
987 1017 _rcpath.append(os.path.join(p, f))
988 1018 else:
989 1019 _rcpath.append(p)
990 1020 else:
991 1021 _rcpath = os_rcpath()
992 1022 return _rcpath
993 1023
994 1024 def bytecount(nbytes):
995 1025 '''return byte count formatted as readable string, with units'''
996 1026
997 1027 units = (
998 1028 (100, 1<<30, _('%.0f GB')),
999 1029 (10, 1<<30, _('%.1f GB')),
1000 1030 (1, 1<<30, _('%.2f GB')),
1001 1031 (100, 1<<20, _('%.0f MB')),
1002 1032 (10, 1<<20, _('%.1f MB')),
1003 1033 (1, 1<<20, _('%.2f MB')),
1004 1034 (100, 1<<10, _('%.0f KB')),
1005 1035 (10, 1<<10, _('%.1f KB')),
1006 1036 (1, 1<<10, _('%.2f KB')),
1007 1037 (1, 1, _('%.0f bytes')),
1008 1038 )
1009 1039
1010 1040 for multiplier, divisor, format in units:
1011 1041 if nbytes >= divisor * multiplier:
1012 1042 return format % (nbytes / float(divisor))
1013 1043 return units[-1][2] % nbytes
1014 1044
1015 1045 def drop_scheme(scheme, path):
1016 1046 sc = scheme + ':'
1017 1047 if path.startswith(sc):
1018 1048 path = path[len(sc):]
1019 1049 if path.startswith('//'):
1020 1050 path = path[2:]
1021 1051 return path
General Comments 0
You need to be logged in to leave comments. Login now