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