##// END OF EJS Templates
Fix issue483 - mq does not work under windows with gnu-win32 patch....
Patrick Mezard -
r4434:439b1c35 default
parent child Browse files
Show More
@@ -1,658 +1,661 b''
1 # patch.py - patch file parsing routines
1 # patch.py - patch file parsing routines
2 #
2 #
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 from node import *
9 from node import *
10 import base85, cmdutil, mdiff, util, context, revlog
10 import base85, cmdutil, mdiff, util, context, revlog
11 import cStringIO, email.Parser, os, popen2, re, sha
11 import cStringIO, email.Parser, os, popen2, re, sha
12 import sys, tempfile, zlib
12 import sys, tempfile, zlib
13
13
14 # helper functions
14 # helper functions
15
15
16 def copyfile(src, dst, basedir=None):
16 def copyfile(src, dst, basedir=None):
17 if not basedir:
17 if not basedir:
18 basedir = os.getcwd()
18 basedir = os.getcwd()
19
19
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 if os.path.exists(absdst):
21 if os.path.exists(absdst):
22 raise util.Abort(_("cannot create %s: destination already exists") %
22 raise util.Abort(_("cannot create %s: destination already exists") %
23 dst)
23 dst)
24
24
25 targetdir = os.path.dirname(absdst)
25 targetdir = os.path.dirname(absdst)
26 if not os.path.isdir(targetdir):
26 if not os.path.isdir(targetdir):
27 os.makedirs(targetdir)
27 os.makedirs(targetdir)
28
28
29 util.copyfile(abssrc, absdst)
29 util.copyfile(abssrc, absdst)
30
30
31 # public functions
31 # public functions
32
32
33 def extract(ui, fileobj):
33 def extract(ui, fileobj):
34 '''extract patch from data read from fileobj.
34 '''extract patch from data read from fileobj.
35
35
36 patch can be a normal patch or contained in an email message.
36 patch can be a normal patch or contained in an email message.
37
37
38 return tuple (filename, message, user, date, node, p1, p2).
38 return tuple (filename, message, user, date, node, p1, p2).
39 Any item in the returned tuple can be None. If filename is None,
39 Any item in the returned tuple can be None. If filename is None,
40 fileobj did not contain a patch. Caller must unlink filename when done.'''
40 fileobj did not contain a patch. Caller must unlink filename when done.'''
41
41
42 # attempt to detect the start of a patch
42 # attempt to detect the start of a patch
43 # (this heuristic is borrowed from quilt)
43 # (this heuristic is borrowed from quilt)
44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
44 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
45 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
46 '(---|\*\*\*)[ \t])', re.MULTILINE)
46 '(---|\*\*\*)[ \t])', re.MULTILINE)
47
47
48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
48 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
49 tmpfp = os.fdopen(fd, 'w')
49 tmpfp = os.fdopen(fd, 'w')
50 try:
50 try:
51 msg = email.Parser.Parser().parse(fileobj)
51 msg = email.Parser.Parser().parse(fileobj)
52
52
53 message = msg['Subject']
53 message = msg['Subject']
54 user = msg['From']
54 user = msg['From']
55 # should try to parse msg['Date']
55 # should try to parse msg['Date']
56 date = None
56 date = None
57 nodeid = None
57 nodeid = None
58 parents = []
58 parents = []
59
59
60 if message:
60 if message:
61 if message.startswith('[PATCH'):
61 if message.startswith('[PATCH'):
62 pend = message.find(']')
62 pend = message.find(']')
63 if pend >= 0:
63 if pend >= 0:
64 message = message[pend+1:].lstrip()
64 message = message[pend+1:].lstrip()
65 message = message.replace('\n\t', ' ')
65 message = message.replace('\n\t', ' ')
66 ui.debug('Subject: %s\n' % message)
66 ui.debug('Subject: %s\n' % message)
67 if user:
67 if user:
68 ui.debug('From: %s\n' % user)
68 ui.debug('From: %s\n' % user)
69 diffs_seen = 0
69 diffs_seen = 0
70 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
70 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
71
71
72 for part in msg.walk():
72 for part in msg.walk():
73 content_type = part.get_content_type()
73 content_type = part.get_content_type()
74 ui.debug('Content-Type: %s\n' % content_type)
74 ui.debug('Content-Type: %s\n' % content_type)
75 if content_type not in ok_types:
75 if content_type not in ok_types:
76 continue
76 continue
77 payload = part.get_payload(decode=True)
77 payload = part.get_payload(decode=True)
78 m = diffre.search(payload)
78 m = diffre.search(payload)
79 if m:
79 if m:
80 hgpatch = False
80 hgpatch = False
81 ignoretext = False
81 ignoretext = False
82
82
83 ui.debug(_('found patch at byte %d\n') % m.start(0))
83 ui.debug(_('found patch at byte %d\n') % m.start(0))
84 diffs_seen += 1
84 diffs_seen += 1
85 cfp = cStringIO.StringIO()
85 cfp = cStringIO.StringIO()
86 if message:
86 if message:
87 cfp.write(message)
87 cfp.write(message)
88 cfp.write('\n')
88 cfp.write('\n')
89 for line in payload[:m.start(0)].splitlines():
89 for line in payload[:m.start(0)].splitlines():
90 if line.startswith('# HG changeset patch'):
90 if line.startswith('# HG changeset patch'):
91 ui.debug(_('patch generated by hg export\n'))
91 ui.debug(_('patch generated by hg export\n'))
92 hgpatch = True
92 hgpatch = True
93 # drop earlier commit message content
93 # drop earlier commit message content
94 cfp.seek(0)
94 cfp.seek(0)
95 cfp.truncate()
95 cfp.truncate()
96 elif hgpatch:
96 elif hgpatch:
97 if line.startswith('# User '):
97 if line.startswith('# User '):
98 user = line[7:]
98 user = line[7:]
99 ui.debug('From: %s\n' % user)
99 ui.debug('From: %s\n' % user)
100 elif line.startswith("# Date "):
100 elif line.startswith("# Date "):
101 date = line[7:]
101 date = line[7:]
102 elif line.startswith("# Node ID "):
102 elif line.startswith("# Node ID "):
103 nodeid = line[10:]
103 nodeid = line[10:]
104 elif line.startswith("# Parent "):
104 elif line.startswith("# Parent "):
105 parents.append(line[10:])
105 parents.append(line[10:])
106 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
106 elif line == '---' and 'git-send-email' in msg['X-Mailer']:
107 ignoretext = True
107 ignoretext = True
108 if not line.startswith('# ') and not ignoretext:
108 if not line.startswith('# ') and not ignoretext:
109 cfp.write(line)
109 cfp.write(line)
110 cfp.write('\n')
110 cfp.write('\n')
111 message = cfp.getvalue()
111 message = cfp.getvalue()
112 if tmpfp:
112 if tmpfp:
113 tmpfp.write(payload)
113 tmpfp.write(payload)
114 if not payload.endswith('\n'):
114 if not payload.endswith('\n'):
115 tmpfp.write('\n')
115 tmpfp.write('\n')
116 elif not diffs_seen and message and content_type == 'text/plain':
116 elif not diffs_seen and message and content_type == 'text/plain':
117 message += '\n' + payload
117 message += '\n' + payload
118 except:
118 except:
119 tmpfp.close()
119 tmpfp.close()
120 os.unlink(tmpname)
120 os.unlink(tmpname)
121 raise
121 raise
122
122
123 tmpfp.close()
123 tmpfp.close()
124 if not diffs_seen:
124 if not diffs_seen:
125 os.unlink(tmpname)
125 os.unlink(tmpname)
126 return None, message, user, date, None, None, None
126 return None, message, user, date, None, None, None
127 p1 = parents and parents.pop(0) or None
127 p1 = parents and parents.pop(0) or None
128 p2 = parents and parents.pop(0) or None
128 p2 = parents and parents.pop(0) or None
129 return tmpname, message, user, date, nodeid, p1, p2
129 return tmpname, message, user, date, nodeid, p1, p2
130
130
131 GP_PATCH = 1 << 0 # we have to run patch
131 GP_PATCH = 1 << 0 # we have to run patch
132 GP_FILTER = 1 << 1 # there's some copy/rename operation
132 GP_FILTER = 1 << 1 # there's some copy/rename operation
133 GP_BINARY = 1 << 2 # there's a binary patch
133 GP_BINARY = 1 << 2 # there's a binary patch
134
134
135 def readgitpatch(patchname):
135 def readgitpatch(patchname):
136 """extract git-style metadata about patches from <patchname>"""
136 """extract git-style metadata about patches from <patchname>"""
137 class gitpatch:
137 class gitpatch:
138 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
138 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
139 def __init__(self, path):
139 def __init__(self, path):
140 self.path = path
140 self.path = path
141 self.oldpath = None
141 self.oldpath = None
142 self.mode = None
142 self.mode = None
143 self.op = 'MODIFY'
143 self.op = 'MODIFY'
144 self.copymod = False
144 self.copymod = False
145 self.lineno = 0
145 self.lineno = 0
146 self.binary = False
146 self.binary = False
147
147
148 # Filter patch for git information
148 # Filter patch for git information
149 gitre = re.compile('diff --git a/(.*) b/(.*)')
149 gitre = re.compile('diff --git a/(.*) b/(.*)')
150 pf = file(patchname)
150 pf = file(patchname)
151 gp = None
151 gp = None
152 gitpatches = []
152 gitpatches = []
153 # Can have a git patch with only metadata, causing patch to complain
153 # Can have a git patch with only metadata, causing patch to complain
154 dopatch = 0
154 dopatch = 0
155
155
156 lineno = 0
156 lineno = 0
157 for line in pf:
157 for line in pf:
158 lineno += 1
158 lineno += 1
159 if line.startswith('diff --git'):
159 if line.startswith('diff --git'):
160 m = gitre.match(line)
160 m = gitre.match(line)
161 if m:
161 if m:
162 if gp:
162 if gp:
163 gitpatches.append(gp)
163 gitpatches.append(gp)
164 src, dst = m.group(1, 2)
164 src, dst = m.group(1, 2)
165 gp = gitpatch(dst)
165 gp = gitpatch(dst)
166 gp.lineno = lineno
166 gp.lineno = lineno
167 elif gp:
167 elif gp:
168 if line.startswith('--- '):
168 if line.startswith('--- '):
169 if gp.op in ('COPY', 'RENAME'):
169 if gp.op in ('COPY', 'RENAME'):
170 gp.copymod = True
170 gp.copymod = True
171 dopatch |= GP_FILTER
171 dopatch |= GP_FILTER
172 gitpatches.append(gp)
172 gitpatches.append(gp)
173 gp = None
173 gp = None
174 dopatch |= GP_PATCH
174 dopatch |= GP_PATCH
175 continue
175 continue
176 if line.startswith('rename from '):
176 if line.startswith('rename from '):
177 gp.op = 'RENAME'
177 gp.op = 'RENAME'
178 gp.oldpath = line[12:].rstrip()
178 gp.oldpath = line[12:].rstrip()
179 elif line.startswith('rename to '):
179 elif line.startswith('rename to '):
180 gp.path = line[10:].rstrip()
180 gp.path = line[10:].rstrip()
181 elif line.startswith('copy from '):
181 elif line.startswith('copy from '):
182 gp.op = 'COPY'
182 gp.op = 'COPY'
183 gp.oldpath = line[10:].rstrip()
183 gp.oldpath = line[10:].rstrip()
184 elif line.startswith('copy to '):
184 elif line.startswith('copy to '):
185 gp.path = line[8:].rstrip()
185 gp.path = line[8:].rstrip()
186 elif line.startswith('deleted file'):
186 elif line.startswith('deleted file'):
187 gp.op = 'DELETE'
187 gp.op = 'DELETE'
188 elif line.startswith('new file mode '):
188 elif line.startswith('new file mode '):
189 gp.op = 'ADD'
189 gp.op = 'ADD'
190 gp.mode = int(line.rstrip()[-3:], 8)
190 gp.mode = int(line.rstrip()[-3:], 8)
191 elif line.startswith('new mode '):
191 elif line.startswith('new mode '):
192 gp.mode = int(line.rstrip()[-3:], 8)
192 gp.mode = int(line.rstrip()[-3:], 8)
193 elif line.startswith('GIT binary patch'):
193 elif line.startswith('GIT binary patch'):
194 dopatch |= GP_BINARY
194 dopatch |= GP_BINARY
195 gp.binary = True
195 gp.binary = True
196 if gp:
196 if gp:
197 gitpatches.append(gp)
197 gitpatches.append(gp)
198
198
199 if not gitpatches:
199 if not gitpatches:
200 dopatch = GP_PATCH
200 dopatch = GP_PATCH
201
201
202 return (dopatch, gitpatches)
202 return (dopatch, gitpatches)
203
203
204 def dogitpatch(patchname, gitpatches, cwd=None):
204 def dogitpatch(patchname, gitpatches, cwd=None):
205 """Preprocess git patch so that vanilla patch can handle it"""
205 """Preprocess git patch so that vanilla patch can handle it"""
206 def extractbin(fp):
206 def extractbin(fp):
207 i = [0] # yuck
207 i = [0] # yuck
208 def readline():
208 def readline():
209 i[0] += 1
209 i[0] += 1
210 return fp.readline().rstrip()
210 return fp.readline().rstrip()
211 line = readline()
211 line = readline()
212 while line and not line.startswith('literal '):
212 while line and not line.startswith('literal '):
213 line = readline()
213 line = readline()
214 if not line:
214 if not line:
215 return None, i[0]
215 return None, i[0]
216 size = int(line[8:])
216 size = int(line[8:])
217 dec = []
217 dec = []
218 line = readline()
218 line = readline()
219 while line:
219 while line:
220 l = line[0]
220 l = line[0]
221 if l <= 'Z' and l >= 'A':
221 if l <= 'Z' and l >= 'A':
222 l = ord(l) - ord('A') + 1
222 l = ord(l) - ord('A') + 1
223 else:
223 else:
224 l = ord(l) - ord('a') + 27
224 l = ord(l) - ord('a') + 27
225 dec.append(base85.b85decode(line[1:])[:l])
225 dec.append(base85.b85decode(line[1:])[:l])
226 line = readline()
226 line = readline()
227 text = zlib.decompress(''.join(dec))
227 text = zlib.decompress(''.join(dec))
228 if len(text) != size:
228 if len(text) != size:
229 raise util.Abort(_('binary patch is %d bytes, not %d') %
229 raise util.Abort(_('binary patch is %d bytes, not %d') %
230 (len(text), size))
230 (len(text), size))
231 return text, i[0]
231 return text, i[0]
232
232
233 pf = file(patchname)
233 pf = file(patchname)
234 pfline = 1
234 pfline = 1
235
235
236 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
236 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
237 tmpfp = os.fdopen(fd, 'w')
237 tmpfp = os.fdopen(fd, 'w')
238
238
239 try:
239 try:
240 for i in xrange(len(gitpatches)):
240 for i in xrange(len(gitpatches)):
241 p = gitpatches[i]
241 p = gitpatches[i]
242 if not p.copymod and not p.binary:
242 if not p.copymod and not p.binary:
243 continue
243 continue
244
244
245 # rewrite patch hunk
245 # rewrite patch hunk
246 while pfline < p.lineno:
246 while pfline < p.lineno:
247 tmpfp.write(pf.readline())
247 tmpfp.write(pf.readline())
248 pfline += 1
248 pfline += 1
249
249
250 if p.binary:
250 if p.binary:
251 text, delta = extractbin(pf)
251 text, delta = extractbin(pf)
252 if not text:
252 if not text:
253 raise util.Abort(_('binary patch extraction failed'))
253 raise util.Abort(_('binary patch extraction failed'))
254 pfline += delta
254 pfline += delta
255 if not cwd:
255 if not cwd:
256 cwd = os.getcwd()
256 cwd = os.getcwd()
257 absdst = os.path.join(cwd, p.path)
257 absdst = os.path.join(cwd, p.path)
258 basedir = os.path.dirname(absdst)
258 basedir = os.path.dirname(absdst)
259 if not os.path.isdir(basedir):
259 if not os.path.isdir(basedir):
260 os.makedirs(basedir)
260 os.makedirs(basedir)
261 out = file(absdst, 'wb')
261 out = file(absdst, 'wb')
262 out.write(text)
262 out.write(text)
263 out.close()
263 out.close()
264 elif p.copymod:
264 elif p.copymod:
265 copyfile(p.oldpath, p.path, basedir=cwd)
265 copyfile(p.oldpath, p.path, basedir=cwd)
266 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
266 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
267 line = pf.readline()
267 line = pf.readline()
268 pfline += 1
268 pfline += 1
269 while not line.startswith('--- a/'):
269 while not line.startswith('--- a/'):
270 tmpfp.write(line)
270 tmpfp.write(line)
271 line = pf.readline()
271 line = pf.readline()
272 pfline += 1
272 pfline += 1
273 tmpfp.write('--- a/%s\n' % p.path)
273 tmpfp.write('--- a/%s\n' % p.path)
274
274
275 line = pf.readline()
275 line = pf.readline()
276 while line:
276 while line:
277 tmpfp.write(line)
277 tmpfp.write(line)
278 line = pf.readline()
278 line = pf.readline()
279 except:
279 except:
280 tmpfp.close()
280 tmpfp.close()
281 os.unlink(patchname)
281 os.unlink(patchname)
282 raise
282 raise
283
283
284 tmpfp.close()
284 tmpfp.close()
285 return patchname
285 return patchname
286
286
287 def patch(patchname, ui, strip=1, cwd=None, files={}):
287 def patch(patchname, ui, strip=1, cwd=None, files={}):
288 """apply the patch <patchname> to the working directory.
288 """apply the patch <patchname> to the working directory.
289 a list of patched files is returned"""
289 a list of patched files is returned"""
290
290
291 # helper function
291 # helper function
292 def __patch(patchname):
292 def __patch(patchname):
293 """patch and updates the files and fuzz variables"""
293 """patch and updates the files and fuzz variables"""
294 fuzz = False
294 fuzz = False
295
295
296 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
296 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''),
297 'patch')
297 'patch')
298 args = []
298 args = []
299 if util.needbinarypatch():
300 args.append('--binary')
301
299 if cwd:
302 if cwd:
300 args.append('-d %s' % util.shellquote(cwd))
303 args.append('-d %s' % util.shellquote(cwd))
301 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
304 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
302 util.shellquote(patchname)))
305 util.shellquote(patchname)))
303
306
304 for line in fp:
307 for line in fp:
305 line = line.rstrip()
308 line = line.rstrip()
306 ui.note(line + '\n')
309 ui.note(line + '\n')
307 if line.startswith('patching file '):
310 if line.startswith('patching file '):
308 pf = util.parse_patch_output(line)
311 pf = util.parse_patch_output(line)
309 printed_file = False
312 printed_file = False
310 files.setdefault(pf, (None, None))
313 files.setdefault(pf, (None, None))
311 elif line.find('with fuzz') >= 0:
314 elif line.find('with fuzz') >= 0:
312 fuzz = True
315 fuzz = True
313 if not printed_file:
316 if not printed_file:
314 ui.warn(pf + '\n')
317 ui.warn(pf + '\n')
315 printed_file = True
318 printed_file = True
316 ui.warn(line + '\n')
319 ui.warn(line + '\n')
317 elif line.find('saving rejects to file') >= 0:
320 elif line.find('saving rejects to file') >= 0:
318 ui.warn(line + '\n')
321 ui.warn(line + '\n')
319 elif line.find('FAILED') >= 0:
322 elif line.find('FAILED') >= 0:
320 if not printed_file:
323 if not printed_file:
321 ui.warn(pf + '\n')
324 ui.warn(pf + '\n')
322 printed_file = True
325 printed_file = True
323 ui.warn(line + '\n')
326 ui.warn(line + '\n')
324 code = fp.close()
327 code = fp.close()
325 if code:
328 if code:
326 raise util.Abort(_("patch command failed: %s") %
329 raise util.Abort(_("patch command failed: %s") %
327 util.explain_exit(code)[0])
330 util.explain_exit(code)[0])
328 return fuzz
331 return fuzz
329
332
330 (dopatch, gitpatches) = readgitpatch(patchname)
333 (dopatch, gitpatches) = readgitpatch(patchname)
331 for gp in gitpatches:
334 for gp in gitpatches:
332 files[gp.path] = (gp.op, gp)
335 files[gp.path] = (gp.op, gp)
333
336
334 fuzz = False
337 fuzz = False
335 if dopatch:
338 if dopatch:
336 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
339 filterpatch = dopatch & (GP_FILTER | GP_BINARY)
337 if filterpatch:
340 if filterpatch:
338 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
341 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
339 try:
342 try:
340 if dopatch & GP_PATCH:
343 if dopatch & GP_PATCH:
341 fuzz = __patch(patchname)
344 fuzz = __patch(patchname)
342 finally:
345 finally:
343 if filterpatch:
346 if filterpatch:
344 os.unlink(patchname)
347 os.unlink(patchname)
345
348
346 return fuzz
349 return fuzz
347
350
348 def diffopts(ui, opts={}, untrusted=False):
351 def diffopts(ui, opts={}, untrusted=False):
349 def get(key, name=None):
352 def get(key, name=None):
350 return (opts.get(key) or
353 return (opts.get(key) or
351 ui.configbool('diff', name or key, None, untrusted=untrusted))
354 ui.configbool('diff', name or key, None, untrusted=untrusted))
352 return mdiff.diffopts(
355 return mdiff.diffopts(
353 text=opts.get('text'),
356 text=opts.get('text'),
354 git=get('git'),
357 git=get('git'),
355 nodates=get('nodates'),
358 nodates=get('nodates'),
356 showfunc=get('show_function', 'showfunc'),
359 showfunc=get('show_function', 'showfunc'),
357 ignorews=get('ignore_all_space', 'ignorews'),
360 ignorews=get('ignore_all_space', 'ignorews'),
358 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
361 ignorewsamount=get('ignore_space_change', 'ignorewsamount'),
359 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
362 ignoreblanklines=get('ignore_blank_lines', 'ignoreblanklines'))
360
363
361 def updatedir(ui, repo, patches, wlock=None):
364 def updatedir(ui, repo, patches, wlock=None):
362 '''Update dirstate after patch application according to metadata'''
365 '''Update dirstate after patch application according to metadata'''
363 if not patches:
366 if not patches:
364 return
367 return
365 copies = []
368 copies = []
366 removes = {}
369 removes = {}
367 cfiles = patches.keys()
370 cfiles = patches.keys()
368 cwd = repo.getcwd()
371 cwd = repo.getcwd()
369 if cwd:
372 if cwd:
370 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
373 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
371 for f in patches:
374 for f in patches:
372 ctype, gp = patches[f]
375 ctype, gp = patches[f]
373 if ctype == 'RENAME':
376 if ctype == 'RENAME':
374 copies.append((gp.oldpath, gp.path, gp.copymod))
377 copies.append((gp.oldpath, gp.path, gp.copymod))
375 removes[gp.oldpath] = 1
378 removes[gp.oldpath] = 1
376 elif ctype == 'COPY':
379 elif ctype == 'COPY':
377 copies.append((gp.oldpath, gp.path, gp.copymod))
380 copies.append((gp.oldpath, gp.path, gp.copymod))
378 elif ctype == 'DELETE':
381 elif ctype == 'DELETE':
379 removes[gp.path] = 1
382 removes[gp.path] = 1
380 for src, dst, after in copies:
383 for src, dst, after in copies:
381 if not after:
384 if not after:
382 copyfile(src, dst, repo.root)
385 copyfile(src, dst, repo.root)
383 repo.copy(src, dst, wlock=wlock)
386 repo.copy(src, dst, wlock=wlock)
384 removes = removes.keys()
387 removes = removes.keys()
385 if removes:
388 if removes:
386 removes.sort()
389 removes.sort()
387 repo.remove(removes, True, wlock=wlock)
390 repo.remove(removes, True, wlock=wlock)
388 for f in patches:
391 for f in patches:
389 ctype, gp = patches[f]
392 ctype, gp = patches[f]
390 if gp and gp.mode:
393 if gp and gp.mode:
391 x = gp.mode & 0100 != 0
394 x = gp.mode & 0100 != 0
392 dst = os.path.join(repo.root, gp.path)
395 dst = os.path.join(repo.root, gp.path)
393 # patch won't create empty files
396 # patch won't create empty files
394 if ctype == 'ADD' and not os.path.exists(dst):
397 if ctype == 'ADD' and not os.path.exists(dst):
395 repo.wwrite(gp.path, '', x and 'x' or '')
398 repo.wwrite(gp.path, '', x and 'x' or '')
396 else:
399 else:
397 util.set_exec(dst, x)
400 util.set_exec(dst, x)
398 cmdutil.addremove(repo, cfiles, wlock=wlock)
401 cmdutil.addremove(repo, cfiles, wlock=wlock)
399 files = patches.keys()
402 files = patches.keys()
400 files.extend([r for r in removes if r not in files])
403 files.extend([r for r in removes if r not in files])
401 files.sort()
404 files.sort()
402
405
403 return files
406 return files
404
407
405 def b85diff(fp, to, tn):
408 def b85diff(fp, to, tn):
406 '''print base85-encoded binary diff'''
409 '''print base85-encoded binary diff'''
407 def gitindex(text):
410 def gitindex(text):
408 if not text:
411 if not text:
409 return '0' * 40
412 return '0' * 40
410 l = len(text)
413 l = len(text)
411 s = sha.new('blob %d\0' % l)
414 s = sha.new('blob %d\0' % l)
412 s.update(text)
415 s.update(text)
413 return s.hexdigest()
416 return s.hexdigest()
414
417
415 def fmtline(line):
418 def fmtline(line):
416 l = len(line)
419 l = len(line)
417 if l <= 26:
420 if l <= 26:
418 l = chr(ord('A') + l - 1)
421 l = chr(ord('A') + l - 1)
419 else:
422 else:
420 l = chr(l - 26 + ord('a') - 1)
423 l = chr(l - 26 + ord('a') - 1)
421 return '%c%s\n' % (l, base85.b85encode(line, True))
424 return '%c%s\n' % (l, base85.b85encode(line, True))
422
425
423 def chunk(text, csize=52):
426 def chunk(text, csize=52):
424 l = len(text)
427 l = len(text)
425 i = 0
428 i = 0
426 while i < l:
429 while i < l:
427 yield text[i:i+csize]
430 yield text[i:i+csize]
428 i += csize
431 i += csize
429
432
430 tohash = gitindex(to)
433 tohash = gitindex(to)
431 tnhash = gitindex(tn)
434 tnhash = gitindex(tn)
432 if tohash == tnhash:
435 if tohash == tnhash:
433 return ""
436 return ""
434
437
435 # TODO: deltas
438 # TODO: deltas
436 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
439 ret = ['index %s..%s\nGIT binary patch\nliteral %s\n' %
437 (tohash, tnhash, len(tn))]
440 (tohash, tnhash, len(tn))]
438 for l in chunk(zlib.compress(tn)):
441 for l in chunk(zlib.compress(tn)):
439 ret.append(fmtline(l))
442 ret.append(fmtline(l))
440 ret.append('\n')
443 ret.append('\n')
441 return ''.join(ret)
444 return ''.join(ret)
442
445
443 def diff(repo, node1=None, node2=None, files=None, match=util.always,
446 def diff(repo, node1=None, node2=None, files=None, match=util.always,
444 fp=None, changes=None, opts=None):
447 fp=None, changes=None, opts=None):
445 '''print diff of changes to files between two nodes, or node and
448 '''print diff of changes to files between two nodes, or node and
446 working directory.
449 working directory.
447
450
448 if node1 is None, use first dirstate parent instead.
451 if node1 is None, use first dirstate parent instead.
449 if node2 is None, compare node1 with working directory.'''
452 if node2 is None, compare node1 with working directory.'''
450
453
451 if opts is None:
454 if opts is None:
452 opts = mdiff.defaultopts
455 opts = mdiff.defaultopts
453 if fp is None:
456 if fp is None:
454 fp = repo.ui
457 fp = repo.ui
455
458
456 if not node1:
459 if not node1:
457 node1 = repo.dirstate.parents()[0]
460 node1 = repo.dirstate.parents()[0]
458
461
459 ccache = {}
462 ccache = {}
460 def getctx(r):
463 def getctx(r):
461 if r not in ccache:
464 if r not in ccache:
462 ccache[r] = context.changectx(repo, r)
465 ccache[r] = context.changectx(repo, r)
463 return ccache[r]
466 return ccache[r]
464
467
465 flcache = {}
468 flcache = {}
466 def getfilectx(f, ctx):
469 def getfilectx(f, ctx):
467 flctx = ctx.filectx(f, filelog=flcache.get(f))
470 flctx = ctx.filectx(f, filelog=flcache.get(f))
468 if f not in flcache:
471 if f not in flcache:
469 flcache[f] = flctx._filelog
472 flcache[f] = flctx._filelog
470 return flctx
473 return flctx
471
474
472 # reading the data for node1 early allows it to play nicely
475 # reading the data for node1 early allows it to play nicely
473 # with repo.status and the revlog cache.
476 # with repo.status and the revlog cache.
474 ctx1 = context.changectx(repo, node1)
477 ctx1 = context.changectx(repo, node1)
475 # force manifest reading
478 # force manifest reading
476 man1 = ctx1.manifest()
479 man1 = ctx1.manifest()
477 date1 = util.datestr(ctx1.date())
480 date1 = util.datestr(ctx1.date())
478
481
479 if not changes:
482 if not changes:
480 changes = repo.status(node1, node2, files, match=match)[:5]
483 changes = repo.status(node1, node2, files, match=match)[:5]
481 modified, added, removed, deleted, unknown = changes
484 modified, added, removed, deleted, unknown = changes
482
485
483 if not modified and not added and not removed:
486 if not modified and not added and not removed:
484 return
487 return
485
488
486 if node2:
489 if node2:
487 ctx2 = context.changectx(repo, node2)
490 ctx2 = context.changectx(repo, node2)
488 else:
491 else:
489 ctx2 = context.workingctx(repo)
492 ctx2 = context.workingctx(repo)
490 man2 = ctx2.manifest()
493 man2 = ctx2.manifest()
491
494
492 # returns False if there was no rename between ctx1 and ctx2
495 # returns False if there was no rename between ctx1 and ctx2
493 # returns None if the file was created between ctx1 and ctx2
496 # returns None if the file was created between ctx1 and ctx2
494 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
497 # returns the (file, node) present in ctx1 that was renamed to f in ctx2
495 def renamed(f):
498 def renamed(f):
496 startrev = ctx1.rev()
499 startrev = ctx1.rev()
497 c = ctx2
500 c = ctx2
498 crev = c.rev()
501 crev = c.rev()
499 if crev is None:
502 if crev is None:
500 crev = repo.changelog.count()
503 crev = repo.changelog.count()
501 orig = f
504 orig = f
502 while crev > startrev:
505 while crev > startrev:
503 if f in c.files():
506 if f in c.files():
504 try:
507 try:
505 src = getfilectx(f, c).renamed()
508 src = getfilectx(f, c).renamed()
506 except revlog.LookupError:
509 except revlog.LookupError:
507 return None
510 return None
508 if src:
511 if src:
509 f = src[0]
512 f = src[0]
510 crev = c.parents()[0].rev()
513 crev = c.parents()[0].rev()
511 # try to reuse
514 # try to reuse
512 c = getctx(crev)
515 c = getctx(crev)
513 if f not in man1:
516 if f not in man1:
514 return None
517 return None
515 if f == orig:
518 if f == orig:
516 return False
519 return False
517 return f
520 return f
518
521
519 if repo.ui.quiet:
522 if repo.ui.quiet:
520 r = None
523 r = None
521 else:
524 else:
522 hexfunc = repo.ui.debugflag and hex or short
525 hexfunc = repo.ui.debugflag and hex or short
523 r = [hexfunc(node) for node in [node1, node2] if node]
526 r = [hexfunc(node) for node in [node1, node2] if node]
524
527
525 if opts.git:
528 if opts.git:
526 copied = {}
529 copied = {}
527 for f in added:
530 for f in added:
528 src = renamed(f)
531 src = renamed(f)
529 if src:
532 if src:
530 copied[f] = src
533 copied[f] = src
531 srcs = [x[1] for x in copied.items()]
534 srcs = [x[1] for x in copied.items()]
532
535
533 all = modified + added + removed
536 all = modified + added + removed
534 all.sort()
537 all.sort()
535 gone = {}
538 gone = {}
536
539
537 for f in all:
540 for f in all:
538 to = None
541 to = None
539 tn = None
542 tn = None
540 dodiff = True
543 dodiff = True
541 header = []
544 header = []
542 if f in man1:
545 if f in man1:
543 to = getfilectx(f, ctx1).data()
546 to = getfilectx(f, ctx1).data()
544 if f not in removed:
547 if f not in removed:
545 tn = getfilectx(f, ctx2).data()
548 tn = getfilectx(f, ctx2).data()
546 if opts.git:
549 if opts.git:
547 def gitmode(x):
550 def gitmode(x):
548 return x and '100755' or '100644'
551 return x and '100755' or '100644'
549 def addmodehdr(header, omode, nmode):
552 def addmodehdr(header, omode, nmode):
550 if omode != nmode:
553 if omode != nmode:
551 header.append('old mode %s\n' % omode)
554 header.append('old mode %s\n' % omode)
552 header.append('new mode %s\n' % nmode)
555 header.append('new mode %s\n' % nmode)
553
556
554 a, b = f, f
557 a, b = f, f
555 if f in added:
558 if f in added:
556 mode = gitmode(man2.execf(f))
559 mode = gitmode(man2.execf(f))
557 if f in copied:
560 if f in copied:
558 a = copied[f]
561 a = copied[f]
559 omode = gitmode(man1.execf(a))
562 omode = gitmode(man1.execf(a))
560 addmodehdr(header, omode, mode)
563 addmodehdr(header, omode, mode)
561 if a in removed and a not in gone:
564 if a in removed and a not in gone:
562 op = 'rename'
565 op = 'rename'
563 gone[a] = 1
566 gone[a] = 1
564 else:
567 else:
565 op = 'copy'
568 op = 'copy'
566 header.append('%s from %s\n' % (op, a))
569 header.append('%s from %s\n' % (op, a))
567 header.append('%s to %s\n' % (op, f))
570 header.append('%s to %s\n' % (op, f))
568 to = getfilectx(a, ctx1).data()
571 to = getfilectx(a, ctx1).data()
569 else:
572 else:
570 header.append('new file mode %s\n' % mode)
573 header.append('new file mode %s\n' % mode)
571 if util.binary(tn):
574 if util.binary(tn):
572 dodiff = 'binary'
575 dodiff = 'binary'
573 elif f in removed:
576 elif f in removed:
574 if f in srcs:
577 if f in srcs:
575 dodiff = False
578 dodiff = False
576 else:
579 else:
577 mode = gitmode(man1.execf(f))
580 mode = gitmode(man1.execf(f))
578 header.append('deleted file mode %s\n' % mode)
581 header.append('deleted file mode %s\n' % mode)
579 else:
582 else:
580 omode = gitmode(man1.execf(f))
583 omode = gitmode(man1.execf(f))
581 nmode = gitmode(man2.execf(f))
584 nmode = gitmode(man2.execf(f))
582 addmodehdr(header, omode, nmode)
585 addmodehdr(header, omode, nmode)
583 if util.binary(to) or util.binary(tn):
586 if util.binary(to) or util.binary(tn):
584 dodiff = 'binary'
587 dodiff = 'binary'
585 r = None
588 r = None
586 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
589 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
587 if dodiff:
590 if dodiff:
588 if dodiff == 'binary':
591 if dodiff == 'binary':
589 text = b85diff(fp, to, tn)
592 text = b85diff(fp, to, tn)
590 else:
593 else:
591 text = mdiff.unidiff(to, date1,
594 text = mdiff.unidiff(to, date1,
592 # ctx2 date may be dynamic
595 # ctx2 date may be dynamic
593 tn, util.datestr(ctx2.date()),
596 tn, util.datestr(ctx2.date()),
594 f, r, opts=opts)
597 f, r, opts=opts)
595 if text or len(header) > 1:
598 if text or len(header) > 1:
596 fp.write(''.join(header))
599 fp.write(''.join(header))
597 fp.write(text)
600 fp.write(text)
598
601
599 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
602 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
600 opts=None):
603 opts=None):
601 '''export changesets as hg patches.'''
604 '''export changesets as hg patches.'''
602
605
603 total = len(revs)
606 total = len(revs)
604 revwidth = max([len(str(rev)) for rev in revs])
607 revwidth = max([len(str(rev)) for rev in revs])
605
608
606 def single(rev, seqno, fp):
609 def single(rev, seqno, fp):
607 ctx = repo.changectx(rev)
610 ctx = repo.changectx(rev)
608 node = ctx.node()
611 node = ctx.node()
609 parents = [p.node() for p in ctx.parents() if p]
612 parents = [p.node() for p in ctx.parents() if p]
610 if switch_parent:
613 if switch_parent:
611 parents.reverse()
614 parents.reverse()
612 prev = (parents and parents[0]) or nullid
615 prev = (parents and parents[0]) or nullid
613
616
614 if not fp:
617 if not fp:
615 fp = cmdutil.make_file(repo, template, node, total=total,
618 fp = cmdutil.make_file(repo, template, node, total=total,
616 seqno=seqno, revwidth=revwidth)
619 seqno=seqno, revwidth=revwidth)
617 if fp != sys.stdout and hasattr(fp, 'name'):
620 if fp != sys.stdout and hasattr(fp, 'name'):
618 repo.ui.note("%s\n" % fp.name)
621 repo.ui.note("%s\n" % fp.name)
619
622
620 fp.write("# HG changeset patch\n")
623 fp.write("# HG changeset patch\n")
621 fp.write("# User %s\n" % ctx.user())
624 fp.write("# User %s\n" % ctx.user())
622 fp.write("# Date %d %d\n" % ctx.date())
625 fp.write("# Date %d %d\n" % ctx.date())
623 fp.write("# Node ID %s\n" % hex(node))
626 fp.write("# Node ID %s\n" % hex(node))
624 fp.write("# Parent %s\n" % hex(prev))
627 fp.write("# Parent %s\n" % hex(prev))
625 if len(parents) > 1:
628 if len(parents) > 1:
626 fp.write("# Parent %s\n" % hex(parents[1]))
629 fp.write("# Parent %s\n" % hex(parents[1]))
627 fp.write(ctx.description().rstrip())
630 fp.write(ctx.description().rstrip())
628 fp.write("\n\n")
631 fp.write("\n\n")
629
632
630 diff(repo, prev, node, fp=fp, opts=opts)
633 diff(repo, prev, node, fp=fp, opts=opts)
631 if fp not in (sys.stdout, repo.ui):
634 if fp not in (sys.stdout, repo.ui):
632 fp.close()
635 fp.close()
633
636
634 for seqno, rev in enumerate(revs):
637 for seqno, rev in enumerate(revs):
635 single(rev, seqno+1, fp)
638 single(rev, seqno+1, fp)
636
639
637 def diffstat(patchlines):
640 def diffstat(patchlines):
638 if not util.find_in_path('diffstat', os.environ.get('PATH', '')):
641 if not util.find_in_path('diffstat', os.environ.get('PATH', '')):
639 return
642 return
640 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
643 fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt")
641 try:
644 try:
642 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
645 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name)
643 try:
646 try:
644 for line in patchlines: print >> p.tochild, line
647 for line in patchlines: print >> p.tochild, line
645 p.tochild.close()
648 p.tochild.close()
646 if p.wait(): return
649 if p.wait(): return
647 fp = os.fdopen(fd, 'r')
650 fp = os.fdopen(fd, 'r')
648 stat = []
651 stat = []
649 for line in fp: stat.append(line.lstrip())
652 for line in fp: stat.append(line.lstrip())
650 last = stat.pop()
653 last = stat.pop()
651 stat.insert(0, last)
654 stat.insert(0, last)
652 stat = ''.join(stat)
655 stat = ''.join(stat)
653 if stat.startswith('0 files'): raise ValueError
656 if stat.startswith('0 files'): raise ValueError
654 return stat
657 return stat
655 except: raise
658 except: raise
656 finally:
659 finally:
657 try: os.unlink(name)
660 try: os.unlink(name)
658 except: pass
661 except: pass
@@ -1,1527 +1,1531 b''
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import _
15 from i18n import _
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
17 import os, threading, time, calendar, ConfigParser, locale, glob
17 import os, threading, time, calendar, ConfigParser, locale, glob
18
18
19 try:
19 try:
20 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
20 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
21 or "ascii"
21 or "ascii"
22 except locale.Error:
22 except locale.Error:
23 _encoding = 'ascii'
23 _encoding = 'ascii'
24 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
24 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
25 _fallbackencoding = 'ISO-8859-1'
25 _fallbackencoding = 'ISO-8859-1'
26
26
27 def tolocal(s):
27 def tolocal(s):
28 """
28 """
29 Convert a string from internal UTF-8 to local encoding
29 Convert a string from internal UTF-8 to local encoding
30
30
31 All internal strings should be UTF-8 but some repos before the
31 All internal strings should be UTF-8 but some repos before the
32 implementation of locale support may contain latin1 or possibly
32 implementation of locale support may contain latin1 or possibly
33 other character sets. We attempt to decode everything strictly
33 other character sets. We attempt to decode everything strictly
34 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
34 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
35 replace unknown characters.
35 replace unknown characters.
36 """
36 """
37 for e in ('UTF-8', _fallbackencoding):
37 for e in ('UTF-8', _fallbackencoding):
38 try:
38 try:
39 u = s.decode(e) # attempt strict decoding
39 u = s.decode(e) # attempt strict decoding
40 return u.encode(_encoding, "replace")
40 return u.encode(_encoding, "replace")
41 except LookupError, k:
41 except LookupError, k:
42 raise Abort(_("%s, please check your locale settings") % k)
42 raise Abort(_("%s, please check your locale settings") % k)
43 except UnicodeDecodeError:
43 except UnicodeDecodeError:
44 pass
44 pass
45 u = s.decode("utf-8", "replace") # last ditch
45 u = s.decode("utf-8", "replace") # last ditch
46 return u.encode(_encoding, "replace")
46 return u.encode(_encoding, "replace")
47
47
48 def fromlocal(s):
48 def fromlocal(s):
49 """
49 """
50 Convert a string from the local character encoding to UTF-8
50 Convert a string from the local character encoding to UTF-8
51
51
52 We attempt to decode strings using the encoding mode set by
52 We attempt to decode strings using the encoding mode set by
53 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
53 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
54 characters will cause an error message. Other modes include
54 characters will cause an error message. Other modes include
55 'replace', which replaces unknown characters with a special
55 'replace', which replaces unknown characters with a special
56 Unicode character, and 'ignore', which drops the character.
56 Unicode character, and 'ignore', which drops the character.
57 """
57 """
58 try:
58 try:
59 return s.decode(_encoding, _encodingmode).encode("utf-8")
59 return s.decode(_encoding, _encodingmode).encode("utf-8")
60 except UnicodeDecodeError, inst:
60 except UnicodeDecodeError, inst:
61 sub = s[max(0, inst.start-10):inst.start+10]
61 sub = s[max(0, inst.start-10):inst.start+10]
62 raise Abort("decoding near '%s': %s!" % (sub, inst))
62 raise Abort("decoding near '%s': %s!" % (sub, inst))
63 except LookupError, k:
63 except LookupError, k:
64 raise Abort(_("%s, please check your locale settings") % k)
64 raise Abort(_("%s, please check your locale settings") % k)
65
65
66 def locallen(s):
66 def locallen(s):
67 """Find the length in characters of a local string"""
67 """Find the length in characters of a local string"""
68 return len(s.decode(_encoding, "replace"))
68 return len(s.decode(_encoding, "replace"))
69
69
70 def localsub(s, a, b=None):
70 def localsub(s, a, b=None):
71 try:
71 try:
72 u = s.decode(_encoding, _encodingmode)
72 u = s.decode(_encoding, _encodingmode)
73 if b is not None:
73 if b is not None:
74 u = u[a:b]
74 u = u[a:b]
75 else:
75 else:
76 u = u[:a]
76 u = u[:a]
77 return u.encode(_encoding, _encodingmode)
77 return u.encode(_encoding, _encodingmode)
78 except UnicodeDecodeError, inst:
78 except UnicodeDecodeError, inst:
79 sub = s[max(0, inst.start-10), inst.start+10]
79 sub = s[max(0, inst.start-10), inst.start+10]
80 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
80 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
81
81
82 # used by parsedate
82 # used by parsedate
83 defaultdateformats = (
83 defaultdateformats = (
84 '%Y-%m-%d %H:%M:%S',
84 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %I:%M:%S%p',
85 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %H:%M',
86 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %I:%M%p',
87 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d',
88 '%Y-%m-%d',
89 '%m-%d',
89 '%m-%d',
90 '%m/%d',
90 '%m/%d',
91 '%m/%d/%y',
91 '%m/%d/%y',
92 '%m/%d/%Y',
92 '%m/%d/%Y',
93 '%a %b %d %H:%M:%S %Y',
93 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %I:%M:%S%p %Y',
94 '%a %b %d %I:%M:%S%p %Y',
95 '%b %d %H:%M:%S %Y',
95 '%b %d %H:%M:%S %Y',
96 '%b %d %I:%M:%S%p %Y',
96 '%b %d %I:%M:%S%p %Y',
97 '%b %d %H:%M:%S',
97 '%b %d %H:%M:%S',
98 '%b %d %I:%M:%S%p',
98 '%b %d %I:%M:%S%p',
99 '%b %d %H:%M',
99 '%b %d %H:%M',
100 '%b %d %I:%M%p',
100 '%b %d %I:%M%p',
101 '%b %d %Y',
101 '%b %d %Y',
102 '%b %d',
102 '%b %d',
103 '%H:%M:%S',
103 '%H:%M:%S',
104 '%I:%M:%SP',
104 '%I:%M:%SP',
105 '%H:%M',
105 '%H:%M',
106 '%I:%M%p',
106 '%I:%M%p',
107 )
107 )
108
108
109 extendeddateformats = defaultdateformats + (
109 extendeddateformats = defaultdateformats + (
110 "%Y",
110 "%Y",
111 "%Y-%m",
111 "%Y-%m",
112 "%b",
112 "%b",
113 "%b %Y",
113 "%b %Y",
114 )
114 )
115
115
116 class SignalInterrupt(Exception):
116 class SignalInterrupt(Exception):
117 """Exception raised on SIGTERM and SIGHUP."""
117 """Exception raised on SIGTERM and SIGHUP."""
118
118
119 # differences from SafeConfigParser:
119 # differences from SafeConfigParser:
120 # - case-sensitive keys
120 # - case-sensitive keys
121 # - allows values that are not strings (this means that you may not
121 # - allows values that are not strings (this means that you may not
122 # be able to save the configuration to a file)
122 # be able to save the configuration to a file)
123 class configparser(ConfigParser.SafeConfigParser):
123 class configparser(ConfigParser.SafeConfigParser):
124 def optionxform(self, optionstr):
124 def optionxform(self, optionstr):
125 return optionstr
125 return optionstr
126
126
127 def set(self, section, option, value):
127 def set(self, section, option, value):
128 return ConfigParser.ConfigParser.set(self, section, option, value)
128 return ConfigParser.ConfigParser.set(self, section, option, value)
129
129
130 def _interpolate(self, section, option, rawval, vars):
130 def _interpolate(self, section, option, rawval, vars):
131 if not isinstance(rawval, basestring):
131 if not isinstance(rawval, basestring):
132 return rawval
132 return rawval
133 return ConfigParser.SafeConfigParser._interpolate(self, section,
133 return ConfigParser.SafeConfigParser._interpolate(self, section,
134 option, rawval, vars)
134 option, rawval, vars)
135
135
136 def cachefunc(func):
136 def cachefunc(func):
137 '''cache the result of function calls'''
137 '''cache the result of function calls'''
138 # XXX doesn't handle keywords args
138 # XXX doesn't handle keywords args
139 cache = {}
139 cache = {}
140 if func.func_code.co_argcount == 1:
140 if func.func_code.co_argcount == 1:
141 # we gain a small amount of time because
141 # we gain a small amount of time because
142 # we don't need to pack/unpack the list
142 # we don't need to pack/unpack the list
143 def f(arg):
143 def f(arg):
144 if arg not in cache:
144 if arg not in cache:
145 cache[arg] = func(arg)
145 cache[arg] = func(arg)
146 return cache[arg]
146 return cache[arg]
147 else:
147 else:
148 def f(*args):
148 def f(*args):
149 if args not in cache:
149 if args not in cache:
150 cache[args] = func(*args)
150 cache[args] = func(*args)
151 return cache[args]
151 return cache[args]
152
152
153 return f
153 return f
154
154
155 def pipefilter(s, cmd):
155 def pipefilter(s, cmd):
156 '''filter string S through command CMD, returning its output'''
156 '''filter string S through command CMD, returning its output'''
157 (pout, pin) = popen2.popen2(cmd, -1, 'b')
157 (pout, pin) = popen2.popen2(cmd, -1, 'b')
158 def writer():
158 def writer():
159 try:
159 try:
160 pin.write(s)
160 pin.write(s)
161 pin.close()
161 pin.close()
162 except IOError, inst:
162 except IOError, inst:
163 if inst.errno != errno.EPIPE:
163 if inst.errno != errno.EPIPE:
164 raise
164 raise
165
165
166 # we should use select instead on UNIX, but this will work on most
166 # we should use select instead on UNIX, but this will work on most
167 # systems, including Windows
167 # systems, including Windows
168 w = threading.Thread(target=writer)
168 w = threading.Thread(target=writer)
169 w.start()
169 w.start()
170 f = pout.read()
170 f = pout.read()
171 pout.close()
171 pout.close()
172 w.join()
172 w.join()
173 return f
173 return f
174
174
175 def tempfilter(s, cmd):
175 def tempfilter(s, cmd):
176 '''filter string S through a pair of temporary files with CMD.
176 '''filter string S through a pair of temporary files with CMD.
177 CMD is used as a template to create the real command to be run,
177 CMD is used as a template to create the real command to be run,
178 with the strings INFILE and OUTFILE replaced by the real names of
178 with the strings INFILE and OUTFILE replaced by the real names of
179 the temporary files generated.'''
179 the temporary files generated.'''
180 inname, outname = None, None
180 inname, outname = None, None
181 try:
181 try:
182 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
182 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
183 fp = os.fdopen(infd, 'wb')
183 fp = os.fdopen(infd, 'wb')
184 fp.write(s)
184 fp.write(s)
185 fp.close()
185 fp.close()
186 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
186 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
187 os.close(outfd)
187 os.close(outfd)
188 cmd = cmd.replace('INFILE', inname)
188 cmd = cmd.replace('INFILE', inname)
189 cmd = cmd.replace('OUTFILE', outname)
189 cmd = cmd.replace('OUTFILE', outname)
190 code = os.system(cmd)
190 code = os.system(cmd)
191 if code: raise Abort(_("command '%s' failed: %s") %
191 if code: raise Abort(_("command '%s' failed: %s") %
192 (cmd, explain_exit(code)))
192 (cmd, explain_exit(code)))
193 return open(outname, 'rb').read()
193 return open(outname, 'rb').read()
194 finally:
194 finally:
195 try:
195 try:
196 if inname: os.unlink(inname)
196 if inname: os.unlink(inname)
197 except: pass
197 except: pass
198 try:
198 try:
199 if outname: os.unlink(outname)
199 if outname: os.unlink(outname)
200 except: pass
200 except: pass
201
201
202 filtertable = {
202 filtertable = {
203 'tempfile:': tempfilter,
203 'tempfile:': tempfilter,
204 'pipe:': pipefilter,
204 'pipe:': pipefilter,
205 }
205 }
206
206
207 def filter(s, cmd):
207 def filter(s, cmd):
208 "filter a string through a command that transforms its input to its output"
208 "filter a string through a command that transforms its input to its output"
209 for name, fn in filtertable.iteritems():
209 for name, fn in filtertable.iteritems():
210 if cmd.startswith(name):
210 if cmd.startswith(name):
211 return fn(s, cmd[len(name):].lstrip())
211 return fn(s, cmd[len(name):].lstrip())
212 return pipefilter(s, cmd)
212 return pipefilter(s, cmd)
213
213
214 def binary(s):
214 def binary(s):
215 """return true if a string is binary data using diff's heuristic"""
215 """return true if a string is binary data using diff's heuristic"""
216 if s and '\0' in s[:4096]:
216 if s and '\0' in s[:4096]:
217 return True
217 return True
218 return False
218 return False
219
219
220 def unique(g):
220 def unique(g):
221 """return the uniq elements of iterable g"""
221 """return the uniq elements of iterable g"""
222 seen = {}
222 seen = {}
223 l = []
223 l = []
224 for f in g:
224 for f in g:
225 if f not in seen:
225 if f not in seen:
226 seen[f] = 1
226 seen[f] = 1
227 l.append(f)
227 l.append(f)
228 return l
228 return l
229
229
230 class Abort(Exception):
230 class Abort(Exception):
231 """Raised if a command needs to print an error and exit."""
231 """Raised if a command needs to print an error and exit."""
232
232
233 class UnexpectedOutput(Abort):
233 class UnexpectedOutput(Abort):
234 """Raised to print an error with part of output and exit."""
234 """Raised to print an error with part of output and exit."""
235
235
236 def always(fn): return True
236 def always(fn): return True
237 def never(fn): return False
237 def never(fn): return False
238
238
239 def expand_glob(pats):
239 def expand_glob(pats):
240 '''On Windows, expand the implicit globs in a list of patterns'''
240 '''On Windows, expand the implicit globs in a list of patterns'''
241 if os.name != 'nt':
241 if os.name != 'nt':
242 return list(pats)
242 return list(pats)
243 ret = []
243 ret = []
244 for p in pats:
244 for p in pats:
245 kind, name = patkind(p, None)
245 kind, name = patkind(p, None)
246 if kind is None:
246 if kind is None:
247 globbed = glob.glob(name)
247 globbed = glob.glob(name)
248 if globbed:
248 if globbed:
249 ret.extend(globbed)
249 ret.extend(globbed)
250 continue
250 continue
251 # if we couldn't expand the glob, just keep it around
251 # if we couldn't expand the glob, just keep it around
252 ret.append(p)
252 ret.append(p)
253 return ret
253 return ret
254
254
255 def patkind(name, dflt_pat='glob'):
255 def patkind(name, dflt_pat='glob'):
256 """Split a string into an optional pattern kind prefix and the
256 """Split a string into an optional pattern kind prefix and the
257 actual pattern."""
257 actual pattern."""
258 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
258 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
259 if name.startswith(prefix + ':'): return name.split(':', 1)
259 if name.startswith(prefix + ':'): return name.split(':', 1)
260 return dflt_pat, name
260 return dflt_pat, name
261
261
262 def globre(pat, head='^', tail='$'):
262 def globre(pat, head='^', tail='$'):
263 "convert a glob pattern into a regexp"
263 "convert a glob pattern into a regexp"
264 i, n = 0, len(pat)
264 i, n = 0, len(pat)
265 res = ''
265 res = ''
266 group = False
266 group = False
267 def peek(): return i < n and pat[i]
267 def peek(): return i < n and pat[i]
268 while i < n:
268 while i < n:
269 c = pat[i]
269 c = pat[i]
270 i = i+1
270 i = i+1
271 if c == '*':
271 if c == '*':
272 if peek() == '*':
272 if peek() == '*':
273 i += 1
273 i += 1
274 res += '.*'
274 res += '.*'
275 else:
275 else:
276 res += '[^/]*'
276 res += '[^/]*'
277 elif c == '?':
277 elif c == '?':
278 res += '.'
278 res += '.'
279 elif c == '[':
279 elif c == '[':
280 j = i
280 j = i
281 if j < n and pat[j] in '!]':
281 if j < n and pat[j] in '!]':
282 j += 1
282 j += 1
283 while j < n and pat[j] != ']':
283 while j < n and pat[j] != ']':
284 j += 1
284 j += 1
285 if j >= n:
285 if j >= n:
286 res += '\\['
286 res += '\\['
287 else:
287 else:
288 stuff = pat[i:j].replace('\\','\\\\')
288 stuff = pat[i:j].replace('\\','\\\\')
289 i = j + 1
289 i = j + 1
290 if stuff[0] == '!':
290 if stuff[0] == '!':
291 stuff = '^' + stuff[1:]
291 stuff = '^' + stuff[1:]
292 elif stuff[0] == '^':
292 elif stuff[0] == '^':
293 stuff = '\\' + stuff
293 stuff = '\\' + stuff
294 res = '%s[%s]' % (res, stuff)
294 res = '%s[%s]' % (res, stuff)
295 elif c == '{':
295 elif c == '{':
296 group = True
296 group = True
297 res += '(?:'
297 res += '(?:'
298 elif c == '}' and group:
298 elif c == '}' and group:
299 res += ')'
299 res += ')'
300 group = False
300 group = False
301 elif c == ',' and group:
301 elif c == ',' and group:
302 res += '|'
302 res += '|'
303 elif c == '\\':
303 elif c == '\\':
304 p = peek()
304 p = peek()
305 if p:
305 if p:
306 i += 1
306 i += 1
307 res += re.escape(p)
307 res += re.escape(p)
308 else:
308 else:
309 res += re.escape(c)
309 res += re.escape(c)
310 else:
310 else:
311 res += re.escape(c)
311 res += re.escape(c)
312 return head + res + tail
312 return head + res + tail
313
313
314 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
314 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
315
315
316 def pathto(root, n1, n2):
316 def pathto(root, n1, n2):
317 '''return the relative path from one place to another.
317 '''return the relative path from one place to another.
318 root should use os.sep to separate directories
318 root should use os.sep to separate directories
319 n1 should use os.sep to separate directories
319 n1 should use os.sep to separate directories
320 n2 should use "/" to separate directories
320 n2 should use "/" to separate directories
321 returns an os.sep-separated path.
321 returns an os.sep-separated path.
322
322
323 If n1 is a relative path, it's assumed it's
323 If n1 is a relative path, it's assumed it's
324 relative to root.
324 relative to root.
325 n2 should always be relative to root.
325 n2 should always be relative to root.
326 '''
326 '''
327 if not n1: return localpath(n2)
327 if not n1: return localpath(n2)
328 if os.path.isabs(n1):
328 if os.path.isabs(n1):
329 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
329 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
330 return os.path.join(root, localpath(n2))
330 return os.path.join(root, localpath(n2))
331 n2 = '/'.join((pconvert(root), n2))
331 n2 = '/'.join((pconvert(root), n2))
332 a, b = n1.split(os.sep), n2.split('/')
332 a, b = n1.split(os.sep), n2.split('/')
333 a.reverse()
333 a.reverse()
334 b.reverse()
334 b.reverse()
335 while a and b and a[-1] == b[-1]:
335 while a and b and a[-1] == b[-1]:
336 a.pop()
336 a.pop()
337 b.pop()
337 b.pop()
338 b.reverse()
338 b.reverse()
339 return os.sep.join((['..'] * len(a)) + b)
339 return os.sep.join((['..'] * len(a)) + b)
340
340
341 def canonpath(root, cwd, myname):
341 def canonpath(root, cwd, myname):
342 """return the canonical path of myname, given cwd and root"""
342 """return the canonical path of myname, given cwd and root"""
343 if root == os.sep:
343 if root == os.sep:
344 rootsep = os.sep
344 rootsep = os.sep
345 elif root.endswith(os.sep):
345 elif root.endswith(os.sep):
346 rootsep = root
346 rootsep = root
347 else:
347 else:
348 rootsep = root + os.sep
348 rootsep = root + os.sep
349 name = myname
349 name = myname
350 if not os.path.isabs(name):
350 if not os.path.isabs(name):
351 name = os.path.join(root, cwd, name)
351 name = os.path.join(root, cwd, name)
352 name = os.path.normpath(name)
352 name = os.path.normpath(name)
353 if name != rootsep and name.startswith(rootsep):
353 if name != rootsep and name.startswith(rootsep):
354 name = name[len(rootsep):]
354 name = name[len(rootsep):]
355 audit_path(name)
355 audit_path(name)
356 return pconvert(name)
356 return pconvert(name)
357 elif name == root:
357 elif name == root:
358 return ''
358 return ''
359 else:
359 else:
360 # Determine whether `name' is in the hierarchy at or beneath `root',
360 # Determine whether `name' is in the hierarchy at or beneath `root',
361 # by iterating name=dirname(name) until that causes no change (can't
361 # by iterating name=dirname(name) until that causes no change (can't
362 # check name == '/', because that doesn't work on windows). For each
362 # check name == '/', because that doesn't work on windows). For each
363 # `name', compare dev/inode numbers. If they match, the list `rel'
363 # `name', compare dev/inode numbers. If they match, the list `rel'
364 # holds the reversed list of components making up the relative file
364 # holds the reversed list of components making up the relative file
365 # name we want.
365 # name we want.
366 root_st = os.stat(root)
366 root_st = os.stat(root)
367 rel = []
367 rel = []
368 while True:
368 while True:
369 try:
369 try:
370 name_st = os.stat(name)
370 name_st = os.stat(name)
371 except OSError:
371 except OSError:
372 break
372 break
373 if samestat(name_st, root_st):
373 if samestat(name_st, root_st):
374 if not rel:
374 if not rel:
375 # name was actually the same as root (maybe a symlink)
375 # name was actually the same as root (maybe a symlink)
376 return ''
376 return ''
377 rel.reverse()
377 rel.reverse()
378 name = os.path.join(*rel)
378 name = os.path.join(*rel)
379 audit_path(name)
379 audit_path(name)
380 return pconvert(name)
380 return pconvert(name)
381 dirname, basename = os.path.split(name)
381 dirname, basename = os.path.split(name)
382 rel.append(basename)
382 rel.append(basename)
383 if dirname == name:
383 if dirname == name:
384 break
384 break
385 name = dirname
385 name = dirname
386
386
387 raise Abort('%s not under root' % myname)
387 raise Abort('%s not under root' % myname)
388
388
389 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
389 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
390 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
390 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
391
391
392 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
392 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
393 globbed=False, default=None):
393 globbed=False, default=None):
394 default = default or 'relpath'
394 default = default or 'relpath'
395 if default == 'relpath' and not globbed:
395 if default == 'relpath' and not globbed:
396 names = expand_glob(names)
396 names = expand_glob(names)
397 return _matcher(canonroot, cwd, names, inc, exc, default, src)
397 return _matcher(canonroot, cwd, names, inc, exc, default, src)
398
398
399 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
399 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
400 """build a function to match a set of file patterns
400 """build a function to match a set of file patterns
401
401
402 arguments:
402 arguments:
403 canonroot - the canonical root of the tree you're matching against
403 canonroot - the canonical root of the tree you're matching against
404 cwd - the current working directory, if relevant
404 cwd - the current working directory, if relevant
405 names - patterns to find
405 names - patterns to find
406 inc - patterns to include
406 inc - patterns to include
407 exc - patterns to exclude
407 exc - patterns to exclude
408 dflt_pat - if a pattern in names has no explicit type, assume this one
408 dflt_pat - if a pattern in names has no explicit type, assume this one
409 src - where these patterns came from (e.g. .hgignore)
409 src - where these patterns came from (e.g. .hgignore)
410
410
411 a pattern is one of:
411 a pattern is one of:
412 'glob:<glob>' - a glob relative to cwd
412 'glob:<glob>' - a glob relative to cwd
413 're:<regexp>' - a regular expression
413 're:<regexp>' - a regular expression
414 'path:<path>' - a path relative to canonroot
414 'path:<path>' - a path relative to canonroot
415 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
415 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
416 'relpath:<path>' - a path relative to cwd
416 'relpath:<path>' - a path relative to cwd
417 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
417 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
418 '<something>' - one of the cases above, selected by the dflt_pat argument
418 '<something>' - one of the cases above, selected by the dflt_pat argument
419
419
420 returns:
420 returns:
421 a 3-tuple containing
421 a 3-tuple containing
422 - list of roots (places where one should start a recursive walk of the fs);
422 - list of roots (places where one should start a recursive walk of the fs);
423 this often matches the explicit non-pattern names passed in, but also
423 this often matches the explicit non-pattern names passed in, but also
424 includes the initial part of glob: patterns that has no glob characters
424 includes the initial part of glob: patterns that has no glob characters
425 - a bool match(filename) function
425 - a bool match(filename) function
426 - a bool indicating if any patterns were passed in
426 - a bool indicating if any patterns were passed in
427 """
427 """
428
428
429 # a common case: no patterns at all
429 # a common case: no patterns at all
430 if not names and not inc and not exc:
430 if not names and not inc and not exc:
431 return [], always, False
431 return [], always, False
432
432
433 def contains_glob(name):
433 def contains_glob(name):
434 for c in name:
434 for c in name:
435 if c in _globchars: return True
435 if c in _globchars: return True
436 return False
436 return False
437
437
438 def regex(kind, name, tail):
438 def regex(kind, name, tail):
439 '''convert a pattern into a regular expression'''
439 '''convert a pattern into a regular expression'''
440 if not name:
440 if not name:
441 return ''
441 return ''
442 if kind == 're':
442 if kind == 're':
443 return name
443 return name
444 elif kind == 'path':
444 elif kind == 'path':
445 return '^' + re.escape(name) + '(?:/|$)'
445 return '^' + re.escape(name) + '(?:/|$)'
446 elif kind == 'relglob':
446 elif kind == 'relglob':
447 return globre(name, '(?:|.*/)', tail)
447 return globre(name, '(?:|.*/)', tail)
448 elif kind == 'relpath':
448 elif kind == 'relpath':
449 return re.escape(name) + '(?:/|$)'
449 return re.escape(name) + '(?:/|$)'
450 elif kind == 'relre':
450 elif kind == 'relre':
451 if name.startswith('^'):
451 if name.startswith('^'):
452 return name
452 return name
453 return '.*' + name
453 return '.*' + name
454 return globre(name, '', tail)
454 return globre(name, '', tail)
455
455
456 def matchfn(pats, tail):
456 def matchfn(pats, tail):
457 """build a matching function from a set of patterns"""
457 """build a matching function from a set of patterns"""
458 if not pats:
458 if not pats:
459 return
459 return
460 try:
460 try:
461 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
461 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
462 return re.compile(pat).match
462 return re.compile(pat).match
463 except re.error:
463 except re.error:
464 for k, p in pats:
464 for k, p in pats:
465 try:
465 try:
466 re.compile('(?:%s)' % regex(k, p, tail))
466 re.compile('(?:%s)' % regex(k, p, tail))
467 except re.error:
467 except re.error:
468 if src:
468 if src:
469 raise Abort("%s: invalid pattern (%s): %s" %
469 raise Abort("%s: invalid pattern (%s): %s" %
470 (src, k, p))
470 (src, k, p))
471 else:
471 else:
472 raise Abort("invalid pattern (%s): %s" % (k, p))
472 raise Abort("invalid pattern (%s): %s" % (k, p))
473 raise Abort("invalid pattern")
473 raise Abort("invalid pattern")
474
474
475 def globprefix(pat):
475 def globprefix(pat):
476 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
476 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
477 root = []
477 root = []
478 for p in pat.split('/'):
478 for p in pat.split('/'):
479 if contains_glob(p): break
479 if contains_glob(p): break
480 root.append(p)
480 root.append(p)
481 return '/'.join(root) or '.'
481 return '/'.join(root) or '.'
482
482
483 def normalizepats(names, default):
483 def normalizepats(names, default):
484 pats = []
484 pats = []
485 roots = []
485 roots = []
486 anypats = False
486 anypats = False
487 for kind, name in [patkind(p, default) for p in names]:
487 for kind, name in [patkind(p, default) for p in names]:
488 if kind in ('glob', 'relpath'):
488 if kind in ('glob', 'relpath'):
489 name = canonpath(canonroot, cwd, name)
489 name = canonpath(canonroot, cwd, name)
490 elif kind in ('relglob', 'path'):
490 elif kind in ('relglob', 'path'):
491 name = normpath(name)
491 name = normpath(name)
492
492
493 pats.append((kind, name))
493 pats.append((kind, name))
494
494
495 if kind in ('glob', 're', 'relglob', 'relre'):
495 if kind in ('glob', 're', 'relglob', 'relre'):
496 anypats = True
496 anypats = True
497
497
498 if kind == 'glob':
498 if kind == 'glob':
499 root = globprefix(name)
499 root = globprefix(name)
500 roots.append(root)
500 roots.append(root)
501 elif kind in ('relpath', 'path'):
501 elif kind in ('relpath', 'path'):
502 roots.append(name or '.')
502 roots.append(name or '.')
503 elif kind == 'relglob':
503 elif kind == 'relglob':
504 roots.append('.')
504 roots.append('.')
505 return roots, pats, anypats
505 return roots, pats, anypats
506
506
507 roots, pats, anypats = normalizepats(names, dflt_pat)
507 roots, pats, anypats = normalizepats(names, dflt_pat)
508
508
509 patmatch = matchfn(pats, '$') or always
509 patmatch = matchfn(pats, '$') or always
510 incmatch = always
510 incmatch = always
511 if inc:
511 if inc:
512 dummy, inckinds, dummy = normalizepats(inc, 'glob')
512 dummy, inckinds, dummy = normalizepats(inc, 'glob')
513 incmatch = matchfn(inckinds, '(?:/|$)')
513 incmatch = matchfn(inckinds, '(?:/|$)')
514 excmatch = lambda fn: False
514 excmatch = lambda fn: False
515 if exc:
515 if exc:
516 dummy, exckinds, dummy = normalizepats(exc, 'glob')
516 dummy, exckinds, dummy = normalizepats(exc, 'glob')
517 excmatch = matchfn(exckinds, '(?:/|$)')
517 excmatch = matchfn(exckinds, '(?:/|$)')
518
518
519 if not names and inc and not exc:
519 if not names and inc and not exc:
520 # common case: hgignore patterns
520 # common case: hgignore patterns
521 match = incmatch
521 match = incmatch
522 else:
522 else:
523 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
523 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
524
524
525 return (roots, match, (inc or exc or anypats) and True)
525 return (roots, match, (inc or exc or anypats) and True)
526
526
527 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
527 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
528 '''enhanced shell command execution.
528 '''enhanced shell command execution.
529 run with environment maybe modified, maybe in different dir.
529 run with environment maybe modified, maybe in different dir.
530
530
531 if command fails and onerr is None, return status. if ui object,
531 if command fails and onerr is None, return status. if ui object,
532 print error message and return status, else raise onerr object as
532 print error message and return status, else raise onerr object as
533 exception.'''
533 exception.'''
534 def py2shell(val):
534 def py2shell(val):
535 'convert python object into string that is useful to shell'
535 'convert python object into string that is useful to shell'
536 if val in (None, False):
536 if val in (None, False):
537 return '0'
537 return '0'
538 if val == True:
538 if val == True:
539 return '1'
539 return '1'
540 return str(val)
540 return str(val)
541 oldenv = {}
541 oldenv = {}
542 for k in environ:
542 for k in environ:
543 oldenv[k] = os.environ.get(k)
543 oldenv[k] = os.environ.get(k)
544 if cwd is not None:
544 if cwd is not None:
545 oldcwd = os.getcwd()
545 oldcwd = os.getcwd()
546 origcmd = cmd
546 origcmd = cmd
547 if os.name == 'nt':
547 if os.name == 'nt':
548 cmd = '"%s"' % cmd
548 cmd = '"%s"' % cmd
549 try:
549 try:
550 for k, v in environ.iteritems():
550 for k, v in environ.iteritems():
551 os.environ[k] = py2shell(v)
551 os.environ[k] = py2shell(v)
552 if cwd is not None and oldcwd != cwd:
552 if cwd is not None and oldcwd != cwd:
553 os.chdir(cwd)
553 os.chdir(cwd)
554 rc = os.system(cmd)
554 rc = os.system(cmd)
555 if rc and onerr:
555 if rc and onerr:
556 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
556 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
557 explain_exit(rc)[0])
557 explain_exit(rc)[0])
558 if errprefix:
558 if errprefix:
559 errmsg = '%s: %s' % (errprefix, errmsg)
559 errmsg = '%s: %s' % (errprefix, errmsg)
560 try:
560 try:
561 onerr.warn(errmsg + '\n')
561 onerr.warn(errmsg + '\n')
562 except AttributeError:
562 except AttributeError:
563 raise onerr(errmsg)
563 raise onerr(errmsg)
564 return rc
564 return rc
565 finally:
565 finally:
566 for k, v in oldenv.iteritems():
566 for k, v in oldenv.iteritems():
567 if v is None:
567 if v is None:
568 del os.environ[k]
568 del os.environ[k]
569 else:
569 else:
570 os.environ[k] = v
570 os.environ[k] = v
571 if cwd is not None and oldcwd != cwd:
571 if cwd is not None and oldcwd != cwd:
572 os.chdir(oldcwd)
572 os.chdir(oldcwd)
573
573
574 # os.path.lexists is not available on python2.3
574 # os.path.lexists is not available on python2.3
575 def lexists(filename):
575 def lexists(filename):
576 "test whether a file with this name exists. does not follow symlinks"
576 "test whether a file with this name exists. does not follow symlinks"
577 try:
577 try:
578 os.lstat(filename)
578 os.lstat(filename)
579 except:
579 except:
580 return False
580 return False
581 return True
581 return True
582
582
583 def rename(src, dst):
583 def rename(src, dst):
584 """forcibly rename a file"""
584 """forcibly rename a file"""
585 try:
585 try:
586 os.rename(src, dst)
586 os.rename(src, dst)
587 except OSError, err:
587 except OSError, err:
588 # on windows, rename to existing file is not allowed, so we
588 # on windows, rename to existing file is not allowed, so we
589 # must delete destination first. but if file is open, unlink
589 # must delete destination first. but if file is open, unlink
590 # schedules it for delete but does not delete it. rename
590 # schedules it for delete but does not delete it. rename
591 # happens immediately even for open files, so we create
591 # happens immediately even for open files, so we create
592 # temporary file, delete it, rename destination to that name,
592 # temporary file, delete it, rename destination to that name,
593 # then delete that. then rename is safe to do.
593 # then delete that. then rename is safe to do.
594 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
594 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
595 os.close(fd)
595 os.close(fd)
596 os.unlink(temp)
596 os.unlink(temp)
597 os.rename(dst, temp)
597 os.rename(dst, temp)
598 os.unlink(temp)
598 os.unlink(temp)
599 os.rename(src, dst)
599 os.rename(src, dst)
600
600
601 def unlink(f):
601 def unlink(f):
602 """unlink and remove the directory if it is empty"""
602 """unlink and remove the directory if it is empty"""
603 os.unlink(f)
603 os.unlink(f)
604 # try removing directories that might now be empty
604 # try removing directories that might now be empty
605 try:
605 try:
606 os.removedirs(os.path.dirname(f))
606 os.removedirs(os.path.dirname(f))
607 except OSError:
607 except OSError:
608 pass
608 pass
609
609
610 def copyfile(src, dest):
610 def copyfile(src, dest):
611 "copy a file, preserving mode"
611 "copy a file, preserving mode"
612 if os.path.islink(src):
612 if os.path.islink(src):
613 try:
613 try:
614 os.unlink(dest)
614 os.unlink(dest)
615 except:
615 except:
616 pass
616 pass
617 os.symlink(os.readlink(src), dest)
617 os.symlink(os.readlink(src), dest)
618 else:
618 else:
619 try:
619 try:
620 shutil.copyfile(src, dest)
620 shutil.copyfile(src, dest)
621 shutil.copymode(src, dest)
621 shutil.copymode(src, dest)
622 except shutil.Error, inst:
622 except shutil.Error, inst:
623 raise Abort(str(inst))
623 raise Abort(str(inst))
624
624
625 def copyfiles(src, dst, hardlink=None):
625 def copyfiles(src, dst, hardlink=None):
626 """Copy a directory tree using hardlinks if possible"""
626 """Copy a directory tree using hardlinks if possible"""
627
627
628 if hardlink is None:
628 if hardlink is None:
629 hardlink = (os.stat(src).st_dev ==
629 hardlink = (os.stat(src).st_dev ==
630 os.stat(os.path.dirname(dst)).st_dev)
630 os.stat(os.path.dirname(dst)).st_dev)
631
631
632 if os.path.isdir(src):
632 if os.path.isdir(src):
633 os.mkdir(dst)
633 os.mkdir(dst)
634 for name in os.listdir(src):
634 for name in os.listdir(src):
635 srcname = os.path.join(src, name)
635 srcname = os.path.join(src, name)
636 dstname = os.path.join(dst, name)
636 dstname = os.path.join(dst, name)
637 copyfiles(srcname, dstname, hardlink)
637 copyfiles(srcname, dstname, hardlink)
638 else:
638 else:
639 if hardlink:
639 if hardlink:
640 try:
640 try:
641 os_link(src, dst)
641 os_link(src, dst)
642 except (IOError, OSError):
642 except (IOError, OSError):
643 hardlink = False
643 hardlink = False
644 shutil.copy(src, dst)
644 shutil.copy(src, dst)
645 else:
645 else:
646 shutil.copy(src, dst)
646 shutil.copy(src, dst)
647
647
648 def audit_path(path):
648 def audit_path(path):
649 """Abort if path contains dangerous components"""
649 """Abort if path contains dangerous components"""
650 parts = os.path.normcase(path).split(os.sep)
650 parts = os.path.normcase(path).split(os.sep)
651 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
651 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
652 or os.pardir in parts):
652 or os.pardir in parts):
653 raise Abort(_("path contains illegal component: %s\n") % path)
653 raise Abort(_("path contains illegal component: %s\n") % path)
654
654
655 def _makelock_file(info, pathname):
655 def _makelock_file(info, pathname):
656 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
656 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
657 os.write(ld, info)
657 os.write(ld, info)
658 os.close(ld)
658 os.close(ld)
659
659
660 def _readlock_file(pathname):
660 def _readlock_file(pathname):
661 return posixfile(pathname).read()
661 return posixfile(pathname).read()
662
662
663 def nlinks(pathname):
663 def nlinks(pathname):
664 """Return number of hardlinks for the given file."""
664 """Return number of hardlinks for the given file."""
665 return os.lstat(pathname).st_nlink
665 return os.lstat(pathname).st_nlink
666
666
667 if hasattr(os, 'link'):
667 if hasattr(os, 'link'):
668 os_link = os.link
668 os_link = os.link
669 else:
669 else:
670 def os_link(src, dst):
670 def os_link(src, dst):
671 raise OSError(0, _("Hardlinks not supported"))
671 raise OSError(0, _("Hardlinks not supported"))
672
672
673 def fstat(fp):
673 def fstat(fp):
674 '''stat file object that may not have fileno method.'''
674 '''stat file object that may not have fileno method.'''
675 try:
675 try:
676 return os.fstat(fp.fileno())
676 return os.fstat(fp.fileno())
677 except AttributeError:
677 except AttributeError:
678 return os.stat(fp.name)
678 return os.stat(fp.name)
679
679
680 posixfile = file
680 posixfile = file
681
681
682 def is_win_9x():
682 def is_win_9x():
683 '''return true if run on windows 95, 98 or me.'''
683 '''return true if run on windows 95, 98 or me.'''
684 try:
684 try:
685 return sys.getwindowsversion()[3] == 1
685 return sys.getwindowsversion()[3] == 1
686 except AttributeError:
686 except AttributeError:
687 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
687 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
688
688
689 getuser_fallback = None
689 getuser_fallback = None
690
690
691 def getuser():
691 def getuser():
692 '''return name of current user'''
692 '''return name of current user'''
693 try:
693 try:
694 return getpass.getuser()
694 return getpass.getuser()
695 except ImportError:
695 except ImportError:
696 # import of pwd will fail on windows - try fallback
696 # import of pwd will fail on windows - try fallback
697 if getuser_fallback:
697 if getuser_fallback:
698 return getuser_fallback()
698 return getuser_fallback()
699 # raised if win32api not available
699 # raised if win32api not available
700 raise Abort(_('user name not available - set USERNAME '
700 raise Abort(_('user name not available - set USERNAME '
701 'environment variable'))
701 'environment variable'))
702
702
703 def username(uid=None):
703 def username(uid=None):
704 """Return the name of the user with the given uid.
704 """Return the name of the user with the given uid.
705
705
706 If uid is None, return the name of the current user."""
706 If uid is None, return the name of the current user."""
707 try:
707 try:
708 import pwd
708 import pwd
709 if uid is None:
709 if uid is None:
710 uid = os.getuid()
710 uid = os.getuid()
711 try:
711 try:
712 return pwd.getpwuid(uid)[0]
712 return pwd.getpwuid(uid)[0]
713 except KeyError:
713 except KeyError:
714 return str(uid)
714 return str(uid)
715 except ImportError:
715 except ImportError:
716 return None
716 return None
717
717
718 def groupname(gid=None):
718 def groupname(gid=None):
719 """Return the name of the group with the given gid.
719 """Return the name of the group with the given gid.
720
720
721 If gid is None, return the name of the current group."""
721 If gid is None, return the name of the current group."""
722 try:
722 try:
723 import grp
723 import grp
724 if gid is None:
724 if gid is None:
725 gid = os.getgid()
725 gid = os.getgid()
726 try:
726 try:
727 return grp.getgrgid(gid)[0]
727 return grp.getgrgid(gid)[0]
728 except KeyError:
728 except KeyError:
729 return str(gid)
729 return str(gid)
730 except ImportError:
730 except ImportError:
731 return None
731 return None
732
732
733 # File system features
733 # File system features
734
734
735 def checkfolding(path):
735 def checkfolding(path):
736 """
736 """
737 Check whether the given path is on a case-sensitive filesystem
737 Check whether the given path is on a case-sensitive filesystem
738
738
739 Requires a path (like /foo/.hg) ending with a foldable final
739 Requires a path (like /foo/.hg) ending with a foldable final
740 directory component.
740 directory component.
741 """
741 """
742 s1 = os.stat(path)
742 s1 = os.stat(path)
743 d, b = os.path.split(path)
743 d, b = os.path.split(path)
744 p2 = os.path.join(d, b.upper())
744 p2 = os.path.join(d, b.upper())
745 if path == p2:
745 if path == p2:
746 p2 = os.path.join(d, b.lower())
746 p2 = os.path.join(d, b.lower())
747 try:
747 try:
748 s2 = os.stat(p2)
748 s2 = os.stat(p2)
749 if s2 == s1:
749 if s2 == s1:
750 return False
750 return False
751 return True
751 return True
752 except:
752 except:
753 return True
753 return True
754
754
755 def checkexec(path):
755 def checkexec(path):
756 """
756 """
757 Check whether the given path is on a filesystem with UNIX-like exec flags
757 Check whether the given path is on a filesystem with UNIX-like exec flags
758
758
759 Requires a directory (like /foo/.hg)
759 Requires a directory (like /foo/.hg)
760 """
760 """
761 fh, fn = tempfile.mkstemp("", "", path)
761 fh, fn = tempfile.mkstemp("", "", path)
762 os.close(fh)
762 os.close(fh)
763 m = os.stat(fn).st_mode
763 m = os.stat(fn).st_mode
764 os.chmod(fn, m ^ 0111)
764 os.chmod(fn, m ^ 0111)
765 r = (os.stat(fn).st_mode != m)
765 r = (os.stat(fn).st_mode != m)
766 os.unlink(fn)
766 os.unlink(fn)
767 return r
767 return r
768
768
769 def execfunc(path, fallback):
769 def execfunc(path, fallback):
770 '''return an is_exec() function with default to fallback'''
770 '''return an is_exec() function with default to fallback'''
771 if checkexec(path):
771 if checkexec(path):
772 return lambda x: is_exec(os.path.join(path, x))
772 return lambda x: is_exec(os.path.join(path, x))
773 return fallback
773 return fallback
774
774
775 def checklink(path):
775 def checklink(path):
776 """check whether the given path is on a symlink-capable filesystem"""
776 """check whether the given path is on a symlink-capable filesystem"""
777 # mktemp is not racy because symlink creation will fail if the
777 # mktemp is not racy because symlink creation will fail if the
778 # file already exists
778 # file already exists
779 name = tempfile.mktemp(dir=path)
779 name = tempfile.mktemp(dir=path)
780 try:
780 try:
781 os.symlink(".", name)
781 os.symlink(".", name)
782 os.unlink(name)
782 os.unlink(name)
783 return True
783 return True
784 except (OSError, AttributeError):
784 except (OSError, AttributeError):
785 return False
785 return False
786
786
787 def linkfunc(path, fallback):
787 def linkfunc(path, fallback):
788 '''return an is_link() function with default to fallback'''
788 '''return an is_link() function with default to fallback'''
789 if checklink(path):
789 if checklink(path):
790 return lambda x: os.path.islink(os.path.join(path, x))
790 return lambda x: os.path.islink(os.path.join(path, x))
791 return fallback
791 return fallback
792
792
793 _umask = os.umask(0)
793 _umask = os.umask(0)
794 os.umask(_umask)
794 os.umask(_umask)
795
795
796 def needbinarypatch():
797 """return True if patches should be applied in binary mode by default."""
798 return os.name == 'nt'
799
796 # Platform specific variants
800 # Platform specific variants
797 if os.name == 'nt':
801 if os.name == 'nt':
798 import msvcrt
802 import msvcrt
799 nulldev = 'NUL:'
803 nulldev = 'NUL:'
800
804
801 class winstdout:
805 class winstdout:
802 '''stdout on windows misbehaves if sent through a pipe'''
806 '''stdout on windows misbehaves if sent through a pipe'''
803
807
804 def __init__(self, fp):
808 def __init__(self, fp):
805 self.fp = fp
809 self.fp = fp
806
810
807 def __getattr__(self, key):
811 def __getattr__(self, key):
808 return getattr(self.fp, key)
812 return getattr(self.fp, key)
809
813
810 def close(self):
814 def close(self):
811 try:
815 try:
812 self.fp.close()
816 self.fp.close()
813 except: pass
817 except: pass
814
818
815 def write(self, s):
819 def write(self, s):
816 try:
820 try:
817 return self.fp.write(s)
821 return self.fp.write(s)
818 except IOError, inst:
822 except IOError, inst:
819 if inst.errno != 0: raise
823 if inst.errno != 0: raise
820 self.close()
824 self.close()
821 raise IOError(errno.EPIPE, 'Broken pipe')
825 raise IOError(errno.EPIPE, 'Broken pipe')
822
826
823 def flush(self):
827 def flush(self):
824 try:
828 try:
825 return self.fp.flush()
829 return self.fp.flush()
826 except IOError, inst:
830 except IOError, inst:
827 if inst.errno != errno.EINVAL: raise
831 if inst.errno != errno.EINVAL: raise
828 self.close()
832 self.close()
829 raise IOError(errno.EPIPE, 'Broken pipe')
833 raise IOError(errno.EPIPE, 'Broken pipe')
830
834
831 sys.stdout = winstdout(sys.stdout)
835 sys.stdout = winstdout(sys.stdout)
832
836
833 def system_rcpath():
837 def system_rcpath():
834 try:
838 try:
835 return system_rcpath_win32()
839 return system_rcpath_win32()
836 except:
840 except:
837 return [r'c:\mercurial\mercurial.ini']
841 return [r'c:\mercurial\mercurial.ini']
838
842
839 def user_rcpath():
843 def user_rcpath():
840 '''return os-specific hgrc search path to the user dir'''
844 '''return os-specific hgrc search path to the user dir'''
841 try:
845 try:
842 userrc = user_rcpath_win32()
846 userrc = user_rcpath_win32()
843 except:
847 except:
844 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
848 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
845 path = [userrc]
849 path = [userrc]
846 userprofile = os.environ.get('USERPROFILE')
850 userprofile = os.environ.get('USERPROFILE')
847 if userprofile:
851 if userprofile:
848 path.append(os.path.join(userprofile, 'mercurial.ini'))
852 path.append(os.path.join(userprofile, 'mercurial.ini'))
849 return path
853 return path
850
854
851 def parse_patch_output(output_line):
855 def parse_patch_output(output_line):
852 """parses the output produced by patch and returns the file name"""
856 """parses the output produced by patch and returns the file name"""
853 pf = output_line[14:]
857 pf = output_line[14:]
854 if pf[0] == '`':
858 if pf[0] == '`':
855 pf = pf[1:-1] # Remove the quotes
859 pf = pf[1:-1] # Remove the quotes
856 return pf
860 return pf
857
861
858 def testpid(pid):
862 def testpid(pid):
859 '''return False if pid dead, True if running or not known'''
863 '''return False if pid dead, True if running or not known'''
860 return True
864 return True
861
865
862 def set_exec(f, mode):
866 def set_exec(f, mode):
863 pass
867 pass
864
868
865 def set_link(f, mode):
869 def set_link(f, mode):
866 pass
870 pass
867
871
868 def set_binary(fd):
872 def set_binary(fd):
869 msvcrt.setmode(fd.fileno(), os.O_BINARY)
873 msvcrt.setmode(fd.fileno(), os.O_BINARY)
870
874
871 def pconvert(path):
875 def pconvert(path):
872 return path.replace("\\", "/")
876 return path.replace("\\", "/")
873
877
874 def localpath(path):
878 def localpath(path):
875 return path.replace('/', '\\')
879 return path.replace('/', '\\')
876
880
877 def normpath(path):
881 def normpath(path):
878 return pconvert(os.path.normpath(path))
882 return pconvert(os.path.normpath(path))
879
883
880 makelock = _makelock_file
884 makelock = _makelock_file
881 readlock = _readlock_file
885 readlock = _readlock_file
882
886
883 def samestat(s1, s2):
887 def samestat(s1, s2):
884 return False
888 return False
885
889
886 # A sequence of backslashes is special iff it precedes a double quote:
890 # A sequence of backslashes is special iff it precedes a double quote:
887 # - if there's an even number of backslashes, the double quote is not
891 # - if there's an even number of backslashes, the double quote is not
888 # quoted (i.e. it ends the quoted region)
892 # quoted (i.e. it ends the quoted region)
889 # - if there's an odd number of backslashes, the double quote is quoted
893 # - if there's an odd number of backslashes, the double quote is quoted
890 # - in both cases, every pair of backslashes is unquoted into a single
894 # - in both cases, every pair of backslashes is unquoted into a single
891 # backslash
895 # backslash
892 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
896 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
893 # So, to quote a string, we must surround it in double quotes, double
897 # So, to quote a string, we must surround it in double quotes, double
894 # the number of backslashes that preceed double quotes and add another
898 # the number of backslashes that preceed double quotes and add another
895 # backslash before every double quote (being careful with the double
899 # backslash before every double quote (being careful with the double
896 # quote we've appended to the end)
900 # quote we've appended to the end)
897 _quotere = None
901 _quotere = None
898 def shellquote(s):
902 def shellquote(s):
899 global _quotere
903 global _quotere
900 if _quotere is None:
904 if _quotere is None:
901 _quotere = re.compile(r'(\\*)("|\\$)')
905 _quotere = re.compile(r'(\\*)("|\\$)')
902 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
906 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
903
907
904 def explain_exit(code):
908 def explain_exit(code):
905 return _("exited with status %d") % code, code
909 return _("exited with status %d") % code, code
906
910
907 # if you change this stub into a real check, please try to implement the
911 # if you change this stub into a real check, please try to implement the
908 # username and groupname functions above, too.
912 # username and groupname functions above, too.
909 def isowner(fp, st=None):
913 def isowner(fp, st=None):
910 return True
914 return True
911
915
912 def find_in_path(name, path, default=None):
916 def find_in_path(name, path, default=None):
913 '''find name in search path. path can be string (will be split
917 '''find name in search path. path can be string (will be split
914 with os.pathsep), or iterable thing that returns strings. if name
918 with os.pathsep), or iterable thing that returns strings. if name
915 found, return path to name. else return default. name is looked up
919 found, return path to name. else return default. name is looked up
916 using cmd.exe rules, using PATHEXT.'''
920 using cmd.exe rules, using PATHEXT.'''
917 if isinstance(path, str):
921 if isinstance(path, str):
918 path = path.split(os.pathsep)
922 path = path.split(os.pathsep)
919
923
920 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
924 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
921 pathext = pathext.lower().split(os.pathsep)
925 pathext = pathext.lower().split(os.pathsep)
922 isexec = os.path.splitext(name)[1].lower() in pathext
926 isexec = os.path.splitext(name)[1].lower() in pathext
923
927
924 for p in path:
928 for p in path:
925 p_name = os.path.join(p, name)
929 p_name = os.path.join(p, name)
926
930
927 if isexec and os.path.exists(p_name):
931 if isexec and os.path.exists(p_name):
928 return p_name
932 return p_name
929
933
930 for ext in pathext:
934 for ext in pathext:
931 p_name_ext = p_name + ext
935 p_name_ext = p_name + ext
932 if os.path.exists(p_name_ext):
936 if os.path.exists(p_name_ext):
933 return p_name_ext
937 return p_name_ext
934 return default
938 return default
935
939
936 try:
940 try:
937 # override functions with win32 versions if possible
941 # override functions with win32 versions if possible
938 from util_win32 import *
942 from util_win32 import *
939 if not is_win_9x():
943 if not is_win_9x():
940 posixfile = posixfile_nt
944 posixfile = posixfile_nt
941 except ImportError:
945 except ImportError:
942 pass
946 pass
943
947
944 else:
948 else:
945 nulldev = '/dev/null'
949 nulldev = '/dev/null'
946
950
947 def rcfiles(path):
951 def rcfiles(path):
948 rcs = [os.path.join(path, 'hgrc')]
952 rcs = [os.path.join(path, 'hgrc')]
949 rcdir = os.path.join(path, 'hgrc.d')
953 rcdir = os.path.join(path, 'hgrc.d')
950 try:
954 try:
951 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
955 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
952 if f.endswith(".rc")])
956 if f.endswith(".rc")])
953 except OSError:
957 except OSError:
954 pass
958 pass
955 return rcs
959 return rcs
956
960
957 def system_rcpath():
961 def system_rcpath():
958 path = []
962 path = []
959 # old mod_python does not set sys.argv
963 # old mod_python does not set sys.argv
960 if len(getattr(sys, 'argv', [])) > 0:
964 if len(getattr(sys, 'argv', [])) > 0:
961 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
965 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
962 '/../etc/mercurial'))
966 '/../etc/mercurial'))
963 path.extend(rcfiles('/etc/mercurial'))
967 path.extend(rcfiles('/etc/mercurial'))
964 return path
968 return path
965
969
966 def user_rcpath():
970 def user_rcpath():
967 return [os.path.expanduser('~/.hgrc')]
971 return [os.path.expanduser('~/.hgrc')]
968
972
969 def parse_patch_output(output_line):
973 def parse_patch_output(output_line):
970 """parses the output produced by patch and returns the file name"""
974 """parses the output produced by patch and returns the file name"""
971 pf = output_line[14:]
975 pf = output_line[14:]
972 if pf.startswith("'") and pf.endswith("'") and " " in pf:
976 if pf.startswith("'") and pf.endswith("'") and " " in pf:
973 pf = pf[1:-1] # Remove the quotes
977 pf = pf[1:-1] # Remove the quotes
974 return pf
978 return pf
975
979
976 def is_exec(f):
980 def is_exec(f):
977 """check whether a file is executable"""
981 """check whether a file is executable"""
978 return (os.lstat(f).st_mode & 0100 != 0)
982 return (os.lstat(f).st_mode & 0100 != 0)
979
983
980 def set_exec(f, mode):
984 def set_exec(f, mode):
981 s = os.lstat(f).st_mode
985 s = os.lstat(f).st_mode
982 if (s & 0100 != 0) == mode:
986 if (s & 0100 != 0) == mode:
983 return
987 return
984 if mode:
988 if mode:
985 # Turn on +x for every +r bit when making a file executable
989 # Turn on +x for every +r bit when making a file executable
986 # and obey umask.
990 # and obey umask.
987 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
991 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
988 else:
992 else:
989 os.chmod(f, s & 0666)
993 os.chmod(f, s & 0666)
990
994
991 def set_link(f, mode):
995 def set_link(f, mode):
992 """make a file a symbolic link/regular file
996 """make a file a symbolic link/regular file
993
997
994 if a file is changed to a link, its contents become the link data
998 if a file is changed to a link, its contents become the link data
995 if a link is changed to a file, its link data become its contents
999 if a link is changed to a file, its link data become its contents
996 """
1000 """
997
1001
998 m = os.path.islink(f)
1002 m = os.path.islink(f)
999 if m == bool(mode):
1003 if m == bool(mode):
1000 return
1004 return
1001
1005
1002 if mode: # switch file to link
1006 if mode: # switch file to link
1003 data = file(f).read()
1007 data = file(f).read()
1004 os.unlink(f)
1008 os.unlink(f)
1005 os.symlink(data, f)
1009 os.symlink(data, f)
1006 else:
1010 else:
1007 data = os.readlink(f)
1011 data = os.readlink(f)
1008 os.unlink(f)
1012 os.unlink(f)
1009 file(f, "w").write(data)
1013 file(f, "w").write(data)
1010
1014
1011 def set_binary(fd):
1015 def set_binary(fd):
1012 pass
1016 pass
1013
1017
1014 def pconvert(path):
1018 def pconvert(path):
1015 return path
1019 return path
1016
1020
1017 def localpath(path):
1021 def localpath(path):
1018 return path
1022 return path
1019
1023
1020 normpath = os.path.normpath
1024 normpath = os.path.normpath
1021 samestat = os.path.samestat
1025 samestat = os.path.samestat
1022
1026
1023 def makelock(info, pathname):
1027 def makelock(info, pathname):
1024 try:
1028 try:
1025 os.symlink(info, pathname)
1029 os.symlink(info, pathname)
1026 except OSError, why:
1030 except OSError, why:
1027 if why.errno == errno.EEXIST:
1031 if why.errno == errno.EEXIST:
1028 raise
1032 raise
1029 else:
1033 else:
1030 _makelock_file(info, pathname)
1034 _makelock_file(info, pathname)
1031
1035
1032 def readlock(pathname):
1036 def readlock(pathname):
1033 try:
1037 try:
1034 return os.readlink(pathname)
1038 return os.readlink(pathname)
1035 except OSError, why:
1039 except OSError, why:
1036 if why.errno == errno.EINVAL:
1040 if why.errno == errno.EINVAL:
1037 return _readlock_file(pathname)
1041 return _readlock_file(pathname)
1038 else:
1042 else:
1039 raise
1043 raise
1040
1044
1041 def shellquote(s):
1045 def shellquote(s):
1042 return "'%s'" % s.replace("'", "'\\''")
1046 return "'%s'" % s.replace("'", "'\\''")
1043
1047
1044 def testpid(pid):
1048 def testpid(pid):
1045 '''return False if pid dead, True if running or not sure'''
1049 '''return False if pid dead, True if running or not sure'''
1046 try:
1050 try:
1047 os.kill(pid, 0)
1051 os.kill(pid, 0)
1048 return True
1052 return True
1049 except OSError, inst:
1053 except OSError, inst:
1050 return inst.errno != errno.ESRCH
1054 return inst.errno != errno.ESRCH
1051
1055
1052 def explain_exit(code):
1056 def explain_exit(code):
1053 """return a 2-tuple (desc, code) describing a process's status"""
1057 """return a 2-tuple (desc, code) describing a process's status"""
1054 if os.WIFEXITED(code):
1058 if os.WIFEXITED(code):
1055 val = os.WEXITSTATUS(code)
1059 val = os.WEXITSTATUS(code)
1056 return _("exited with status %d") % val, val
1060 return _("exited with status %d") % val, val
1057 elif os.WIFSIGNALED(code):
1061 elif os.WIFSIGNALED(code):
1058 val = os.WTERMSIG(code)
1062 val = os.WTERMSIG(code)
1059 return _("killed by signal %d") % val, val
1063 return _("killed by signal %d") % val, val
1060 elif os.WIFSTOPPED(code):
1064 elif os.WIFSTOPPED(code):
1061 val = os.WSTOPSIG(code)
1065 val = os.WSTOPSIG(code)
1062 return _("stopped by signal %d") % val, val
1066 return _("stopped by signal %d") % val, val
1063 raise ValueError(_("invalid exit code"))
1067 raise ValueError(_("invalid exit code"))
1064
1068
1065 def isowner(fp, st=None):
1069 def isowner(fp, st=None):
1066 """Return True if the file object f belongs to the current user.
1070 """Return True if the file object f belongs to the current user.
1067
1071
1068 The return value of a util.fstat(f) may be passed as the st argument.
1072 The return value of a util.fstat(f) may be passed as the st argument.
1069 """
1073 """
1070 if st is None:
1074 if st is None:
1071 st = fstat(fp)
1075 st = fstat(fp)
1072 return st.st_uid == os.getuid()
1076 return st.st_uid == os.getuid()
1073
1077
1074 def find_in_path(name, path, default=None):
1078 def find_in_path(name, path, default=None):
1075 '''find name in search path. path can be string (will be split
1079 '''find name in search path. path can be string (will be split
1076 with os.pathsep), or iterable thing that returns strings. if name
1080 with os.pathsep), or iterable thing that returns strings. if name
1077 found, return path to name. else return default.'''
1081 found, return path to name. else return default.'''
1078 if isinstance(path, str):
1082 if isinstance(path, str):
1079 path = path.split(os.pathsep)
1083 path = path.split(os.pathsep)
1080 for p in path:
1084 for p in path:
1081 p_name = os.path.join(p, name)
1085 p_name = os.path.join(p, name)
1082 if os.path.exists(p_name):
1086 if os.path.exists(p_name):
1083 return p_name
1087 return p_name
1084 return default
1088 return default
1085
1089
1086 def _buildencodefun():
1090 def _buildencodefun():
1087 e = '_'
1091 e = '_'
1088 win_reserved = [ord(x) for x in '\\:*?"<>|']
1092 win_reserved = [ord(x) for x in '\\:*?"<>|']
1089 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1093 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1090 for x in (range(32) + range(126, 256) + win_reserved):
1094 for x in (range(32) + range(126, 256) + win_reserved):
1091 cmap[chr(x)] = "~%02x" % x
1095 cmap[chr(x)] = "~%02x" % x
1092 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1096 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1093 cmap[chr(x)] = e + chr(x).lower()
1097 cmap[chr(x)] = e + chr(x).lower()
1094 dmap = {}
1098 dmap = {}
1095 for k, v in cmap.iteritems():
1099 for k, v in cmap.iteritems():
1096 dmap[v] = k
1100 dmap[v] = k
1097 def decode(s):
1101 def decode(s):
1098 i = 0
1102 i = 0
1099 while i < len(s):
1103 while i < len(s):
1100 for l in xrange(1, 4):
1104 for l in xrange(1, 4):
1101 try:
1105 try:
1102 yield dmap[s[i:i+l]]
1106 yield dmap[s[i:i+l]]
1103 i += l
1107 i += l
1104 break
1108 break
1105 except KeyError:
1109 except KeyError:
1106 pass
1110 pass
1107 else:
1111 else:
1108 raise KeyError
1112 raise KeyError
1109 return (lambda s: "".join([cmap[c] for c in s]),
1113 return (lambda s: "".join([cmap[c] for c in s]),
1110 lambda s: "".join(list(decode(s))))
1114 lambda s: "".join(list(decode(s))))
1111
1115
1112 encodefilename, decodefilename = _buildencodefun()
1116 encodefilename, decodefilename = _buildencodefun()
1113
1117
1114 def encodedopener(openerfn, fn):
1118 def encodedopener(openerfn, fn):
1115 def o(path, *args, **kw):
1119 def o(path, *args, **kw):
1116 return openerfn(fn(path), *args, **kw)
1120 return openerfn(fn(path), *args, **kw)
1117 return o
1121 return o
1118
1122
1119 def opener(base, audit=True):
1123 def opener(base, audit=True):
1120 """
1124 """
1121 return a function that opens files relative to base
1125 return a function that opens files relative to base
1122
1126
1123 this function is used to hide the details of COW semantics and
1127 this function is used to hide the details of COW semantics and
1124 remote file access from higher level code.
1128 remote file access from higher level code.
1125 """
1129 """
1126 p = base
1130 p = base
1127 audit_p = audit
1131 audit_p = audit
1128
1132
1129 def mktempcopy(name, emptyok=False):
1133 def mktempcopy(name, emptyok=False):
1130 d, fn = os.path.split(name)
1134 d, fn = os.path.split(name)
1131 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1135 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1132 os.close(fd)
1136 os.close(fd)
1133 # Temporary files are created with mode 0600, which is usually not
1137 # Temporary files are created with mode 0600, which is usually not
1134 # what we want. If the original file already exists, just copy
1138 # what we want. If the original file already exists, just copy
1135 # its mode. Otherwise, manually obey umask.
1139 # its mode. Otherwise, manually obey umask.
1136 try:
1140 try:
1137 st_mode = os.lstat(name).st_mode
1141 st_mode = os.lstat(name).st_mode
1138 except OSError, inst:
1142 except OSError, inst:
1139 if inst.errno != errno.ENOENT:
1143 if inst.errno != errno.ENOENT:
1140 raise
1144 raise
1141 st_mode = 0666 & ~_umask
1145 st_mode = 0666 & ~_umask
1142 os.chmod(temp, st_mode)
1146 os.chmod(temp, st_mode)
1143 if emptyok:
1147 if emptyok:
1144 return temp
1148 return temp
1145 try:
1149 try:
1146 try:
1150 try:
1147 ifp = posixfile(name, "rb")
1151 ifp = posixfile(name, "rb")
1148 except IOError, inst:
1152 except IOError, inst:
1149 if inst.errno == errno.ENOENT:
1153 if inst.errno == errno.ENOENT:
1150 return temp
1154 return temp
1151 if not getattr(inst, 'filename', None):
1155 if not getattr(inst, 'filename', None):
1152 inst.filename = name
1156 inst.filename = name
1153 raise
1157 raise
1154 ofp = posixfile(temp, "wb")
1158 ofp = posixfile(temp, "wb")
1155 for chunk in filechunkiter(ifp):
1159 for chunk in filechunkiter(ifp):
1156 ofp.write(chunk)
1160 ofp.write(chunk)
1157 ifp.close()
1161 ifp.close()
1158 ofp.close()
1162 ofp.close()
1159 except:
1163 except:
1160 try: os.unlink(temp)
1164 try: os.unlink(temp)
1161 except: pass
1165 except: pass
1162 raise
1166 raise
1163 return temp
1167 return temp
1164
1168
1165 class atomictempfile(posixfile):
1169 class atomictempfile(posixfile):
1166 """the file will only be copied when rename is called"""
1170 """the file will only be copied when rename is called"""
1167 def __init__(self, name, mode):
1171 def __init__(self, name, mode):
1168 self.__name = name
1172 self.__name = name
1169 self.temp = mktempcopy(name, emptyok=('w' in mode))
1173 self.temp = mktempcopy(name, emptyok=('w' in mode))
1170 posixfile.__init__(self, self.temp, mode)
1174 posixfile.__init__(self, self.temp, mode)
1171 def rename(self):
1175 def rename(self):
1172 if not self.closed:
1176 if not self.closed:
1173 posixfile.close(self)
1177 posixfile.close(self)
1174 rename(self.temp, localpath(self.__name))
1178 rename(self.temp, localpath(self.__name))
1175 def __del__(self):
1179 def __del__(self):
1176 if not self.closed:
1180 if not self.closed:
1177 try:
1181 try:
1178 os.unlink(self.temp)
1182 os.unlink(self.temp)
1179 except: pass
1183 except: pass
1180 posixfile.close(self)
1184 posixfile.close(self)
1181
1185
1182 class atomicfile(atomictempfile):
1186 class atomicfile(atomictempfile):
1183 """the file will only be copied on close"""
1187 """the file will only be copied on close"""
1184 def __init__(self, name, mode):
1188 def __init__(self, name, mode):
1185 self._err = False
1189 self._err = False
1186 atomictempfile.__init__(self, name, mode)
1190 atomictempfile.__init__(self, name, mode)
1187 def write(self, s):
1191 def write(self, s):
1188 try:
1192 try:
1189 atomictempfile.write(self, s)
1193 atomictempfile.write(self, s)
1190 except:
1194 except:
1191 self._err = True
1195 self._err = True
1192 raise
1196 raise
1193 def close(self):
1197 def close(self):
1194 self.rename()
1198 self.rename()
1195 def __del__(self):
1199 def __del__(self):
1196 if not self._err:
1200 if not self._err:
1197 self.rename()
1201 self.rename()
1198
1202
1199 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1203 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1200 if audit_p:
1204 if audit_p:
1201 audit_path(path)
1205 audit_path(path)
1202 f = os.path.join(p, path)
1206 f = os.path.join(p, path)
1203
1207
1204 if not text:
1208 if not text:
1205 mode += "b" # for that other OS
1209 mode += "b" # for that other OS
1206
1210
1207 if mode[0] != "r":
1211 if mode[0] != "r":
1208 try:
1212 try:
1209 nlink = nlinks(f)
1213 nlink = nlinks(f)
1210 except OSError:
1214 except OSError:
1211 nlink = 0
1215 nlink = 0
1212 d = os.path.dirname(f)
1216 d = os.path.dirname(f)
1213 if not os.path.isdir(d):
1217 if not os.path.isdir(d):
1214 os.makedirs(d)
1218 os.makedirs(d)
1215 if atomic:
1219 if atomic:
1216 return atomicfile(f, mode)
1220 return atomicfile(f, mode)
1217 elif atomictemp:
1221 elif atomictemp:
1218 return atomictempfile(f, mode)
1222 return atomictempfile(f, mode)
1219 if nlink > 1:
1223 if nlink > 1:
1220 rename(mktempcopy(f), f)
1224 rename(mktempcopy(f), f)
1221 return posixfile(f, mode)
1225 return posixfile(f, mode)
1222
1226
1223 return o
1227 return o
1224
1228
1225 class chunkbuffer(object):
1229 class chunkbuffer(object):
1226 """Allow arbitrary sized chunks of data to be efficiently read from an
1230 """Allow arbitrary sized chunks of data to be efficiently read from an
1227 iterator over chunks of arbitrary size."""
1231 iterator over chunks of arbitrary size."""
1228
1232
1229 def __init__(self, in_iter, targetsize = 2**16):
1233 def __init__(self, in_iter, targetsize = 2**16):
1230 """in_iter is the iterator that's iterating over the input chunks.
1234 """in_iter is the iterator that's iterating over the input chunks.
1231 targetsize is how big a buffer to try to maintain."""
1235 targetsize is how big a buffer to try to maintain."""
1232 self.in_iter = iter(in_iter)
1236 self.in_iter = iter(in_iter)
1233 self.buf = ''
1237 self.buf = ''
1234 self.targetsize = int(targetsize)
1238 self.targetsize = int(targetsize)
1235 if self.targetsize <= 0:
1239 if self.targetsize <= 0:
1236 raise ValueError(_("targetsize must be greater than 0, was %d") %
1240 raise ValueError(_("targetsize must be greater than 0, was %d") %
1237 targetsize)
1241 targetsize)
1238 self.iterempty = False
1242 self.iterempty = False
1239
1243
1240 def fillbuf(self):
1244 def fillbuf(self):
1241 """Ignore target size; read every chunk from iterator until empty."""
1245 """Ignore target size; read every chunk from iterator until empty."""
1242 if not self.iterempty:
1246 if not self.iterempty:
1243 collector = cStringIO.StringIO()
1247 collector = cStringIO.StringIO()
1244 collector.write(self.buf)
1248 collector.write(self.buf)
1245 for ch in self.in_iter:
1249 for ch in self.in_iter:
1246 collector.write(ch)
1250 collector.write(ch)
1247 self.buf = collector.getvalue()
1251 self.buf = collector.getvalue()
1248 self.iterempty = True
1252 self.iterempty = True
1249
1253
1250 def read(self, l):
1254 def read(self, l):
1251 """Read L bytes of data from the iterator of chunks of data.
1255 """Read L bytes of data from the iterator of chunks of data.
1252 Returns less than L bytes if the iterator runs dry."""
1256 Returns less than L bytes if the iterator runs dry."""
1253 if l > len(self.buf) and not self.iterempty:
1257 if l > len(self.buf) and not self.iterempty:
1254 # Clamp to a multiple of self.targetsize
1258 # Clamp to a multiple of self.targetsize
1255 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1259 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1256 collector = cStringIO.StringIO()
1260 collector = cStringIO.StringIO()
1257 collector.write(self.buf)
1261 collector.write(self.buf)
1258 collected = len(self.buf)
1262 collected = len(self.buf)
1259 for chunk in self.in_iter:
1263 for chunk in self.in_iter:
1260 collector.write(chunk)
1264 collector.write(chunk)
1261 collected += len(chunk)
1265 collected += len(chunk)
1262 if collected >= targetsize:
1266 if collected >= targetsize:
1263 break
1267 break
1264 if collected < targetsize:
1268 if collected < targetsize:
1265 self.iterempty = True
1269 self.iterempty = True
1266 self.buf = collector.getvalue()
1270 self.buf = collector.getvalue()
1267 s, self.buf = self.buf[:l], buffer(self.buf, l)
1271 s, self.buf = self.buf[:l], buffer(self.buf, l)
1268 return s
1272 return s
1269
1273
1270 def filechunkiter(f, size=65536, limit=None):
1274 def filechunkiter(f, size=65536, limit=None):
1271 """Create a generator that produces the data in the file size
1275 """Create a generator that produces the data in the file size
1272 (default 65536) bytes at a time, up to optional limit (default is
1276 (default 65536) bytes at a time, up to optional limit (default is
1273 to read all data). Chunks may be less than size bytes if the
1277 to read all data). Chunks may be less than size bytes if the
1274 chunk is the last chunk in the file, or the file is a socket or
1278 chunk is the last chunk in the file, or the file is a socket or
1275 some other type of file that sometimes reads less data than is
1279 some other type of file that sometimes reads less data than is
1276 requested."""
1280 requested."""
1277 assert size >= 0
1281 assert size >= 0
1278 assert limit is None or limit >= 0
1282 assert limit is None or limit >= 0
1279 while True:
1283 while True:
1280 if limit is None: nbytes = size
1284 if limit is None: nbytes = size
1281 else: nbytes = min(limit, size)
1285 else: nbytes = min(limit, size)
1282 s = nbytes and f.read(nbytes)
1286 s = nbytes and f.read(nbytes)
1283 if not s: break
1287 if not s: break
1284 if limit: limit -= len(s)
1288 if limit: limit -= len(s)
1285 yield s
1289 yield s
1286
1290
1287 def makedate():
1291 def makedate():
1288 lt = time.localtime()
1292 lt = time.localtime()
1289 if lt[8] == 1 and time.daylight:
1293 if lt[8] == 1 and time.daylight:
1290 tz = time.altzone
1294 tz = time.altzone
1291 else:
1295 else:
1292 tz = time.timezone
1296 tz = time.timezone
1293 return time.mktime(lt), tz
1297 return time.mktime(lt), tz
1294
1298
1295 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1299 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1296 """represent a (unixtime, offset) tuple as a localized time.
1300 """represent a (unixtime, offset) tuple as a localized time.
1297 unixtime is seconds since the epoch, and offset is the time zone's
1301 unixtime is seconds since the epoch, and offset is the time zone's
1298 number of seconds away from UTC. if timezone is false, do not
1302 number of seconds away from UTC. if timezone is false, do not
1299 append time zone to string."""
1303 append time zone to string."""
1300 t, tz = date or makedate()
1304 t, tz = date or makedate()
1301 s = time.strftime(format, time.gmtime(float(t) - tz))
1305 s = time.strftime(format, time.gmtime(float(t) - tz))
1302 if timezone:
1306 if timezone:
1303 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1307 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1304 return s
1308 return s
1305
1309
1306 def strdate(string, format, defaults):
1310 def strdate(string, format, defaults):
1307 """parse a localized time string and return a (unixtime, offset) tuple.
1311 """parse a localized time string and return a (unixtime, offset) tuple.
1308 if the string cannot be parsed, ValueError is raised."""
1312 if the string cannot be parsed, ValueError is raised."""
1309 def timezone(string):
1313 def timezone(string):
1310 tz = string.split()[-1]
1314 tz = string.split()[-1]
1311 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1315 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1312 tz = int(tz)
1316 tz = int(tz)
1313 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1317 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1314 return offset
1318 return offset
1315 if tz == "GMT" or tz == "UTC":
1319 if tz == "GMT" or tz == "UTC":
1316 return 0
1320 return 0
1317 return None
1321 return None
1318
1322
1319 # NOTE: unixtime = localunixtime + offset
1323 # NOTE: unixtime = localunixtime + offset
1320 offset, date = timezone(string), string
1324 offset, date = timezone(string), string
1321 if offset != None:
1325 if offset != None:
1322 date = " ".join(string.split()[:-1])
1326 date = " ".join(string.split()[:-1])
1323
1327
1324 # add missing elements from defaults
1328 # add missing elements from defaults
1325 for part in defaults:
1329 for part in defaults:
1326 found = [True for p in part if ("%"+p) in format]
1330 found = [True for p in part if ("%"+p) in format]
1327 if not found:
1331 if not found:
1328 date += "@" + defaults[part]
1332 date += "@" + defaults[part]
1329 format += "@%" + part[0]
1333 format += "@%" + part[0]
1330
1334
1331 timetuple = time.strptime(date, format)
1335 timetuple = time.strptime(date, format)
1332 localunixtime = int(calendar.timegm(timetuple))
1336 localunixtime = int(calendar.timegm(timetuple))
1333 if offset is None:
1337 if offset is None:
1334 # local timezone
1338 # local timezone
1335 unixtime = int(time.mktime(timetuple))
1339 unixtime = int(time.mktime(timetuple))
1336 offset = unixtime - localunixtime
1340 offset = unixtime - localunixtime
1337 else:
1341 else:
1338 unixtime = localunixtime + offset
1342 unixtime = localunixtime + offset
1339 return unixtime, offset
1343 return unixtime, offset
1340
1344
1341 def parsedate(string, formats=None, defaults=None):
1345 def parsedate(string, formats=None, defaults=None):
1342 """parse a localized time string and return a (unixtime, offset) tuple.
1346 """parse a localized time string and return a (unixtime, offset) tuple.
1343 The date may be a "unixtime offset" string or in one of the specified
1347 The date may be a "unixtime offset" string or in one of the specified
1344 formats."""
1348 formats."""
1345 if not string:
1349 if not string:
1346 return 0, 0
1350 return 0, 0
1347 if not formats:
1351 if not formats:
1348 formats = defaultdateformats
1352 formats = defaultdateformats
1349 string = string.strip()
1353 string = string.strip()
1350 try:
1354 try:
1351 when, offset = map(int, string.split(' '))
1355 when, offset = map(int, string.split(' '))
1352 except ValueError:
1356 except ValueError:
1353 # fill out defaults
1357 # fill out defaults
1354 if not defaults:
1358 if not defaults:
1355 defaults = {}
1359 defaults = {}
1356 now = makedate()
1360 now = makedate()
1357 for part in "d mb yY HI M S".split():
1361 for part in "d mb yY HI M S".split():
1358 if part not in defaults:
1362 if part not in defaults:
1359 if part[0] in "HMS":
1363 if part[0] in "HMS":
1360 defaults[part] = "00"
1364 defaults[part] = "00"
1361 elif part[0] in "dm":
1365 elif part[0] in "dm":
1362 defaults[part] = "1"
1366 defaults[part] = "1"
1363 else:
1367 else:
1364 defaults[part] = datestr(now, "%" + part[0], False)
1368 defaults[part] = datestr(now, "%" + part[0], False)
1365
1369
1366 for format in formats:
1370 for format in formats:
1367 try:
1371 try:
1368 when, offset = strdate(string, format, defaults)
1372 when, offset = strdate(string, format, defaults)
1369 except ValueError:
1373 except ValueError:
1370 pass
1374 pass
1371 else:
1375 else:
1372 break
1376 break
1373 else:
1377 else:
1374 raise Abort(_('invalid date: %r ') % string)
1378 raise Abort(_('invalid date: %r ') % string)
1375 # validate explicit (probably user-specified) date and
1379 # validate explicit (probably user-specified) date and
1376 # time zone offset. values must fit in signed 32 bits for
1380 # time zone offset. values must fit in signed 32 bits for
1377 # current 32-bit linux runtimes. timezones go from UTC-12
1381 # current 32-bit linux runtimes. timezones go from UTC-12
1378 # to UTC+14
1382 # to UTC+14
1379 if abs(when) > 0x7fffffff:
1383 if abs(when) > 0x7fffffff:
1380 raise Abort(_('date exceeds 32 bits: %d') % when)
1384 raise Abort(_('date exceeds 32 bits: %d') % when)
1381 if offset < -50400 or offset > 43200:
1385 if offset < -50400 or offset > 43200:
1382 raise Abort(_('impossible time zone offset: %d') % offset)
1386 raise Abort(_('impossible time zone offset: %d') % offset)
1383 return when, offset
1387 return when, offset
1384
1388
1385 def matchdate(date):
1389 def matchdate(date):
1386 """Return a function that matches a given date match specifier
1390 """Return a function that matches a given date match specifier
1387
1391
1388 Formats include:
1392 Formats include:
1389
1393
1390 '{date}' match a given date to the accuracy provided
1394 '{date}' match a given date to the accuracy provided
1391
1395
1392 '<{date}' on or before a given date
1396 '<{date}' on or before a given date
1393
1397
1394 '>{date}' on or after a given date
1398 '>{date}' on or after a given date
1395
1399
1396 """
1400 """
1397
1401
1398 def lower(date):
1402 def lower(date):
1399 return parsedate(date, extendeddateformats)[0]
1403 return parsedate(date, extendeddateformats)[0]
1400
1404
1401 def upper(date):
1405 def upper(date):
1402 d = dict(mb="12", HI="23", M="59", S="59")
1406 d = dict(mb="12", HI="23", M="59", S="59")
1403 for days in "31 30 29".split():
1407 for days in "31 30 29".split():
1404 try:
1408 try:
1405 d["d"] = days
1409 d["d"] = days
1406 return parsedate(date, extendeddateformats, d)[0]
1410 return parsedate(date, extendeddateformats, d)[0]
1407 except:
1411 except:
1408 pass
1412 pass
1409 d["d"] = "28"
1413 d["d"] = "28"
1410 return parsedate(date, extendeddateformats, d)[0]
1414 return parsedate(date, extendeddateformats, d)[0]
1411
1415
1412 if date[0] == "<":
1416 if date[0] == "<":
1413 when = upper(date[1:])
1417 when = upper(date[1:])
1414 return lambda x: x <= when
1418 return lambda x: x <= when
1415 elif date[0] == ">":
1419 elif date[0] == ">":
1416 when = lower(date[1:])
1420 when = lower(date[1:])
1417 return lambda x: x >= when
1421 return lambda x: x >= when
1418 elif date[0] == "-":
1422 elif date[0] == "-":
1419 try:
1423 try:
1420 days = int(date[1:])
1424 days = int(date[1:])
1421 except ValueError:
1425 except ValueError:
1422 raise Abort(_("invalid day spec: %s") % date[1:])
1426 raise Abort(_("invalid day spec: %s") % date[1:])
1423 when = makedate()[0] - days * 3600 * 24
1427 when = makedate()[0] - days * 3600 * 24
1424 return lambda x: x >= when
1428 return lambda x: x >= when
1425 elif " to " in date:
1429 elif " to " in date:
1426 a, b = date.split(" to ")
1430 a, b = date.split(" to ")
1427 start, stop = lower(a), upper(b)
1431 start, stop = lower(a), upper(b)
1428 return lambda x: x >= start and x <= stop
1432 return lambda x: x >= start and x <= stop
1429 else:
1433 else:
1430 start, stop = lower(date), upper(date)
1434 start, stop = lower(date), upper(date)
1431 return lambda x: x >= start and x <= stop
1435 return lambda x: x >= start and x <= stop
1432
1436
1433 def shortuser(user):
1437 def shortuser(user):
1434 """Return a short representation of a user name or email address."""
1438 """Return a short representation of a user name or email address."""
1435 f = user.find('@')
1439 f = user.find('@')
1436 if f >= 0:
1440 if f >= 0:
1437 user = user[:f]
1441 user = user[:f]
1438 f = user.find('<')
1442 f = user.find('<')
1439 if f >= 0:
1443 if f >= 0:
1440 user = user[f+1:]
1444 user = user[f+1:]
1441 f = user.find(' ')
1445 f = user.find(' ')
1442 if f >= 0:
1446 if f >= 0:
1443 user = user[:f]
1447 user = user[:f]
1444 f = user.find('.')
1448 f = user.find('.')
1445 if f >= 0:
1449 if f >= 0:
1446 user = user[:f]
1450 user = user[:f]
1447 return user
1451 return user
1448
1452
1449 def ellipsis(text, maxlength=400):
1453 def ellipsis(text, maxlength=400):
1450 """Trim string to at most maxlength (default: 400) characters."""
1454 """Trim string to at most maxlength (default: 400) characters."""
1451 if len(text) <= maxlength:
1455 if len(text) <= maxlength:
1452 return text
1456 return text
1453 else:
1457 else:
1454 return "%s..." % (text[:maxlength-3])
1458 return "%s..." % (text[:maxlength-3])
1455
1459
1456 def walkrepos(path):
1460 def walkrepos(path):
1457 '''yield every hg repository under path, recursively.'''
1461 '''yield every hg repository under path, recursively.'''
1458 def errhandler(err):
1462 def errhandler(err):
1459 if err.filename == path:
1463 if err.filename == path:
1460 raise err
1464 raise err
1461
1465
1462 for root, dirs, files in os.walk(path, onerror=errhandler):
1466 for root, dirs, files in os.walk(path, onerror=errhandler):
1463 for d in dirs:
1467 for d in dirs:
1464 if d == '.hg':
1468 if d == '.hg':
1465 yield root
1469 yield root
1466 dirs[:] = []
1470 dirs[:] = []
1467 break
1471 break
1468
1472
1469 _rcpath = None
1473 _rcpath = None
1470
1474
1471 def os_rcpath():
1475 def os_rcpath():
1472 '''return default os-specific hgrc search path'''
1476 '''return default os-specific hgrc search path'''
1473 path = system_rcpath()
1477 path = system_rcpath()
1474 path.extend(user_rcpath())
1478 path.extend(user_rcpath())
1475 path = [os.path.normpath(f) for f in path]
1479 path = [os.path.normpath(f) for f in path]
1476 return path
1480 return path
1477
1481
1478 def rcpath():
1482 def rcpath():
1479 '''return hgrc search path. if env var HGRCPATH is set, use it.
1483 '''return hgrc search path. if env var HGRCPATH is set, use it.
1480 for each item in path, if directory, use files ending in .rc,
1484 for each item in path, if directory, use files ending in .rc,
1481 else use item.
1485 else use item.
1482 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1486 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1483 if no HGRCPATH, use default os-specific path.'''
1487 if no HGRCPATH, use default os-specific path.'''
1484 global _rcpath
1488 global _rcpath
1485 if _rcpath is None:
1489 if _rcpath is None:
1486 if 'HGRCPATH' in os.environ:
1490 if 'HGRCPATH' in os.environ:
1487 _rcpath = []
1491 _rcpath = []
1488 for p in os.environ['HGRCPATH'].split(os.pathsep):
1492 for p in os.environ['HGRCPATH'].split(os.pathsep):
1489 if not p: continue
1493 if not p: continue
1490 if os.path.isdir(p):
1494 if os.path.isdir(p):
1491 for f in os.listdir(p):
1495 for f in os.listdir(p):
1492 if f.endswith('.rc'):
1496 if f.endswith('.rc'):
1493 _rcpath.append(os.path.join(p, f))
1497 _rcpath.append(os.path.join(p, f))
1494 else:
1498 else:
1495 _rcpath.append(p)
1499 _rcpath.append(p)
1496 else:
1500 else:
1497 _rcpath = os_rcpath()
1501 _rcpath = os_rcpath()
1498 return _rcpath
1502 return _rcpath
1499
1503
1500 def bytecount(nbytes):
1504 def bytecount(nbytes):
1501 '''return byte count formatted as readable string, with units'''
1505 '''return byte count formatted as readable string, with units'''
1502
1506
1503 units = (
1507 units = (
1504 (100, 1<<30, _('%.0f GB')),
1508 (100, 1<<30, _('%.0f GB')),
1505 (10, 1<<30, _('%.1f GB')),
1509 (10, 1<<30, _('%.1f GB')),
1506 (1, 1<<30, _('%.2f GB')),
1510 (1, 1<<30, _('%.2f GB')),
1507 (100, 1<<20, _('%.0f MB')),
1511 (100, 1<<20, _('%.0f MB')),
1508 (10, 1<<20, _('%.1f MB')),
1512 (10, 1<<20, _('%.1f MB')),
1509 (1, 1<<20, _('%.2f MB')),
1513 (1, 1<<20, _('%.2f MB')),
1510 (100, 1<<10, _('%.0f KB')),
1514 (100, 1<<10, _('%.0f KB')),
1511 (10, 1<<10, _('%.1f KB')),
1515 (10, 1<<10, _('%.1f KB')),
1512 (1, 1<<10, _('%.2f KB')),
1516 (1, 1<<10, _('%.2f KB')),
1513 (1, 1, _('%.0f bytes')),
1517 (1, 1, _('%.0f bytes')),
1514 )
1518 )
1515
1519
1516 for multiplier, divisor, format in units:
1520 for multiplier, divisor, format in units:
1517 if nbytes >= divisor * multiplier:
1521 if nbytes >= divisor * multiplier:
1518 return format % (nbytes / float(divisor))
1522 return format % (nbytes / float(divisor))
1519 return units[-1][2] % nbytes
1523 return units[-1][2] % nbytes
1520
1524
1521 def drop_scheme(scheme, path):
1525 def drop_scheme(scheme, path):
1522 sc = scheme + ':'
1526 sc = scheme + ':'
1523 if path.startswith(sc):
1527 if path.startswith(sc):
1524 path = path[len(sc):]
1528 path = path[len(sc):]
1525 if path.startswith('//'):
1529 if path.startswith('//'):
1526 path = path[2:]
1530 path = path[2:]
1527 return path
1531 return path
General Comments 0
You need to be logged in to leave comments. Login now