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