##// END OF EJS Templates
convert: drop a duplicate implementation of `dateutil.makedate()`...
Matt Harbison -
r52577:20e2a206 default
parent child Browse files
Show More
@@ -1,576 +1,571
1 # common.py - common code for the convert extension
1 # common.py - common code for the convert extension
2 #
2 #
3 # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2005-2009 Olivia Mackall <olivia@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 import base64
8 import base64
9 import datetime
10 import os
9 import os
11 import pickle
10 import pickle
12 import re
11 import re
13 import shlex
12 import shlex
14 import subprocess
13 import subprocess
15
14
16 from mercurial.i18n import _
15 from mercurial.i18n import _
17 from mercurial.pycompat import open
16 from mercurial.pycompat import open
18 from mercurial import (
17 from mercurial import (
19 encoding,
18 encoding,
20 error,
19 error,
21 phases,
20 phases,
22 pycompat,
21 pycompat,
23 util,
22 util,
24 )
23 )
25 from mercurial.utils import procutil
24 from mercurial.utils import (
25 dateutil,
26 procutil,
27 )
26
28
27 propertycache = util.propertycache
29 propertycache = util.propertycache
28
30
29
31
30 def _encodeornone(d):
32 def _encodeornone(d):
31 if d is None:
33 if d is None:
32 return
34 return
33 return d.encode('latin1')
35 return d.encode('latin1')
34
36
35
37
36 class _shlexpy3proxy:
38 class _shlexpy3proxy:
37 def __init__(self, l):
39 def __init__(self, l):
38 self._l = l
40 self._l = l
39
41
40 def __iter__(self):
42 def __iter__(self):
41 return (_encodeornone(v) for v in self._l)
43 return (_encodeornone(v) for v in self._l)
42
44
43 def get_token(self):
45 def get_token(self):
44 return _encodeornone(self._l.get_token())
46 return _encodeornone(self._l.get_token())
45
47
46 @property
48 @property
47 def infile(self):
49 def infile(self):
48 return self._l.infile or b'<unknown>'
50 return self._l.infile or b'<unknown>'
49
51
50 @property
52 @property
51 def lineno(self):
53 def lineno(self):
52 return self._l.lineno
54 return self._l.lineno
53
55
54
56
55 def shlexer(data=None, filepath=None, wordchars=None, whitespace=None):
57 def shlexer(data=None, filepath=None, wordchars=None, whitespace=None):
56 if data is None:
58 if data is None:
57 data = open(filepath, b'r', encoding='latin1')
59 data = open(filepath, b'r', encoding='latin1')
58 else:
60 else:
59 if filepath is not None:
61 if filepath is not None:
60 raise error.ProgrammingError(
62 raise error.ProgrammingError(
61 b'shlexer only accepts data or filepath, not both'
63 b'shlexer only accepts data or filepath, not both'
62 )
64 )
63 data = data.decode('latin1')
65 data = data.decode('latin1')
64 l = shlex.shlex(data, infile=filepath, posix=True)
66 l = shlex.shlex(data, infile=filepath, posix=True)
65 if whitespace is not None:
67 if whitespace is not None:
66 l.whitespace_split = True
68 l.whitespace_split = True
67 l.whitespace += whitespace.decode('latin1')
69 l.whitespace += whitespace.decode('latin1')
68 if wordchars is not None:
70 if wordchars is not None:
69 l.wordchars += wordchars.decode('latin1')
71 l.wordchars += wordchars.decode('latin1')
70 return _shlexpy3proxy(l)
72 return _shlexpy3proxy(l)
71
73
72
74
73 def encodeargs(args):
75 def encodeargs(args):
74 def encodearg(s):
76 def encodearg(s):
75 lines = base64.encodebytes(s)
77 lines = base64.encodebytes(s)
76 lines = [l.splitlines()[0] for l in pycompat.iterbytestr(lines)]
78 lines = [l.splitlines()[0] for l in pycompat.iterbytestr(lines)]
77 return b''.join(lines)
79 return b''.join(lines)
78
80
79 s = pickle.dumps(args)
81 s = pickle.dumps(args)
80 return encodearg(s)
82 return encodearg(s)
81
83
82
84
83 def decodeargs(s):
85 def decodeargs(s):
84 s = base64.decodebytes(s)
86 s = base64.decodebytes(s)
85 return pickle.loads(s)
87 return pickle.loads(s)
86
88
87
89
88 class MissingTool(Exception):
90 class MissingTool(Exception):
89 pass
91 pass
90
92
91
93
92 def checktool(exe, name=None, abort=True):
94 def checktool(exe, name=None, abort=True):
93 name = name or exe
95 name = name or exe
94 if not procutil.findexe(exe):
96 if not procutil.findexe(exe):
95 if abort:
97 if abort:
96 exc = error.Abort
98 exc = error.Abort
97 else:
99 else:
98 exc = MissingTool
100 exc = MissingTool
99 raise exc(_(b'cannot find required "%s" tool') % name)
101 raise exc(_(b'cannot find required "%s" tool') % name)
100
102
101
103
102 class NoRepo(Exception):
104 class NoRepo(Exception):
103 pass
105 pass
104
106
105
107
106 SKIPREV = b'SKIP'
108 SKIPREV = b'SKIP'
107
109
108
110
109 class commit:
111 class commit:
110 def __init__(
112 def __init__(
111 self,
113 self,
112 author,
114 author,
113 date,
115 date,
114 desc,
116 desc,
115 parents,
117 parents,
116 branch=None,
118 branch=None,
117 rev=None,
119 rev=None,
118 extra=None,
120 extra=None,
119 sortkey=None,
121 sortkey=None,
120 saverev=True,
122 saverev=True,
121 phase=phases.draft,
123 phase=phases.draft,
122 optparents=None,
124 optparents=None,
123 ctx=None,
125 ctx=None,
124 ):
126 ):
125 self.author = author or b'unknown'
127 self.author = author or b'unknown'
126 self.date = date or b'0 0'
128 self.date = date or b'0 0'
127 self.desc = desc
129 self.desc = desc
128 self.parents = parents # will be converted and used as parents
130 self.parents = parents # will be converted and used as parents
129 self.optparents = optparents or [] # will be used if already converted
131 self.optparents = optparents or [] # will be used if already converted
130 self.branch = branch
132 self.branch = branch
131 self.rev = rev
133 self.rev = rev
132 self.extra = extra or {}
134 self.extra = extra or {}
133 self.sortkey = sortkey
135 self.sortkey = sortkey
134 self.saverev = saverev
136 self.saverev = saverev
135 self.phase = phase
137 self.phase = phase
136 self.ctx = ctx # for hg to hg conversions
138 self.ctx = ctx # for hg to hg conversions
137
139
138
140
139 class converter_source:
141 class converter_source:
140 """Conversion source interface"""
142 """Conversion source interface"""
141
143
142 def __init__(self, ui, repotype, path=None, revs=None):
144 def __init__(self, ui, repotype, path=None, revs=None):
143 """Initialize conversion source (or raise NoRepo("message")
145 """Initialize conversion source (or raise NoRepo("message")
144 exception if path is not a valid repository)"""
146 exception if path is not a valid repository)"""
145 self.ui = ui
147 self.ui = ui
146 self.path = path
148 self.path = path
147 self.revs = revs
149 self.revs = revs
148 self.repotype = repotype
150 self.repotype = repotype
149
151
150 self.encoding = b'utf-8'
152 self.encoding = b'utf-8'
151
153
152 def checkhexformat(self, revstr, mapname=b'splicemap'):
154 def checkhexformat(self, revstr, mapname=b'splicemap'):
153 """fails if revstr is not a 40 byte hex. mercurial and git both uses
155 """fails if revstr is not a 40 byte hex. mercurial and git both uses
154 such format for their revision numbering
156 such format for their revision numbering
155 """
157 """
156 if not re.match(br'[0-9a-fA-F]{40,40}$', revstr):
158 if not re.match(br'[0-9a-fA-F]{40,40}$', revstr):
157 raise error.Abort(
159 raise error.Abort(
158 _(b'%s entry %s is not a valid revision identifier')
160 _(b'%s entry %s is not a valid revision identifier')
159 % (mapname, revstr)
161 % (mapname, revstr)
160 )
162 )
161
163
162 def before(self):
164 def before(self):
163 pass
165 pass
164
166
165 def after(self):
167 def after(self):
166 pass
168 pass
167
169
168 def targetfilebelongstosource(self, targetfilename):
170 def targetfilebelongstosource(self, targetfilename):
169 """Returns true if the given targetfile belongs to the source repo. This
171 """Returns true if the given targetfile belongs to the source repo. This
170 is useful when only a subdirectory of the target belongs to the source
172 is useful when only a subdirectory of the target belongs to the source
171 repo."""
173 repo."""
172 # For normal full repo converts, this is always True.
174 # For normal full repo converts, this is always True.
173 return True
175 return True
174
176
175 def setrevmap(self, revmap):
177 def setrevmap(self, revmap):
176 """set the map of already-converted revisions"""
178 """set the map of already-converted revisions"""
177
179
178 def getheads(self):
180 def getheads(self):
179 """Return a list of this repository's heads"""
181 """Return a list of this repository's heads"""
180 raise NotImplementedError
182 raise NotImplementedError
181
183
182 def getfile(self, name, rev):
184 def getfile(self, name, rev):
183 """Return a pair (data, mode) where data is the file content
185 """Return a pair (data, mode) where data is the file content
184 as a string and mode one of '', 'x' or 'l'. rev is the
186 as a string and mode one of '', 'x' or 'l'. rev is the
185 identifier returned by a previous call to getchanges().
187 identifier returned by a previous call to getchanges().
186 Data is None if file is missing/deleted in rev.
188 Data is None if file is missing/deleted in rev.
187 """
189 """
188 raise NotImplementedError
190 raise NotImplementedError
189
191
190 def getchanges(self, version, full):
192 def getchanges(self, version, full):
191 """Returns a tuple of (files, copies, cleanp2).
193 """Returns a tuple of (files, copies, cleanp2).
192
194
193 files is a sorted list of (filename, id) tuples for all files
195 files is a sorted list of (filename, id) tuples for all files
194 changed between version and its first parent returned by
196 changed between version and its first parent returned by
195 getcommit(). If full, all files in that revision is returned.
197 getcommit(). If full, all files in that revision is returned.
196 id is the source revision id of the file.
198 id is the source revision id of the file.
197
199
198 copies is a dictionary of dest: source
200 copies is a dictionary of dest: source
199
201
200 cleanp2 is the set of files filenames that are clean against p2.
202 cleanp2 is the set of files filenames that are clean against p2.
201 (Files that are clean against p1 are already not in files (unless
203 (Files that are clean against p1 are already not in files (unless
202 full). This makes it possible to handle p2 clean files similarly.)
204 full). This makes it possible to handle p2 clean files similarly.)
203 """
205 """
204 raise NotImplementedError
206 raise NotImplementedError
205
207
206 def getcommit(self, version):
208 def getcommit(self, version):
207 """Return the commit object for version"""
209 """Return the commit object for version"""
208 raise NotImplementedError
210 raise NotImplementedError
209
211
210 def numcommits(self):
212 def numcommits(self):
211 """Return the number of commits in this source.
213 """Return the number of commits in this source.
212
214
213 If unknown, return None.
215 If unknown, return None.
214 """
216 """
215 return None
217 return None
216
218
217 def gettags(self):
219 def gettags(self):
218 """Return the tags as a dictionary of name: revision
220 """Return the tags as a dictionary of name: revision
219
221
220 Tag names must be UTF-8 strings.
222 Tag names must be UTF-8 strings.
221 """
223 """
222 raise NotImplementedError
224 raise NotImplementedError
223
225
224 def recode(self, s, encoding=None):
226 def recode(self, s, encoding=None):
225 if not encoding:
227 if not encoding:
226 encoding = self.encoding or b'utf-8'
228 encoding = self.encoding or b'utf-8'
227
229
228 if isinstance(s, str):
230 if isinstance(s, str):
229 return s.encode("utf-8")
231 return s.encode("utf-8")
230 try:
232 try:
231 return s.decode(pycompat.sysstr(encoding)).encode("utf-8")
233 return s.decode(pycompat.sysstr(encoding)).encode("utf-8")
232 except UnicodeError:
234 except UnicodeError:
233 try:
235 try:
234 return s.decode("latin-1").encode("utf-8")
236 return s.decode("latin-1").encode("utf-8")
235 except UnicodeError:
237 except UnicodeError:
236 return s.decode(pycompat.sysstr(encoding), "replace").encode(
238 return s.decode(pycompat.sysstr(encoding), "replace").encode(
237 "utf-8"
239 "utf-8"
238 )
240 )
239
241
240 def getchangedfiles(self, rev, i):
242 def getchangedfiles(self, rev, i):
241 """Return the files changed by rev compared to parent[i].
243 """Return the files changed by rev compared to parent[i].
242
244
243 i is an index selecting one of the parents of rev. The return
245 i is an index selecting one of the parents of rev. The return
244 value should be the list of files that are different in rev and
246 value should be the list of files that are different in rev and
245 this parent.
247 this parent.
246
248
247 If rev has no parents, i is None.
249 If rev has no parents, i is None.
248
250
249 This function is only needed to support --filemap
251 This function is only needed to support --filemap
250 """
252 """
251 raise NotImplementedError
253 raise NotImplementedError
252
254
253 def converted(self, rev, sinkrev):
255 def converted(self, rev, sinkrev):
254 '''Notify the source that a revision has been converted.'''
256 '''Notify the source that a revision has been converted.'''
255
257
256 def hasnativeorder(self):
258 def hasnativeorder(self):
257 """Return true if this source has a meaningful, native revision
259 """Return true if this source has a meaningful, native revision
258 order. For instance, Mercurial revisions are store sequentially
260 order. For instance, Mercurial revisions are store sequentially
259 while there is no such global ordering with Darcs.
261 while there is no such global ordering with Darcs.
260 """
262 """
261 return False
263 return False
262
264
263 def hasnativeclose(self):
265 def hasnativeclose(self):
264 """Return true if this source has ability to close branch."""
266 """Return true if this source has ability to close branch."""
265 return False
267 return False
266
268
267 def lookuprev(self, rev):
269 def lookuprev(self, rev):
268 """If rev is a meaningful revision reference in source, return
270 """If rev is a meaningful revision reference in source, return
269 the referenced identifier in the same format used by getcommit().
271 the referenced identifier in the same format used by getcommit().
270 return None otherwise.
272 return None otherwise.
271 """
273 """
272 return None
274 return None
273
275
274 def getbookmarks(self):
276 def getbookmarks(self):
275 """Return the bookmarks as a dictionary of name: revision
277 """Return the bookmarks as a dictionary of name: revision
276
278
277 Bookmark names are to be UTF-8 strings.
279 Bookmark names are to be UTF-8 strings.
278 """
280 """
279 return {}
281 return {}
280
282
281 def checkrevformat(self, revstr, mapname=b'splicemap'):
283 def checkrevformat(self, revstr, mapname=b'splicemap'):
282 """revstr is a string that describes a revision in the given
284 """revstr is a string that describes a revision in the given
283 source control system. Return true if revstr has correct
285 source control system. Return true if revstr has correct
284 format.
286 format.
285 """
287 """
286 return True
288 return True
287
289
288
290
289 class converter_sink:
291 class converter_sink:
290 """Conversion sink (target) interface"""
292 """Conversion sink (target) interface"""
291
293
292 def __init__(self, ui, repotype, path):
294 def __init__(self, ui, repotype, path):
293 """Initialize conversion sink (or raise NoRepo("message")
295 """Initialize conversion sink (or raise NoRepo("message")
294 exception if path is not a valid repository)
296 exception if path is not a valid repository)
295
297
296 created is a list of paths to remove if a fatal error occurs
298 created is a list of paths to remove if a fatal error occurs
297 later"""
299 later"""
298 self.ui = ui
300 self.ui = ui
299 self.path = path
301 self.path = path
300 self.created = []
302 self.created = []
301 self.repotype = repotype
303 self.repotype = repotype
302
304
303 def revmapfile(self):
305 def revmapfile(self):
304 """Path to a file that will contain lines
306 """Path to a file that will contain lines
305 source_rev_id sink_rev_id
307 source_rev_id sink_rev_id
306 mapping equivalent revision identifiers for each system."""
308 mapping equivalent revision identifiers for each system."""
307 raise NotImplementedError
309 raise NotImplementedError
308
310
309 def authorfile(self):
311 def authorfile(self):
310 """Path to a file that will contain lines
312 """Path to a file that will contain lines
311 srcauthor=dstauthor
313 srcauthor=dstauthor
312 mapping equivalent authors identifiers for each system."""
314 mapping equivalent authors identifiers for each system."""
313 return None
315 return None
314
316
315 def putcommit(
317 def putcommit(
316 self, files, copies, parents, commit, source, revmap, full, cleanp2
318 self, files, copies, parents, commit, source, revmap, full, cleanp2
317 ):
319 ):
318 """Create a revision with all changed files listed in 'files'
320 """Create a revision with all changed files listed in 'files'
319 and having listed parents. 'commit' is a commit object
321 and having listed parents. 'commit' is a commit object
320 containing at a minimum the author, date, and message for this
322 containing at a minimum the author, date, and message for this
321 changeset. 'files' is a list of (path, version) tuples,
323 changeset. 'files' is a list of (path, version) tuples,
322 'copies' is a dictionary mapping destinations to sources,
324 'copies' is a dictionary mapping destinations to sources,
323 'source' is the source repository, and 'revmap' is a mapfile
325 'source' is the source repository, and 'revmap' is a mapfile
324 of source revisions to converted revisions. Only getfile() and
326 of source revisions to converted revisions. Only getfile() and
325 lookuprev() should be called on 'source'. 'full' means that 'files'
327 lookuprev() should be called on 'source'. 'full' means that 'files'
326 is complete and all other files should be removed.
328 is complete and all other files should be removed.
327 'cleanp2' is a set of the filenames that are unchanged from p2
329 'cleanp2' is a set of the filenames that are unchanged from p2
328 (only in the common merge case where there two parents).
330 (only in the common merge case where there two parents).
329
331
330 Note that the sink repository is not told to update itself to
332 Note that the sink repository is not told to update itself to
331 a particular revision (or even what that revision would be)
333 a particular revision (or even what that revision would be)
332 before it receives the file data.
334 before it receives the file data.
333 """
335 """
334 raise NotImplementedError
336 raise NotImplementedError
335
337
336 def puttags(self, tags):
338 def puttags(self, tags):
337 """Put tags into sink.
339 """Put tags into sink.
338
340
339 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
341 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
340 Return a pair (tag_revision, tag_parent_revision), or (None, None)
342 Return a pair (tag_revision, tag_parent_revision), or (None, None)
341 if nothing was changed.
343 if nothing was changed.
342 """
344 """
343 raise NotImplementedError
345 raise NotImplementedError
344
346
345 def setbranch(self, branch, pbranches):
347 def setbranch(self, branch, pbranches):
346 """Set the current branch name. Called before the first putcommit
348 """Set the current branch name. Called before the first putcommit
347 on the branch.
349 on the branch.
348 branch: branch name for subsequent commits
350 branch: branch name for subsequent commits
349 pbranches: (converted parent revision, parent branch) tuples"""
351 pbranches: (converted parent revision, parent branch) tuples"""
350
352
351 def setfilemapmode(self, active):
353 def setfilemapmode(self, active):
352 """Tell the destination that we're using a filemap
354 """Tell the destination that we're using a filemap
353
355
354 Some converter_sources (svn in particular) can claim that a file
356 Some converter_sources (svn in particular) can claim that a file
355 was changed in a revision, even if there was no change. This method
357 was changed in a revision, even if there was no change. This method
356 tells the destination that we're using a filemap and that it should
358 tells the destination that we're using a filemap and that it should
357 filter empty revisions.
359 filter empty revisions.
358 """
360 """
359
361
360 def before(self):
362 def before(self):
361 pass
363 pass
362
364
363 def after(self):
365 def after(self):
364 pass
366 pass
365
367
366 def putbookmarks(self, bookmarks):
368 def putbookmarks(self, bookmarks):
367 """Put bookmarks into sink.
369 """Put bookmarks into sink.
368
370
369 bookmarks: {bookmarkname: sink_rev_id, ...}
371 bookmarks: {bookmarkname: sink_rev_id, ...}
370 where bookmarkname is an UTF-8 string.
372 where bookmarkname is an UTF-8 string.
371 """
373 """
372
374
373 def hascommitfrommap(self, rev):
375 def hascommitfrommap(self, rev):
374 """Return False if a rev mentioned in a filemap is known to not be
376 """Return False if a rev mentioned in a filemap is known to not be
375 present."""
377 present."""
376 raise NotImplementedError
378 raise NotImplementedError
377
379
378 def hascommitforsplicemap(self, rev):
380 def hascommitforsplicemap(self, rev):
379 """This method is for the special needs for splicemap handling and not
381 """This method is for the special needs for splicemap handling and not
380 for general use. Returns True if the sink contains rev, aborts on some
382 for general use. Returns True if the sink contains rev, aborts on some
381 special cases."""
383 special cases."""
382 raise NotImplementedError
384 raise NotImplementedError
383
385
384
386
385 class commandline:
387 class commandline:
386 def __init__(self, ui, command):
388 def __init__(self, ui, command):
387 self.ui = ui
389 self.ui = ui
388 self.command = command
390 self.command = command
389
391
390 def prerun(self):
392 def prerun(self):
391 pass
393 pass
392
394
393 def postrun(self):
395 def postrun(self):
394 pass
396 pass
395
397
396 def _cmdline(self, cmd, *args, **kwargs):
398 def _cmdline(self, cmd, *args, **kwargs):
397 kwargs = pycompat.byteskwargs(kwargs)
399 kwargs = pycompat.byteskwargs(kwargs)
398 cmdline = [self.command, cmd] + list(args)
400 cmdline = [self.command, cmd] + list(args)
399 for k, v in kwargs.items():
401 for k, v in kwargs.items():
400 if len(k) == 1:
402 if len(k) == 1:
401 cmdline.append(b'-' + k)
403 cmdline.append(b'-' + k)
402 else:
404 else:
403 cmdline.append(b'--' + k.replace(b'_', b'-'))
405 cmdline.append(b'--' + k.replace(b'_', b'-'))
404 try:
406 try:
405 if len(k) == 1:
407 if len(k) == 1:
406 cmdline.append(b'' + v)
408 cmdline.append(b'' + v)
407 else:
409 else:
408 cmdline[-1] += b'=' + v
410 cmdline[-1] += b'=' + v
409 except TypeError:
411 except TypeError:
410 pass
412 pass
411 cmdline = [procutil.shellquote(arg) for arg in cmdline]
413 cmdline = [procutil.shellquote(arg) for arg in cmdline]
412 if not self.ui.debugflag:
414 if not self.ui.debugflag:
413 cmdline += [b'2>', pycompat.bytestr(os.devnull)]
415 cmdline += [b'2>', pycompat.bytestr(os.devnull)]
414 cmdline = b' '.join(cmdline)
416 cmdline = b' '.join(cmdline)
415 return cmdline
417 return cmdline
416
418
417 def _run(self, cmd, *args, **kwargs):
419 def _run(self, cmd, *args, **kwargs):
418 def popen(cmdline):
420 def popen(cmdline):
419 p = subprocess.Popen(
421 p = subprocess.Popen(
420 procutil.tonativestr(cmdline),
422 procutil.tonativestr(cmdline),
421 shell=True,
423 shell=True,
422 bufsize=-1,
424 bufsize=-1,
423 close_fds=procutil.closefds,
425 close_fds=procutil.closefds,
424 stdout=subprocess.PIPE,
426 stdout=subprocess.PIPE,
425 )
427 )
426 return p
428 return p
427
429
428 return self._dorun(popen, cmd, *args, **kwargs)
430 return self._dorun(popen, cmd, *args, **kwargs)
429
431
430 def _run2(self, cmd, *args, **kwargs):
432 def _run2(self, cmd, *args, **kwargs):
431 return self._dorun(procutil.popen2, cmd, *args, **kwargs)
433 return self._dorun(procutil.popen2, cmd, *args, **kwargs)
432
434
433 def _run3(self, cmd, *args, **kwargs):
435 def _run3(self, cmd, *args, **kwargs):
434 return self._dorun(procutil.popen3, cmd, *args, **kwargs)
436 return self._dorun(procutil.popen3, cmd, *args, **kwargs)
435
437
436 def _dorun(self, openfunc, cmd, *args, **kwargs):
438 def _dorun(self, openfunc, cmd, *args, **kwargs):
437 cmdline = self._cmdline(cmd, *args, **kwargs)
439 cmdline = self._cmdline(cmd, *args, **kwargs)
438 self.ui.debug(b'running: %s\n' % (cmdline,))
440 self.ui.debug(b'running: %s\n' % (cmdline,))
439 self.prerun()
441 self.prerun()
440 try:
442 try:
441 return openfunc(cmdline)
443 return openfunc(cmdline)
442 finally:
444 finally:
443 self.postrun()
445 self.postrun()
444
446
445 def run(self, cmd, *args, **kwargs):
447 def run(self, cmd, *args, **kwargs):
446 p = self._run(cmd, *args, **kwargs)
448 p = self._run(cmd, *args, **kwargs)
447 output = p.communicate()[0]
449 output = p.communicate()[0]
448 self.ui.debug(output)
450 self.ui.debug(output)
449 return output, p.returncode
451 return output, p.returncode
450
452
451 def runlines(self, cmd, *args, **kwargs):
453 def runlines(self, cmd, *args, **kwargs):
452 p = self._run(cmd, *args, **kwargs)
454 p = self._run(cmd, *args, **kwargs)
453 output = p.stdout.readlines()
455 output = p.stdout.readlines()
454 p.wait()
456 p.wait()
455 self.ui.debug(b''.join(output))
457 self.ui.debug(b''.join(output))
456 return output, p.returncode
458 return output, p.returncode
457
459
458 def checkexit(self, status, output=b''):
460 def checkexit(self, status, output=b''):
459 if status:
461 if status:
460 if output:
462 if output:
461 self.ui.warn(_(b'%s error:\n') % self.command)
463 self.ui.warn(_(b'%s error:\n') % self.command)
462 self.ui.warn(output)
464 self.ui.warn(output)
463 msg = procutil.explainexit(status)
465 msg = procutil.explainexit(status)
464 raise error.Abort(b'%s %s' % (self.command, msg))
466 raise error.Abort(b'%s %s' % (self.command, msg))
465
467
466 def run0(self, cmd, *args, **kwargs):
468 def run0(self, cmd, *args, **kwargs):
467 output, status = self.run(cmd, *args, **kwargs)
469 output, status = self.run(cmd, *args, **kwargs)
468 self.checkexit(status, output)
470 self.checkexit(status, output)
469 return output
471 return output
470
472
471 def runlines0(self, cmd, *args, **kwargs):
473 def runlines0(self, cmd, *args, **kwargs):
472 output, status = self.runlines(cmd, *args, **kwargs)
474 output, status = self.runlines(cmd, *args, **kwargs)
473 self.checkexit(status, b''.join(output))
475 self.checkexit(status, b''.join(output))
474 return output
476 return output
475
477
476 @propertycache
478 @propertycache
477 def argmax(self):
479 def argmax(self):
478 # POSIX requires at least 4096 bytes for ARG_MAX
480 # POSIX requires at least 4096 bytes for ARG_MAX
479 argmax = 4096
481 argmax = 4096
480 try:
482 try:
481 argmax = os.sysconf("SC_ARG_MAX")
483 argmax = os.sysconf("SC_ARG_MAX")
482 except (AttributeError, ValueError):
484 except (AttributeError, ValueError):
483 pass
485 pass
484
486
485 # Windows shells impose their own limits on command line length,
487 # Windows shells impose their own limits on command line length,
486 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
488 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
487 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
489 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
488 # details about cmd.exe limitations.
490 # details about cmd.exe limitations.
489
491
490 # Since ARG_MAX is for command line _and_ environment, lower our limit
492 # Since ARG_MAX is for command line _and_ environment, lower our limit
491 # (and make happy Windows shells while doing this).
493 # (and make happy Windows shells while doing this).
492 return argmax // 2 - 1
494 return argmax // 2 - 1
493
495
494 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
496 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
495 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
497 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
496 limit = self.argmax - cmdlen
498 limit = self.argmax - cmdlen
497 numbytes = 0
499 numbytes = 0
498 fl = []
500 fl = []
499 for fn in arglist:
501 for fn in arglist:
500 b = len(fn) + 3
502 b = len(fn) + 3
501 if numbytes + b < limit or len(fl) == 0:
503 if numbytes + b < limit or len(fl) == 0:
502 fl.append(fn)
504 fl.append(fn)
503 numbytes += b
505 numbytes += b
504 else:
506 else:
505 yield fl
507 yield fl
506 fl = [fn]
508 fl = [fn]
507 numbytes = b
509 numbytes = b
508 if fl:
510 if fl:
509 yield fl
511 yield fl
510
512
511 def xargs(self, arglist, cmd, *args, **kwargs):
513 def xargs(self, arglist, cmd, *args, **kwargs):
512 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
514 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
513 self.run0(cmd, *(list(args) + l), **kwargs)
515 self.run0(cmd, *(list(args) + l), **kwargs)
514
516
515
517
516 class mapfile(dict):
518 class mapfile(dict):
517 def __init__(self, ui, path):
519 def __init__(self, ui, path):
518 super(mapfile, self).__init__()
520 super(mapfile, self).__init__()
519 self.ui = ui
521 self.ui = ui
520 self.path = path
522 self.path = path
521 self.fp = None
523 self.fp = None
522 self.order = []
524 self.order = []
523 self._read()
525 self._read()
524
526
525 def _read(self):
527 def _read(self):
526 if not self.path:
528 if not self.path:
527 return
529 return
528 try:
530 try:
529 fp = open(self.path, b'rb')
531 fp = open(self.path, b'rb')
530 except FileNotFoundError:
532 except FileNotFoundError:
531 return
533 return
532 for i, line in enumerate(fp):
534 for i, line in enumerate(fp):
533 line = line.splitlines()[0].rstrip()
535 line = line.splitlines()[0].rstrip()
534 if not line:
536 if not line:
535 # Ignore blank lines
537 # Ignore blank lines
536 continue
538 continue
537 try:
539 try:
538 key, value = line.rsplit(b' ', 1)
540 key, value = line.rsplit(b' ', 1)
539 except ValueError:
541 except ValueError:
540 raise error.Abort(
542 raise error.Abort(
541 _(b'syntax error in %s(%d): key/value pair expected')
543 _(b'syntax error in %s(%d): key/value pair expected')
542 % (self.path, i + 1)
544 % (self.path, i + 1)
543 )
545 )
544 if key not in self:
546 if key not in self:
545 self.order.append(key)
547 self.order.append(key)
546 super(mapfile, self).__setitem__(key, value)
548 super(mapfile, self).__setitem__(key, value)
547 fp.close()
549 fp.close()
548
550
549 def __setitem__(self, key, value):
551 def __setitem__(self, key, value):
550 if self.fp is None:
552 if self.fp is None:
551 try:
553 try:
552 self.fp = open(self.path, b'ab')
554 self.fp = open(self.path, b'ab')
553 except IOError as err:
555 except IOError as err:
554 raise error.Abort(
556 raise error.Abort(
555 _(b'could not open map file %r: %s')
557 _(b'could not open map file %r: %s')
556 % (self.path, encoding.strtolocal(err.strerror))
558 % (self.path, encoding.strtolocal(err.strerror))
557 )
559 )
558 self.fp.write(util.tonativeeol(b'%s %s\n' % (key, value)))
560 self.fp.write(util.tonativeeol(b'%s %s\n' % (key, value)))
559 self.fp.flush()
561 self.fp.flush()
560 super(mapfile, self).__setitem__(key, value)
562 super(mapfile, self).__setitem__(key, value)
561
563
562 def close(self):
564 def close(self):
563 if self.fp:
565 if self.fp:
564 self.fp.close()
566 self.fp.close()
565 self.fp = None
567 self.fp = None
566
568
567
569
568 def makedatetimestamp(t):
570 def makedatetimestamp(t: float) -> dateutil.hgdate:
569 """Like dateutil.makedate() but for time t instead of current time"""
571 return dateutil.makedate(t)
570 tz = round(
571 t
572 - datetime.datetime.fromtimestamp(t)
573 .replace(tzinfo=datetime.timezone.utc)
574 .timestamp()
575 )
576 return t, tz
General Comments 0
You need to be logged in to leave comments. Login now