##// END OF EJS Templates
if hgignore contains errors, print message that is not confusing.
Vadim Gelfer -
r1610:84e9b348 default
parent child Browse files
Show More
@@ -1,418 +1,420 b''
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 as a list of patterns.
38
38
39 trailing white space is dropped.
39 trailing white space is dropped.
40 the escape character is backslash.
40 the escape character is backslash.
41 comments start with #.
41 comments start with #.
42 empty lines are skipped.
42 empty lines are skipped.
43
43
44 lines can be of the following formats:
44 lines can be of the following formats:
45
45
46 syntax: regexp # defaults following lines to non-rooted regexps
46 syntax: regexp # defaults following lines to non-rooted regexps
47 syntax: glob # defaults following lines to non-rooted globs
47 syntax: glob # defaults following lines to non-rooted globs
48 re:pattern # non-rooted regular expression
48 re:pattern # non-rooted regular expression
49 glob:pattern # non-rooted glob
49 glob:pattern # non-rooted glob
50 pattern # pattern of the current default type'''
50 pattern # pattern of the current default type'''
51 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
51 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
52 def parselines(fp):
52 def parselines(fp):
53 for line in fp:
53 for line in fp:
54 escape = False
54 escape = False
55 for i in xrange(len(line)):
55 for i in xrange(len(line)):
56 if escape: escape = False
56 if escape: escape = False
57 elif line[i] == '\\': escape = True
57 elif line[i] == '\\': escape = True
58 elif line[i] == '#': break
58 elif line[i] == '#': break
59 line = line[:i].rstrip()
59 line = line[:i].rstrip()
60 if line: yield line
60 if line: yield line
61 pats = []
61 pats = []
62 try:
62 try:
63 fp = open(self.wjoin('.hgignore'))
63 fp = open(self.wjoin('.hgignore'))
64 syntax = 'relre:'
64 syntax = 'relre:'
65 for line in parselines(fp):
65 for line in parselines(fp):
66 if line.startswith('syntax:'):
66 if line.startswith('syntax:'):
67 s = line[7:].strip()
67 s = line[7:].strip()
68 try:
68 try:
69 syntax = syntaxes[s]
69 syntax = syntaxes[s]
70 except KeyError:
70 except KeyError:
71 self.ui.warn(_("ignoring invalid syntax '%s'\n") % s)
71 self.ui.warn(_(".hgignore: ignoring invalid "
72 "syntax '%s'\n") % s)
72 continue
73 continue
73 pat = syntax + line
74 pat = syntax + line
74 for s in syntaxes.values():
75 for s in syntaxes.values():
75 if line.startswith(s):
76 if line.startswith(s):
76 pat = line
77 pat = line
77 break
78 break
78 pats.append(pat)
79 pats.append(pat)
79 except IOError: pass
80 except IOError: pass
80 return pats
81 return pats
81
82
82 def ignore(self, fn):
83 def ignore(self, fn):
83 '''default match function used by dirstate and localrepository.
84 '''default match function used by dirstate and localrepository.
84 this honours the .hgignore file, and nothing more.'''
85 this honours the .hgignore file, and nothing more.'''
85 if self.blockignore:
86 if self.blockignore:
86 return False
87 return False
87 if not self.ignorefunc:
88 if not self.ignorefunc:
88 ignore = self.hgignore()
89 ignore = self.hgignore()
89 if ignore:
90 if ignore:
90 files, self.ignorefunc, anypats = util.matcher(self.root,
91 files, self.ignorefunc, anypats = util.matcher(self.root,
91 inc=ignore)
92 inc=ignore,
93 src='.hgignore')
92 else:
94 else:
93 self.ignorefunc = util.never
95 self.ignorefunc = util.never
94 return self.ignorefunc(fn)
96 return self.ignorefunc(fn)
95
97
96 def __del__(self):
98 def __del__(self):
97 if self.dirty:
99 if self.dirty:
98 self.write()
100 self.write()
99
101
100 def __getitem__(self, key):
102 def __getitem__(self, key):
101 try:
103 try:
102 return self.map[key]
104 return self.map[key]
103 except TypeError:
105 except TypeError:
104 self.lazyread()
106 self.lazyread()
105 return self[key]
107 return self[key]
106
108
107 def __contains__(self, key):
109 def __contains__(self, key):
108 self.lazyread()
110 self.lazyread()
109 return key in self.map
111 return key in self.map
110
112
111 def parents(self):
113 def parents(self):
112 self.lazyread()
114 self.lazyread()
113 return self.pl
115 return self.pl
114
116
115 def markdirty(self):
117 def markdirty(self):
116 if not self.dirty:
118 if not self.dirty:
117 self.dirty = 1
119 self.dirty = 1
118
120
119 def setparents(self, p1, p2=nullid):
121 def setparents(self, p1, p2=nullid):
120 self.lazyread()
122 self.lazyread()
121 self.markdirty()
123 self.markdirty()
122 self.pl = p1, p2
124 self.pl = p1, p2
123
125
124 def state(self, key):
126 def state(self, key):
125 try:
127 try:
126 return self[key][0]
128 return self[key][0]
127 except KeyError:
129 except KeyError:
128 return "?"
130 return "?"
129
131
130 def lazyread(self):
132 def lazyread(self):
131 if self.map is None:
133 if self.map is None:
132 self.read()
134 self.read()
133
135
134 def read(self):
136 def read(self):
135 self.map = {}
137 self.map = {}
136 self.pl = [nullid, nullid]
138 self.pl = [nullid, nullid]
137 try:
139 try:
138 st = self.opener("dirstate").read()
140 st = self.opener("dirstate").read()
139 if not st: return
141 if not st: return
140 except: return
142 except: return
141
143
142 self.pl = [st[:20], st[20: 40]]
144 self.pl = [st[:20], st[20: 40]]
143
145
144 pos = 40
146 pos = 40
145 while pos < len(st):
147 while pos < len(st):
146 e = struct.unpack(">cllll", st[pos:pos+17])
148 e = struct.unpack(">cllll", st[pos:pos+17])
147 l = e[4]
149 l = e[4]
148 pos += 17
150 pos += 17
149 f = st[pos:pos + l]
151 f = st[pos:pos + l]
150 if '\0' in f:
152 if '\0' in f:
151 f, c = f.split('\0')
153 f, c = f.split('\0')
152 self.copies[f] = c
154 self.copies[f] = c
153 self.map[f] = e[:4]
155 self.map[f] = e[:4]
154 pos += l
156 pos += l
155
157
156 def copy(self, source, dest):
158 def copy(self, source, dest):
157 self.lazyread()
159 self.lazyread()
158 self.markdirty()
160 self.markdirty()
159 self.copies[dest] = source
161 self.copies[dest] = source
160
162
161 def copied(self, file):
163 def copied(self, file):
162 return self.copies.get(file, None)
164 return self.copies.get(file, None)
163
165
164 def update(self, files, state, **kw):
166 def update(self, files, state, **kw):
165 ''' current states:
167 ''' current states:
166 n normal
168 n normal
167 m needs merging
169 m needs merging
168 r marked for removal
170 r marked for removal
169 a marked for addition'''
171 a marked for addition'''
170
172
171 if not files: return
173 if not files: return
172 self.lazyread()
174 self.lazyread()
173 self.markdirty()
175 self.markdirty()
174 for f in files:
176 for f in files:
175 if state == "r":
177 if state == "r":
176 self.map[f] = ('r', 0, 0, 0)
178 self.map[f] = ('r', 0, 0, 0)
177 else:
179 else:
178 s = os.lstat(self.wjoin(f))
180 s = os.lstat(self.wjoin(f))
179 st_size = kw.get('st_size', s.st_size)
181 st_size = kw.get('st_size', s.st_size)
180 st_mtime = kw.get('st_mtime', s.st_mtime)
182 st_mtime = kw.get('st_mtime', s.st_mtime)
181 self.map[f] = (state, s.st_mode, st_size, st_mtime)
183 self.map[f] = (state, s.st_mode, st_size, st_mtime)
182 if self.copies.has_key(f):
184 if self.copies.has_key(f):
183 del self.copies[f]
185 del self.copies[f]
184
186
185 def forget(self, files):
187 def forget(self, files):
186 if not files: return
188 if not files: return
187 self.lazyread()
189 self.lazyread()
188 self.markdirty()
190 self.markdirty()
189 for f in files:
191 for f in files:
190 try:
192 try:
191 del self.map[f]
193 del self.map[f]
192 except KeyError:
194 except KeyError:
193 self.ui.warn(_("not in dirstate: %s!\n") % f)
195 self.ui.warn(_("not in dirstate: %s!\n") % f)
194 pass
196 pass
195
197
196 def clear(self):
198 def clear(self):
197 self.map = {}
199 self.map = {}
198 self.markdirty()
200 self.markdirty()
199
201
200 def write(self):
202 def write(self):
201 st = self.opener("dirstate", "w", atomic=True)
203 st = self.opener("dirstate", "w", atomic=True)
202 st.write("".join(self.pl))
204 st.write("".join(self.pl))
203 for f, e in self.map.items():
205 for f, e in self.map.items():
204 c = self.copied(f)
206 c = self.copied(f)
205 if c:
207 if c:
206 f = f + "\0" + c
208 f = f + "\0" + c
207 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
209 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
208 st.write(e + f)
210 st.write(e + f)
209 self.dirty = 0
211 self.dirty = 0
210
212
211 def filterfiles(self, files):
213 def filterfiles(self, files):
212 ret = {}
214 ret = {}
213 unknown = []
215 unknown = []
214
216
215 for x in files:
217 for x in files:
216 if x == '.':
218 if x == '.':
217 return self.map.copy()
219 return self.map.copy()
218 if x not in self.map:
220 if x not in self.map:
219 unknown.append(x)
221 unknown.append(x)
220 else:
222 else:
221 ret[x] = self.map[x]
223 ret[x] = self.map[x]
222
224
223 if not unknown:
225 if not unknown:
224 return ret
226 return ret
225
227
226 b = self.map.keys()
228 b = self.map.keys()
227 b.sort()
229 b.sort()
228 blen = len(b)
230 blen = len(b)
229
231
230 for x in unknown:
232 for x in unknown:
231 bs = bisect.bisect(b, x)
233 bs = bisect.bisect(b, x)
232 if bs != 0 and b[bs-1] == x:
234 if bs != 0 and b[bs-1] == x:
233 ret[x] = self.map[x]
235 ret[x] = self.map[x]
234 continue
236 continue
235 while bs < blen:
237 while bs < blen:
236 s = b[bs]
238 s = b[bs]
237 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
239 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
238 ret[s] = self.map[s]
240 ret[s] = self.map[s]
239 else:
241 else:
240 break
242 break
241 bs += 1
243 bs += 1
242 return ret
244 return ret
243
245
244 def supported_type(self, f, st, verbose=False):
246 def supported_type(self, f, st, verbose=False):
245 if stat.S_ISREG(st.st_mode):
247 if stat.S_ISREG(st.st_mode):
246 return True
248 return True
247 if verbose:
249 if verbose:
248 kind = 'unknown'
250 kind = 'unknown'
249 if stat.S_ISCHR(st.st_mode): kind = _('character device')
251 if stat.S_ISCHR(st.st_mode): kind = _('character device')
250 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
252 elif stat.S_ISBLK(st.st_mode): kind = _('block device')
251 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
253 elif stat.S_ISFIFO(st.st_mode): kind = _('fifo')
252 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
254 elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link')
253 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
255 elif stat.S_ISSOCK(st.st_mode): kind = _('socket')
254 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
256 elif stat.S_ISDIR(st.st_mode): kind = _('directory')
255 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
257 self.ui.warn(_('%s: unsupported file type (type is %s)\n') % (
256 util.pathto(self.getcwd(), f),
258 util.pathto(self.getcwd(), f),
257 kind))
259 kind))
258 return False
260 return False
259
261
260 def statwalk(self, files=None, match=util.always, dc=None):
262 def statwalk(self, files=None, match=util.always, dc=None):
261 self.lazyread()
263 self.lazyread()
262
264
263 # walk all files by default
265 # walk all files by default
264 if not files:
266 if not files:
265 files = [self.root]
267 files = [self.root]
266 if not dc:
268 if not dc:
267 dc = self.map.copy()
269 dc = self.map.copy()
268 elif not dc:
270 elif not dc:
269 dc = self.filterfiles(files)
271 dc = self.filterfiles(files)
270
272
271 def statmatch(file, stat):
273 def statmatch(file, stat):
272 file = util.pconvert(file)
274 file = util.pconvert(file)
273 if file not in dc and self.ignore(file):
275 if file not in dc and self.ignore(file):
274 return False
276 return False
275 return match(file)
277 return match(file)
276
278
277 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
279 return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
278
280
279 def walk(self, files=None, match=util.always, dc=None):
281 def walk(self, files=None, match=util.always, dc=None):
280 # filter out the stat
282 # filter out the stat
281 for src, f, st in self.statwalk(files, match, dc):
283 for src, f, st in self.statwalk(files, match, dc):
282 yield src, f
284 yield src, f
283
285
284 # walk recursively through the directory tree, finding all files
286 # walk recursively through the directory tree, finding all files
285 # matched by the statmatch function
287 # matched by the statmatch function
286 #
288 #
287 # results are yielded in a tuple (src, filename, st), where src
289 # results are yielded in a tuple (src, filename, st), where src
288 # is one of:
290 # is one of:
289 # 'f' the file was found in the directory tree
291 # 'f' the file was found in the directory tree
290 # 'm' the file was only in the dirstate and not in the tree
292 # 'm' the file was only in the dirstate and not in the tree
291 # and st is the stat result if the file was found in the directory.
293 # and st is the stat result if the file was found in the directory.
292 #
294 #
293 # dc is an optional arg for the current dirstate. dc is not modified
295 # dc is an optional arg for the current dirstate. dc is not modified
294 # directly by this function, but might be modified by your statmatch call.
296 # directly by this function, but might be modified by your statmatch call.
295 #
297 #
296 def walkhelper(self, files, statmatch, dc):
298 def walkhelper(self, files, statmatch, dc):
297 # recursion free walker, faster than os.walk.
299 # recursion free walker, faster than os.walk.
298 def findfiles(s):
300 def findfiles(s):
299 work = [s]
301 work = [s]
300 while work:
302 while work:
301 top = work.pop()
303 top = work.pop()
302 names = os.listdir(top)
304 names = os.listdir(top)
303 names.sort()
305 names.sort()
304 # nd is the top of the repository dir tree
306 # nd is the top of the repository dir tree
305 nd = util.normpath(top[len(self.root) + 1:])
307 nd = util.normpath(top[len(self.root) + 1:])
306 if nd == '.': nd = ''
308 if nd == '.': nd = ''
307 for f in names:
309 for f in names:
308 np = util.pconvert(os.path.join(nd, f))
310 np = util.pconvert(os.path.join(nd, f))
309 if seen(np):
311 if seen(np):
310 continue
312 continue
311 p = os.path.join(top, f)
313 p = os.path.join(top, f)
312 # don't trip over symlinks
314 # don't trip over symlinks
313 st = os.lstat(p)
315 st = os.lstat(p)
314 if stat.S_ISDIR(st.st_mode):
316 if stat.S_ISDIR(st.st_mode):
315 ds = os.path.join(nd, f +'/')
317 ds = os.path.join(nd, f +'/')
316 if statmatch(ds, st):
318 if statmatch(ds, st):
317 work.append(p)
319 work.append(p)
318 if statmatch(np, st) and np in dc:
320 if statmatch(np, st) and np in dc:
319 yield 'm', np, st
321 yield 'm', np, st
320 elif statmatch(np, st):
322 elif statmatch(np, st):
321 if self.supported_type(np, st):
323 if self.supported_type(np, st):
322 yield 'f', np, st
324 yield 'f', np, st
323 elif np in dc:
325 elif np in dc:
324 yield 'm', np, st
326 yield 'm', np, st
325
327
326 known = {'.hg': 1}
328 known = {'.hg': 1}
327 def seen(fn):
329 def seen(fn):
328 if fn in known: return True
330 if fn in known: return True
329 known[fn] = 1
331 known[fn] = 1
330
332
331 # step one, find all files that match our criteria
333 # step one, find all files that match our criteria
332 files.sort()
334 files.sort()
333 for ff in util.unique(files):
335 for ff in util.unique(files):
334 f = self.wjoin(ff)
336 f = self.wjoin(ff)
335 try:
337 try:
336 st = os.lstat(f)
338 st = os.lstat(f)
337 except OSError, inst:
339 except OSError, inst:
338 nf = util.normpath(ff)
340 nf = util.normpath(ff)
339 found = False
341 found = False
340 for fn in dc:
342 for fn in dc:
341 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
343 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
342 found = True
344 found = True
343 break
345 break
344 if not found:
346 if not found:
345 self.ui.warn('%s: %s\n' % (
347 self.ui.warn('%s: %s\n' % (
346 util.pathto(self.getcwd(), ff),
348 util.pathto(self.getcwd(), ff),
347 inst.strerror))
349 inst.strerror))
348 continue
350 continue
349 if stat.S_ISDIR(st.st_mode):
351 if stat.S_ISDIR(st.st_mode):
350 cmp1 = (lambda x, y: cmp(x[1], y[1]))
352 cmp1 = (lambda x, y: cmp(x[1], y[1]))
351 sorted = [ x for x in findfiles(f) ]
353 sorted = [ x for x in findfiles(f) ]
352 sorted.sort(cmp1)
354 sorted.sort(cmp1)
353 for e in sorted:
355 for e in sorted:
354 yield e
356 yield e
355 else:
357 else:
356 ff = util.normpath(ff)
358 ff = util.normpath(ff)
357 if seen(ff):
359 if seen(ff):
358 continue
360 continue
359 self.blockignore = True
361 self.blockignore = True
360 if statmatch(ff, st):
362 if statmatch(ff, st):
361 if self.supported_type(ff, st, verbose=True):
363 if self.supported_type(ff, st, verbose=True):
362 yield 'f', ff, st
364 yield 'f', ff, st
363 elif ff in dc:
365 elif ff in dc:
364 yield 'm', ff, st
366 yield 'm', ff, st
365 self.blockignore = False
367 self.blockignore = False
366
368
367 # step two run through anything left in the dc hash and yield
369 # step two run through anything left in the dc hash and yield
368 # if we haven't already seen it
370 # if we haven't already seen it
369 ks = dc.keys()
371 ks = dc.keys()
370 ks.sort()
372 ks.sort()
371 for k in ks:
373 for k in ks:
372 if not seen(k) and (statmatch(k, None)):
374 if not seen(k) and (statmatch(k, None)):
373 yield 'm', k, None
375 yield 'm', k, None
374
376
375 def changes(self, files=None, match=util.always):
377 def changes(self, files=None, match=util.always):
376 lookup, modified, added, unknown = [], [], [], []
378 lookup, modified, added, unknown = [], [], [], []
377 removed, deleted = [], []
379 removed, deleted = [], []
378
380
379 for src, fn, st in self.statwalk(files, match):
381 for src, fn, st in self.statwalk(files, match):
380 try:
382 try:
381 type, mode, size, time = self[fn]
383 type, mode, size, time = self[fn]
382 except KeyError:
384 except KeyError:
383 unknown.append(fn)
385 unknown.append(fn)
384 continue
386 continue
385 if src == 'm':
387 if src == 'm':
386 nonexistent = True
388 nonexistent = True
387 if not st:
389 if not st:
388 try:
390 try:
389 f = self.wjoin(fn)
391 f = self.wjoin(fn)
390 st = os.lstat(f)
392 st = os.lstat(f)
391 except OSError, inst:
393 except OSError, inst:
392 if inst.errno != errno.ENOENT:
394 if inst.errno != errno.ENOENT:
393 raise
395 raise
394 st = None
396 st = None
395 # We need to re-check that it is a valid file
397 # We need to re-check that it is a valid file
396 if st and self.supported_type(fn, st):
398 if st and self.supported_type(fn, st):
397 nonexistent = False
399 nonexistent = False
398 # XXX: what to do with file no longer present in the fs
400 # XXX: what to do with file no longer present in the fs
399 # who are not removed in the dirstate ?
401 # who are not removed in the dirstate ?
400 if nonexistent and type in "nm":
402 if nonexistent and type in "nm":
401 deleted.append(fn)
403 deleted.append(fn)
402 continue
404 continue
403 # check the common case first
405 # check the common case first
404 if type == 'n':
406 if type == 'n':
405 if not st:
407 if not st:
406 st = os.stat(fn)
408 st = os.stat(fn)
407 if size != st.st_size or (mode ^ st.st_mode) & 0100:
409 if size != st.st_size or (mode ^ st.st_mode) & 0100:
408 modified.append(fn)
410 modified.append(fn)
409 elif time != st.st_mtime:
411 elif time != st.st_mtime:
410 lookup.append(fn)
412 lookup.append(fn)
411 elif type == 'm':
413 elif type == 'm':
412 modified.append(fn)
414 modified.append(fn)
413 elif type == 'a':
415 elif type == 'a':
414 added.append(fn)
416 added.append(fn)
415 elif type == 'r':
417 elif type == 'r':
416 removed.append(fn)
418 removed.append(fn)
417
419
418 return (lookup, modified, added, removed + deleted, unknown)
420 return (lookup, modified, added, removed + deleted, unknown)
@@ -1,689 +1,690 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5
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 This contains helper routines that are independent of the SCM core and hide
9 This contains helper routines that are independent of the SCM core and hide
10 platform-specific details from the core.
10 platform-specific details from the core.
11 """
11 """
12
12
13 import os, errno
13 import os, errno
14 from i18n import gettext as _
14 from i18n import gettext as _
15 from demandload import *
15 from demandload import *
16 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
16 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
17 demandload(globals(), "threading time")
17 demandload(globals(), "threading time")
18
18
19 def pipefilter(s, cmd):
19 def pipefilter(s, cmd):
20 '''filter string S through command CMD, returning its output'''
20 '''filter string S through command CMD, returning its output'''
21 (pout, pin) = popen2.popen2(cmd, -1, 'b')
21 (pout, pin) = popen2.popen2(cmd, -1, 'b')
22 def writer():
22 def writer():
23 pin.write(s)
23 pin.write(s)
24 pin.close()
24 pin.close()
25
25
26 # we should use select instead on UNIX, but this will work on most
26 # we should use select instead on UNIX, but this will work on most
27 # systems, including Windows
27 # systems, including Windows
28 w = threading.Thread(target=writer)
28 w = threading.Thread(target=writer)
29 w.start()
29 w.start()
30 f = pout.read()
30 f = pout.read()
31 pout.close()
31 pout.close()
32 w.join()
32 w.join()
33 return f
33 return f
34
34
35 def tempfilter(s, cmd):
35 def tempfilter(s, cmd):
36 '''filter string S through a pair of temporary files with CMD.
36 '''filter string S through a pair of temporary files with CMD.
37 CMD is used as a template to create the real command to be run,
37 CMD is used as a template to create the real command to be run,
38 with the strings INFILE and OUTFILE replaced by the real names of
38 with the strings INFILE and OUTFILE replaced by the real names of
39 the temporary files generated.'''
39 the temporary files generated.'''
40 inname, outname = None, None
40 inname, outname = None, None
41 try:
41 try:
42 infd, inname = tempfile.mkstemp(prefix='hgfin')
42 infd, inname = tempfile.mkstemp(prefix='hgfin')
43 fp = os.fdopen(infd, 'wb')
43 fp = os.fdopen(infd, 'wb')
44 fp.write(s)
44 fp.write(s)
45 fp.close()
45 fp.close()
46 outfd, outname = tempfile.mkstemp(prefix='hgfout')
46 outfd, outname = tempfile.mkstemp(prefix='hgfout')
47 os.close(outfd)
47 os.close(outfd)
48 cmd = cmd.replace('INFILE', inname)
48 cmd = cmd.replace('INFILE', inname)
49 cmd = cmd.replace('OUTFILE', outname)
49 cmd = cmd.replace('OUTFILE', outname)
50 code = os.system(cmd)
50 code = os.system(cmd)
51 if code: raise Abort(_("command '%s' failed: %s") %
51 if code: raise Abort(_("command '%s' failed: %s") %
52 (cmd, explain_exit(code)))
52 (cmd, explain_exit(code)))
53 return open(outname, 'rb').read()
53 return open(outname, 'rb').read()
54 finally:
54 finally:
55 try:
55 try:
56 if inname: os.unlink(inname)
56 if inname: os.unlink(inname)
57 except: pass
57 except: pass
58 try:
58 try:
59 if outname: os.unlink(outname)
59 if outname: os.unlink(outname)
60 except: pass
60 except: pass
61
61
62 filtertable = {
62 filtertable = {
63 'tempfile:': tempfilter,
63 'tempfile:': tempfilter,
64 'pipe:': pipefilter,
64 'pipe:': pipefilter,
65 }
65 }
66
66
67 def filter(s, cmd):
67 def filter(s, cmd):
68 "filter a string through a command that transforms its input to its output"
68 "filter a string through a command that transforms its input to its output"
69 for name, fn in filtertable.iteritems():
69 for name, fn in filtertable.iteritems():
70 if cmd.startswith(name):
70 if cmd.startswith(name):
71 return fn(s, cmd[len(name):].lstrip())
71 return fn(s, cmd[len(name):].lstrip())
72 return pipefilter(s, cmd)
72 return pipefilter(s, cmd)
73
73
74 def patch(strip, patchname, ui):
74 def patch(strip, patchname, ui):
75 """apply the patch <patchname> to the working directory.
75 """apply the patch <patchname> to the working directory.
76 a list of patched files is returned"""
76 a list of patched files is returned"""
77 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
77 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
78 files = {}
78 files = {}
79 for line in fp:
79 for line in fp:
80 line = line.rstrip()
80 line = line.rstrip()
81 ui.status("%s\n" % line)
81 ui.status("%s\n" % line)
82 if line.startswith('patching file '):
82 if line.startswith('patching file '):
83 pf = parse_patch_output(line)
83 pf = parse_patch_output(line)
84 files.setdefault(pf, 1)
84 files.setdefault(pf, 1)
85 code = fp.close()
85 code = fp.close()
86 if code:
86 if code:
87 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
87 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
88 return files.keys()
88 return files.keys()
89
89
90 def binary(s):
90 def binary(s):
91 """return true if a string is binary data using diff's heuristic"""
91 """return true if a string is binary data using diff's heuristic"""
92 if s and '\0' in s[:4096]:
92 if s and '\0' in s[:4096]:
93 return True
93 return True
94 return False
94 return False
95
95
96 def unique(g):
96 def unique(g):
97 """return the uniq elements of iterable g"""
97 """return the uniq elements of iterable g"""
98 seen = {}
98 seen = {}
99 for f in g:
99 for f in g:
100 if f not in seen:
100 if f not in seen:
101 seen[f] = 1
101 seen[f] = 1
102 yield f
102 yield f
103
103
104 class Abort(Exception):
104 class Abort(Exception):
105 """Raised if a command needs to print an error and exit."""
105 """Raised if a command needs to print an error and exit."""
106
106
107 def always(fn): return True
107 def always(fn): return True
108 def never(fn): return False
108 def never(fn): return False
109
109
110 def patkind(name, dflt_pat='glob'):
110 def patkind(name, dflt_pat='glob'):
111 """Split a string into an optional pattern kind prefix and the
111 """Split a string into an optional pattern kind prefix and the
112 actual pattern."""
112 actual pattern."""
113 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
113 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
114 if name.startswith(prefix + ':'): return name.split(':', 1)
114 if name.startswith(prefix + ':'): return name.split(':', 1)
115 return dflt_pat, name
115 return dflt_pat, name
116
116
117 def globre(pat, head='^', tail='$'):
117 def globre(pat, head='^', tail='$'):
118 "convert a glob pattern into a regexp"
118 "convert a glob pattern into a regexp"
119 i, n = 0, len(pat)
119 i, n = 0, len(pat)
120 res = ''
120 res = ''
121 group = False
121 group = False
122 def peek(): return i < n and pat[i]
122 def peek(): return i < n and pat[i]
123 while i < n:
123 while i < n:
124 c = pat[i]
124 c = pat[i]
125 i = i+1
125 i = i+1
126 if c == '*':
126 if c == '*':
127 if peek() == '*':
127 if peek() == '*':
128 i += 1
128 i += 1
129 res += '.*'
129 res += '.*'
130 else:
130 else:
131 res += '[^/]*'
131 res += '[^/]*'
132 elif c == '?':
132 elif c == '?':
133 res += '.'
133 res += '.'
134 elif c == '[':
134 elif c == '[':
135 j = i
135 j = i
136 if j < n and pat[j] in '!]':
136 if j < n and pat[j] in '!]':
137 j += 1
137 j += 1
138 while j < n and pat[j] != ']':
138 while j < n and pat[j] != ']':
139 j += 1
139 j += 1
140 if j >= n:
140 if j >= n:
141 res += '\\['
141 res += '\\['
142 else:
142 else:
143 stuff = pat[i:j].replace('\\','\\\\')
143 stuff = pat[i:j].replace('\\','\\\\')
144 i = j + 1
144 i = j + 1
145 if stuff[0] == '!':
145 if stuff[0] == '!':
146 stuff = '^' + stuff[1:]
146 stuff = '^' + stuff[1:]
147 elif stuff[0] == '^':
147 elif stuff[0] == '^':
148 stuff = '\\' + stuff
148 stuff = '\\' + stuff
149 res = '%s[%s]' % (res, stuff)
149 res = '%s[%s]' % (res, stuff)
150 elif c == '{':
150 elif c == '{':
151 group = True
151 group = True
152 res += '(?:'
152 res += '(?:'
153 elif c == '}' and group:
153 elif c == '}' and group:
154 res += ')'
154 res += ')'
155 group = False
155 group = False
156 elif c == ',' and group:
156 elif c == ',' and group:
157 res += '|'
157 res += '|'
158 else:
158 else:
159 res += re.escape(c)
159 res += re.escape(c)
160 return head + res + tail
160 return head + res + tail
161
161
162 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
162 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
163
163
164 def pathto(n1, n2):
164 def pathto(n1, n2):
165 '''return the relative path from one place to another.
165 '''return the relative path from one place to another.
166 this returns a path in the form used by the local filesystem, not hg.'''
166 this returns a path in the form used by the local filesystem, not hg.'''
167 if not n1: return localpath(n2)
167 if not n1: return localpath(n2)
168 a, b = n1.split('/'), n2.split('/')
168 a, b = n1.split('/'), n2.split('/')
169 a.reverse()
169 a.reverse()
170 b.reverse()
170 b.reverse()
171 while a and b and a[-1] == b[-1]:
171 while a and b and a[-1] == b[-1]:
172 a.pop()
172 a.pop()
173 b.pop()
173 b.pop()
174 b.reverse()
174 b.reverse()
175 return os.sep.join((['..'] * len(a)) + b)
175 return os.sep.join((['..'] * len(a)) + b)
176
176
177 def canonpath(root, cwd, myname):
177 def canonpath(root, cwd, myname):
178 """return the canonical path of myname, given cwd and root"""
178 """return the canonical path of myname, given cwd and root"""
179 if root == os.sep:
179 if root == os.sep:
180 rootsep = os.sep
180 rootsep = os.sep
181 else:
181 else:
182 rootsep = root + os.sep
182 rootsep = root + os.sep
183 name = myname
183 name = myname
184 if not name.startswith(os.sep):
184 if not name.startswith(os.sep):
185 name = os.path.join(root, cwd, name)
185 name = os.path.join(root, cwd, name)
186 name = os.path.normpath(name)
186 name = os.path.normpath(name)
187 if name.startswith(rootsep):
187 if name.startswith(rootsep):
188 return pconvert(name[len(rootsep):])
188 return pconvert(name[len(rootsep):])
189 elif name == root:
189 elif name == root:
190 return ''
190 return ''
191 else:
191 else:
192 raise Abort('%s not under root' % myname)
192 raise Abort('%s not under root' % myname)
193
193
194 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
194 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
195 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob')
195 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
196
196
197 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''):
197 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
198 if os.name == 'nt':
198 if os.name == 'nt':
199 dflt_pat = 'glob'
199 dflt_pat = 'glob'
200 else:
200 else:
201 dflt_pat = 'relpath'
201 dflt_pat = 'relpath'
202 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat)
202 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
203
203
204 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat):
204 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
205 """build a function to match a set of file patterns
205 """build a function to match a set of file patterns
206
206
207 arguments:
207 arguments:
208 canonroot - the canonical root of the tree you're matching against
208 canonroot - the canonical root of the tree you're matching against
209 cwd - the current working directory, if relevant
209 cwd - the current working directory, if relevant
210 names - patterns to find
210 names - patterns to find
211 inc - patterns to include
211 inc - patterns to include
212 exc - patterns to exclude
212 exc - patterns to exclude
213 head - a regex to prepend to patterns to control whether a match is rooted
213 head - a regex to prepend to patterns to control whether a match is rooted
214
214
215 a pattern is one of:
215 a pattern is one of:
216 'glob:<rooted glob>'
216 'glob:<rooted glob>'
217 're:<rooted regexp>'
217 're:<rooted regexp>'
218 'path:<rooted path>'
218 'path:<rooted path>'
219 'relglob:<relative glob>'
219 'relglob:<relative glob>'
220 'relpath:<relative path>'
220 'relpath:<relative path>'
221 'relre:<relative regexp>'
221 'relre:<relative regexp>'
222 '<rooted path or regexp>'
222 '<rooted path or regexp>'
223
223
224 returns:
224 returns:
225 a 3-tuple containing
225 a 3-tuple containing
226 - list of explicit non-pattern names passed in
226 - list of explicit non-pattern names passed in
227 - a bool match(filename) function
227 - a bool match(filename) function
228 - a bool indicating if any patterns were passed in
228 - a bool indicating if any patterns were passed in
229
229
230 todo:
230 todo:
231 make head regex a rooted bool
231 make head regex a rooted bool
232 """
232 """
233
233
234 def contains_glob(name):
234 def contains_glob(name):
235 for c in name:
235 for c in name:
236 if c in _globchars: return True
236 if c in _globchars: return True
237 return False
237 return False
238
238
239 def regex(kind, name, tail):
239 def regex(kind, name, tail):
240 '''convert a pattern into a regular expression'''
240 '''convert a pattern into a regular expression'''
241 if kind == 're':
241 if kind == 're':
242 return name
242 return name
243 elif kind == 'path':
243 elif kind == 'path':
244 return '^' + re.escape(name) + '(?:/|$)'
244 return '^' + re.escape(name) + '(?:/|$)'
245 elif kind == 'relglob':
245 elif kind == 'relglob':
246 return head + globre(name, '(?:|.*/)', tail)
246 return head + globre(name, '(?:|.*/)', tail)
247 elif kind == 'relpath':
247 elif kind == 'relpath':
248 return head + re.escape(name) + tail
248 return head + re.escape(name) + tail
249 elif kind == 'relre':
249 elif kind == 'relre':
250 if name.startswith('^'):
250 if name.startswith('^'):
251 return name
251 return name
252 return '.*' + name
252 return '.*' + name
253 return head + globre(name, '', tail)
253 return head + globre(name, '', tail)
254
254
255 def matchfn(pats, tail):
255 def matchfn(pats, tail):
256 """build a matching function from a set of patterns"""
256 """build a matching function from a set of patterns"""
257 if not pats:
257 if not pats:
258 return
258 return
259 matches = []
259 matches = []
260 for k, p in pats:
260 for k, p in pats:
261 try:
261 try:
262 pat = '(?:%s)' % regex(k, p, tail)
262 pat = '(?:%s)' % regex(k, p, tail)
263 matches.append(re.compile(pat).match)
263 matches.append(re.compile(pat).match)
264 except re.error:
264 except re.error:
265 raise Abort("invalid pattern: %s:%s" % (k, p))
265 if src: raise Abort("%s: invalid pattern: %s:%s" % (src, k, p))
266 else: raise Abort("invalid pattern: %s:%s" % (k, p))
266
267
267 def buildfn(text):
268 def buildfn(text):
268 for m in matches:
269 for m in matches:
269 r = m(text)
270 r = m(text)
270 if r:
271 if r:
271 return r
272 return r
272
273
273 return buildfn
274 return buildfn
274
275
275 def globprefix(pat):
276 def globprefix(pat):
276 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
277 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
277 root = []
278 root = []
278 for p in pat.split(os.sep):
279 for p in pat.split(os.sep):
279 if contains_glob(p): break
280 if contains_glob(p): break
280 root.append(p)
281 root.append(p)
281 return '/'.join(root)
282 return '/'.join(root)
282
283
283 pats = []
284 pats = []
284 files = []
285 files = []
285 roots = []
286 roots = []
286 for kind, name in [patkind(p, dflt_pat) for p in names]:
287 for kind, name in [patkind(p, dflt_pat) for p in names]:
287 if kind in ('glob', 'relpath'):
288 if kind in ('glob', 'relpath'):
288 name = canonpath(canonroot, cwd, name)
289 name = canonpath(canonroot, cwd, name)
289 if name == '':
290 if name == '':
290 kind, name = 'glob', '**'
291 kind, name = 'glob', '**'
291 if kind in ('glob', 'path', 're'):
292 if kind in ('glob', 'path', 're'):
292 pats.append((kind, name))
293 pats.append((kind, name))
293 if kind == 'glob':
294 if kind == 'glob':
294 root = globprefix(name)
295 root = globprefix(name)
295 if root: roots.append(root)
296 if root: roots.append(root)
296 elif kind == 'relpath':
297 elif kind == 'relpath':
297 files.append((kind, name))
298 files.append((kind, name))
298 roots.append(name)
299 roots.append(name)
299
300
300 patmatch = matchfn(pats, '$') or always
301 patmatch = matchfn(pats, '$') or always
301 filematch = matchfn(files, '(?:/|$)') or always
302 filematch = matchfn(files, '(?:/|$)') or always
302 incmatch = always
303 incmatch = always
303 if inc:
304 if inc:
304 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
305 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
305 excmatch = lambda fn: False
306 excmatch = lambda fn: False
306 if exc:
307 if exc:
307 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
308 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
308
309
309 return (roots,
310 return (roots,
310 lambda fn: (incmatch(fn) and not excmatch(fn) and
311 lambda fn: (incmatch(fn) and not excmatch(fn) and
311 (fn.endswith('/') or
312 (fn.endswith('/') or
312 (not pats and not files) or
313 (not pats and not files) or
313 (pats and patmatch(fn)) or
314 (pats and patmatch(fn)) or
314 (files and filematch(fn)))),
315 (files and filematch(fn)))),
315 (inc or exc or (pats and pats != [('glob', '**')])) and True)
316 (inc or exc or (pats and pats != [('glob', '**')])) and True)
316
317
317 def system(cmd, errprefix=None):
318 def system(cmd, errprefix=None):
318 """execute a shell command that must succeed"""
319 """execute a shell command that must succeed"""
319 rc = os.system(cmd)
320 rc = os.system(cmd)
320 if rc:
321 if rc:
321 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
322 errmsg = "%s %s" % (os.path.basename(cmd.split(None, 1)[0]),
322 explain_exit(rc)[0])
323 explain_exit(rc)[0])
323 if errprefix:
324 if errprefix:
324 errmsg = "%s: %s" % (errprefix, errmsg)
325 errmsg = "%s: %s" % (errprefix, errmsg)
325 raise Abort(errmsg)
326 raise Abort(errmsg)
326
327
327 def rename(src, dst):
328 def rename(src, dst):
328 """forcibly rename a file"""
329 """forcibly rename a file"""
329 try:
330 try:
330 os.rename(src, dst)
331 os.rename(src, dst)
331 except:
332 except:
332 os.unlink(dst)
333 os.unlink(dst)
333 os.rename(src, dst)
334 os.rename(src, dst)
334
335
335 def unlink(f):
336 def unlink(f):
336 """unlink and remove the directory if it is empty"""
337 """unlink and remove the directory if it is empty"""
337 os.unlink(f)
338 os.unlink(f)
338 # try removing directories that might now be empty
339 # try removing directories that might now be empty
339 try: os.removedirs(os.path.dirname(f))
340 try: os.removedirs(os.path.dirname(f))
340 except: pass
341 except: pass
341
342
342 def copyfiles(src, dst, hardlink=None):
343 def copyfiles(src, dst, hardlink=None):
343 """Copy a directory tree using hardlinks if possible"""
344 """Copy a directory tree using hardlinks if possible"""
344
345
345 if hardlink is None:
346 if hardlink is None:
346 hardlink = (os.stat(src).st_dev ==
347 hardlink = (os.stat(src).st_dev ==
347 os.stat(os.path.dirname(dst)).st_dev)
348 os.stat(os.path.dirname(dst)).st_dev)
348
349
349 if os.path.isdir(src):
350 if os.path.isdir(src):
350 os.mkdir(dst)
351 os.mkdir(dst)
351 for name in os.listdir(src):
352 for name in os.listdir(src):
352 srcname = os.path.join(src, name)
353 srcname = os.path.join(src, name)
353 dstname = os.path.join(dst, name)
354 dstname = os.path.join(dst, name)
354 copyfiles(srcname, dstname, hardlink)
355 copyfiles(srcname, dstname, hardlink)
355 else:
356 else:
356 if hardlink:
357 if hardlink:
357 try:
358 try:
358 os_link(src, dst)
359 os_link(src, dst)
359 except:
360 except:
360 hardlink = False
361 hardlink = False
361 shutil.copy(src, dst)
362 shutil.copy(src, dst)
362 else:
363 else:
363 shutil.copy(src, dst)
364 shutil.copy(src, dst)
364
365
365 def opener(base):
366 def opener(base):
366 """
367 """
367 return a function that opens files relative to base
368 return a function that opens files relative to base
368
369
369 this function is used to hide the details of COW semantics and
370 this function is used to hide the details of COW semantics and
370 remote file access from higher level code.
371 remote file access from higher level code.
371 """
372 """
372 p = base
373 p = base
373
374
374 def mktempcopy(name):
375 def mktempcopy(name):
375 d, fn = os.path.split(name)
376 d, fn = os.path.split(name)
376 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
377 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
377 fp = os.fdopen(fd, "wb")
378 fp = os.fdopen(fd, "wb")
378 try:
379 try:
379 fp.write(file(name, "rb").read())
380 fp.write(file(name, "rb").read())
380 except:
381 except:
381 try: os.unlink(temp)
382 try: os.unlink(temp)
382 except: pass
383 except: pass
383 raise
384 raise
384 fp.close()
385 fp.close()
385 st = os.lstat(name)
386 st = os.lstat(name)
386 os.chmod(temp, st.st_mode)
387 os.chmod(temp, st.st_mode)
387 return temp
388 return temp
388
389
389 class atomicfile(file):
390 class atomicfile(file):
390 """the file will only be copied on close"""
391 """the file will only be copied on close"""
391 def __init__(self, name, mode, atomic=False):
392 def __init__(self, name, mode, atomic=False):
392 self.__name = name
393 self.__name = name
393 self.temp = mktempcopy(name)
394 self.temp = mktempcopy(name)
394 file.__init__(self, self.temp, mode)
395 file.__init__(self, self.temp, mode)
395 def close(self):
396 def close(self):
396 if not self.closed:
397 if not self.closed:
397 file.close(self)
398 file.close(self)
398 rename(self.temp, self.__name)
399 rename(self.temp, self.__name)
399 def __del__(self):
400 def __del__(self):
400 self.close()
401 self.close()
401
402
402 def o(path, mode="r", text=False, atomic=False):
403 def o(path, mode="r", text=False, atomic=False):
403 f = os.path.join(p, path)
404 f = os.path.join(p, path)
404
405
405 if not text:
406 if not text:
406 mode += "b" # for that other OS
407 mode += "b" # for that other OS
407
408
408 if mode[0] != "r":
409 if mode[0] != "r":
409 try:
410 try:
410 nlink = nlinks(f)
411 nlink = nlinks(f)
411 except OSError:
412 except OSError:
412 d = os.path.dirname(f)
413 d = os.path.dirname(f)
413 if not os.path.isdir(d):
414 if not os.path.isdir(d):
414 os.makedirs(d)
415 os.makedirs(d)
415 else:
416 else:
416 if atomic:
417 if atomic:
417 return atomicfile(f, mode)
418 return atomicfile(f, mode)
418 if nlink > 1:
419 if nlink > 1:
419 rename(mktempcopy(f), f)
420 rename(mktempcopy(f), f)
420 return file(f, mode)
421 return file(f, mode)
421
422
422 return o
423 return o
423
424
424 def _makelock_file(info, pathname):
425 def _makelock_file(info, pathname):
425 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
426 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
426 os.write(ld, info)
427 os.write(ld, info)
427 os.close(ld)
428 os.close(ld)
428
429
429 def _readlock_file(pathname):
430 def _readlock_file(pathname):
430 return file(pathname).read()
431 return file(pathname).read()
431
432
432 def nlinks(pathname):
433 def nlinks(pathname):
433 """Return number of hardlinks for the given file."""
434 """Return number of hardlinks for the given file."""
434 return os.stat(pathname).st_nlink
435 return os.stat(pathname).st_nlink
435
436
436 if hasattr(os, 'link'):
437 if hasattr(os, 'link'):
437 os_link = os.link
438 os_link = os.link
438 else:
439 else:
439 def os_link(src, dst):
440 def os_link(src, dst):
440 raise OSError(0, _("Hardlinks not supported"))
441 raise OSError(0, _("Hardlinks not supported"))
441
442
442 # Platform specific variants
443 # Platform specific variants
443 if os.name == 'nt':
444 if os.name == 'nt':
444 demandload(globals(), "msvcrt")
445 demandload(globals(), "msvcrt")
445 nulldev = 'NUL:'
446 nulldev = 'NUL:'
446
447
447 class winstdout:
448 class winstdout:
448 '''stdout on windows misbehaves if sent through a pipe'''
449 '''stdout on windows misbehaves if sent through a pipe'''
449
450
450 def __init__(self, fp):
451 def __init__(self, fp):
451 self.fp = fp
452 self.fp = fp
452
453
453 def __getattr__(self, key):
454 def __getattr__(self, key):
454 return getattr(self.fp, key)
455 return getattr(self.fp, key)
455
456
456 def close(self):
457 def close(self):
457 try:
458 try:
458 self.fp.close()
459 self.fp.close()
459 except: pass
460 except: pass
460
461
461 def write(self, s):
462 def write(self, s):
462 try:
463 try:
463 return self.fp.write(s)
464 return self.fp.write(s)
464 except IOError, inst:
465 except IOError, inst:
465 if inst.errno != 0: raise
466 if inst.errno != 0: raise
466 self.close()
467 self.close()
467 raise IOError(errno.EPIPE, 'Broken pipe')
468 raise IOError(errno.EPIPE, 'Broken pipe')
468
469
469 sys.stdout = winstdout(sys.stdout)
470 sys.stdout = winstdout(sys.stdout)
470
471
471 try:
472 try:
472 import win32api, win32process
473 import win32api, win32process
473 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
474 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
474 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
475 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
475
476
476 except ImportError:
477 except ImportError:
477 systemrc = r'c:\mercurial\mercurial.ini'
478 systemrc = r'c:\mercurial\mercurial.ini'
478 pass
479 pass
479
480
480 rcpath = (systemrc,
481 rcpath = (systemrc,
481 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
482 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
482
483
483 def parse_patch_output(output_line):
484 def parse_patch_output(output_line):
484 """parses the output produced by patch and returns the file name"""
485 """parses the output produced by patch and returns the file name"""
485 pf = output_line[14:]
486 pf = output_line[14:]
486 if pf[0] == '`':
487 if pf[0] == '`':
487 pf = pf[1:-1] # Remove the quotes
488 pf = pf[1:-1] # Remove the quotes
488 return pf
489 return pf
489
490
490 try: # ActivePython can create hard links using win32file module
491 try: # ActivePython can create hard links using win32file module
491 import win32file
492 import win32file
492
493
493 def os_link(src, dst): # NB will only succeed on NTFS
494 def os_link(src, dst): # NB will only succeed on NTFS
494 win32file.CreateHardLink(dst, src)
495 win32file.CreateHardLink(dst, src)
495
496
496 def nlinks(pathname):
497 def nlinks(pathname):
497 """Return number of hardlinks for the given file."""
498 """Return number of hardlinks for the given file."""
498 try:
499 try:
499 fh = win32file.CreateFile(pathname,
500 fh = win32file.CreateFile(pathname,
500 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
501 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
501 None, win32file.OPEN_EXISTING, 0, None)
502 None, win32file.OPEN_EXISTING, 0, None)
502 res = win32file.GetFileInformationByHandle(fh)
503 res = win32file.GetFileInformationByHandle(fh)
503 fh.Close()
504 fh.Close()
504 return res[7]
505 return res[7]
505 except:
506 except:
506 return os.stat(pathname).st_nlink
507 return os.stat(pathname).st_nlink
507
508
508 except ImportError:
509 except ImportError:
509 pass
510 pass
510
511
511 def is_exec(f, last):
512 def is_exec(f, last):
512 return last
513 return last
513
514
514 def set_exec(f, mode):
515 def set_exec(f, mode):
515 pass
516 pass
516
517
517 def set_binary(fd):
518 def set_binary(fd):
518 msvcrt.setmode(fd.fileno(), os.O_BINARY)
519 msvcrt.setmode(fd.fileno(), os.O_BINARY)
519
520
520 def pconvert(path):
521 def pconvert(path):
521 return path.replace("\\", "/")
522 return path.replace("\\", "/")
522
523
523 def localpath(path):
524 def localpath(path):
524 return path.replace('/', '\\')
525 return path.replace('/', '\\')
525
526
526 def normpath(path):
527 def normpath(path):
527 return pconvert(os.path.normpath(path))
528 return pconvert(os.path.normpath(path))
528
529
529 makelock = _makelock_file
530 makelock = _makelock_file
530 readlock = _readlock_file
531 readlock = _readlock_file
531
532
532 def explain_exit(code):
533 def explain_exit(code):
533 return _("exited with status %d") % code, code
534 return _("exited with status %d") % code, code
534
535
535 else:
536 else:
536 nulldev = '/dev/null'
537 nulldev = '/dev/null'
537
538
538 def rcfiles(path):
539 def rcfiles(path):
539 rcs = [os.path.join(path, 'hgrc')]
540 rcs = [os.path.join(path, 'hgrc')]
540 rcdir = os.path.join(path, 'hgrc.d')
541 rcdir = os.path.join(path, 'hgrc.d')
541 try:
542 try:
542 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
543 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
543 if f.endswith(".rc")])
544 if f.endswith(".rc")])
544 except OSError, inst: pass
545 except OSError, inst: pass
545 return rcs
546 return rcs
546 rcpath = rcfiles(os.path.dirname(sys.argv[0]) + '/../etc/mercurial')
547 rcpath = rcfiles(os.path.dirname(sys.argv[0]) + '/../etc/mercurial')
547 rcpath.extend(rcfiles('/etc/mercurial'))
548 rcpath.extend(rcfiles('/etc/mercurial'))
548 rcpath.append(os.path.expanduser('~/.hgrc'))
549 rcpath.append(os.path.expanduser('~/.hgrc'))
549 rcpath = [os.path.normpath(f) for f in rcpath]
550 rcpath = [os.path.normpath(f) for f in rcpath]
550
551
551 def parse_patch_output(output_line):
552 def parse_patch_output(output_line):
552 """parses the output produced by patch and returns the file name"""
553 """parses the output produced by patch and returns the file name"""
553 pf = output_line[14:]
554 pf = output_line[14:]
554 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
555 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
555 pf = pf[1:-1] # Remove the quotes
556 pf = pf[1:-1] # Remove the quotes
556 return pf
557 return pf
557
558
558 def is_exec(f, last):
559 def is_exec(f, last):
559 """check whether a file is executable"""
560 """check whether a file is executable"""
560 return (os.stat(f).st_mode & 0100 != 0)
561 return (os.stat(f).st_mode & 0100 != 0)
561
562
562 def set_exec(f, mode):
563 def set_exec(f, mode):
563 s = os.stat(f).st_mode
564 s = os.stat(f).st_mode
564 if (s & 0100 != 0) == mode:
565 if (s & 0100 != 0) == mode:
565 return
566 return
566 if mode:
567 if mode:
567 # Turn on +x for every +r bit when making a file executable
568 # Turn on +x for every +r bit when making a file executable
568 # and obey umask.
569 # and obey umask.
569 umask = os.umask(0)
570 umask = os.umask(0)
570 os.umask(umask)
571 os.umask(umask)
571 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
572 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
572 else:
573 else:
573 os.chmod(f, s & 0666)
574 os.chmod(f, s & 0666)
574
575
575 def set_binary(fd):
576 def set_binary(fd):
576 pass
577 pass
577
578
578 def pconvert(path):
579 def pconvert(path):
579 return path
580 return path
580
581
581 def localpath(path):
582 def localpath(path):
582 return path
583 return path
583
584
584 normpath = os.path.normpath
585 normpath = os.path.normpath
585
586
586 def makelock(info, pathname):
587 def makelock(info, pathname):
587 try:
588 try:
588 os.symlink(info, pathname)
589 os.symlink(info, pathname)
589 except OSError, why:
590 except OSError, why:
590 if why.errno == errno.EEXIST:
591 if why.errno == errno.EEXIST:
591 raise
592 raise
592 else:
593 else:
593 _makelock_file(info, pathname)
594 _makelock_file(info, pathname)
594
595
595 def readlock(pathname):
596 def readlock(pathname):
596 try:
597 try:
597 return os.readlink(pathname)
598 return os.readlink(pathname)
598 except OSError, why:
599 except OSError, why:
599 if why.errno == errno.EINVAL:
600 if why.errno == errno.EINVAL:
600 return _readlock_file(pathname)
601 return _readlock_file(pathname)
601 else:
602 else:
602 raise
603 raise
603
604
604 def explain_exit(code):
605 def explain_exit(code):
605 """return a 2-tuple (desc, code) describing a process's status"""
606 """return a 2-tuple (desc, code) describing a process's status"""
606 if os.WIFEXITED(code):
607 if os.WIFEXITED(code):
607 val = os.WEXITSTATUS(code)
608 val = os.WEXITSTATUS(code)
608 return _("exited with status %d") % val, val
609 return _("exited with status %d") % val, val
609 elif os.WIFSIGNALED(code):
610 elif os.WIFSIGNALED(code):
610 val = os.WTERMSIG(code)
611 val = os.WTERMSIG(code)
611 return _("killed by signal %d") % val, val
612 return _("killed by signal %d") % val, val
612 elif os.WIFSTOPPED(code):
613 elif os.WIFSTOPPED(code):
613 val = os.WSTOPSIG(code)
614 val = os.WSTOPSIG(code)
614 return _("stopped by signal %d") % val, val
615 return _("stopped by signal %d") % val, val
615 raise ValueError(_("invalid exit code"))
616 raise ValueError(_("invalid exit code"))
616
617
617 class chunkbuffer(object):
618 class chunkbuffer(object):
618 """Allow arbitrary sized chunks of data to be efficiently read from an
619 """Allow arbitrary sized chunks of data to be efficiently read from an
619 iterator over chunks of arbitrary size."""
620 iterator over chunks of arbitrary size."""
620
621
621 def __init__(self, in_iter, targetsize = 2**16):
622 def __init__(self, in_iter, targetsize = 2**16):
622 """in_iter is the iterator that's iterating over the input chunks.
623 """in_iter is the iterator that's iterating over the input chunks.
623 targetsize is how big a buffer to try to maintain."""
624 targetsize is how big a buffer to try to maintain."""
624 self.in_iter = iter(in_iter)
625 self.in_iter = iter(in_iter)
625 self.buf = ''
626 self.buf = ''
626 self.targetsize = int(targetsize)
627 self.targetsize = int(targetsize)
627 if self.targetsize <= 0:
628 if self.targetsize <= 0:
628 raise ValueError(_("targetsize must be greater than 0, was %d") %
629 raise ValueError(_("targetsize must be greater than 0, was %d") %
629 targetsize)
630 targetsize)
630 self.iterempty = False
631 self.iterempty = False
631
632
632 def fillbuf(self):
633 def fillbuf(self):
633 """Ignore target size; read every chunk from iterator until empty."""
634 """Ignore target size; read every chunk from iterator until empty."""
634 if not self.iterempty:
635 if not self.iterempty:
635 collector = cStringIO.StringIO()
636 collector = cStringIO.StringIO()
636 collector.write(self.buf)
637 collector.write(self.buf)
637 for ch in self.in_iter:
638 for ch in self.in_iter:
638 collector.write(ch)
639 collector.write(ch)
639 self.buf = collector.getvalue()
640 self.buf = collector.getvalue()
640 self.iterempty = True
641 self.iterempty = True
641
642
642 def read(self, l):
643 def read(self, l):
643 """Read L bytes of data from the iterator of chunks of data.
644 """Read L bytes of data from the iterator of chunks of data.
644 Returns less than L bytes if the iterator runs dry."""
645 Returns less than L bytes if the iterator runs dry."""
645 if l > len(self.buf) and not self.iterempty:
646 if l > len(self.buf) and not self.iterempty:
646 # Clamp to a multiple of self.targetsize
647 # Clamp to a multiple of self.targetsize
647 targetsize = self.targetsize * ((l // self.targetsize) + 1)
648 targetsize = self.targetsize * ((l // self.targetsize) + 1)
648 collector = cStringIO.StringIO()
649 collector = cStringIO.StringIO()
649 collector.write(self.buf)
650 collector.write(self.buf)
650 collected = len(self.buf)
651 collected = len(self.buf)
651 for chunk in self.in_iter:
652 for chunk in self.in_iter:
652 collector.write(chunk)
653 collector.write(chunk)
653 collected += len(chunk)
654 collected += len(chunk)
654 if collected >= targetsize:
655 if collected >= targetsize:
655 break
656 break
656 if collected < targetsize:
657 if collected < targetsize:
657 self.iterempty = True
658 self.iterempty = True
658 self.buf = collector.getvalue()
659 self.buf = collector.getvalue()
659 s, self.buf = self.buf[:l], buffer(self.buf, l)
660 s, self.buf = self.buf[:l], buffer(self.buf, l)
660 return s
661 return s
661
662
662 def filechunkiter(f, size = 65536):
663 def filechunkiter(f, size = 65536):
663 """Create a generator that produces all the data in the file size
664 """Create a generator that produces all the data in the file size
664 (default 65536) bytes at a time. Chunks may be less than size
665 (default 65536) bytes at a time. Chunks may be less than size
665 bytes if the chunk is the last chunk in the file, or the file is a
666 bytes if the chunk is the last chunk in the file, or the file is a
666 socket or some other type of file that sometimes reads less data
667 socket or some other type of file that sometimes reads less data
667 than is requested."""
668 than is requested."""
668 s = f.read(size)
669 s = f.read(size)
669 while len(s) > 0:
670 while len(s) > 0:
670 yield s
671 yield s
671 s = f.read(size)
672 s = f.read(size)
672
673
673 def makedate():
674 def makedate():
674 lt = time.localtime()
675 lt = time.localtime()
675 if lt[8] == 1 and time.daylight:
676 if lt[8] == 1 and time.daylight:
676 tz = time.altzone
677 tz = time.altzone
677 else:
678 else:
678 tz = time.timezone
679 tz = time.timezone
679 return time.mktime(lt), tz
680 return time.mktime(lt), tz
680
681
681 def datestr(date=None, format='%c'):
682 def datestr(date=None, format='%c'):
682 """represent a (unixtime, offset) tuple as a localized time.
683 """represent a (unixtime, offset) tuple as a localized time.
683 unixtime is seconds since the epoch, and offset is the time zone's
684 unixtime is seconds since the epoch, and offset is the time zone's
684 number of seconds away from UTC."""
685 number of seconds away from UTC."""
685 t, tz = date or makedate()
686 t, tz = date or makedate()
686 return ("%s %+03d%02d" %
687 return ("%s %+03d%02d" %
687 (time.strftime(format, time.gmtime(float(t) - tz)),
688 (time.strftime(format, time.gmtime(float(t) - tz)),
688 -tz / 3600,
689 -tz / 3600,
689 ((-tz % 3600) / 60)))
690 ((-tz % 3600) / 60)))
General Comments 0
You need to be logged in to leave comments. Login now