##// END OF EJS Templates
Implementation of per-user .hgignore....
mcmillen@cs.cmu.edu -
r2003:62647394 default
parent child Browse files
Show More
@@ -1,319 +1,325
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 Per-user configuration file, for the user running Mercurial.
40 Per-user configuration file, for the user running Mercurial.
41 Options in this file apply to all Mercurial commands executed by
41 Options in this file apply to all Mercurial commands executed by
42 any user in any directory. Options in this file override
42 any user in any directory. Options in this file override
43 per-installation and per-system options.
43 per-installation and per-system options.
44
44
45 (Unix, Windows) <repo>/.hg/hgrc::
45 (Unix, Windows) <repo>/.hg/hgrc::
46 Per-repository configuration options that only apply in a
46 Per-repository configuration options that only apply in a
47 particular repository. This file is not version-controlled, and
47 particular repository. This file is not version-controlled, and
48 will not get transferred during a "clone" operation. Options in
48 will not get transferred during a "clone" operation. Options in
49 this file override options in all other configuration files.
49 this file override options in all other configuration files.
50
50
51 SYNTAX
51 SYNTAX
52 ------
52 ------
53
53
54 A configuration file consists of sections, led by a "[section]" header
54 A configuration file consists of sections, led by a "[section]" header
55 and followed by "name: value" entries; "name=value" is also accepted.
55 and followed by "name: value" entries; "name=value" is also accepted.
56
56
57 [spam]
57 [spam]
58 eggs=ham
58 eggs=ham
59 green=
59 green=
60 eggs
60 eggs
61
61
62 Each line contains one entry. If the lines that follow are indented,
62 Each line contains one entry. If the lines that follow are indented,
63 they are treated as continuations of that entry.
63 they are treated as continuations of that entry.
64
64
65 Leading whitespace is removed from values. Empty lines are skipped.
65 Leading whitespace is removed from values. Empty lines are skipped.
66
66
67 The optional values can contain format strings which refer to other
67 The optional values can contain format strings which refer to other
68 values in the same section, or values in a special DEFAULT section.
68 values in the same section, or values in a special DEFAULT section.
69
69
70 Lines beginning with "#" or ";" are ignored and may be used to provide
70 Lines beginning with "#" or ";" are ignored and may be used to provide
71 comments.
71 comments.
72
72
73 SECTIONS
73 SECTIONS
74 --------
74 --------
75
75
76 This section describes the different sections that may appear in a
76 This section describes the different sections that may appear in a
77 Mercurial "hgrc" file, the purpose of each section, its possible
77 Mercurial "hgrc" file, the purpose of each section, its possible
78 keys, and their possible values.
78 keys, and their possible values.
79
79
80 decode/encode::
80 decode/encode::
81 Filters for transforming files on checkout/checkin. This would
81 Filters for transforming files on checkout/checkin. This would
82 typically be used for newline processing or other
82 typically be used for newline processing or other
83 localization/canonicalization of files.
83 localization/canonicalization of files.
84
84
85 Filters consist of a filter pattern followed by a filter command.
85 Filters consist of a filter pattern followed by a filter command.
86 Filter patterns are globs by default, rooted at the repository
86 Filter patterns are globs by default, rooted at the repository
87 root. For example, to match any file ending in ".txt" in the root
87 root. For example, to match any file ending in ".txt" in the root
88 directory only, use the pattern "*.txt". To match any file ending
88 directory only, use the pattern "*.txt". To match any file ending
89 in ".c" anywhere in the repository, use the pattern "**.c".
89 in ".c" anywhere in the repository, use the pattern "**.c".
90
90
91 The filter command can start with a specifier, either "pipe:" or
91 The filter command can start with a specifier, either "pipe:" or
92 "tempfile:". If no specifier is given, "pipe:" is used by default.
92 "tempfile:". If no specifier is given, "pipe:" is used by default.
93
93
94 A "pipe:" command must accept data on stdin and return the
94 A "pipe:" command must accept data on stdin and return the
95 transformed data on stdout.
95 transformed data on stdout.
96
96
97 Pipe example:
97 Pipe example:
98
98
99 [encode]
99 [encode]
100 # uncompress gzip files on checkin to improve delta compression
100 # uncompress gzip files on checkin to improve delta compression
101 # note: not necessarily a good idea, just an example
101 # note: not necessarily a good idea, just an example
102 *.gz = pipe: gunzip
102 *.gz = pipe: gunzip
103
103
104 [decode]
104 [decode]
105 # recompress gzip files when writing them to the working dir (we
105 # recompress gzip files when writing them to the working dir (we
106 # can safely omit "pipe:", because it's the default)
106 # can safely omit "pipe:", because it's the default)
107 *.gz = gzip
107 *.gz = gzip
108
108
109 A "tempfile:" command is a template. The string INFILE is replaced
109 A "tempfile:" command is a template. The string INFILE is replaced
110 with the name of a temporary file that contains the data to be
110 with the name of a temporary file that contains the data to be
111 filtered by the command. The string OUTFILE is replaced with the
111 filtered by the command. The string OUTFILE is replaced with the
112 name of an empty temporary file, where the filtered data must be
112 name of an empty temporary file, where the filtered data must be
113 written by the command.
113 written by the command.
114
114
115 NOTE: the tempfile mechanism is recommended for Windows systems,
115 NOTE: the tempfile mechanism is recommended for Windows systems,
116 where the standard shell I/O redirection operators often have
116 where the standard shell I/O redirection operators often have
117 strange effects. In particular, if you are doing line ending
117 strange effects. In particular, if you are doing line ending
118 conversion on Windows using the popular dos2unix and unix2dos
118 conversion on Windows using the popular dos2unix and unix2dos
119 programs, you *must* use the tempfile mechanism, as using pipes will
119 programs, you *must* use the tempfile mechanism, as using pipes will
120 corrupt the contents of your files.
120 corrupt the contents of your files.
121
121
122 Tempfile example:
122 Tempfile example:
123
123
124 [encode]
124 [encode]
125 # convert files to unix line ending conventions on checkin
125 # convert files to unix line ending conventions on checkin
126 **.txt = tempfile: dos2unix -n INFILE OUTFILE
126 **.txt = tempfile: dos2unix -n INFILE OUTFILE
127
127
128 [decode]
128 [decode]
129 # convert files to windows line ending conventions when writing
129 # convert files to windows line ending conventions when writing
130 # them to the working dir
130 # them to the working dir
131 **.txt = tempfile: unix2dos -n INFILE OUTFILE
131 **.txt = tempfile: unix2dos -n INFILE OUTFILE
132
132
133 hooks::
133 hooks::
134 Commands that get automatically executed by various actions such as
134 Commands that get automatically executed by various actions such as
135 starting or finishing a commit. Multiple commands can be run for
135 starting or finishing a commit. Multiple commands can be run for
136 the same action by appending a suffix to the action. Overriding a
136 the same action by appending a suffix to the action. Overriding a
137 site-wide hook can be done by changing its value or setting it to
137 site-wide hook can be done by changing its value or setting it to
138 an empty string.
138 an empty string.
139
139
140 Example .hg/hgrc:
140 Example .hg/hgrc:
141
141
142 [hooks]
142 [hooks]
143 # do not use the site-wide hook
143 # do not use the site-wide hook
144 incoming =
144 incoming =
145 incoming.email = /my/email/hook
145 incoming.email = /my/email/hook
146 incoming.autobuild = /my/build/hook
146 incoming.autobuild = /my/build/hook
147
147
148 Most hooks are run with environment variables set that give added
148 Most hooks are run with environment variables set that give added
149 useful information. For each hook below, the environment variables
149 useful information. For each hook below, the environment variables
150 it is passed are listed with names of the form "$HG_foo".
150 it is passed are listed with names of the form "$HG_foo".
151
151
152 changegroup;;
152 changegroup;;
153 Run after a changegroup has been added via push, pull or
153 Run after a changegroup has been added via push, pull or
154 unbundle. ID of the first new changeset is in $HG_NODE.
154 unbundle. ID of the first new changeset is in $HG_NODE.
155 commit;;
155 commit;;
156 Run after a changeset has been created in the local repository.
156 Run after a changeset has been created in the local repository.
157 ID of the newly created changeset is in $HG_NODE. Parent
157 ID of the newly created changeset is in $HG_NODE. Parent
158 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
158 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
159 incoming;;
159 incoming;;
160 Run after a changeset has been pulled, pushed, or unbundled into
160 Run after a changeset has been pulled, pushed, or unbundled into
161 the local repository. The ID of the newly arrived changeset is in
161 the local repository. The ID of the newly arrived changeset is in
162 $HG_NODE.
162 $HG_NODE.
163 outgoing;;
163 outgoing;;
164 Run after sending changes from local repository to another. ID of
164 Run after sending changes from local repository to another. ID of
165 first changeset sent is in $HG_NODE. Source of operation is in
165 first changeset sent is in $HG_NODE. Source of operation is in
166 $HG_SOURCE; see "preoutgoing" hook for description.
166 $HG_SOURCE; see "preoutgoing" hook for description.
167 prechangegroup;;
167 prechangegroup;;
168 Run before a changegroup is added via push, pull or unbundle.
168 Run before a changegroup is added via push, pull or unbundle.
169 Exit status 0 allows the changegroup to proceed. Non-zero status
169 Exit status 0 allows the changegroup to proceed. Non-zero status
170 will cause the push, pull or unbundle to fail.
170 will cause the push, pull or unbundle to fail.
171 precommit;;
171 precommit;;
172 Run before starting a local commit. Exit status 0 allows the
172 Run before starting a local commit. Exit status 0 allows the
173 commit to proceed. Non-zero status will cause the commit to fail.
173 commit to proceed. Non-zero status will cause the commit to fail.
174 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
174 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
175 preoutgoing;;
175 preoutgoing;;
176 Run before computing changes to send from the local repository to
176 Run before computing changes to send from the local repository to
177 another. Non-zero status will cause failure. This lets you
177 another. Non-zero status will cause failure. This lets you
178 prevent pull over http or ssh. Also prevents against local pull,
178 prevent pull over http or ssh. Also prevents against local pull,
179 push (outbound) or bundle commands, but not effective, since you
179 push (outbound) or bundle commands, but not effective, since you
180 can just copy files instead then. Source of operation is in
180 can just copy files instead then. Source of operation is in
181 $HG_SOURCE. If "serve", operation is happening on behalf of
181 $HG_SOURCE. If "serve", operation is happening on behalf of
182 remote ssh or http repository. If "push", "pull" or "bundle",
182 remote ssh or http repository. If "push", "pull" or "bundle",
183 operation is happening on behalf of repository on same system.
183 operation is happening on behalf of repository on same system.
184 pretag;;
184 pretag;;
185 Run before creating a tag. Exit status 0 allows the tag to be
185 Run before creating a tag. Exit status 0 allows the tag to be
186 created. Non-zero status will cause the tag to fail. ID of
186 created. Non-zero status will cause the tag to fail. ID of
187 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
187 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
188 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
188 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
189 pretxnchangegroup;;
189 pretxnchangegroup;;
190 Run after a changegroup has been added via push, pull or unbundle,
190 Run after a changegroup has been added via push, pull or unbundle,
191 but before the transaction has been committed. Changegroup is
191 but before the transaction has been committed. Changegroup is
192 visible to hook program. This lets you validate incoming changes
192 visible to hook program. This lets you validate incoming changes
193 before accepting them. Passed the ID of the first new changeset
193 before accepting them. Passed the ID of the first new changeset
194 in $HG_NODE. Exit status 0 allows the transaction to commit.
194 in $HG_NODE. Exit status 0 allows the transaction to commit.
195 Non-zero status will cause the transaction to be rolled back and
195 Non-zero status will cause the transaction to be rolled back and
196 the push, pull or unbundle will fail.
196 the push, pull or unbundle will fail.
197 pretxncommit;;
197 pretxncommit;;
198 Run after a changeset has been created but the transaction not yet
198 Run after a changeset has been created but the transaction not yet
199 committed. Changeset is visible to hook program. This lets you
199 committed. Changeset is visible to hook program. This lets you
200 validate commit message and changes. Exit status 0 allows the
200 validate commit message and changes. Exit status 0 allows the
201 commit to proceed. Non-zero status will cause the transaction to
201 commit to proceed. Non-zero status will cause the transaction to
202 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
202 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
203 IDs are in $HG_PARENT1 and $HG_PARENT2.
203 IDs are in $HG_PARENT1 and $HG_PARENT2.
204 tag;;
204 tag;;
205 Run after a tag is created. ID of tagged changeset is in
205 Run after a tag is created. ID of tagged changeset is in
206 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
206 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
207 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
207 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
208
208
209 In earlier releases, the names of hook environment variables did not
209 In earlier releases, the names of hook environment variables did not
210 have a "HG_" prefix. These unprefixed names are still provided in
210 have a "HG_" prefix. These unprefixed names are still provided in
211 the environment for backwards compatibility, but their use is
211 the environment for backwards compatibility, but their use is
212 deprecated, and they will be removed in a future release.
212 deprecated, and they will be removed in a future release.
213
213
214 http_proxy::
214 http_proxy::
215 Used to access web-based Mercurial repositories through a HTTP
215 Used to access web-based Mercurial repositories through a HTTP
216 proxy.
216 proxy.
217 host;;
217 host;;
218 Host name and (optional) port of the proxy server, for example
218 Host name and (optional) port of the proxy server, for example
219 "myproxy:8000".
219 "myproxy:8000".
220 no;;
220 no;;
221 Optional. Comma-separated list of host names that should bypass
221 Optional. Comma-separated list of host names that should bypass
222 the proxy.
222 the proxy.
223 passwd;;
223 passwd;;
224 Optional. Password to authenticate with at the proxy server.
224 Optional. Password to authenticate with at the proxy server.
225 user;;
225 user;;
226 Optional. User name to authenticate with at the proxy server.
226 Optional. User name to authenticate with at the proxy server.
227
227
228 paths::
228 paths::
229 Assigns symbolic names to repositories. The left side is the
229 Assigns symbolic names to repositories. The left side is the
230 symbolic name, and the right gives the directory or URL that is the
230 symbolic name, and the right gives the directory or URL that is the
231 location of the repository.
231 location of the repository.
232
232
233 ui::
233 ui::
234 User interface controls.
234 User interface controls.
235 debug;;
235 debug;;
236 Print debugging information. True or False. Default is False.
236 Print debugging information. True or False. Default is False.
237 editor;;
237 editor;;
238 The editor to use during a commit. Default is $EDITOR or "vi".
238 The editor to use during a commit. Default is $EDITOR or "vi".
239 ignore;;
240 A file to read per-user ignore patterns from. This file should be in
241 the same format as a repository-wide .hgignore file. This option
242 supports hook syntax, so if you want to specify multiple ignore
243 files, you can do so by setting something like
244 "ignore.other = ~/.hgignore2".
239 interactive;;
245 interactive;;
240 Allow to prompt the user. True or False. Default is True.
246 Allow to prompt the user. True or False. Default is True.
241 logtemplate;;
247 logtemplate;;
242 Template string for commands that print changesets.
248 Template string for commands that print changesets.
243 style;;
249 style;;
244 Name of style to use for command output.
250 Name of style to use for command output.
245 merge;;
251 merge;;
246 The conflict resolution program to use during a manual merge.
252 The conflict resolution program to use during a manual merge.
247 Default is "hgmerge".
253 Default is "hgmerge".
248 quiet;;
254 quiet;;
249 Reduce the amount of output printed. True or False. Default is False.
255 Reduce the amount of output printed. True or False. Default is False.
250 remotecmd;;
256 remotecmd;;
251 remote command to use for clone/push/pull operations. Default is 'hg'.
257 remote command to use for clone/push/pull operations. Default is 'hg'.
252 ssh;;
258 ssh;;
253 command to use for SSH connections. Default is 'ssh'.
259 command to use for SSH connections. Default is 'ssh'.
254 timeout;;
260 timeout;;
255 The timeout used when a lock is held (in seconds), a negative value
261 The timeout used when a lock is held (in seconds), a negative value
256 means no timeout. Default is 600.
262 means no timeout. Default is 600.
257 username;;
263 username;;
258 The committer of a changeset created when running "commit".
264 The committer of a changeset created when running "commit".
259 Typically a person's name and email address, e.g. "Fred Widget
265 Typically a person's name and email address, e.g. "Fred Widget
260 <fred@example.com>". Default is $EMAIL or username@hostname, unless
266 <fred@example.com>". Default is $EMAIL or username@hostname, unless
261 username is set to an empty string, which enforces specifying the
267 username is set to an empty string, which enforces specifying the
262 username manually.
268 username manually.
263 verbose;;
269 verbose;;
264 Increase the amount of output printed. True or False. Default is False.
270 Increase the amount of output printed. True or False. Default is False.
265
271
266
272
267 web::
273 web::
268 Web interface configuration.
274 Web interface configuration.
269 accesslog;;
275 accesslog;;
270 Where to output the access log. Default is stdout.
276 Where to output the access log. Default is stdout.
271 address;;
277 address;;
272 Interface address to bind to. Default is all.
278 Interface address to bind to. Default is all.
273 allowbz2;;
279 allowbz2;;
274 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
280 Whether to allow .tar.bz2 downloading of repo revisions. Default is false.
275 allowgz;;
281 allowgz;;
276 Whether to allow .tar.gz downloading of repo revisions. Default is false.
282 Whether to allow .tar.gz downloading of repo revisions. Default is false.
277 allowpull;;
283 allowpull;;
278 Whether to allow pulling from the repository. Default is true.
284 Whether to allow pulling from the repository. Default is true.
279 allowzip;;
285 allowzip;;
280 Whether to allow .zip downloading of repo revisions. Default is false.
286 Whether to allow .zip downloading of repo revisions. Default is false.
281 This feature creates temporary files.
287 This feature creates temporary files.
282 description;;
288 description;;
283 Textual description of the repository's purpose or contents.
289 Textual description of the repository's purpose or contents.
284 Default is "unknown".
290 Default is "unknown".
285 errorlog;;
291 errorlog;;
286 Where to output the error log. Default is stderr.
292 Where to output the error log. Default is stderr.
287 ipv6;;
293 ipv6;;
288 Whether to use IPv6. Default is false.
294 Whether to use IPv6. Default is false.
289 name;;
295 name;;
290 Repository name to use in the web interface. Default is current
296 Repository name to use in the web interface. Default is current
291 working directory.
297 working directory.
292 maxchanges;;
298 maxchanges;;
293 Maximum number of changes to list on the changelog. Default is 10.
299 Maximum number of changes to list on the changelog. Default is 10.
294 maxfiles;;
300 maxfiles;;
295 Maximum number of files to list per changeset. Default is 10.
301 Maximum number of files to list per changeset. Default is 10.
296 port;;
302 port;;
297 Port to listen on. Default is 8000.
303 Port to listen on. Default is 8000.
298 style;;
304 style;;
299 Which template map style to use.
305 Which template map style to use.
300 templates;;
306 templates;;
301 Where to find the HTML templates. Default is install path.
307 Where to find the HTML templates. Default is install path.
302
308
303
309
304 AUTHOR
310 AUTHOR
305 ------
311 ------
306 Bryan O'Sullivan <bos@serpentine.com>.
312 Bryan O'Sullivan <bos@serpentine.com>.
307
313
308 Mercurial was written by Matt Mackall <mpm@selenic.com>.
314 Mercurial was written by Matt Mackall <mpm@selenic.com>.
309
315
310 SEE ALSO
316 SEE ALSO
311 --------
317 --------
312 hg(1)
318 hg(1)
313
319
314 COPYING
320 COPYING
315 -------
321 -------
316 This manual page is copyright 2005 Bryan O'Sullivan.
322 This manual page is copyright 2005 Bryan O'Sullivan.
317 Mercurial is copyright 2005 Matt Mackall.
323 Mercurial is copyright 2005 Matt Mackall.
318 Free use of this software is granted under the terms of the GNU General
324 Free use of this software is granted under the terms of the GNU General
319 Public License (GPL).
325 Public License (GPL).
@@ -1,436 +1,448
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005 Matt Mackall <mpm@selenic.com>
4 Copyright 2005 Matt Mackall <mpm@selenic.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8 """
8 """
9
9
10 import struct, os
10 import struct, os
11 from node import *
11 from node import *
12 from i18n import gettext as _
12 from i18n import gettext as _
13 from demandload import *
13 from demandload import *
14 demandload(globals(), "time bisect stat util re errno")
14 demandload(globals(), "time bisect stat util re errno")
15
15
16 class dirstate(object):
16 class dirstate(object):
17 def __init__(self, opener, ui, root):
17 def __init__(self, opener, ui, root):
18 self.opener = opener
18 self.opener = opener
19 self.root = root
19 self.root = root
20 self.dirty = 0
20 self.dirty = 0
21 self.ui = ui
21 self.ui = ui
22 self.map = None
22 self.map = None
23 self.pl = None
23 self.pl = None
24 self.copies = {}
24 self.copies = {}
25 self.ignorefunc = None
25 self.ignorefunc = None
26 self.blockignore = False
26 self.blockignore = False
27
27
28 def wjoin(self, f):
28 def wjoin(self, f):
29 return os.path.join(self.root, f)
29 return os.path.join(self.root, f)
30
30
31 def getcwd(self):
31 def getcwd(self):
32 cwd = os.getcwd()
32 cwd = os.getcwd()
33 if cwd == self.root: return ''
33 if cwd == self.root: return ''
34 return cwd[len(self.root) + 1:]
34 return cwd[len(self.root) + 1:]
35
35
36 def hgignore(self):
36 def hgignore(self):
37 '''return the contents of .hgignore as a list of patterns.
37 '''return the contents of .hgignore files as a list of patterns.
38
39 the files parsed for patterns include:
40 .hgignore in the repository root
41 any additional files specified in the [ui] section of ~/.hgrc
38
42
39 trailing white space is dropped.
43 trailing white space is dropped.
40 the escape character is backslash.
44 the escape character is backslash.
41 comments start with #.
45 comments start with #.
42 empty lines are skipped.
46 empty lines are skipped.
43
47
44 lines can be of the following formats:
48 lines can be of the following formats:
45
49
46 syntax: regexp # defaults following lines to non-rooted regexps
50 syntax: regexp # defaults following lines to non-rooted regexps
47 syntax: glob # defaults following lines to non-rooted globs
51 syntax: glob # defaults following lines to non-rooted globs
48 re:pattern # non-rooted regular expression
52 re:pattern # non-rooted regular expression
49 glob:pattern # non-rooted glob
53 glob:pattern # non-rooted glob
50 pattern # pattern of the current default type'''
54 pattern # pattern of the current default type'''
51 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
55 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
52 def parselines(fp):
56 def parselines(fp):
53 for line in fp:
57 for line in fp:
54 escape = False
58 escape = False
55 for i in xrange(len(line)):
59 for i in xrange(len(line)):
56 if escape: escape = False
60 if escape: escape = False
57 elif line[i] == '\\': escape = True
61 elif line[i] == '\\': escape = True
58 elif line[i] == '#': break
62 elif line[i] == '#': break
59 line = line[:i].rstrip()
63 line = line[:i].rstrip()
60 if line: yield line
64 if line: yield line
65 files = [self.wjoin('.hgignore')]
66 files.extend(self.ui.hgignorefiles())
61 pats = []
67 pats = []
62 try:
68 for f in files:
63 fp = open(self.wjoin('.hgignore'))
69 try:
64 syntax = 'relre:'
70 fp = open(f)
65 for line in parselines(fp):
71 syntax = 'relre:'
66 if line.startswith('syntax:'):
72 for line in parselines(fp):
67 s = line[7:].strip()
73 if line.startswith('syntax:'):
68 try:
74 s = line[7:].strip()
69 syntax = syntaxes[s]
75 try:
70 except KeyError:
76 syntax = syntaxes[s]
71 self.ui.warn(_(".hgignore: ignoring invalid "
77 except KeyError:
72 "syntax '%s'\n") % s)
78 self.ui.warn(_("%s: ignoring invalid "
73 continue
79 "syntax '%s'\n") % (f, s))
74 pat = syntax + line
80 continue
75 for s in syntaxes.values():
81 pat = syntax + line
76 if line.startswith(s):
82 for s in syntaxes.values():
77 pat = line
83 if line.startswith(s):
78 break
84 pat = line
79 pats.append(pat)
85 break
80 except IOError: pass
86 pats.append(pat)
87 except IOError: pass
81 return pats
88 return pats
82
89
83 def ignore(self, fn):
90 def ignore(self, fn):
84 '''default match function used by dirstate and localrepository.
91 '''default match function used by dirstate and
85 this honours the .hgignore file, and nothing more.'''
92 localrepository. this honours the repository .hgignore file
93 and any other files specified in the [ui] section of .hgrc.'''
86 if self.blockignore:
94 if self.blockignore:
87 return False
95 return False
88 if not self.ignorefunc:
96 if not self.ignorefunc:
89 ignore = self.hgignore()
97 ignore = self.hgignore()
90 if ignore:
98 if ignore:
99 # FIXME: if there are errors in patterns, matcher will
100 # print out an error containing src ('.hgignore');
101 # really, we want the original source file to be
102 # printed instead.
91 files, self.ignorefunc, anypats = util.matcher(self.root,
103 files, self.ignorefunc, anypats = util.matcher(self.root,
92 inc=ignore,
104 inc=ignore,
93 src='.hgignore')
105 src='.hgignore')
94 else:
106 else:
95 self.ignorefunc = util.never
107 self.ignorefunc = util.never
96 return self.ignorefunc(fn)
108 return self.ignorefunc(fn)
97
109
98 def __del__(self):
110 def __del__(self):
99 if self.dirty:
111 if self.dirty:
100 self.write()
112 self.write()
101
113
102 def __getitem__(self, key):
114 def __getitem__(self, key):
103 try:
115 try:
104 return self.map[key]
116 return self.map[key]
105 except TypeError:
117 except TypeError:
106 self.lazyread()
118 self.lazyread()
107 return self[key]
119 return self[key]
108
120
109 def __contains__(self, key):
121 def __contains__(self, key):
110 self.lazyread()
122 self.lazyread()
111 return key in self.map
123 return key in self.map
112
124
113 def parents(self):
125 def parents(self):
114 self.lazyread()
126 self.lazyread()
115 return self.pl
127 return self.pl
116
128
117 def markdirty(self):
129 def markdirty(self):
118 if not self.dirty:
130 if not self.dirty:
119 self.dirty = 1
131 self.dirty = 1
120
132
121 def setparents(self, p1, p2=nullid):
133 def setparents(self, p1, p2=nullid):
122 self.lazyread()
134 self.lazyread()
123 self.markdirty()
135 self.markdirty()
124 self.pl = p1, p2
136 self.pl = p1, p2
125
137
126 def state(self, key):
138 def state(self, key):
127 try:
139 try:
128 return self[key][0]
140 return self[key][0]
129 except KeyError:
141 except KeyError:
130 return "?"
142 return "?"
131
143
132 def lazyread(self):
144 def lazyread(self):
133 if self.map is None:
145 if self.map is None:
134 self.read()
146 self.read()
135
147
136 def read(self):
148 def read(self):
137 self.map = {}
149 self.map = {}
138 self.pl = [nullid, nullid]
150 self.pl = [nullid, nullid]
139 try:
151 try:
140 st = self.opener("dirstate").read()
152 st = self.opener("dirstate").read()
141 if not st: return
153 if not st: return
142 except: return
154 except: return
143
155
144 self.pl = [st[:20], st[20: 40]]
156 self.pl = [st[:20], st[20: 40]]
145
157
146 pos = 40
158 pos = 40
147 while pos < len(st):
159 while pos < len(st):
148 e = struct.unpack(">cllll", st[pos:pos+17])
160 e = struct.unpack(">cllll", st[pos:pos+17])
149 l = e[4]
161 l = e[4]
150 pos += 17
162 pos += 17
151 f = st[pos:pos + l]
163 f = st[pos:pos + l]
152 if '\0' in f:
164 if '\0' in f:
153 f, c = f.split('\0')
165 f, c = f.split('\0')
154 self.copies[f] = c
166 self.copies[f] = c
155 self.map[f] = e[:4]
167 self.map[f] = e[:4]
156 pos += l
168 pos += l
157
169
158 def copy(self, source, dest):
170 def copy(self, source, dest):
159 self.lazyread()
171 self.lazyread()
160 self.markdirty()
172 self.markdirty()
161 self.copies[dest] = source
173 self.copies[dest] = source
162
174
163 def copied(self, file):
175 def copied(self, file):
164 return self.copies.get(file, None)
176 return self.copies.get(file, None)
165
177
166 def update(self, files, state, **kw):
178 def update(self, files, state, **kw):
167 ''' current states:
179 ''' current states:
168 n normal
180 n normal
169 m needs merging
181 m needs merging
170 r marked for removal
182 r marked for removal
171 a marked for addition'''
183 a marked for addition'''
172
184
173 if not files: return
185 if not files: return
174 self.lazyread()
186 self.lazyread()
175 self.markdirty()
187 self.markdirty()
176 for f in files:
188 for f in files:
177 if state == "r":
189 if state == "r":
178 self.map[f] = ('r', 0, 0, 0)
190 self.map[f] = ('r', 0, 0, 0)
179 else:
191 else:
180 s = os.lstat(self.wjoin(f))
192 s = os.lstat(self.wjoin(f))
181 st_size = kw.get('st_size', s.st_size)
193 st_size = kw.get('st_size', s.st_size)
182 st_mtime = kw.get('st_mtime', s.st_mtime)
194 st_mtime = kw.get('st_mtime', s.st_mtime)
183 self.map[f] = (state, s.st_mode, st_size, st_mtime)
195 self.map[f] = (state, s.st_mode, st_size, st_mtime)
184 if self.copies.has_key(f):
196 if self.copies.has_key(f):
185 del self.copies[f]
197 del self.copies[f]
186
198
187 def forget(self, files):
199 def forget(self, files):
188 if not files: return
200 if not files: return
189 self.lazyread()
201 self.lazyread()
190 self.markdirty()
202 self.markdirty()
191 for f in files:
203 for f in files:
192 try:
204 try:
193 del self.map[f]
205 del self.map[f]
194 except KeyError:
206 except KeyError:
195 self.ui.warn(_("not in dirstate: %s!\n") % f)
207 self.ui.warn(_("not in dirstate: %s!\n") % f)
196 pass
208 pass
197
209
198 def clear(self):
210 def clear(self):
199 self.map = {}
211 self.map = {}
200 self.copies = {}
212 self.copies = {}
201 self.markdirty()
213 self.markdirty()
202
214
203 def rebuild(self, parent, files):
215 def rebuild(self, parent, files):
204 self.clear()
216 self.clear()
205 umask = os.umask(0)
217 umask = os.umask(0)
206 os.umask(umask)
218 os.umask(umask)
207 for f, mode in files:
219 for f, mode in files:
208 if mode:
220 if mode:
209 self.map[f] = ('n', ~umask, -1, 0)
221 self.map[f] = ('n', ~umask, -1, 0)
210 else:
222 else:
211 self.map[f] = ('n', ~umask & 0666, -1, 0)
223 self.map[f] = ('n', ~umask & 0666, -1, 0)
212 self.pl = (parent, nullid)
224 self.pl = (parent, nullid)
213 self.markdirty()
225 self.markdirty()
214
226
215 def write(self):
227 def write(self):
216 if not self.dirty:
228 if not self.dirty:
217 return
229 return
218 st = self.opener("dirstate", "w", atomic=True)
230 st = self.opener("dirstate", "w", atomic=True)
219 st.write("".join(self.pl))
231 st.write("".join(self.pl))
220 for f, e in self.map.items():
232 for f, e in self.map.items():
221 c = self.copied(f)
233 c = self.copied(f)
222 if c:
234 if c:
223 f = f + "\0" + c
235 f = f + "\0" + c
224 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
236 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
225 st.write(e + f)
237 st.write(e + f)
226 self.dirty = 0
238 self.dirty = 0
227
239
228 def filterfiles(self, files):
240 def filterfiles(self, files):
229 ret = {}
241 ret = {}
230 unknown = []
242 unknown = []
231
243
232 for x in files:
244 for x in files:
233 if x == '.':
245 if x == '.':
234 return self.map.copy()
246 return self.map.copy()
235 if x not in self.map:
247 if x not in self.map:
236 unknown.append(x)
248 unknown.append(x)
237 else:
249 else:
238 ret[x] = self.map[x]
250 ret[x] = self.map[x]
239
251
240 if not unknown:
252 if not unknown:
241 return ret
253 return ret
242
254
243 b = self.map.keys()
255 b = self.map.keys()
244 b.sort()
256 b.sort()
245 blen = len(b)
257 blen = len(b)
246
258
247 for x in unknown:
259 for x in unknown:
248 bs = bisect.bisect(b, x)
260 bs = bisect.bisect(b, x)
249 if bs != 0 and b[bs-1] == x:
261 if bs != 0 and b[bs-1] == x:
250 ret[x] = self.map[x]
262 ret[x] = self.map[x]
251 continue
263 continue
252 while bs < blen:
264 while bs < blen:
253 s = b[bs]
265 s = b[bs]
254 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
266 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
255 ret[s] = self.map[s]
267 ret[s] = self.map[s]
256 else:
268 else:
257 break
269 break
258 bs += 1
270 bs += 1
259 return ret
271 return ret
260
272
261 def supported_type(self, f, st, verbose=False):
273 def supported_type(self, f, st, verbose=False):
262 if stat.S_ISREG(st.st_mode):
274 if stat.S_ISREG(st.st_mode):
263 return True
275 return True
264 if verbose:
276 if verbose:
265 kind = 'unknown'
277 kind = 'unknown'
266 if stat.S_ISCHR(st.st_mode): kind = _('character device')
278 if stat.S_ISCHR(st.st_mode): kind = _('character device')
267 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
279 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
268 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
280 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
269 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
281 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
270 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
282 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
271 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
283 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
272 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
284 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
273 util.pathto(self.getcwd(), f),
285 util.pathto(self.getcwd(), f),
274 kind))
286 kind))
275 return False
287 return False
276
288
277 def statwalk(self, files=None, match=util.always, dc=None):
289 def statwalk(self, files=None, match=util.always, dc=None):
278 self.lazyread()
290 self.lazyread()
279
291
280 # walk all files by default
292 # walk all files by default
281 if not files:
293 if not files:
282 files = [self.root]
294 files = [self.root]
283 if not dc:
295 if not dc:
284 dc = self.map.copy()
296 dc = self.map.copy()
285 elif not dc:
297 elif not dc:
286 dc = self.filterfiles(files)
298 dc = self.filterfiles(files)
287
299
288 def statmatch(file_, stat):
300 def statmatch(file_, stat):
289 file_ = util.pconvert(file_)
301 file_ = util.pconvert(file_)
290 if file_ not in dc and self.ignore(file_):
302 if file_ not in dc and self.ignore(file_):
291 return False
303 return False
292 return match(file_)
304 return match(file_)
293
305
294 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
306 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
295
307
296 def walk(self, files=None, match=util.always, dc=None):
308 def walk(self, files=None, match=util.always, dc=None):
297 # filter out the stat
309 # filter out the stat
298 for src, f, st in self.statwalk(files, match, dc):
310 for src, f, st in self.statwalk(files, match, dc):
299 yield src, f
311 yield src, f
300
312
301 # walk recursively through the directory tree, finding all files
313 # walk recursively through the directory tree, finding all files
302 # matched by the statmatch function
314 # matched by the statmatch function
303 #
315 #
304 # results are yielded in a tuple (src, filename, st), where src
316 # results are yielded in a tuple (src, filename, st), where src
305 # is one of:
317 # is one of:
306 # 'f' the file was found in the directory tree
318 # 'f' the file was found in the directory tree
307 # 'm' the file was only in the dirstate and not in the tree
319 # 'm' the file was only in the dirstate and not in the tree
308 # and st is the stat result if the file was found in the directory.
320 # and st is the stat result if the file was found in the directory.
309 #
321 #
310 # dc is an optional arg for the current dirstate. dc is not modified
322 # dc is an optional arg for the current dirstate. dc is not modified
311 # directly by this function, but might be modified by your statmatch call.
323 # directly by this function, but might be modified by your statmatch call.
312 #
324 #
313 def walkhelper(self, files, statmatch, dc):
325 def walkhelper(self, files, statmatch, dc):
314 # recursion free walker, faster than os.walk.
326 # recursion free walker, faster than os.walk.
315 def findfiles(s):
327 def findfiles(s):
316 work = [s]
328 work = [s]
317 while work:
329 while work:
318 top = work.pop()
330 top = work.pop()
319 names = os.listdir(top)
331 names = os.listdir(top)
320 names.sort()
332 names.sort()
321 # nd is the top of the repository dir tree
333 # nd is the top of the repository dir tree
322 nd = util.normpath(top[len(self.root) + 1:])
334 nd = util.normpath(top[len(self.root) + 1:])
323 if nd == '.': nd = ''
335 if nd == '.': nd = ''
324 for f in names:
336 for f in names:
325 np = util.pconvert(os.path.join(nd, f))
337 np = util.pconvert(os.path.join(nd, f))
326 if seen(np):
338 if seen(np):
327 continue
339 continue
328 p = os.path.join(top, f)
340 p = os.path.join(top, f)
329 # don't trip over symlinks
341 # don't trip over symlinks
330 st = os.lstat(p)
342 st = os.lstat(p)
331 if stat.S_ISDIR(st.st_mode):
343 if stat.S_ISDIR(st.st_mode):
332 ds = os.path.join(nd, f +'/')
344 ds = os.path.join(nd, f +'/')
333 if statmatch(ds, st):
345 if statmatch(ds, st):
334 work.append(p)
346 work.append(p)
335 if statmatch(np, st) and np in dc:
347 if statmatch(np, st) and np in dc:
336 yield 'm', np, st
348 yield 'm', np, st
337 elif statmatch(np, st):
349 elif statmatch(np, st):
338 if self.supported_type(np, st):
350 if self.supported_type(np, st):
339 yield 'f', np, st
351 yield 'f', np, st
340 elif np in dc:
352 elif np in dc:
341 yield 'm', np, st
353 yield 'm', np, st
342
354
343 known = {'.hg': 1}
355 known = {'.hg': 1}
344 def seen(fn):
356 def seen(fn):
345 if fn in known: return True
357 if fn in known: return True
346 known[fn] = 1
358 known[fn] = 1
347
359
348 # step one, find all files that match our criteria
360 # step one, find all files that match our criteria
349 files.sort()
361 files.sort()
350 for ff in util.unique(files):
362 for ff in util.unique(files):
351 f = self.wjoin(ff)
363 f = self.wjoin(ff)
352 try:
364 try:
353 st = os.lstat(f)
365 st = os.lstat(f)
354 except OSError, inst:
366 except OSError, inst:
355 nf = util.normpath(ff)
367 nf = util.normpath(ff)
356 found = False
368 found = False
357 for fn in dc:
369 for fn in dc:
358 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
370 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
359 found = True
371 found = True
360 break
372 break
361 if not found:
373 if not found:
362 self.ui.warn('%s: %s\n' % (
374 self.ui.warn('%s: %s\n' % (
363 util.pathto(self.getcwd(), ff),
375 util.pathto(self.getcwd(), ff),
364 inst.strerror))
376 inst.strerror))
365 continue
377 continue
366 if stat.S_ISDIR(st.st_mode):
378 if stat.S_ISDIR(st.st_mode):
367 cmp1 = (lambda x, y: cmp(x[1], y[1]))
379 cmp1 = (lambda x, y: cmp(x[1], y[1]))
368 sorted_ = [ x for x in findfiles(f) ]
380 sorted_ = [ x for x in findfiles(f) ]
369 sorted_.sort(cmp1)
381 sorted_.sort(cmp1)
370 for e in sorted_:
382 for e in sorted_:
371 yield e
383 yield e
372 else:
384 else:
373 ff = util.normpath(ff)
385 ff = util.normpath(ff)
374 if seen(ff):
386 if seen(ff):
375 continue
387 continue
376 self.blockignore = True
388 self.blockignore = True
377 if statmatch(ff, st):
389 if statmatch(ff, st):
378 if self.supported_type(ff, st, verbose=True):
390 if self.supported_type(ff, st, verbose=True):
379 yield 'f', ff, st
391 yield 'f', ff, st
380 elif ff in dc:
392 elif ff in dc:
381 yield 'm', ff, st
393 yield 'm', ff, st
382 self.blockignore = False
394 self.blockignore = False
383
395
384 # step two run through anything left in the dc hash and yield
396 # step two run through anything left in the dc hash and yield
385 # if we haven't already seen it
397 # if we haven't already seen it
386 ks = dc.keys()
398 ks = dc.keys()
387 ks.sort()
399 ks.sort()
388 for k in ks:
400 for k in ks:
389 if not seen(k) and (statmatch(k, None)):
401 if not seen(k) and (statmatch(k, None)):
390 yield 'm', k, None
402 yield 'm', k, None
391
403
392 def changes(self, files=None, match=util.always):
404 def changes(self, files=None, match=util.always):
393 lookup, modified, added, unknown = [], [], [], []
405 lookup, modified, added, unknown = [], [], [], []
394 removed, deleted = [], []
406 removed, deleted = [], []
395
407
396 for src, fn, st in self.statwalk(files, match):
408 for src, fn, st in self.statwalk(files, match):
397 try:
409 try:
398 type_, mode, size, time = self[fn]
410 type_, mode, size, time = self[fn]
399 except KeyError:
411 except KeyError:
400 unknown.append(fn)
412 unknown.append(fn)
401 continue
413 continue
402 if src == 'm':
414 if src == 'm':
403 nonexistent = True
415 nonexistent = True
404 if not st:
416 if not st:
405 try:
417 try:
406 f = self.wjoin(fn)
418 f = self.wjoin(fn)
407 st = os.lstat(f)
419 st = os.lstat(f)
408 except OSError, inst:
420 except OSError, inst:
409 if inst.errno != errno.ENOENT:
421 if inst.errno != errno.ENOENT:
410 raise
422 raise
411 st = None
423 st = None
412 # We need to re-check that it is a valid file
424 # We need to re-check that it is a valid file
413 if st and self.supported_type(fn, st):
425 if st and self.supported_type(fn, st):
414 nonexistent = False
426 nonexistent = False
415 # XXX: what to do with file no longer present in the fs
427 # XXX: what to do with file no longer present in the fs
416 # who are not removed in the dirstate ?
428 # who are not removed in the dirstate ?
417 if nonexistent and type_ in "nm":
429 if nonexistent and type_ in "nm":
418 deleted.append(fn)
430 deleted.append(fn)
419 continue
431 continue
420 # check the common case first
432 # check the common case first
421 if type_ == 'n':
433 if type_ == 'n':
422 if not st:
434 if not st:
423 st = os.stat(fn)
435 st = os.stat(fn)
424 if size >= 0 and (size != st.st_size
436 if size >= 0 and (size != st.st_size
425 or (mode ^ st.st_mode) & 0100):
437 or (mode ^ st.st_mode) & 0100):
426 modified.append(fn)
438 modified.append(fn)
427 elif time != st.st_mtime:
439 elif time != st.st_mtime:
428 lookup.append(fn)
440 lookup.append(fn)
429 elif type_ == 'm':
441 elif type_ == 'm':
430 modified.append(fn)
442 modified.append(fn)
431 elif type_ == 'a':
443 elif type_ == 'a':
432 added.append(fn)
444 added.append(fn)
433 elif type_ == 'r':
445 elif type_ == 'r':
434 removed.append(fn)
446 removed.append(fn)
435
447
436 return (lookup, modified, added, removed, deleted, unknown)
448 return (lookup, modified, added, removed, deleted, unknown)
@@ -1,237 +1,246
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 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 import ConfigParser
8 import ConfigParser
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "errno os re socket sys tempfile util")
11 demandload(globals(), "errno os re socket sys tempfile util")
12
12
13 class ui(object):
13 class ui(object):
14 def __init__(self, verbose=False, debug=False, quiet=False,
14 def __init__(self, verbose=False, debug=False, quiet=False,
15 interactive=True, parentui=None):
15 interactive=True, parentui=None):
16 self.overlay = {}
16 self.overlay = {}
17 if parentui is None:
17 if parentui is None:
18 # this is the parent of all ui children
18 # this is the parent of all ui children
19 self.parentui = None
19 self.parentui = None
20 self.cdata = ConfigParser.SafeConfigParser()
20 self.cdata = ConfigParser.SafeConfigParser()
21 self.readconfig(util.rcpath())
21 self.readconfig(util.rcpath())
22
22
23 self.quiet = self.configbool("ui", "quiet")
23 self.quiet = self.configbool("ui", "quiet")
24 self.verbose = self.configbool("ui", "verbose")
24 self.verbose = self.configbool("ui", "verbose")
25 self.debugflag = self.configbool("ui", "debug")
25 self.debugflag = self.configbool("ui", "debug")
26 self.interactive = self.configbool("ui", "interactive", True)
26 self.interactive = self.configbool("ui", "interactive", True)
27
27
28 self.updateopts(verbose, debug, quiet, interactive)
28 self.updateopts(verbose, debug, quiet, interactive)
29 self.diffcache = None
29 self.diffcache = None
30 else:
30 else:
31 # parentui may point to an ui object which is already a child
31 # parentui may point to an ui object which is already a child
32 self.parentui = parentui.parentui or parentui
32 self.parentui = parentui.parentui or parentui
33 parent_cdata = self.parentui.cdata
33 parent_cdata = self.parentui.cdata
34 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
34 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
35 # make interpolation work
35 # make interpolation work
36 for section in parent_cdata.sections():
36 for section in parent_cdata.sections():
37 self.cdata.add_section(section)
37 self.cdata.add_section(section)
38 for name, value in parent_cdata.items(section, raw=True):
38 for name, value in parent_cdata.items(section, raw=True):
39 self.cdata.set(section, name, value)
39 self.cdata.set(section, name, value)
40
40
41 def __getattr__(self, key):
41 def __getattr__(self, key):
42 return getattr(self.parentui, key)
42 return getattr(self.parentui, key)
43
43
44 def updateopts(self, verbose=False, debug=False, quiet=False,
44 def updateopts(self, verbose=False, debug=False, quiet=False,
45 interactive=True):
45 interactive=True):
46 self.quiet = (self.quiet or quiet) and not verbose and not debug
46 self.quiet = (self.quiet or quiet) and not verbose and not debug
47 self.verbose = (self.verbose or verbose) or debug
47 self.verbose = (self.verbose or verbose) or debug
48 self.debugflag = (self.debugflag or debug)
48 self.debugflag = (self.debugflag or debug)
49 self.interactive = (self.interactive and interactive)
49 self.interactive = (self.interactive and interactive)
50
50
51 def readconfig(self, fn, root=None):
51 def readconfig(self, fn, root=None):
52 if isinstance(fn, basestring):
52 if isinstance(fn, basestring):
53 fn = [fn]
53 fn = [fn]
54 for f in fn:
54 for f in fn:
55 try:
55 try:
56 self.cdata.read(f)
56 self.cdata.read(f)
57 except ConfigParser.ParsingError, inst:
57 except ConfigParser.ParsingError, inst:
58 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
58 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
59 # translate paths relative to root (or home) into absolute paths
59 # translate paths relative to root (or home) into absolute paths
60 if root is None:
60 if root is None:
61 root = os.path.expanduser('~')
61 root = os.path.expanduser('~')
62 for name, path in self.configitems("paths"):
62 for name, path in self.configitems("paths"):
63 if path and path.find("://") == -1 and not os.path.isabs(path):
63 if path and path.find("://") == -1 and not os.path.isabs(path):
64 self.cdata.set("paths", name, os.path.join(root, path))
64 self.cdata.set("paths", name, os.path.join(root, path))
65
65
66 def setconfig(self, section, name, val):
66 def setconfig(self, section, name, val):
67 self.overlay[(section, name)] = val
67 self.overlay[(section, name)] = val
68
68
69 def config(self, section, name, default=None):
69 def config(self, section, name, default=None):
70 if self.overlay.has_key((section, name)):
70 if self.overlay.has_key((section, name)):
71 return self.overlay[(section, name)]
71 return self.overlay[(section, name)]
72 if self.cdata.has_option(section, name):
72 if self.cdata.has_option(section, name):
73 try:
73 try:
74 return self.cdata.get(section, name)
74 return self.cdata.get(section, name)
75 except ConfigParser.InterpolationError, inst:
75 except ConfigParser.InterpolationError, inst:
76 raise util.Abort(_("Error in configuration:\n%s") % inst)
76 raise util.Abort(_("Error in configuration:\n%s") % inst)
77 if self.parentui is None:
77 if self.parentui is None:
78 return default
78 return default
79 else:
79 else:
80 return self.parentui.config(section, name, default)
80 return self.parentui.config(section, name, default)
81
81
82 def configbool(self, section, name, default=False):
82 def configbool(self, section, name, default=False):
83 if self.overlay.has_key((section, name)):
83 if self.overlay.has_key((section, name)):
84 return self.overlay[(section, name)]
84 return self.overlay[(section, name)]
85 if self.cdata.has_option(section, name):
85 if self.cdata.has_option(section, name):
86 try:
86 try:
87 return self.cdata.getboolean(section, name)
87 return self.cdata.getboolean(section, name)
88 except ConfigParser.InterpolationError, inst:
88 except ConfigParser.InterpolationError, inst:
89 raise util.Abort(_("Error in configuration:\n%s") % inst)
89 raise util.Abort(_("Error in configuration:\n%s") % inst)
90 if self.parentui is None:
90 if self.parentui is None:
91 return default
91 return default
92 else:
92 else:
93 return self.parentui.configbool(section, name, default)
93 return self.parentui.configbool(section, name, default)
94
94
95 def configitems(self, section):
95 def configitems(self, section):
96 items = {}
96 items = {}
97 if self.parentui is not None:
97 if self.parentui is not None:
98 items = dict(self.parentui.configitems(section))
98 items = dict(self.parentui.configitems(section))
99 if self.cdata.has_section(section):
99 if self.cdata.has_section(section):
100 try:
100 try:
101 items.update(dict(self.cdata.items(section)))
101 items.update(dict(self.cdata.items(section)))
102 except ConfigParser.InterpolationError, inst:
102 except ConfigParser.InterpolationError, inst:
103 raise util.Abort(_("Error in configuration:\n%s") % inst)
103 raise util.Abort(_("Error in configuration:\n%s") % inst)
104 x = items.items()
104 x = items.items()
105 x.sort()
105 x.sort()
106 return x
106 return x
107
107
108 def walkconfig(self, seen=None):
108 def walkconfig(self, seen=None):
109 if seen is None:
109 if seen is None:
110 seen = {}
110 seen = {}
111 for (section, name), value in self.overlay.iteritems():
111 for (section, name), value in self.overlay.iteritems():
112 yield section, name, value
112 yield section, name, value
113 seen[section, name] = 1
113 seen[section, name] = 1
114 for section in self.cdata.sections():
114 for section in self.cdata.sections():
115 for name, value in self.cdata.items(section):
115 for name, value in self.cdata.items(section):
116 if (section, name) in seen: continue
116 if (section, name) in seen: continue
117 yield section, name, value.replace('\n', '\\n')
117 yield section, name, value.replace('\n', '\\n')
118 seen[section, name] = 1
118 seen[section, name] = 1
119 if self.parentui is not None:
119 if self.parentui is not None:
120 for parent in self.parentui.walkconfig(seen):
120 for parent in self.parentui.walkconfig(seen):
121 yield parent
121 yield parent
122
122
123 def extensions(self):
123 def extensions(self):
124 return self.configitems("extensions")
124 return self.configitems("extensions")
125
125
126 def hgignorefiles(self):
127 result = []
128 cfgitems = self.configitems("ui")
129 for key, value in cfgitems:
130 if key == 'ignore' or key.startswith('ignore.'):
131 path = os.path.expanduser(value)
132 result.append(path)
133 return result
134
126 def diffopts(self):
135 def diffopts(self):
127 if self.diffcache:
136 if self.diffcache:
128 return self.diffcache
137 return self.diffcache
129 ret = { 'showfunc' : True, 'ignorews' : False}
138 ret = { 'showfunc' : True, 'ignorews' : False}
130 for x in self.configitems("diff"):
139 for x in self.configitems("diff"):
131 k = x[0].lower()
140 k = x[0].lower()
132 v = x[1]
141 v = x[1]
133 if v:
142 if v:
134 v = v.lower()
143 v = v.lower()
135 if v == 'true':
144 if v == 'true':
136 value = True
145 value = True
137 else:
146 else:
138 value = False
147 value = False
139 ret[k] = value
148 ret[k] = value
140 self.diffcache = ret
149 self.diffcache = ret
141 return ret
150 return ret
142
151
143 def username(self):
152 def username(self):
144 """Return default username to be used in commits.
153 """Return default username to be used in commits.
145
154
146 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
155 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
147 and stop searching if one of these is set.
156 and stop searching if one of these is set.
148 Abort if found username is an empty string to force specifying
157 Abort if found username is an empty string to force specifying
149 the commit user elsewhere, e.g. with line option or repo hgrc.
158 the commit user elsewhere, e.g. with line option or repo hgrc.
150 If not found, use $LOGNAME or $USERNAME +"@full.hostname".
159 If not found, use $LOGNAME or $USERNAME +"@full.hostname".
151 """
160 """
152 user = os.environ.get("HGUSER")
161 user = os.environ.get("HGUSER")
153 if user is None:
162 if user is None:
154 user = self.config("ui", "username")
163 user = self.config("ui", "username")
155 if user is None:
164 if user is None:
156 user = os.environ.get("EMAIL")
165 user = os.environ.get("EMAIL")
157 if user is None:
166 if user is None:
158 user = os.environ.get("LOGNAME") or os.environ.get("USERNAME")
167 user = os.environ.get("LOGNAME") or os.environ.get("USERNAME")
159 if user:
168 if user:
160 user = "%s@%s" % (user, socket.getfqdn())
169 user = "%s@%s" % (user, socket.getfqdn())
161 if not user:
170 if not user:
162 raise util.Abort(_("Please specify a username."))
171 raise util.Abort(_("Please specify a username."))
163 return user
172 return user
164
173
165 def shortuser(self, user):
174 def shortuser(self, user):
166 """Return a short representation of a user name or email address."""
175 """Return a short representation of a user name or email address."""
167 if not self.verbose: user = util.shortuser(user)
176 if not self.verbose: user = util.shortuser(user)
168 return user
177 return user
169
178
170 def expandpath(self, loc):
179 def expandpath(self, loc):
171 """Return repository location relative to cwd or from [paths]"""
180 """Return repository location relative to cwd or from [paths]"""
172 if loc.find("://") != -1 or os.path.exists(loc):
181 if loc.find("://") != -1 or os.path.exists(loc):
173 return loc
182 return loc
174
183
175 return self.config("paths", loc, loc)
184 return self.config("paths", loc, loc)
176
185
177 def write(self, *args):
186 def write(self, *args):
178 for a in args:
187 for a in args:
179 sys.stdout.write(str(a))
188 sys.stdout.write(str(a))
180
189
181 def write_err(self, *args):
190 def write_err(self, *args):
182 try:
191 try:
183 if not sys.stdout.closed: sys.stdout.flush()
192 if not sys.stdout.closed: sys.stdout.flush()
184 for a in args:
193 for a in args:
185 sys.stderr.write(str(a))
194 sys.stderr.write(str(a))
186 except IOError, inst:
195 except IOError, inst:
187 if inst.errno != errno.EPIPE:
196 if inst.errno != errno.EPIPE:
188 raise
197 raise
189
198
190 def flush(self):
199 def flush(self):
191 try:
200 try:
192 sys.stdout.flush()
201 sys.stdout.flush()
193 finally:
202 finally:
194 sys.stderr.flush()
203 sys.stderr.flush()
195
204
196 def readline(self):
205 def readline(self):
197 return sys.stdin.readline()[:-1]
206 return sys.stdin.readline()[:-1]
198 def prompt(self, msg, pat, default="y"):
207 def prompt(self, msg, pat, default="y"):
199 if not self.interactive: return default
208 if not self.interactive: return default
200 while 1:
209 while 1:
201 self.write(msg, " ")
210 self.write(msg, " ")
202 r = self.readline()
211 r = self.readline()
203 if re.match(pat, r):
212 if re.match(pat, r):
204 return r
213 return r
205 else:
214 else:
206 self.write(_("unrecognized response\n"))
215 self.write(_("unrecognized response\n"))
207 def status(self, *msg):
216 def status(self, *msg):
208 if not self.quiet: self.write(*msg)
217 if not self.quiet: self.write(*msg)
209 def warn(self, *msg):
218 def warn(self, *msg):
210 self.write_err(*msg)
219 self.write_err(*msg)
211 def note(self, *msg):
220 def note(self, *msg):
212 if self.verbose: self.write(*msg)
221 if self.verbose: self.write(*msg)
213 def debug(self, *msg):
222 def debug(self, *msg):
214 if self.debugflag: self.write(*msg)
223 if self.debugflag: self.write(*msg)
215 def edit(self, text, user):
224 def edit(self, text, user):
216 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt")
225 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt")
217 try:
226 try:
218 f = os.fdopen(fd, "w")
227 f = os.fdopen(fd, "w")
219 f.write(text)
228 f.write(text)
220 f.close()
229 f.close()
221
230
222 editor = (os.environ.get("HGEDITOR") or
231 editor = (os.environ.get("HGEDITOR") or
223 self.config("ui", "editor") or
232 self.config("ui", "editor") or
224 os.environ.get("EDITOR", "vi"))
233 os.environ.get("EDITOR", "vi"))
225
234
226 util.system("%s \"%s\"" % (editor, name),
235 util.system("%s \"%s\"" % (editor, name),
227 environ={'HGUSER': user},
236 environ={'HGUSER': user},
228 onerr=util.Abort, errprefix=_("edit failed"))
237 onerr=util.Abort, errprefix=_("edit failed"))
229
238
230 f = open(name)
239 f = open(name)
231 t = f.read()
240 t = f.read()
232 f.close()
241 f.close()
233 t = re.sub("(?m)^HG:.*\n", "", t)
242 t = re.sub("(?m)^HG:.*\n", "", t)
234 finally:
243 finally:
235 os.unlink(name)
244 os.unlink(name)
236
245
237 return t
246 return t
General Comments 0
You need to be logged in to leave comments. Login now