common.py
618 lines
| 18.4 KiB
| text/x-python
|
PythonLexer
Martin Geisler
|
r8250 | # common.py - common code for the convert extension | ||
# | ||||
Raphaël Gomès
|
r47575 | # Copyright 2005-2009 Olivia Mackall <olivia@selenic.com> and others | ||
Martin Geisler
|
r8250 | # | ||
# This software may be used and distributed according to the terms of the | ||||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Martin Geisler
|
r8250 | |||
timeless
|
r28410 | import base64 | ||
import os | ||||
Gregory Szorc
|
r49725 | import pickle | ||
timeless
|
r28410 | import re | ||
Augie Fackler
|
r36575 | import shlex | ||
timeless
|
r28410 | import subprocess | ||
Matt Harbison
|
r52578 | import typing | ||
from typing import ( | ||||
Any, | ||||
AnyStr, | ||||
Optional, | ||||
) | ||||
timeless
|
r28410 | |||
Yuya Nishihara
|
r29205 | from mercurial.i18n import _ | ||
Gregory Szorc
|
r43355 | from mercurial.pycompat import open | ||
timeless
|
r28410 | from mercurial import ( | ||
Augie Fackler
|
r34024 | encoding, | ||
timeless
|
r28410 | error, | ||
phases, | ||||
Pulkit Goyal
|
r36347 | pycompat, | ||
timeless
|
r28410 | util, | ||
) | ||||
Matt Harbison
|
r52577 | from mercurial.utils import ( | ||
dateutil, | ||||
procutil, | ||||
) | ||||
Patrick Mezard
|
r5127 | |||
Matt Harbison
|
r52578 | if typing.TYPE_CHECKING: | ||
from typing import ( | ||||
overload, | ||||
) | ||||
from mercurial import ( | ||||
ui as uimod, | ||||
) | ||||
Patrick Mezard
|
r15606 | propertycache = util.propertycache | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52578 | if typing.TYPE_CHECKING: | ||
@overload | ||||
def _encodeornone(d: str) -> bytes: | ||||
pass | ||||
@overload | ||||
def _encodeornone(d: None) -> None: | ||||
pass | ||||
Augie Fackler
|
r36575 | def _encodeornone(d): | ||
if d is None: | ||||
return | ||||
return d.encode('latin1') | ||||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class _shlexpy3proxy: | ||
Matt Harbison
|
r52578 | def __init__(self, l: shlex.shlex) -> None: | ||
Augie Fackler
|
r36575 | self._l = l | ||
def __iter__(self): | ||||
return (_encodeornone(v) for v in self._l) | ||||
def get_token(self): | ||||
return _encodeornone(self._l.get_token()) | ||||
@property | ||||
Matt Harbison
|
r52579 | def infile(self) -> bytes: | ||
if self._l.infile is not None: | ||||
return encoding.strtolocal(self._l.infile) | ||||
return b'<unknown>' | ||||
Augie Fackler
|
r36575 | |||
@property | ||||
Matt Harbison
|
r52578 | def lineno(self) -> int: | ||
Augie Fackler
|
r36575 | return self._l.lineno | ||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52578 | def shlexer( | ||
data=None, | ||||
Matt Harbison
|
r52579 | filepath: Optional[bytes] = None, | ||
Matt Harbison
|
r52578 | wordchars: Optional[bytes] = None, | ||
whitespace: Optional[bytes] = None, | ||||
): | ||||
Augie Fackler
|
r36575 | if data is None: | ||
Manuel Jacob
|
r50183 | data = open(filepath, b'r', encoding='latin1') | ||
Augie Fackler
|
r36575 | else: | ||
if filepath is not None: | ||||
raise error.ProgrammingError( | ||||
Augie Fackler
|
r43347 | b'shlexer only accepts data or filepath, not both' | ||
Augie Fackler
|
r43346 | ) | ||
Manuel Jacob
|
r50183 | data = data.decode('latin1') | ||
Matt Harbison
|
r52579 | infile = encoding.strfromlocal(filepath) if filepath is not None else None | ||
l = shlex.shlex(data, infile=infile, posix=True) | ||||
Augie Fackler
|
r36575 | if whitespace is not None: | ||
l.whitespace_split = True | ||||
Manuel Jacob
|
r50183 | l.whitespace += whitespace.decode('latin1') | ||
Augie Fackler
|
r36575 | if wordchars is not None: | ||
Manuel Jacob
|
r50183 | l.wordchars += wordchars.decode('latin1') | ||
return _shlexpy3proxy(l) | ||||
Manuel Jacob
|
r45494 | |||
Matt Harbison
|
r52578 | def encodeargs(args: Any) -> bytes: | ||
def encodearg(s: bytes) -> bytes: | ||||
Manuel Jacob
|
r50183 | lines = base64.encodebytes(s) | ||
Manuel Jacob
|
r45491 | lines = [l.splitlines()[0] for l in pycompat.iterbytestr(lines)] | ||
Augie Fackler
|
r43347 | return b''.join(lines) | ||
Thomas Arendsen Hein
|
r5143 | |||
Patrick Mezard
|
r5127 | s = pickle.dumps(args) | ||
return encodearg(s) | ||||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52578 | def decodeargs(s: bytes) -> Any: | ||
Manuel Jacob
|
r50183 | s = base64.decodebytes(s) | ||
Patrick Mezard
|
r5127 | return pickle.loads(s) | ||
Brendan Cully
|
r4536 | |||
Augie Fackler
|
r43346 | |||
Matt Mackall
|
r10282 | class MissingTool(Exception): | ||
pass | ||||
Patrick Mezard
|
r6332 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52578 | def checktool( | ||
exe: bytes, name: Optional[bytes] = None, abort: bool = True | ||||
) -> None: | ||||
Patrick Mezard
|
r5497 | name = name or exe | ||
Yuya Nishihara
|
r37138 | if not procutil.findexe(exe): | ||
Jordi Gutiérrez Hermoso
|
r24306 | if abort: | ||
Pierre-Yves David
|
r26587 | exc = error.Abort | ||
Jordi Gutiérrez Hermoso
|
r24306 | else: | ||
exc = MissingTool | ||||
Augie Fackler
|
r43347 | raise exc(_(b'cannot find required "%s" tool') % name) | ||
Patrick Mezard
|
r5497 | |||
Augie Fackler
|
r43346 | |||
Matt Mackall
|
r10282 | class NoRepo(Exception): | ||
pass | ||||
Brendan Cully
|
r4536 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52578 | SKIPREV: bytes = b'SKIP' | ||
Alexis S. L. Carvalho
|
r5374 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class commit: | ||
Augie Fackler
|
r43346 | def __init__( | ||
self, | ||||
Matt Harbison
|
r52578 | author: bytes, | ||
date: bytes, | ||||
desc: bytes, | ||||
Augie Fackler
|
r43346 | parents, | ||
Matt Harbison
|
r52578 | branch: Optional[bytes] = None, | ||
Augie Fackler
|
r43346 | rev=None, | ||
extra=None, | ||||
sortkey=None, | ||||
saverev=True, | ||||
Matt Harbison
|
r52578 | phase: int = phases.draft, | ||
Augie Fackler
|
r43346 | optparents=None, | ||
ctx=None, | ||||
Matt Harbison
|
r52578 | ) -> None: | ||
Augie Fackler
|
r43347 | self.author = author or b'unknown' | ||
self.date = date or b'0 0' | ||||
Bryan O'Sullivan
|
r5024 | self.desc = desc | ||
Augie Fackler
|
r43346 | self.parents = parents # will be converted and used as parents | ||
self.optparents = optparents or [] # will be used if already converted | ||||
Bryan O'Sullivan
|
r5012 | self.branch = branch | ||
self.rev = rev | ||||
Gregory Szorc
|
r30659 | self.extra = extra or {} | ||
Patrick Mezard
|
r8690 | self.sortkey = sortkey | ||
Matt Harbison
|
r25570 | self.saverev = saverev | ||
Matt Harbison
|
r25571 | self.phase = phase | ||
Augie Fackler
|
r43346 | self.ctx = ctx # for hg to hg conversions | ||
Brendan Cully
|
r4536 | |||
Gregory Szorc
|
r49801 | class converter_source: | ||
Brendan Cully
|
r4536 | """Conversion source interface""" | ||
Matt Harbison
|
r52578 | def __init__( | ||
self, | ||||
ui: "uimod.ui", | ||||
repotype: bytes, | ||||
path: Optional[bytes] = None, | ||||
revs=None, | ||||
) -> None: | ||||
Brendan Cully
|
r4536 | """Initialize conversion source (or raise NoRepo("message") | ||
exception if path is not a valid repository)""" | ||||
Brendan Cully
|
r4810 | self.ui = ui | ||
self.path = path | ||||
Durham Goode
|
r25748 | self.revs = revs | ||
Matt Harbison
|
r35168 | self.repotype = repotype | ||
Brendan Cully
|
r4810 | |||
Augie Fackler
|
r43347 | self.encoding = b'utf-8' | ||
Brendan Cully
|
r4812 | |||
Matt Harbison
|
r52578 | def checkhexformat( | ||
self, revstr: bytes, mapname: bytes = b'splicemap' | ||||
) -> None: | ||||
Augie Fackler
|
r46554 | """fails if revstr is not a 40 byte hex. mercurial and git both uses | ||
such format for their revision numbering | ||||
Ben Goswami
|
r19120 | """ | ||
Pulkit Goyal
|
r37600 | if not re.match(br'[0-9a-fA-F]{40,40}$', revstr): | ||
Augie Fackler
|
r43346 | raise error.Abort( | ||
Martin von Zweigbergk
|
r43387 | _(b'%s entry %s is not a valid revision identifier') | ||
Augie Fackler
|
r43346 | % (mapname, revstr) | ||
) | ||||
Ben Goswami
|
r19120 | |||
Matt Harbison
|
r52578 | def before(self) -> None: | ||
Bryan O'Sullivan
|
r5356 | pass | ||
Matt Harbison
|
r52578 | def after(self) -> None: | ||
Bryan O'Sullivan
|
r5356 | pass | ||
Durham Goode
|
r26035 | def targetfilebelongstosource(self, targetfilename): | ||
"""Returns true if the given targetfile belongs to the source repo. This | ||||
is useful when only a subdirectory of the target belongs to the source | ||||
repo.""" | ||||
# For normal full repo converts, this is always True. | ||||
return True | ||||
Bryan O'Sullivan
|
r5510 | def setrevmap(self, revmap): | ||
"""set the map of already-converted revisions""" | ||||
Brendan Cully
|
r4536 | |||
def getheads(self): | ||||
"""Return a list of this repository's heads""" | ||||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Brendan Cully
|
r4536 | |||
def getfile(self, name, rev): | ||||
Patrick Mezard
|
r11134 | """Return a pair (data, mode) where data is the file content | ||
as a string and mode one of '', 'x' or 'l'. rev is the | ||||
Mads Kiilerich
|
r22296 | identifier returned by a previous call to getchanges(). | ||
Data is None if file is missing/deleted in rev. | ||||
Patrick Mezard
|
r7055 | """ | ||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Brendan Cully
|
r4536 | |||
Mads Kiilerich
|
r22300 | def getchanges(self, version, full): | ||
Mads Kiilerich
|
r24395 | """Returns a tuple of (files, copies, cleanp2). | ||
Patrick Mezard
|
r7055 | |||
files is a sorted list of (filename, id) tuples for all files | ||||
Greg Ward
|
r8444 | changed between version and its first parent returned by | ||
Mads Kiilerich
|
r22300 | getcommit(). If full, all files in that revision is returned. | ||
id is the source revision id of the file. | ||||
Brendan Cully
|
r4536 | |||
Brendan Cully
|
r5121 | copies is a dictionary of dest: source | ||
Mads Kiilerich
|
r24395 | |||
cleanp2 is the set of files filenames that are clean against p2. | ||||
(Files that are clean against p1 are already not in files (unless | ||||
full). This makes it possible to handle p2 clean files similarly.) | ||||
Brendan Cully
|
r5121 | """ | ||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Brendan Cully
|
r4536 | |||
def getcommit(self, version): | ||||
"""Return the commit object for version""" | ||||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Brendan Cully
|
r4536 | |||
Augie Fackler
|
r22411 | def numcommits(self): | ||
"""Return the number of commits in this source. | ||||
If unknown, return None. | ||||
""" | ||||
return None | ||||
Brendan Cully
|
r4536 | def gettags(self): | ||
Patrick Mezard
|
r8887 | """Return the tags as a dictionary of name: revision | ||
Tag names must be UTF-8 strings. | ||||
""" | ||||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Brendan Cully
|
r4536 | |||
Matt Harbison
|
r52578 | def recode(self, s: AnyStr, encoding: Optional[bytes] = None) -> bytes: | ||
Brendan Cully
|
r4759 | if not encoding: | ||
Augie Fackler
|
r43347 | encoding = self.encoding or b'utf-8' | ||
Thomas Arendsen Hein
|
r4957 | |||
Gregory Szorc
|
r49789 | if isinstance(s, str): | ||
Thomas Arendsen Hein
|
r5287 | return s.encode("utf-8") | ||
Brendan Cully
|
r4759 | try: | ||
Pulkit Goyal
|
r37640 | return s.decode(pycompat.sysstr(encoding)).encode("utf-8") | ||
Brodie Rao
|
r16688 | except UnicodeError: | ||
Brendan Cully
|
r4759 | try: | ||
return s.decode("latin-1").encode("utf-8") | ||||
Brodie Rao
|
r16688 | except UnicodeError: | ||
Augie Fackler
|
r43346 | return s.decode(pycompat.sysstr(encoding), "replace").encode( | ||
"utf-8" | ||||
) | ||||
Brendan Cully
|
r4759 | |||
Alexis S. L. Carvalho
|
r5377 | def getchangedfiles(self, rev, i): | ||
"""Return the files changed by rev compared to parent[i]. | ||||
Thomas Arendsen Hein
|
r5760 | |||
Alexis S. L. Carvalho
|
r5377 | i is an index selecting one of the parents of rev. The return | ||
value should be the list of files that are different in rev and | ||||
this parent. | ||||
If rev has no parents, i is None. | ||||
Thomas Arendsen Hein
|
r5760 | |||
Alexis S. L. Carvalho
|
r5377 | This function is only needed to support --filemap | ||
""" | ||||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Alexis S. L. Carvalho
|
r5377 | |||
Matt Harbison
|
r52578 | def converted(self, rev, sinkrev) -> None: | ||
Bryan O'Sullivan
|
r5554 | '''Notify the source that a revision has been converted.''' | ||
Matt Harbison
|
r52578 | def hasnativeorder(self) -> bool: | ||
Patrick Mezard
|
r8691 | """Return true if this source has a meaningful, native revision | ||
order. For instance, Mercurial revisions are store sequentially | ||||
while there is no such global ordering with Darcs. | ||||
""" | ||||
return False | ||||
Matt Harbison
|
r52578 | def hasnativeclose(self) -> bool: | ||
Augie Fackler
|
r46554 | """Return true if this source has ability to close branch.""" | ||
Constantine Linnick
|
r18819 | return False | ||
Patrick Mezard
|
r8693 | def lookuprev(self, rev): | ||
"""If rev is a meaningful revision reference in source, return | ||||
the referenced identifier in the same format used by getcommit(). | ||||
return None otherwise. | ||||
""" | ||||
return None | ||||
Bryan O'Sullivan
|
r5554 | |||
Edouard Gomez
|
r13744 | def getbookmarks(self): | ||
"""Return the bookmarks as a dictionary of name: revision | ||||
Bookmark names are to be UTF-8 strings. | ||||
""" | ||||
return {} | ||||
Matt Harbison
|
r52578 | def checkrevformat(self, revstr, mapname: bytes = b'splicemap') -> bool: | ||
Ben Goswami
|
r19120 | """revstr is a string that describes a revision in the given | ||
Augie Fackler
|
r46554 | source control system. Return true if revstr has correct | ||
format. | ||||
Ben Goswami
|
r19120 | """ | ||
return True | ||||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class converter_sink: | ||
Brendan Cully
|
r4536 | """Conversion sink (target) interface""" | ||
Matt Harbison
|
r52578 | def __init__(self, ui: "uimod.ui", repotype: bytes, path: bytes) -> None: | ||
Brendan Cully
|
r4536 | """Initialize conversion sink (or raise NoRepo("message") | ||
Bryan O'Sullivan
|
r5441 | exception if path is not a valid repository) | ||
created is a list of paths to remove if a fatal error occurs | ||||
later""" | ||||
self.ui = ui | ||||
Bryan O'Sullivan
|
r5440 | self.path = path | ||
Bryan O'Sullivan
|
r5441 | self.created = [] | ||
Matt Harbison
|
r35168 | self.repotype = repotype | ||
Brendan Cully
|
r4536 | |||
Bryan O'Sullivan
|
r5011 | def revmapfile(self): | ||
Brendan Cully
|
r4536 | """Path to a file that will contain lines | ||
source_rev_id sink_rev_id | ||||
mapping equivalent revision identifiers for each system.""" | ||||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Brendan Cully
|
r4536 | |||
Edouard Gomez
|
r4589 | def authorfile(self): | ||
"""Path to a file that will contain lines | ||||
srcauthor=dstauthor | ||||
mapping equivalent authors identifiers for each system.""" | ||||
Brendan Cully
|
r4590 | return None | ||
Edouard Gomez
|
r4589 | |||
Augie Fackler
|
r43346 | def putcommit( | ||
self, files, copies, parents, commit, source, revmap, full, cleanp2 | ||||
): | ||||
Brendan Cully
|
r4536 | """Create a revision with all changed files listed in 'files' | ||
Patrick Mezard
|
r8693 | and having listed parents. 'commit' is a commit object | ||
containing at a minimum the author, date, and message for this | ||||
changeset. 'files' is a list of (path, version) tuples, | ||||
'copies' is a dictionary mapping destinations to sources, | ||||
'source' is the source repository, and 'revmap' is a mapfile | ||||
Patrick Mezard
|
r11134 | of source revisions to converted revisions. Only getfile() and | ||
Mads Kiilerich
|
r22300 | lookuprev() should be called on 'source'. 'full' means that 'files' | ||
is complete and all other files should be removed. | ||||
Mads Kiilerich
|
r24395 | 'cleanp2' is a set of the filenames that are unchanged from p2 | ||
(only in the common merge case where there two parents). | ||||
Patrick Mezard
|
r6716 | |||
Note that the sink repository is not told to update itself to | ||||
a particular revision (or even what that revision would be) | ||||
before it receives the file data. | ||||
""" | ||||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Brendan Cully
|
r4536 | |||
def puttags(self, tags): | ||||
"""Put tags into sink. | ||||
Patrick Mezard
|
r8887 | |||
tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string. | ||||
Patrick Mezard
|
r9431 | Return a pair (tag_revision, tag_parent_revision), or (None, None) | ||
if nothing was changed. | ||||
Patrick Mezard
|
r8887 | """ | ||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Patrick Mezard
|
r5127 | |||
Patrick Mezard
|
r5934 | def setbranch(self, branch, pbranches): | ||
Patrick Mezard
|
r6716 | """Set the current branch name. Called before the first putcommit | ||
Brendan Cully
|
r5173 | on the branch. | ||
branch: branch name for subsequent commits | ||||
Patrick Mezard
|
r5934 | pbranches: (converted parent revision, parent branch) tuples""" | ||
Alexis S. L. Carvalho
|
r5378 | |||
def setfilemapmode(self, active): | ||||
"""Tell the destination that we're using a filemap | ||||
Some converter_sources (svn in particular) can claim that a file | ||||
was changed in a revision, even if there was no change. This method | ||||
tells the destination that we're using a filemap and that it should | ||||
filter empty revisions. | ||||
""" | ||||
Bryan O'Sullivan
|
r5510 | |||
Matt Harbison
|
r52578 | def before(self) -> None: | ||
Bryan O'Sullivan
|
r5512 | pass | ||
Matt Harbison
|
r52578 | def after(self) -> None: | ||
Bryan O'Sullivan
|
r5512 | pass | ||
Edouard Gomez
|
r13744 | def putbookmarks(self, bookmarks): | ||
"""Put bookmarks into sink. | ||||
bookmarks: {bookmarkname: sink_rev_id, ...} | ||||
where bookmarkname is an UTF-8 string. | ||||
""" | ||||
Bryan O'Sullivan
|
r5512 | |||
Mads Kiilerich
|
r21635 | def hascommitfrommap(self, rev): | ||
"""Return False if a rev mentioned in a filemap is known to not be | ||||
present.""" | ||||
raise NotImplementedError | ||||
Mads Kiilerich
|
r21634 | def hascommitforsplicemap(self, rev): | ||
"""This method is for the special needs for splicemap handling and not | ||||
for general use. Returns True if the sink contains rev, aborts on some | ||||
special cases.""" | ||||
Brodie Rao
|
r16687 | raise NotImplementedError | ||
Patrick Mezard
|
r16106 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
r49801 | class commandline: | ||
Matt Harbison
|
r52578 | def __init__(self, ui: "uimod.ui", command: bytes) -> None: | ||
Bryan O'Sullivan
|
r5512 | self.ui = ui | ||
self.command = command | ||||
Matt Harbison
|
r52578 | def prerun(self) -> None: | ||
Bryan O'Sullivan
|
r5512 | pass | ||
Matt Harbison
|
r52578 | def postrun(self) -> None: | ||
Bryan O'Sullivan
|
r5512 | pass | ||
Matt Harbison
|
r52578 | def _cmdline(self, cmd: bytes, *args: bytes, **kwargs) -> bytes: | ||
Pulkit Goyal
|
r36347 | kwargs = pycompat.byteskwargs(kwargs) | ||
Bryan O'Sullivan
|
r5512 | cmdline = [self.command, cmd] + list(args) | ||
Gregory Szorc
|
r49768 | for k, v in kwargs.items(): | ||
Bryan O'Sullivan
|
r5512 | if len(k) == 1: | ||
Augie Fackler
|
r43347 | cmdline.append(b'-' + k) | ||
Bryan O'Sullivan
|
r5512 | else: | ||
Augie Fackler
|
r43347 | cmdline.append(b'--' + k.replace(b'_', b'-')) | ||
Bryan O'Sullivan
|
r5512 | try: | ||
if len(k) == 1: | ||||
Augie Fackler
|
r43347 | cmdline.append(b'' + v) | ||
Bryan O'Sullivan
|
r5512 | else: | ||
Augie Fackler
|
r43347 | cmdline[-1] += b'=' + v | ||
Bryan O'Sullivan
|
r5512 | except TypeError: | ||
pass | ||||
Yuya Nishihara
|
r37138 | cmdline = [procutil.shellquote(arg) for arg in cmdline] | ||
Patrick Mezard
|
r7611 | if not self.ui.debugflag: | ||
Augie Fackler
|
r43347 | cmdline += [b'2>', pycompat.bytestr(os.devnull)] | ||
cmdline = b' '.join(cmdline) | ||||
Maxim Dounin
|
r5832 | return cmdline | ||
Bryan O'Sullivan
|
r5512 | |||
Matt Harbison
|
r52578 | def _run(self, cmd: bytes, *args: bytes, **kwargs): | ||
Patrick Mezard
|
r17413 | def popen(cmdline): | ||
Augie Fackler
|
r43346 | p = subprocess.Popen( | ||
procutil.tonativestr(cmdline), | ||||
shell=True, | ||||
bufsize=-1, | ||||
close_fds=procutil.closefds, | ||||
stdout=subprocess.PIPE, | ||||
) | ||||
Patrick Mezard
|
r17413 | return p | ||
Augie Fackler
|
r43346 | |||
Patrick Mezard
|
r17413 | return self._dorun(popen, cmd, *args, **kwargs) | ||
Daniel Atallah
|
r13759 | |||
Matt Harbison
|
r52578 | def _run2(self, cmd: bytes, *args: bytes, **kwargs): | ||
Yuya Nishihara
|
r37138 | return self._dorun(procutil.popen2, cmd, *args, **kwargs) | ||
Daniel Atallah
|
r13759 | |||
Matt Harbison
|
r52578 | def _run3(self, cmd: bytes, *args: bytes, **kwargs): | ||
Yuya Nishihara
|
r37138 | return self._dorun(procutil.popen3, cmd, *args, **kwargs) | ||
Mateusz Kwapich
|
r28662 | |||
Matt Harbison
|
r52578 | def _dorun(self, openfunc, cmd: bytes, *args: bytes, **kwargs): | ||
Patrick Mezard
|
r17413 | cmdline = self._cmdline(cmd, *args, **kwargs) | ||
Augie Fackler
|
r43347 | self.ui.debug(b'running: %s\n' % (cmdline,)) | ||
Bryan O'Sullivan
|
r5512 | self.prerun() | ||
try: | ||||
Daniel Atallah
|
r13759 | return openfunc(cmdline) | ||
Bryan O'Sullivan
|
r5512 | finally: | ||
self.postrun() | ||||
Matt Harbison
|
r52578 | def run(self, cmd: bytes, *args: bytes, **kwargs): | ||
Patrick Mezard
|
r17413 | p = self._run(cmd, *args, **kwargs) | ||
output = p.communicate()[0] | ||||
Bryan O'Sullivan
|
r5512 | self.ui.debug(output) | ||
Patrick Mezard
|
r17413 | return output, p.returncode | ||
Bryan O'Sullivan
|
r5512 | |||
Matt Harbison
|
r52578 | def runlines(self, cmd: bytes, *args: bytes, **kwargs): | ||
Patrick Mezard
|
r17413 | p = self._run(cmd, *args, **kwargs) | ||
output = p.stdout.readlines() | ||||
p.wait() | ||||
Augie Fackler
|
r43347 | self.ui.debug(b''.join(output)) | ||
Patrick Mezard
|
r17413 | return output, p.returncode | ||
Aleix Conchillo Flaque
|
r6035 | |||
Matt Harbison
|
r52578 | def checkexit(self, status, output: bytes = b'') -> None: | ||
Bryan O'Sullivan
|
r5512 | if status: | ||
if output: | ||||
Augie Fackler
|
r43347 | self.ui.warn(_(b'%s error:\n') % self.command) | ||
Bryan O'Sullivan
|
r5512 | self.ui.warn(output) | ||
Yuya Nishihara
|
r37481 | msg = procutil.explainexit(status) | ||
Augie Fackler
|
r43347 | raise error.Abort(b'%s %s' % (self.command, msg)) | ||
Bryan O'Sullivan
|
r5512 | |||
Matt Harbison
|
r52578 | def run0(self, cmd: bytes, *args: bytes, **kwargs): | ||
Bryan O'Sullivan
|
r5512 | output, status = self.run(cmd, *args, **kwargs) | ||
self.checkexit(status, output) | ||||
return output | ||||
Matt Harbison
|
r52578 | def runlines0(self, cmd: bytes, *args: bytes, **kwargs): | ||
Aleix Conchillo Flaque
|
r6035 | output, status = self.runlines(cmd, *args, **kwargs) | ||
Augie Fackler
|
r43347 | self.checkexit(status, b''.join(output)) | ||
Aleix Conchillo Flaque
|
r6035 | return output | ||
Patrick Mezard
|
r15606 | @propertycache | ||
def argmax(self): | ||||
Maxim Dounin
|
r5832 | # POSIX requires at least 4096 bytes for ARG_MAX | ||
Patrick Mezard
|
r15606 | argmax = 4096 | ||
Maxim Dounin
|
r5832 | try: | ||
Augie Fackler
|
r43809 | argmax = os.sysconf("SC_ARG_MAX") | ||
Brodie Rao
|
r16688 | except (AttributeError, ValueError): | ||
Maxim Dounin
|
r5832 | pass | ||
# Windows shells impose their own limits on command line length, | ||||
# down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes | ||||
# for older 4nt.exe. See http://support.microsoft.com/kb/830473 for | ||||
# details about cmd.exe limitations. | ||||
# Since ARG_MAX is for command line _and_ environment, lower our limit | ||||
# (and make happy Windows shells while doing this). | ||||
Martin Geisler
|
r15791 | return argmax // 2 - 1 | ||
Maxim Dounin
|
r5832 | |||
Matt Harbison
|
r52578 | def _limit_arglist(self, arglist, cmd: bytes, *args: bytes, **kwargs): | ||
Patrick Mezard
|
r17412 | cmdlen = len(self._cmdline(cmd, *args, **kwargs)) | ||
Patrick Mezard
|
r15606 | limit = self.argmax - cmdlen | ||
Pulkit Goyal
|
r36349 | numbytes = 0 | ||
Maxim Dounin
|
r5832 | fl = [] | ||
for fn in arglist: | ||||
b = len(fn) + 3 | ||||
Pulkit Goyal
|
r36349 | if numbytes + b < limit or len(fl) == 0: | ||
Maxim Dounin
|
r5832 | fl.append(fn) | ||
Pulkit Goyal
|
r36349 | numbytes += b | ||
Maxim Dounin
|
r5832 | else: | ||
yield fl | ||||
fl = [fn] | ||||
Pulkit Goyal
|
r36349 | numbytes = b | ||
Maxim Dounin
|
r5832 | if fl: | ||
yield fl | ||||
Matt Harbison
|
r52578 | def xargs(self, arglist, cmd: bytes, *args: bytes, **kwargs): | ||
Patrick Mezard
|
r17412 | for l in self._limit_arglist(arglist, cmd, *args, **kwargs): | ||
Maxim Dounin
|
r5832 | self.run0(cmd, *(list(args) + l), **kwargs) | ||
Bryan O'Sullivan
|
r5510 | |||
Augie Fackler
|
r43346 | |||
Bryan O'Sullivan
|
r5510 | class mapfile(dict): | ||
Matt Harbison
|
r52578 | def __init__(self, ui: "uimod.ui", path: bytes) -> None: | ||
Bryan O'Sullivan
|
r5510 | super(mapfile, self).__init__() | ||
self.ui = ui | ||||
self.path = path | ||||
self.fp = None | ||||
self.order = [] | ||||
self._read() | ||||
Matt Harbison
|
r52578 | def _read(self) -> None: | ||
Stefan Rusek
|
r7774 | if not self.path: | ||
Bryan O'Sullivan
|
r5996 | return | ||
Bryan O'Sullivan
|
r5510 | try: | ||
Augie Fackler
|
r43347 | fp = open(self.path, b'rb') | ||
Manuel Jacob
|
r50201 | except FileNotFoundError: | ||
Bryan O'Sullivan
|
r5510 | return | ||
Matt Harbison
|
r52580 | |||
try: | ||||
for i, line in enumerate(fp): | ||||
line = line.splitlines()[0].rstrip() | ||||
if not line: | ||||
# Ignore blank lines | ||||
continue | ||||
try: | ||||
key, value = line.rsplit(b' ', 1) | ||||
except ValueError: | ||||
raise error.Abort( | ||||
_(b'syntax error in %s(%d): key/value pair expected') | ||||
% (self.path, i + 1) | ||||
) | ||||
if key not in self: | ||||
self.order.append(key) | ||||
super(mapfile, self).__setitem__(key, value) | ||||
finally: | ||||
fp.close() | ||||
Thomas Arendsen Hein
|
r5760 | |||
Matt Harbison
|
r52578 | def __setitem__(self, key, value) -> None: | ||
Bryan O'Sullivan
|
r5510 | if self.fp is None: | ||
try: | ||||
Augie Fackler
|
r43347 | self.fp = open(self.path, b'ab') | ||
Gregory Szorc
|
r25660 | except IOError as err: | ||
Augie Fackler
|
r34024 | raise error.Abort( | ||
Augie Fackler
|
r43347 | _(b'could not open map file %r: %s') | ||
Augie Fackler
|
r43346 | % (self.path, encoding.strtolocal(err.strerror)) | ||
) | ||||
Augie Fackler
|
r43347 | self.fp.write(util.tonativeeol(b'%s %s\n' % (key, value))) | ||
Bryan O'Sullivan
|
r5510 | self.fp.flush() | ||
super(mapfile, self).__setitem__(key, value) | ||||
Matt Harbison
|
r52578 | def close(self) -> None: | ||
Bryan O'Sullivan
|
r5512 | if self.fp: | ||
self.fp.close() | ||||
self.fp = None | ||||
Patrick Mezard
|
r16105 | |||
Augie Fackler
|
r43346 | |||
Matt Harbison
|
r52577 | def makedatetimestamp(t: float) -> dateutil.hgdate: | ||
return dateutil.makedate(t) | ||||