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