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