##// END OF EJS Templates
py3: convert os.devnull to bytes using pycompat.bytestr...
Pulkit Goyal -
r36475:0e8b7664 default
parent child Browse files
Show More
@@ -1,495 +1,495 b''
1 # common.py - common code for the convert extension
1 # common.py - common code for the convert extension
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import base64
9 import base64
10 import datetime
10 import datetime
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import subprocess
14 import subprocess
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 from mercurial import (
17 from mercurial import (
18 encoding,
18 encoding,
19 error,
19 error,
20 phases,
20 phases,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 )
23 )
24
24
25 pickle = util.pickle
25 pickle = util.pickle
26 propertycache = util.propertycache
26 propertycache = util.propertycache
27
27
28 def encodeargs(args):
28 def encodeargs(args):
29 def encodearg(s):
29 def encodearg(s):
30 lines = base64.encodestring(s)
30 lines = base64.encodestring(s)
31 lines = [l.splitlines()[0] for l in lines]
31 lines = [l.splitlines()[0] for l in lines]
32 return ''.join(lines)
32 return ''.join(lines)
33
33
34 s = pickle.dumps(args)
34 s = pickle.dumps(args)
35 return encodearg(s)
35 return encodearg(s)
36
36
37 def decodeargs(s):
37 def decodeargs(s):
38 s = base64.decodestring(s)
38 s = base64.decodestring(s)
39 return pickle.loads(s)
39 return pickle.loads(s)
40
40
41 class MissingTool(Exception):
41 class MissingTool(Exception):
42 pass
42 pass
43
43
44 def checktool(exe, name=None, abort=True):
44 def checktool(exe, name=None, abort=True):
45 name = name or exe
45 name = name or exe
46 if not util.findexe(exe):
46 if not util.findexe(exe):
47 if abort:
47 if abort:
48 exc = error.Abort
48 exc = error.Abort
49 else:
49 else:
50 exc = MissingTool
50 exc = MissingTool
51 raise exc(_('cannot find required "%s" tool') % name)
51 raise exc(_('cannot find required "%s" tool') % name)
52
52
53 class NoRepo(Exception):
53 class NoRepo(Exception):
54 pass
54 pass
55
55
56 SKIPREV = 'SKIP'
56 SKIPREV = 'SKIP'
57
57
58 class commit(object):
58 class commit(object):
59 def __init__(self, author, date, desc, parents, branch=None, rev=None,
59 def __init__(self, author, date, desc, parents, branch=None, rev=None,
60 extra=None, sortkey=None, saverev=True, phase=phases.draft,
60 extra=None, sortkey=None, saverev=True, phase=phases.draft,
61 optparents=None):
61 optparents=None):
62 self.author = author or 'unknown'
62 self.author = author or 'unknown'
63 self.date = date or '0 0'
63 self.date = date or '0 0'
64 self.desc = desc
64 self.desc = desc
65 self.parents = parents # will be converted and used as parents
65 self.parents = parents # will be converted and used as parents
66 self.optparents = optparents or [] # will be used if already converted
66 self.optparents = optparents or [] # will be used if already converted
67 self.branch = branch
67 self.branch = branch
68 self.rev = rev
68 self.rev = rev
69 self.extra = extra or {}
69 self.extra = extra or {}
70 self.sortkey = sortkey
70 self.sortkey = sortkey
71 self.saverev = saverev
71 self.saverev = saverev
72 self.phase = phase
72 self.phase = phase
73
73
74 class converter_source(object):
74 class converter_source(object):
75 """Conversion source interface"""
75 """Conversion source interface"""
76
76
77 def __init__(self, ui, repotype, path=None, revs=None):
77 def __init__(self, ui, repotype, path=None, revs=None):
78 """Initialize conversion source (or raise NoRepo("message")
78 """Initialize conversion source (or raise NoRepo("message")
79 exception if path is not a valid repository)"""
79 exception if path is not a valid repository)"""
80 self.ui = ui
80 self.ui = ui
81 self.path = path
81 self.path = path
82 self.revs = revs
82 self.revs = revs
83 self.repotype = repotype
83 self.repotype = repotype
84
84
85 self.encoding = 'utf-8'
85 self.encoding = 'utf-8'
86
86
87 def checkhexformat(self, revstr, mapname='splicemap'):
87 def checkhexformat(self, revstr, mapname='splicemap'):
88 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
88 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
89 such format for their revision numbering
89 such format for their revision numbering
90 """
90 """
91 if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
91 if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
92 raise error.Abort(_('%s entry %s is not a valid revision'
92 raise error.Abort(_('%s entry %s is not a valid revision'
93 ' identifier') % (mapname, revstr))
93 ' identifier') % (mapname, revstr))
94
94
95 def before(self):
95 def before(self):
96 pass
96 pass
97
97
98 def after(self):
98 def after(self):
99 pass
99 pass
100
100
101 def targetfilebelongstosource(self, targetfilename):
101 def targetfilebelongstosource(self, targetfilename):
102 """Returns true if the given targetfile belongs to the source repo. This
102 """Returns true if the given targetfile belongs to the source repo. This
103 is useful when only a subdirectory of the target belongs to the source
103 is useful when only a subdirectory of the target belongs to the source
104 repo."""
104 repo."""
105 # For normal full repo converts, this is always True.
105 # For normal full repo converts, this is always True.
106 return True
106 return True
107
107
108 def setrevmap(self, revmap):
108 def setrevmap(self, revmap):
109 """set the map of already-converted revisions"""
109 """set the map of already-converted revisions"""
110
110
111 def getheads(self):
111 def getheads(self):
112 """Return a list of this repository's heads"""
112 """Return a list of this repository's heads"""
113 raise NotImplementedError
113 raise NotImplementedError
114
114
115 def getfile(self, name, rev):
115 def getfile(self, name, rev):
116 """Return a pair (data, mode) where data is the file content
116 """Return a pair (data, mode) where data is the file content
117 as a string and mode one of '', 'x' or 'l'. rev is the
117 as a string and mode one of '', 'x' or 'l'. rev is the
118 identifier returned by a previous call to getchanges().
118 identifier returned by a previous call to getchanges().
119 Data is None if file is missing/deleted in rev.
119 Data is None if file is missing/deleted in rev.
120 """
120 """
121 raise NotImplementedError
121 raise NotImplementedError
122
122
123 def getchanges(self, version, full):
123 def getchanges(self, version, full):
124 """Returns a tuple of (files, copies, cleanp2).
124 """Returns a tuple of (files, copies, cleanp2).
125
125
126 files is a sorted list of (filename, id) tuples for all files
126 files is a sorted list of (filename, id) tuples for all files
127 changed between version and its first parent returned by
127 changed between version and its first parent returned by
128 getcommit(). If full, all files in that revision is returned.
128 getcommit(). If full, all files in that revision is returned.
129 id is the source revision id of the file.
129 id is the source revision id of the file.
130
130
131 copies is a dictionary of dest: source
131 copies is a dictionary of dest: source
132
132
133 cleanp2 is the set of files filenames that are clean against p2.
133 cleanp2 is the set of files filenames that are clean against p2.
134 (Files that are clean against p1 are already not in files (unless
134 (Files that are clean against p1 are already not in files (unless
135 full). This makes it possible to handle p2 clean files similarly.)
135 full). This makes it possible to handle p2 clean files similarly.)
136 """
136 """
137 raise NotImplementedError
137 raise NotImplementedError
138
138
139 def getcommit(self, version):
139 def getcommit(self, version):
140 """Return the commit object for version"""
140 """Return the commit object for version"""
141 raise NotImplementedError
141 raise NotImplementedError
142
142
143 def numcommits(self):
143 def numcommits(self):
144 """Return the number of commits in this source.
144 """Return the number of commits in this source.
145
145
146 If unknown, return None.
146 If unknown, return None.
147 """
147 """
148 return None
148 return None
149
149
150 def gettags(self):
150 def gettags(self):
151 """Return the tags as a dictionary of name: revision
151 """Return the tags as a dictionary of name: revision
152
152
153 Tag names must be UTF-8 strings.
153 Tag names must be UTF-8 strings.
154 """
154 """
155 raise NotImplementedError
155 raise NotImplementedError
156
156
157 def recode(self, s, encoding=None):
157 def recode(self, s, encoding=None):
158 if not encoding:
158 if not encoding:
159 encoding = self.encoding or 'utf-8'
159 encoding = self.encoding or 'utf-8'
160
160
161 if isinstance(s, unicode):
161 if isinstance(s, unicode):
162 return s.encode("utf-8")
162 return s.encode("utf-8")
163 try:
163 try:
164 return s.decode(encoding).encode("utf-8")
164 return s.decode(encoding).encode("utf-8")
165 except UnicodeError:
165 except UnicodeError:
166 try:
166 try:
167 return s.decode("latin-1").encode("utf-8")
167 return s.decode("latin-1").encode("utf-8")
168 except UnicodeError:
168 except UnicodeError:
169 return s.decode(encoding, "replace").encode("utf-8")
169 return s.decode(encoding, "replace").encode("utf-8")
170
170
171 def getchangedfiles(self, rev, i):
171 def getchangedfiles(self, rev, i):
172 """Return the files changed by rev compared to parent[i].
172 """Return the files changed by rev compared to parent[i].
173
173
174 i is an index selecting one of the parents of rev. The return
174 i is an index selecting one of the parents of rev. The return
175 value should be the list of files that are different in rev and
175 value should be the list of files that are different in rev and
176 this parent.
176 this parent.
177
177
178 If rev has no parents, i is None.
178 If rev has no parents, i is None.
179
179
180 This function is only needed to support --filemap
180 This function is only needed to support --filemap
181 """
181 """
182 raise NotImplementedError
182 raise NotImplementedError
183
183
184 def converted(self, rev, sinkrev):
184 def converted(self, rev, sinkrev):
185 '''Notify the source that a revision has been converted.'''
185 '''Notify the source that a revision has been converted.'''
186
186
187 def hasnativeorder(self):
187 def hasnativeorder(self):
188 """Return true if this source has a meaningful, native revision
188 """Return true if this source has a meaningful, native revision
189 order. For instance, Mercurial revisions are store sequentially
189 order. For instance, Mercurial revisions are store sequentially
190 while there is no such global ordering with Darcs.
190 while there is no such global ordering with Darcs.
191 """
191 """
192 return False
192 return False
193
193
194 def hasnativeclose(self):
194 def hasnativeclose(self):
195 """Return true if this source has ability to close branch.
195 """Return true if this source has ability to close branch.
196 """
196 """
197 return False
197 return False
198
198
199 def lookuprev(self, rev):
199 def lookuprev(self, rev):
200 """If rev is a meaningful revision reference in source, return
200 """If rev is a meaningful revision reference in source, return
201 the referenced identifier in the same format used by getcommit().
201 the referenced identifier in the same format used by getcommit().
202 return None otherwise.
202 return None otherwise.
203 """
203 """
204 return None
204 return None
205
205
206 def getbookmarks(self):
206 def getbookmarks(self):
207 """Return the bookmarks as a dictionary of name: revision
207 """Return the bookmarks as a dictionary of name: revision
208
208
209 Bookmark names are to be UTF-8 strings.
209 Bookmark names are to be UTF-8 strings.
210 """
210 """
211 return {}
211 return {}
212
212
213 def checkrevformat(self, revstr, mapname='splicemap'):
213 def checkrevformat(self, revstr, mapname='splicemap'):
214 """revstr is a string that describes a revision in the given
214 """revstr is a string that describes a revision in the given
215 source control system. Return true if revstr has correct
215 source control system. Return true if revstr has correct
216 format.
216 format.
217 """
217 """
218 return True
218 return True
219
219
220 class converter_sink(object):
220 class converter_sink(object):
221 """Conversion sink (target) interface"""
221 """Conversion sink (target) interface"""
222
222
223 def __init__(self, ui, repotype, path):
223 def __init__(self, ui, repotype, path):
224 """Initialize conversion sink (or raise NoRepo("message")
224 """Initialize conversion sink (or raise NoRepo("message")
225 exception if path is not a valid repository)
225 exception if path is not a valid repository)
226
226
227 created is a list of paths to remove if a fatal error occurs
227 created is a list of paths to remove if a fatal error occurs
228 later"""
228 later"""
229 self.ui = ui
229 self.ui = ui
230 self.path = path
230 self.path = path
231 self.created = []
231 self.created = []
232 self.repotype = repotype
232 self.repotype = repotype
233
233
234 def revmapfile(self):
234 def revmapfile(self):
235 """Path to a file that will contain lines
235 """Path to a file that will contain lines
236 source_rev_id sink_rev_id
236 source_rev_id sink_rev_id
237 mapping equivalent revision identifiers for each system."""
237 mapping equivalent revision identifiers for each system."""
238 raise NotImplementedError
238 raise NotImplementedError
239
239
240 def authorfile(self):
240 def authorfile(self):
241 """Path to a file that will contain lines
241 """Path to a file that will contain lines
242 srcauthor=dstauthor
242 srcauthor=dstauthor
243 mapping equivalent authors identifiers for each system."""
243 mapping equivalent authors identifiers for each system."""
244 return None
244 return None
245
245
246 def putcommit(self, files, copies, parents, commit, source, revmap, full,
246 def putcommit(self, files, copies, parents, commit, source, revmap, full,
247 cleanp2):
247 cleanp2):
248 """Create a revision with all changed files listed in 'files'
248 """Create a revision with all changed files listed in 'files'
249 and having listed parents. 'commit' is a commit object
249 and having listed parents. 'commit' is a commit object
250 containing at a minimum the author, date, and message for this
250 containing at a minimum the author, date, and message for this
251 changeset. 'files' is a list of (path, version) tuples,
251 changeset. 'files' is a list of (path, version) tuples,
252 'copies' is a dictionary mapping destinations to sources,
252 'copies' is a dictionary mapping destinations to sources,
253 'source' is the source repository, and 'revmap' is a mapfile
253 'source' is the source repository, and 'revmap' is a mapfile
254 of source revisions to converted revisions. Only getfile() and
254 of source revisions to converted revisions. Only getfile() and
255 lookuprev() should be called on 'source'. 'full' means that 'files'
255 lookuprev() should be called on 'source'. 'full' means that 'files'
256 is complete and all other files should be removed.
256 is complete and all other files should be removed.
257 'cleanp2' is a set of the filenames that are unchanged from p2
257 'cleanp2' is a set of the filenames that are unchanged from p2
258 (only in the common merge case where there two parents).
258 (only in the common merge case where there two parents).
259
259
260 Note that the sink repository is not told to update itself to
260 Note that the sink repository is not told to update itself to
261 a particular revision (or even what that revision would be)
261 a particular revision (or even what that revision would be)
262 before it receives the file data.
262 before it receives the file data.
263 """
263 """
264 raise NotImplementedError
264 raise NotImplementedError
265
265
266 def puttags(self, tags):
266 def puttags(self, tags):
267 """Put tags into sink.
267 """Put tags into sink.
268
268
269 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
269 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
270 Return a pair (tag_revision, tag_parent_revision), or (None, None)
270 Return a pair (tag_revision, tag_parent_revision), or (None, None)
271 if nothing was changed.
271 if nothing was changed.
272 """
272 """
273 raise NotImplementedError
273 raise NotImplementedError
274
274
275 def setbranch(self, branch, pbranches):
275 def setbranch(self, branch, pbranches):
276 """Set the current branch name. Called before the first putcommit
276 """Set the current branch name. Called before the first putcommit
277 on the branch.
277 on the branch.
278 branch: branch name for subsequent commits
278 branch: branch name for subsequent commits
279 pbranches: (converted parent revision, parent branch) tuples"""
279 pbranches: (converted parent revision, parent branch) tuples"""
280
280
281 def setfilemapmode(self, active):
281 def setfilemapmode(self, active):
282 """Tell the destination that we're using a filemap
282 """Tell the destination that we're using a filemap
283
283
284 Some converter_sources (svn in particular) can claim that a file
284 Some converter_sources (svn in particular) can claim that a file
285 was changed in a revision, even if there was no change. This method
285 was changed in a revision, even if there was no change. This method
286 tells the destination that we're using a filemap and that it should
286 tells the destination that we're using a filemap and that it should
287 filter empty revisions.
287 filter empty revisions.
288 """
288 """
289
289
290 def before(self):
290 def before(self):
291 pass
291 pass
292
292
293 def after(self):
293 def after(self):
294 pass
294 pass
295
295
296 def putbookmarks(self, bookmarks):
296 def putbookmarks(self, bookmarks):
297 """Put bookmarks into sink.
297 """Put bookmarks into sink.
298
298
299 bookmarks: {bookmarkname: sink_rev_id, ...}
299 bookmarks: {bookmarkname: sink_rev_id, ...}
300 where bookmarkname is an UTF-8 string.
300 where bookmarkname is an UTF-8 string.
301 """
301 """
302
302
303 def hascommitfrommap(self, rev):
303 def hascommitfrommap(self, rev):
304 """Return False if a rev mentioned in a filemap is known to not be
304 """Return False if a rev mentioned in a filemap is known to not be
305 present."""
305 present."""
306 raise NotImplementedError
306 raise NotImplementedError
307
307
308 def hascommitforsplicemap(self, rev):
308 def hascommitforsplicemap(self, rev):
309 """This method is for the special needs for splicemap handling and not
309 """This method is for the special needs for splicemap handling and not
310 for general use. Returns True if the sink contains rev, aborts on some
310 for general use. Returns True if the sink contains rev, aborts on some
311 special cases."""
311 special cases."""
312 raise NotImplementedError
312 raise NotImplementedError
313
313
314 class commandline(object):
314 class commandline(object):
315 def __init__(self, ui, command):
315 def __init__(self, ui, command):
316 self.ui = ui
316 self.ui = ui
317 self.command = command
317 self.command = command
318
318
319 def prerun(self):
319 def prerun(self):
320 pass
320 pass
321
321
322 def postrun(self):
322 def postrun(self):
323 pass
323 pass
324
324
325 def _cmdline(self, cmd, *args, **kwargs):
325 def _cmdline(self, cmd, *args, **kwargs):
326 kwargs = pycompat.byteskwargs(kwargs)
326 kwargs = pycompat.byteskwargs(kwargs)
327 cmdline = [self.command, cmd] + list(args)
327 cmdline = [self.command, cmd] + list(args)
328 for k, v in kwargs.iteritems():
328 for k, v in kwargs.iteritems():
329 if len(k) == 1:
329 if len(k) == 1:
330 cmdline.append('-' + k)
330 cmdline.append('-' + k)
331 else:
331 else:
332 cmdline.append('--' + k.replace('_', '-'))
332 cmdline.append('--' + k.replace('_', '-'))
333 try:
333 try:
334 if len(k) == 1:
334 if len(k) == 1:
335 cmdline.append('' + v)
335 cmdline.append('' + v)
336 else:
336 else:
337 cmdline[-1] += '=' + v
337 cmdline[-1] += '=' + v
338 except TypeError:
338 except TypeError:
339 pass
339 pass
340 cmdline = [util.shellquote(arg) for arg in cmdline]
340 cmdline = [util.shellquote(arg) for arg in cmdline]
341 if not self.ui.debugflag:
341 if not self.ui.debugflag:
342 cmdline += ['2>', os.devnull]
342 cmdline += ['2>', pycompat.bytestr(os.devnull)]
343 cmdline = ' '.join(cmdline)
343 cmdline = ' '.join(cmdline)
344 return cmdline
344 return cmdline
345
345
346 def _run(self, cmd, *args, **kwargs):
346 def _run(self, cmd, *args, **kwargs):
347 def popen(cmdline):
347 def popen(cmdline):
348 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
348 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
349 close_fds=util.closefds,
349 close_fds=util.closefds,
350 stdout=subprocess.PIPE)
350 stdout=subprocess.PIPE)
351 return p
351 return p
352 return self._dorun(popen, cmd, *args, **kwargs)
352 return self._dorun(popen, cmd, *args, **kwargs)
353
353
354 def _run2(self, cmd, *args, **kwargs):
354 def _run2(self, cmd, *args, **kwargs):
355 return self._dorun(util.popen2, cmd, *args, **kwargs)
355 return self._dorun(util.popen2, cmd, *args, **kwargs)
356
356
357 def _run3(self, cmd, *args, **kwargs):
357 def _run3(self, cmd, *args, **kwargs):
358 return self._dorun(util.popen3, cmd, *args, **kwargs)
358 return self._dorun(util.popen3, cmd, *args, **kwargs)
359
359
360 def _dorun(self, openfunc, cmd, *args, **kwargs):
360 def _dorun(self, openfunc, cmd, *args, **kwargs):
361 cmdline = self._cmdline(cmd, *args, **kwargs)
361 cmdline = self._cmdline(cmd, *args, **kwargs)
362 self.ui.debug('running: %s\n' % (cmdline,))
362 self.ui.debug('running: %s\n' % (cmdline,))
363 self.prerun()
363 self.prerun()
364 try:
364 try:
365 return openfunc(cmdline)
365 return openfunc(cmdline)
366 finally:
366 finally:
367 self.postrun()
367 self.postrun()
368
368
369 def run(self, cmd, *args, **kwargs):
369 def run(self, cmd, *args, **kwargs):
370 p = self._run(cmd, *args, **kwargs)
370 p = self._run(cmd, *args, **kwargs)
371 output = p.communicate()[0]
371 output = p.communicate()[0]
372 self.ui.debug(output)
372 self.ui.debug(output)
373 return output, p.returncode
373 return output, p.returncode
374
374
375 def runlines(self, cmd, *args, **kwargs):
375 def runlines(self, cmd, *args, **kwargs):
376 p = self._run(cmd, *args, **kwargs)
376 p = self._run(cmd, *args, **kwargs)
377 output = p.stdout.readlines()
377 output = p.stdout.readlines()
378 p.wait()
378 p.wait()
379 self.ui.debug(''.join(output))
379 self.ui.debug(''.join(output))
380 return output, p.returncode
380 return output, p.returncode
381
381
382 def checkexit(self, status, output=''):
382 def checkexit(self, status, output=''):
383 if status:
383 if status:
384 if output:
384 if output:
385 self.ui.warn(_('%s error:\n') % self.command)
385 self.ui.warn(_('%s error:\n') % self.command)
386 self.ui.warn(output)
386 self.ui.warn(output)
387 msg = util.explainexit(status)[0]
387 msg = util.explainexit(status)[0]
388 raise error.Abort('%s %s' % (self.command, msg))
388 raise error.Abort('%s %s' % (self.command, msg))
389
389
390 def run0(self, cmd, *args, **kwargs):
390 def run0(self, cmd, *args, **kwargs):
391 output, status = self.run(cmd, *args, **kwargs)
391 output, status = self.run(cmd, *args, **kwargs)
392 self.checkexit(status, output)
392 self.checkexit(status, output)
393 return output
393 return output
394
394
395 def runlines0(self, cmd, *args, **kwargs):
395 def runlines0(self, cmd, *args, **kwargs):
396 output, status = self.runlines(cmd, *args, **kwargs)
396 output, status = self.runlines(cmd, *args, **kwargs)
397 self.checkexit(status, ''.join(output))
397 self.checkexit(status, ''.join(output))
398 return output
398 return output
399
399
400 @propertycache
400 @propertycache
401 def argmax(self):
401 def argmax(self):
402 # POSIX requires at least 4096 bytes for ARG_MAX
402 # POSIX requires at least 4096 bytes for ARG_MAX
403 argmax = 4096
403 argmax = 4096
404 try:
404 try:
405 argmax = os.sysconf("SC_ARG_MAX")
405 argmax = os.sysconf("SC_ARG_MAX")
406 except (AttributeError, ValueError):
406 except (AttributeError, ValueError):
407 pass
407 pass
408
408
409 # Windows shells impose their own limits on command line length,
409 # Windows shells impose their own limits on command line length,
410 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
410 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
411 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
411 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
412 # details about cmd.exe limitations.
412 # details about cmd.exe limitations.
413
413
414 # Since ARG_MAX is for command line _and_ environment, lower our limit
414 # Since ARG_MAX is for command line _and_ environment, lower our limit
415 # (and make happy Windows shells while doing this).
415 # (and make happy Windows shells while doing this).
416 return argmax // 2 - 1
416 return argmax // 2 - 1
417
417
418 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
418 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
419 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
419 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
420 limit = self.argmax - cmdlen
420 limit = self.argmax - cmdlen
421 numbytes = 0
421 numbytes = 0
422 fl = []
422 fl = []
423 for fn in arglist:
423 for fn in arglist:
424 b = len(fn) + 3
424 b = len(fn) + 3
425 if numbytes + b < limit or len(fl) == 0:
425 if numbytes + b < limit or len(fl) == 0:
426 fl.append(fn)
426 fl.append(fn)
427 numbytes += b
427 numbytes += b
428 else:
428 else:
429 yield fl
429 yield fl
430 fl = [fn]
430 fl = [fn]
431 numbytes = b
431 numbytes = b
432 if fl:
432 if fl:
433 yield fl
433 yield fl
434
434
435 def xargs(self, arglist, cmd, *args, **kwargs):
435 def xargs(self, arglist, cmd, *args, **kwargs):
436 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
436 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
437 self.run0(cmd, *(list(args) + l), **kwargs)
437 self.run0(cmd, *(list(args) + l), **kwargs)
438
438
439 class mapfile(dict):
439 class mapfile(dict):
440 def __init__(self, ui, path):
440 def __init__(self, ui, path):
441 super(mapfile, self).__init__()
441 super(mapfile, self).__init__()
442 self.ui = ui
442 self.ui = ui
443 self.path = path
443 self.path = path
444 self.fp = None
444 self.fp = None
445 self.order = []
445 self.order = []
446 self._read()
446 self._read()
447
447
448 def _read(self):
448 def _read(self):
449 if not self.path:
449 if not self.path:
450 return
450 return
451 try:
451 try:
452 fp = open(self.path, 'rb')
452 fp = open(self.path, 'rb')
453 except IOError as err:
453 except IOError as err:
454 if err.errno != errno.ENOENT:
454 if err.errno != errno.ENOENT:
455 raise
455 raise
456 return
456 return
457 for i, line in enumerate(util.iterfile(fp)):
457 for i, line in enumerate(util.iterfile(fp)):
458 line = line.splitlines()[0].rstrip()
458 line = line.splitlines()[0].rstrip()
459 if not line:
459 if not line:
460 # Ignore blank lines
460 # Ignore blank lines
461 continue
461 continue
462 try:
462 try:
463 key, value = line.rsplit(' ', 1)
463 key, value = line.rsplit(' ', 1)
464 except ValueError:
464 except ValueError:
465 raise error.Abort(
465 raise error.Abort(
466 _('syntax error in %s(%d): key/value pair expected')
466 _('syntax error in %s(%d): key/value pair expected')
467 % (self.path, i + 1))
467 % (self.path, i + 1))
468 if key not in self:
468 if key not in self:
469 self.order.append(key)
469 self.order.append(key)
470 super(mapfile, self).__setitem__(key, value)
470 super(mapfile, self).__setitem__(key, value)
471 fp.close()
471 fp.close()
472
472
473 def __setitem__(self, key, value):
473 def __setitem__(self, key, value):
474 if self.fp is None:
474 if self.fp is None:
475 try:
475 try:
476 self.fp = open(self.path, 'ab')
476 self.fp = open(self.path, 'ab')
477 except IOError as err:
477 except IOError as err:
478 raise error.Abort(
478 raise error.Abort(
479 _('could not open map file %r: %s') %
479 _('could not open map file %r: %s') %
480 (self.path, encoding.strtolocal(err.strerror)))
480 (self.path, encoding.strtolocal(err.strerror)))
481 self.fp.write(util.tonativeeol('%s %s\n' % (key, value)))
481 self.fp.write(util.tonativeeol('%s %s\n' % (key, value)))
482 self.fp.flush()
482 self.fp.flush()
483 super(mapfile, self).__setitem__(key, value)
483 super(mapfile, self).__setitem__(key, value)
484
484
485 def close(self):
485 def close(self):
486 if self.fp:
486 if self.fp:
487 self.fp.close()
487 self.fp.close()
488 self.fp = None
488 self.fp = None
489
489
490 def makedatetimestamp(t):
490 def makedatetimestamp(t):
491 """Like util.makedate() but for time t instead of current time"""
491 """Like util.makedate() but for time t instead of current time"""
492 delta = (datetime.datetime.utcfromtimestamp(t) -
492 delta = (datetime.datetime.utcfromtimestamp(t) -
493 datetime.datetime.fromtimestamp(t))
493 datetime.datetime.fromtimestamp(t))
494 tz = delta.days * 86400 + delta.seconds
494 tz = delta.days * 86400 + delta.seconds
495 return t, tz
495 return t, tz
@@ -1,496 +1,496 b''
1 # windows.py - Windows utility function implementations for Mercurial
1 # windows.py - Windows utility function implementations for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import msvcrt
11 import msvcrt
12 import os
12 import os
13 import re
13 import re
14 import stat
14 import stat
15 import sys
15 import sys
16
16
17 from .i18n import _
17 from .i18n import _
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 policy,
21 policy,
22 pycompat,
22 pycompat,
23 win32,
23 win32,
24 )
24 )
25
25
26 try:
26 try:
27 import _winreg as winreg
27 import _winreg as winreg
28 winreg.CloseKey
28 winreg.CloseKey
29 except ImportError:
29 except ImportError:
30 import winreg
30 import winreg
31
31
32 osutil = policy.importmod(r'osutil')
32 osutil = policy.importmod(r'osutil')
33
33
34 executablepath = win32.executablepath
34 executablepath = win32.executablepath
35 getfsmountpoint = win32.getvolumename
35 getfsmountpoint = win32.getvolumename
36 getfstype = win32.getfstype
36 getfstype = win32.getfstype
37 getuser = win32.getuser
37 getuser = win32.getuser
38 hidewindow = win32.hidewindow
38 hidewindow = win32.hidewindow
39 makedir = win32.makedir
39 makedir = win32.makedir
40 nlinks = win32.nlinks
40 nlinks = win32.nlinks
41 oslink = win32.oslink
41 oslink = win32.oslink
42 samedevice = win32.samedevice
42 samedevice = win32.samedevice
43 samefile = win32.samefile
43 samefile = win32.samefile
44 setsignalhandler = win32.setsignalhandler
44 setsignalhandler = win32.setsignalhandler
45 spawndetached = win32.spawndetached
45 spawndetached = win32.spawndetached
46 split = os.path.split
46 split = os.path.split
47 testpid = win32.testpid
47 testpid = win32.testpid
48 unlink = win32.unlink
48 unlink = win32.unlink
49
49
50 umask = 0o022
50 umask = 0o022
51
51
52 class mixedfilemodewrapper(object):
52 class mixedfilemodewrapper(object):
53 """Wraps a file handle when it is opened in read/write mode.
53 """Wraps a file handle when it is opened in read/write mode.
54
54
55 fopen() and fdopen() on Windows have a specific-to-Windows requirement
55 fopen() and fdopen() on Windows have a specific-to-Windows requirement
56 that files opened with mode r+, w+, or a+ make a call to a file positioning
56 that files opened with mode r+, w+, or a+ make a call to a file positioning
57 function when switching between reads and writes. Without this extra call,
57 function when switching between reads and writes. Without this extra call,
58 Python will raise a not very intuitive "IOError: [Errno 0] Error."
58 Python will raise a not very intuitive "IOError: [Errno 0] Error."
59
59
60 This class wraps posixfile instances when the file is opened in read/write
60 This class wraps posixfile instances when the file is opened in read/write
61 mode and automatically adds checks or inserts appropriate file positioning
61 mode and automatically adds checks or inserts appropriate file positioning
62 calls when necessary.
62 calls when necessary.
63 """
63 """
64 OPNONE = 0
64 OPNONE = 0
65 OPREAD = 1
65 OPREAD = 1
66 OPWRITE = 2
66 OPWRITE = 2
67
67
68 def __init__(self, fp):
68 def __init__(self, fp):
69 object.__setattr__(self, r'_fp', fp)
69 object.__setattr__(self, r'_fp', fp)
70 object.__setattr__(self, r'_lastop', 0)
70 object.__setattr__(self, r'_lastop', 0)
71
71
72 def __enter__(self):
72 def __enter__(self):
73 return self._fp.__enter__()
73 return self._fp.__enter__()
74
74
75 def __exit__(self, exc_type, exc_val, exc_tb):
75 def __exit__(self, exc_type, exc_val, exc_tb):
76 self._fp.__exit__(exc_type, exc_val, exc_tb)
76 self._fp.__exit__(exc_type, exc_val, exc_tb)
77
77
78 def __getattr__(self, name):
78 def __getattr__(self, name):
79 return getattr(self._fp, name)
79 return getattr(self._fp, name)
80
80
81 def __setattr__(self, name, value):
81 def __setattr__(self, name, value):
82 return self._fp.__setattr__(name, value)
82 return self._fp.__setattr__(name, value)
83
83
84 def _noopseek(self):
84 def _noopseek(self):
85 self._fp.seek(0, os.SEEK_CUR)
85 self._fp.seek(0, os.SEEK_CUR)
86
86
87 def seek(self, *args, **kwargs):
87 def seek(self, *args, **kwargs):
88 object.__setattr__(self, r'_lastop', self.OPNONE)
88 object.__setattr__(self, r'_lastop', self.OPNONE)
89 return self._fp.seek(*args, **kwargs)
89 return self._fp.seek(*args, **kwargs)
90
90
91 def write(self, d):
91 def write(self, d):
92 if self._lastop == self.OPREAD:
92 if self._lastop == self.OPREAD:
93 self._noopseek()
93 self._noopseek()
94
94
95 object.__setattr__(self, r'_lastop', self.OPWRITE)
95 object.__setattr__(self, r'_lastop', self.OPWRITE)
96 return self._fp.write(d)
96 return self._fp.write(d)
97
97
98 def writelines(self, *args, **kwargs):
98 def writelines(self, *args, **kwargs):
99 if self._lastop == self.OPREAD:
99 if self._lastop == self.OPREAD:
100 self._noopeseek()
100 self._noopeseek()
101
101
102 object.__setattr__(self, r'_lastop', self.OPWRITE)
102 object.__setattr__(self, r'_lastop', self.OPWRITE)
103 return self._fp.writelines(*args, **kwargs)
103 return self._fp.writelines(*args, **kwargs)
104
104
105 def read(self, *args, **kwargs):
105 def read(self, *args, **kwargs):
106 if self._lastop == self.OPWRITE:
106 if self._lastop == self.OPWRITE:
107 self._noopseek()
107 self._noopseek()
108
108
109 object.__setattr__(self, r'_lastop', self.OPREAD)
109 object.__setattr__(self, r'_lastop', self.OPREAD)
110 return self._fp.read(*args, **kwargs)
110 return self._fp.read(*args, **kwargs)
111
111
112 def readline(self, *args, **kwargs):
112 def readline(self, *args, **kwargs):
113 if self._lastop == self.OPWRITE:
113 if self._lastop == self.OPWRITE:
114 self._noopseek()
114 self._noopseek()
115
115
116 object.__setattr__(self, r'_lastop', self.OPREAD)
116 object.__setattr__(self, r'_lastop', self.OPREAD)
117 return self._fp.readline(*args, **kwargs)
117 return self._fp.readline(*args, **kwargs)
118
118
119 def readlines(self, *args, **kwargs):
119 def readlines(self, *args, **kwargs):
120 if self._lastop == self.OPWRITE:
120 if self._lastop == self.OPWRITE:
121 self._noopseek()
121 self._noopseek()
122
122
123 object.__setattr__(self, r'_lastop', self.OPREAD)
123 object.__setattr__(self, r'_lastop', self.OPREAD)
124 return self._fp.readlines(*args, **kwargs)
124 return self._fp.readlines(*args, **kwargs)
125
125
126 def posixfile(name, mode='r', buffering=-1):
126 def posixfile(name, mode='r', buffering=-1):
127 '''Open a file with even more POSIX-like semantics'''
127 '''Open a file with even more POSIX-like semantics'''
128 try:
128 try:
129 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
129 fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
130
130
131 # The position when opening in append mode is implementation defined, so
131 # The position when opening in append mode is implementation defined, so
132 # make it consistent with other platforms, which position at EOF.
132 # make it consistent with other platforms, which position at EOF.
133 if 'a' in mode:
133 if 'a' in mode:
134 fp.seek(0, os.SEEK_END)
134 fp.seek(0, os.SEEK_END)
135
135
136 if '+' in mode:
136 if '+' in mode:
137 return mixedfilemodewrapper(fp)
137 return mixedfilemodewrapper(fp)
138
138
139 return fp
139 return fp
140 except WindowsError as err:
140 except WindowsError as err:
141 # convert to a friendlier exception
141 # convert to a friendlier exception
142 raise IOError(err.errno, '%s: %s' % (
142 raise IOError(err.errno, '%s: %s' % (
143 name, encoding.strtolocal(err.strerror)))
143 name, encoding.strtolocal(err.strerror)))
144
144
145 # may be wrapped by win32mbcs extension
145 # may be wrapped by win32mbcs extension
146 listdir = osutil.listdir
146 listdir = osutil.listdir
147
147
148 class winstdout(object):
148 class winstdout(object):
149 '''stdout on windows misbehaves if sent through a pipe'''
149 '''stdout on windows misbehaves if sent through a pipe'''
150
150
151 def __init__(self, fp):
151 def __init__(self, fp):
152 self.fp = fp
152 self.fp = fp
153
153
154 def __getattr__(self, key):
154 def __getattr__(self, key):
155 return getattr(self.fp, key)
155 return getattr(self.fp, key)
156
156
157 def close(self):
157 def close(self):
158 try:
158 try:
159 self.fp.close()
159 self.fp.close()
160 except IOError:
160 except IOError:
161 pass
161 pass
162
162
163 def write(self, s):
163 def write(self, s):
164 try:
164 try:
165 # This is workaround for "Not enough space" error on
165 # This is workaround for "Not enough space" error on
166 # writing large size of data to console.
166 # writing large size of data to console.
167 limit = 16000
167 limit = 16000
168 l = len(s)
168 l = len(s)
169 start = 0
169 start = 0
170 self.softspace = 0
170 self.softspace = 0
171 while start < l:
171 while start < l:
172 end = start + limit
172 end = start + limit
173 self.fp.write(s[start:end])
173 self.fp.write(s[start:end])
174 start = end
174 start = end
175 except IOError as inst:
175 except IOError as inst:
176 if inst.errno != 0:
176 if inst.errno != 0:
177 raise
177 raise
178 self.close()
178 self.close()
179 raise IOError(errno.EPIPE, 'Broken pipe')
179 raise IOError(errno.EPIPE, 'Broken pipe')
180
180
181 def flush(self):
181 def flush(self):
182 try:
182 try:
183 return self.fp.flush()
183 return self.fp.flush()
184 except IOError as inst:
184 except IOError as inst:
185 if inst.errno != errno.EINVAL:
185 if inst.errno != errno.EINVAL:
186 raise
186 raise
187 raise IOError(errno.EPIPE, 'Broken pipe')
187 raise IOError(errno.EPIPE, 'Broken pipe')
188
188
189 def _is_win_9x():
189 def _is_win_9x():
190 '''return true if run on windows 95, 98 or me.'''
190 '''return true if run on windows 95, 98 or me.'''
191 try:
191 try:
192 return sys.getwindowsversion()[3] == 1
192 return sys.getwindowsversion()[3] == 1
193 except AttributeError:
193 except AttributeError:
194 return 'command' in encoding.environ.get('comspec', '')
194 return 'command' in encoding.environ.get('comspec', '')
195
195
196 def openhardlinks():
196 def openhardlinks():
197 return not _is_win_9x()
197 return not _is_win_9x()
198
198
199 def parsepatchoutput(output_line):
199 def parsepatchoutput(output_line):
200 """parses the output produced by patch and returns the filename"""
200 """parses the output produced by patch and returns the filename"""
201 pf = output_line[14:]
201 pf = output_line[14:]
202 if pf[0] == '`':
202 if pf[0] == '`':
203 pf = pf[1:-1] # Remove the quotes
203 pf = pf[1:-1] # Remove the quotes
204 return pf
204 return pf
205
205
206 def sshargs(sshcmd, host, user, port):
206 def sshargs(sshcmd, host, user, port):
207 '''Build argument list for ssh or Plink'''
207 '''Build argument list for ssh or Plink'''
208 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
208 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
209 args = user and ("%s@%s" % (user, host)) or host
209 args = user and ("%s@%s" % (user, host)) or host
210 if args.startswith('-') or args.startswith('/'):
210 if args.startswith('-') or args.startswith('/'):
211 raise error.Abort(
211 raise error.Abort(
212 _('illegal ssh hostname or username starting with - or /: %s') %
212 _('illegal ssh hostname or username starting with - or /: %s') %
213 args)
213 args)
214 args = shellquote(args)
214 args = shellquote(args)
215 if port:
215 if port:
216 args = '%s %s %s' % (pflag, shellquote(port), args)
216 args = '%s %s %s' % (pflag, shellquote(port), args)
217 return args
217 return args
218
218
219 def setflags(f, l, x):
219 def setflags(f, l, x):
220 pass
220 pass
221
221
222 def copymode(src, dst, mode=None):
222 def copymode(src, dst, mode=None):
223 pass
223 pass
224
224
225 def checkexec(path):
225 def checkexec(path):
226 return False
226 return False
227
227
228 def checklink(path):
228 def checklink(path):
229 return False
229 return False
230
230
231 def setbinary(fd):
231 def setbinary(fd):
232 # When run without console, pipes may expose invalid
232 # When run without console, pipes may expose invalid
233 # fileno(), usually set to -1.
233 # fileno(), usually set to -1.
234 fno = getattr(fd, 'fileno', None)
234 fno = getattr(fd, 'fileno', None)
235 if fno is not None and fno() >= 0:
235 if fno is not None and fno() >= 0:
236 msvcrt.setmode(fno(), os.O_BINARY)
236 msvcrt.setmode(fno(), os.O_BINARY)
237
237
238 def pconvert(path):
238 def pconvert(path):
239 return path.replace(pycompat.ossep, '/')
239 return path.replace(pycompat.ossep, '/')
240
240
241 def localpath(path):
241 def localpath(path):
242 return path.replace('/', '\\')
242 return path.replace('/', '\\')
243
243
244 def normpath(path):
244 def normpath(path):
245 return pconvert(os.path.normpath(path))
245 return pconvert(os.path.normpath(path))
246
246
247 def normcase(path):
247 def normcase(path):
248 return encoding.upper(path) # NTFS compares via upper()
248 return encoding.upper(path) # NTFS compares via upper()
249
249
250 # see posix.py for definitions
250 # see posix.py for definitions
251 normcasespec = encoding.normcasespecs.upper
251 normcasespec = encoding.normcasespecs.upper
252 normcasefallback = encoding.upperfallback
252 normcasefallback = encoding.upperfallback
253
253
254 def samestat(s1, s2):
254 def samestat(s1, s2):
255 return False
255 return False
256
256
257 # A sequence of backslashes is special iff it precedes a double quote:
257 # A sequence of backslashes is special iff it precedes a double quote:
258 # - if there's an even number of backslashes, the double quote is not
258 # - if there's an even number of backslashes, the double quote is not
259 # quoted (i.e. it ends the quoted region)
259 # quoted (i.e. it ends the quoted region)
260 # - if there's an odd number of backslashes, the double quote is quoted
260 # - if there's an odd number of backslashes, the double quote is quoted
261 # - in both cases, every pair of backslashes is unquoted into a single
261 # - in both cases, every pair of backslashes is unquoted into a single
262 # backslash
262 # backslash
263 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
263 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
264 # So, to quote a string, we must surround it in double quotes, double
264 # So, to quote a string, we must surround it in double quotes, double
265 # the number of backslashes that precede double quotes and add another
265 # the number of backslashes that precede double quotes and add another
266 # backslash before every double quote (being careful with the double
266 # backslash before every double quote (being careful with the double
267 # quote we've appended to the end)
267 # quote we've appended to the end)
268 _quotere = None
268 _quotere = None
269 _needsshellquote = None
269 _needsshellquote = None
270 def shellquote(s):
270 def shellquote(s):
271 r"""
271 r"""
272 >>> shellquote(br'C:\Users\xyz')
272 >>> shellquote(br'C:\Users\xyz')
273 '"C:\\Users\\xyz"'
273 '"C:\\Users\\xyz"'
274 >>> shellquote(br'C:\Users\xyz/mixed')
274 >>> shellquote(br'C:\Users\xyz/mixed')
275 '"C:\\Users\\xyz/mixed"'
275 '"C:\\Users\\xyz/mixed"'
276 >>> # Would be safe not to quote too, since it is all double backslashes
276 >>> # Would be safe not to quote too, since it is all double backslashes
277 >>> shellquote(br'C:\\Users\\xyz')
277 >>> shellquote(br'C:\\Users\\xyz')
278 '"C:\\\\Users\\\\xyz"'
278 '"C:\\\\Users\\\\xyz"'
279 >>> # But this must be quoted
279 >>> # But this must be quoted
280 >>> shellquote(br'C:\\Users\\xyz/abc')
280 >>> shellquote(br'C:\\Users\\xyz/abc')
281 '"C:\\\\Users\\\\xyz/abc"'
281 '"C:\\\\Users\\\\xyz/abc"'
282 """
282 """
283 global _quotere
283 global _quotere
284 if _quotere is None:
284 if _quotere is None:
285 _quotere = re.compile(r'(\\*)("|\\$)')
285 _quotere = re.compile(r'(\\*)("|\\$)')
286 global _needsshellquote
286 global _needsshellquote
287 if _needsshellquote is None:
287 if _needsshellquote is None:
288 # ":" is also treated as "safe character", because it is used as a part
288 # ":" is also treated as "safe character", because it is used as a part
289 # of path name on Windows. "\" is also part of a path name, but isn't
289 # of path name on Windows. "\" is also part of a path name, but isn't
290 # safe because shlex.split() (kind of) treats it as an escape char and
290 # safe because shlex.split() (kind of) treats it as an escape char and
291 # drops it. It will leave the next character, even if it is another
291 # drops it. It will leave the next character, even if it is another
292 # "\".
292 # "\".
293 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
293 _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
294 if s and not _needsshellquote(s) and not _quotere.search(s):
294 if s and not _needsshellquote(s) and not _quotere.search(s):
295 # "s" shouldn't have to be quoted
295 # "s" shouldn't have to be quoted
296 return s
296 return s
297 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
297 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
298
298
299 def _unquote(s):
299 def _unquote(s):
300 if s.startswith(b'"') and s.endswith(b'"'):
300 if s.startswith(b'"') and s.endswith(b'"'):
301 return s[1:-1]
301 return s[1:-1]
302 return s
302 return s
303
303
304 def shellsplit(s):
304 def shellsplit(s):
305 """Parse a command string in cmd.exe way (best-effort)"""
305 """Parse a command string in cmd.exe way (best-effort)"""
306 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
306 return pycompat.maplist(_unquote, pycompat.shlexsplit(s, posix=False))
307
307
308 def quotecommand(cmd):
308 def quotecommand(cmd):
309 """Build a command string suitable for os.popen* calls."""
309 """Build a command string suitable for os.popen* calls."""
310 if sys.version_info < (2, 7, 1):
310 if sys.version_info < (2, 7, 1):
311 # Python versions since 2.7.1 do this extra quoting themselves
311 # Python versions since 2.7.1 do this extra quoting themselves
312 return '"' + cmd + '"'
312 return '"' + cmd + '"'
313 return cmd
313 return cmd
314
314
315 def popen(command, mode='r'):
315 def popen(command, mode='r'):
316 # Work around "popen spawned process may not write to stdout
316 # Work around "popen spawned process may not write to stdout
317 # under windows"
317 # under windows"
318 # http://bugs.python.org/issue1366
318 # http://bugs.python.org/issue1366
319 command += " 2> %s" % os.devnull
319 command += " 2> %s" % pycompat.bytestr(os.devnull)
320 return os.popen(quotecommand(command), mode)
320 return os.popen(quotecommand(command), mode)
321
321
322 def explainexit(code):
322 def explainexit(code):
323 return _("exited with status %d") % code, code
323 return _("exited with status %d") % code, code
324
324
325 # if you change this stub into a real check, please try to implement the
325 # if you change this stub into a real check, please try to implement the
326 # username and groupname functions above, too.
326 # username and groupname functions above, too.
327 def isowner(st):
327 def isowner(st):
328 return True
328 return True
329
329
330 def findexe(command):
330 def findexe(command):
331 '''Find executable for command searching like cmd.exe does.
331 '''Find executable for command searching like cmd.exe does.
332 If command is a basename then PATH is searched for command.
332 If command is a basename then PATH is searched for command.
333 PATH isn't searched if command is an absolute or relative path.
333 PATH isn't searched if command is an absolute or relative path.
334 An extension from PATHEXT is found and added if not present.
334 An extension from PATHEXT is found and added if not present.
335 If command isn't found None is returned.'''
335 If command isn't found None is returned.'''
336 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
336 pathext = encoding.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
337 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
337 pathexts = [ext for ext in pathext.lower().split(pycompat.ospathsep)]
338 if os.path.splitext(command)[1].lower() in pathexts:
338 if os.path.splitext(command)[1].lower() in pathexts:
339 pathexts = ['']
339 pathexts = ['']
340
340
341 def findexisting(pathcommand):
341 def findexisting(pathcommand):
342 'Will append extension (if needed) and return existing file'
342 'Will append extension (if needed) and return existing file'
343 for ext in pathexts:
343 for ext in pathexts:
344 executable = pathcommand + ext
344 executable = pathcommand + ext
345 if os.path.exists(executable):
345 if os.path.exists(executable):
346 return executable
346 return executable
347 return None
347 return None
348
348
349 if pycompat.ossep in command:
349 if pycompat.ossep in command:
350 return findexisting(command)
350 return findexisting(command)
351
351
352 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
352 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
353 executable = findexisting(os.path.join(path, command))
353 executable = findexisting(os.path.join(path, command))
354 if executable is not None:
354 if executable is not None:
355 return executable
355 return executable
356 return findexisting(os.path.expanduser(os.path.expandvars(command)))
356 return findexisting(os.path.expanduser(os.path.expandvars(command)))
357
357
358 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
358 _wantedkinds = {stat.S_IFREG, stat.S_IFLNK}
359
359
360 def statfiles(files):
360 def statfiles(files):
361 '''Stat each file in files. Yield each stat, or None if a file
361 '''Stat each file in files. Yield each stat, or None if a file
362 does not exist or has a type we don't care about.
362 does not exist or has a type we don't care about.
363
363
364 Cluster and cache stat per directory to minimize number of OS stat calls.'''
364 Cluster and cache stat per directory to minimize number of OS stat calls.'''
365 dircache = {} # dirname -> filename -> status | None if file does not exist
365 dircache = {} # dirname -> filename -> status | None if file does not exist
366 getkind = stat.S_IFMT
366 getkind = stat.S_IFMT
367 for nf in files:
367 for nf in files:
368 nf = normcase(nf)
368 nf = normcase(nf)
369 dir, base = os.path.split(nf)
369 dir, base = os.path.split(nf)
370 if not dir:
370 if not dir:
371 dir = '.'
371 dir = '.'
372 cache = dircache.get(dir, None)
372 cache = dircache.get(dir, None)
373 if cache is None:
373 if cache is None:
374 try:
374 try:
375 dmap = dict([(normcase(n), s)
375 dmap = dict([(normcase(n), s)
376 for n, k, s in listdir(dir, True)
376 for n, k, s in listdir(dir, True)
377 if getkind(s.st_mode) in _wantedkinds])
377 if getkind(s.st_mode) in _wantedkinds])
378 except OSError as err:
378 except OSError as err:
379 # Python >= 2.5 returns ENOENT and adds winerror field
379 # Python >= 2.5 returns ENOENT and adds winerror field
380 # EINVAL is raised if dir is not a directory.
380 # EINVAL is raised if dir is not a directory.
381 if err.errno not in (errno.ENOENT, errno.EINVAL,
381 if err.errno not in (errno.ENOENT, errno.EINVAL,
382 errno.ENOTDIR):
382 errno.ENOTDIR):
383 raise
383 raise
384 dmap = {}
384 dmap = {}
385 cache = dircache.setdefault(dir, dmap)
385 cache = dircache.setdefault(dir, dmap)
386 yield cache.get(base, None)
386 yield cache.get(base, None)
387
387
388 def username(uid=None):
388 def username(uid=None):
389 """Return the name of the user with the given uid.
389 """Return the name of the user with the given uid.
390
390
391 If uid is None, return the name of the current user."""
391 If uid is None, return the name of the current user."""
392 return None
392 return None
393
393
394 def groupname(gid=None):
394 def groupname(gid=None):
395 """Return the name of the group with the given gid.
395 """Return the name of the group with the given gid.
396
396
397 If gid is None, return the name of the current group."""
397 If gid is None, return the name of the current group."""
398 return None
398 return None
399
399
400 def removedirs(name):
400 def removedirs(name):
401 """special version of os.removedirs that does not remove symlinked
401 """special version of os.removedirs that does not remove symlinked
402 directories or junction points if they actually contain files"""
402 directories or junction points if they actually contain files"""
403 if listdir(name):
403 if listdir(name):
404 return
404 return
405 os.rmdir(name)
405 os.rmdir(name)
406 head, tail = os.path.split(name)
406 head, tail = os.path.split(name)
407 if not tail:
407 if not tail:
408 head, tail = os.path.split(head)
408 head, tail = os.path.split(head)
409 while head and tail:
409 while head and tail:
410 try:
410 try:
411 if listdir(head):
411 if listdir(head):
412 return
412 return
413 os.rmdir(head)
413 os.rmdir(head)
414 except (ValueError, OSError):
414 except (ValueError, OSError):
415 break
415 break
416 head, tail = os.path.split(head)
416 head, tail = os.path.split(head)
417
417
418 def rename(src, dst):
418 def rename(src, dst):
419 '''atomically rename file src to dst, replacing dst if it exists'''
419 '''atomically rename file src to dst, replacing dst if it exists'''
420 try:
420 try:
421 os.rename(src, dst)
421 os.rename(src, dst)
422 except OSError as e:
422 except OSError as e:
423 if e.errno != errno.EEXIST:
423 if e.errno != errno.EEXIST:
424 raise
424 raise
425 unlink(dst)
425 unlink(dst)
426 os.rename(src, dst)
426 os.rename(src, dst)
427
427
428 def gethgcmd():
428 def gethgcmd():
429 return [sys.executable] + sys.argv[:1]
429 return [sys.executable] + sys.argv[:1]
430
430
431 def groupmembers(name):
431 def groupmembers(name):
432 # Don't support groups on Windows for now
432 # Don't support groups on Windows for now
433 raise KeyError
433 raise KeyError
434
434
435 def isexec(f):
435 def isexec(f):
436 return False
436 return False
437
437
438 class cachestat(object):
438 class cachestat(object):
439 def __init__(self, path):
439 def __init__(self, path):
440 pass
440 pass
441
441
442 def cacheable(self):
442 def cacheable(self):
443 return False
443 return False
444
444
445 def lookupreg(key, valname=None, scope=None):
445 def lookupreg(key, valname=None, scope=None):
446 ''' Look up a key/value name in the Windows registry.
446 ''' Look up a key/value name in the Windows registry.
447
447
448 valname: value name. If unspecified, the default value for the key
448 valname: value name. If unspecified, the default value for the key
449 is used.
449 is used.
450 scope: optionally specify scope for registry lookup, this can be
450 scope: optionally specify scope for registry lookup, this can be
451 a sequence of scopes to look up in order. Default (CURRENT_USER,
451 a sequence of scopes to look up in order. Default (CURRENT_USER,
452 LOCAL_MACHINE).
452 LOCAL_MACHINE).
453 '''
453 '''
454 if scope is None:
454 if scope is None:
455 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
455 scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
456 elif not isinstance(scope, (list, tuple)):
456 elif not isinstance(scope, (list, tuple)):
457 scope = (scope,)
457 scope = (scope,)
458 for s in scope:
458 for s in scope:
459 try:
459 try:
460 val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
460 val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
461 # never let a Unicode string escape into the wild
461 # never let a Unicode string escape into the wild
462 return encoding.unitolocal(val)
462 return encoding.unitolocal(val)
463 except EnvironmentError:
463 except EnvironmentError:
464 pass
464 pass
465
465
466 expandglobs = True
466 expandglobs = True
467
467
468 def statislink(st):
468 def statislink(st):
469 '''check whether a stat result is a symlink'''
469 '''check whether a stat result is a symlink'''
470 return False
470 return False
471
471
472 def statisexec(st):
472 def statisexec(st):
473 '''check whether a stat result is an executable file'''
473 '''check whether a stat result is an executable file'''
474 return False
474 return False
475
475
476 def poll(fds):
476 def poll(fds):
477 # see posix.py for description
477 # see posix.py for description
478 raise NotImplementedError()
478 raise NotImplementedError()
479
479
480 def readpipe(pipe):
480 def readpipe(pipe):
481 """Read all available data from a pipe."""
481 """Read all available data from a pipe."""
482 chunks = []
482 chunks = []
483 while True:
483 while True:
484 size = win32.peekpipe(pipe)
484 size = win32.peekpipe(pipe)
485 if not size:
485 if not size:
486 break
486 break
487
487
488 s = pipe.read(size)
488 s = pipe.read(size)
489 if not s:
489 if not s:
490 break
490 break
491 chunks.append(s)
491 chunks.append(s)
492
492
493 return ''.join(chunks)
493 return ''.join(chunks)
494
494
495 def bindunixsocket(sock, path):
495 def bindunixsocket(sock, path):
496 raise NotImplementedError('unsupported platform')
496 raise NotImplementedError('unsupported platform')
General Comments 0
You need to be logged in to leave comments. Login now