|
|
4b749c |
diff --git a/hgext/convert/common.py b/hgext/convert/common.py
|
|
|
4b749c |
index e27346b..842ae71 100644
|
|
|
4b749c |
--- a/hgext/convert/common.py
|
|
|
4b749c |
+++ b/hgext/convert/common.py
|
|
|
4b749c |
@@ -295,6 +295,9 @@ class commandline(object):
|
|
|
4b749c |
def _run2(self, cmd, *args, **kwargs):
|
|
|
4b749c |
return self._dorun(util.popen2, cmd, *args, **kwargs)
|
|
|
4b749c |
|
|
|
4b749c |
+ def _run3(self, cmd, *args, **kwargs):
|
|
|
4b749c |
+ return self._dorun(util.popen3, cmd, *args, **kwargs)
|
|
|
4b749c |
+
|
|
|
4b749c |
def _dorun(self, openfunc, cmd, *args, **kwargs):
|
|
|
4b749c |
cmdline = self._cmdline(cmd, *args, **kwargs)
|
|
|
4b749c |
self.ui.debug('running: %s\n' % (cmdline,))
|
|
|
4b749c |
diff --git a/hgext/convert/git.py b/hgext/convert/git.py
|
|
|
4b749c |
index 3fd0884..8123b55 100644
|
|
|
4b749c |
--- a/hgext/convert/git.py
|
|
|
4b749c |
+++ b/hgext/convert/git.py
|
|
|
4b749c |
@@ -7,11 +7,11 @@
|
|
|
4b749c |
|
|
|
4b749c |
import os
|
|
|
4b749c |
import subprocess
|
|
|
4b749c |
-from mercurial import util, config
|
|
|
4b749c |
+from mercurial import util, config, error
|
|
|
4b749c |
from mercurial.node import hex, nullid
|
|
|
4b749c |
from mercurial.i18n import _
|
|
|
4b749c |
|
|
|
4b749c |
-from common import NoRepo, commit, converter_source, checktool
|
|
|
4b749c |
+from common import NoRepo, commit, converter_source, checktool, commandline
|
|
|
4b749c |
|
|
|
4b749c |
class submodule(object):
|
|
|
4b749c |
def __init__(self, path, node, url):
|
|
|
4b749c |
@@ -25,54 +25,32 @@ class submodule(object):
|
|
|
4b749c |
def hgsubstate(self):
|
|
|
4b749c |
return "%s %s" % (self.node, self.path)
|
|
|
4b749c |
|
|
|
4b749c |
-class convert_git(converter_source):
|
|
|
4b749c |
+class convert_git(converter_source, commandline):
|
|
|
4b749c |
# Windows does not support GIT_DIR= construct while other systems
|
|
|
4b749c |
# cannot remove environment variable. Just assume none have
|
|
|
4b749c |
# both issues.
|
|
|
4b749c |
- if util.safehasattr(os, 'unsetenv'):
|
|
|
4b749c |
- def gitopen(self, s, err=None):
|
|
|
4b749c |
- prevgitdir = os.environ.get('GIT_DIR')
|
|
|
4b749c |
- os.environ['GIT_DIR'] = self.path
|
|
|
4b749c |
- try:
|
|
|
4b749c |
- if err == subprocess.PIPE:
|
|
|
4b749c |
- (stdin, stdout, stderr) = util.popen3(s)
|
|
|
4b749c |
- return stdout
|
|
|
4b749c |
- elif err == subprocess.STDOUT:
|
|
|
4b749c |
- return self.popen_with_stderr(s)
|
|
|
4b749c |
- else:
|
|
|
4b749c |
- return util.popen(s, 'rb')
|
|
|
4b749c |
- finally:
|
|
|
4b749c |
- if prevgitdir is None:
|
|
|
4b749c |
- del os.environ['GIT_DIR']
|
|
|
4b749c |
- else:
|
|
|
4b749c |
- os.environ['GIT_DIR'] = prevgitdir
|
|
|
4b749c |
- else:
|
|
|
4b749c |
- def gitopen(self, s, err=None):
|
|
|
4b749c |
- if err == subprocess.PIPE:
|
|
|
4b749c |
- (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
|
|
|
4b749c |
- return so
|
|
|
4b749c |
- elif err == subprocess.STDOUT:
|
|
|
4b749c |
- return self.popen_with_stderr(s)
|
|
|
4b749c |
- else:
|
|
|
4b749c |
- return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
|
|
|
4b749c |
-
|
|
|
4b749c |
- def popen_with_stderr(self, s):
|
|
|
4b749c |
- p = subprocess.Popen(s, shell=True, bufsize=-1,
|
|
|
4b749c |
- close_fds=util.closefds,
|
|
|
4b749c |
- stdin=subprocess.PIPE,
|
|
|
4b749c |
- stdout=subprocess.PIPE,
|
|
|
4b749c |
- stderr=subprocess.STDOUT,
|
|
|
4b749c |
- universal_newlines=False,
|
|
|
4b749c |
- env=None)
|
|
|
4b749c |
- return p.stdout
|
|
|
4b749c |
-
|
|
|
4b749c |
- def gitread(self, s):
|
|
|
4b749c |
- fh = self.gitopen(s)
|
|
|
4b749c |
- data = fh.read()
|
|
|
4b749c |
- return data, fh.close()
|
|
|
4b749c |
+
|
|
|
4b749c |
+ def _gitcmd(self, cmd, *args, **kwargs):
|
|
|
4b749c |
+ return cmd('--git-dir=%s' % self.path, *args, **kwargs)
|
|
|
4b749c |
+
|
|
|
4b749c |
+ def gitrun0(self, *args, **kwargs):
|
|
|
4b749c |
+ return self._gitcmd(self.run0, *args, **kwargs)
|
|
|
4b749c |
+
|
|
|
4b749c |
+ def gitrun(self, *args, **kwargs):
|
|
|
4b749c |
+ return self._gitcmd(self.run, *args, **kwargs)
|
|
|
4b749c |
+
|
|
|
4b749c |
+ def gitrunlines0(self, *args, **kwargs):
|
|
|
4b749c |
+ return self._gitcmd(self.runlines0, *args, **kwargs)
|
|
|
4b749c |
+
|
|
|
4b749c |
+ def gitrunlines(self, *args, **kwargs):
|
|
|
4b749c |
+ return self._gitcmd(self.runlines, *args, **kwargs)
|
|
|
4b749c |
+
|
|
|
4b749c |
+ def gitpipe(self, *args, **kwargs):
|
|
|
4b749c |
+ return self._gitcmd(self._run3, *args, **kwargs)
|
|
|
4b749c |
|
|
|
4b749c |
def __init__(self, ui, path, rev=None):
|
|
|
4b749c |
super(convert_git, self).__init__(ui, path, rev=rev)
|
|
|
4b749c |
+ commandline.__init__(self, ui, 'git')
|
|
|
4b749c |
|
|
|
4b749c |
if os.path.isdir(path + "/.git"):
|
|
|
4b749c |
path += "/.git"
|
|
|
4b749c |
@@ -86,11 +64,11 @@ class convert_git(converter_source):
|
|
|
4b749c |
|
|
|
4b749c |
def getheads(self):
|
|
|
4b749c |
if not self.rev:
|
|
|
4b749c |
- heads, ret = self.gitread('git rev-parse --branches --remotes')
|
|
|
4b749c |
- heads = heads.splitlines()
|
|
|
4b749c |
+ output, ret = self.gitrun('rev-parse', '--branches', '--remotes')
|
|
|
4b749c |
+ heads = output.splitlines()
|
|
|
4b749c |
else:
|
|
|
4b749c |
- heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
|
|
|
4b749c |
- heads = [heads[:-1]]
|
|
|
4b749c |
+ rawhead, ret = self.gitrun('rev-parse', '--verify', self.rev)
|
|
|
4b749c |
+ heads = [rawhead[:-1]]
|
|
|
4b749c |
if ret:
|
|
|
4b749c |
raise util.Abort(_('cannot retrieve git heads'))
|
|
|
4b749c |
return heads
|
|
|
4b749c |
@@ -98,7 +76,7 @@ class convert_git(converter_source):
|
|
|
4b749c |
def catfile(self, rev, type):
|
|
|
4b749c |
if rev == hex(nullid):
|
|
|
4b749c |
raise IOError
|
|
|
4b749c |
- data, ret = self.gitread("git cat-file %s %s" % (type, rev))
|
|
|
4b749c |
+ data, ret = self.gitrun('cat-file', type, rev)
|
|
|
4b749c |
if ret:
|
|
|
4b749c |
raise util.Abort(_('cannot read %r object at %s') % (type, rev))
|
|
|
4b749c |
return data
|
|
|
4b749c |
@@ -137,25 +115,28 @@ class convert_git(converter_source):
|
|
|
4b749c |
self.submodules.append(submodule(s['path'], '', s['url']))
|
|
|
4b749c |
|
|
|
4b749c |
def retrievegitmodules(self, version):
|
|
|
4b749c |
- modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
|
|
|
4b749c |
+ modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules'))
|
|
|
4b749c |
if ret:
|
|
|
4b749c |
raise util.Abort(_('cannot read submodules config file in %s') %
|
|
|
4b749c |
version)
|
|
|
4b749c |
self.parsegitmodules(modules)
|
|
|
4b749c |
for m in self.submodules:
|
|
|
4b749c |
- node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
|
|
|
4b749c |
+ node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path))
|
|
|
4b749c |
if ret:
|
|
|
4b749c |
continue
|
|
|
4b749c |
m.node = node.strip()
|
|
|
4b749c |
|
|
|
4b749c |
def getchanges(self, version):
|
|
|
4b749c |
self.modecache = {}
|
|
|
4b749c |
- fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
|
|
|
4b749c |
+ cmd = ['diff-tree','-z', '--root', '-m', '-r'] + [version]
|
|
|
4b749c |
+ output, status = self.gitrun(*cmd)
|
|
|
4b749c |
+ if status:
|
|
|
4b749c |
+ raise error.Abort(_('cannot read changes in %s') % version)
|
|
|
4b749c |
changes = []
|
|
|
4b749c |
seen = set()
|
|
|
4b749c |
entry = None
|
|
|
4b749c |
subexists = False
|
|
|
4b749c |
- for l in fh.read().split('\x00'):
|
|
|
4b749c |
+ for l in output.split('\x00'):
|
|
|
4b749c |
if not entry:
|
|
|
4b749c |
if not l.startswith(':'):
|
|
|
4b749c |
continue
|
|
|
4b749c |
@@ -178,8 +159,6 @@ class convert_git(converter_source):
|
|
|
4b749c |
self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
|
|
|
4b749c |
changes.append((f, h))
|
|
|
4b749c |
entry = None
|
|
|
4b749c |
- if fh.close():
|
|
|
4b749c |
- raise util.Abort(_('cannot read changes in %s') % version)
|
|
|
4b749c |
|
|
|
4b749c |
if subexists:
|
|
|
4b749c |
self.retrievegitmodules(version)
|
|
|
4b749c |
@@ -224,12 +203,14 @@ class convert_git(converter_source):
|
|
|
4b749c |
def gettags(self):
|
|
|
4b749c |
tags = {}
|
|
|
4b749c |
alltags = {}
|
|
|
4b749c |
- fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
|
|
|
4b749c |
- err=subprocess.STDOUT)
|
|
|
4b749c |
+ output, status = self.gitrunlines('ls-remote', '--tags', self.path)
|
|
|
4b749c |
+
|
|
|
4b749c |
+ if status:
|
|
|
4b749c |
+ raise error.Abort(_('cannot read tags from %s') % self.path)
|
|
|
4b749c |
prefix = 'refs/tags/'
|
|
|
4b749c |
|
|
|
4b749c |
# Build complete list of tags, both annotated and bare ones
|
|
|
4b749c |
- for line in fh:
|
|
|
4b749c |
+ for line in output:
|
|
|
4b749c |
line = line.strip()
|
|
|
4b749c |
if line.startswith("error:") or line.startswith("fatal:"):
|
|
|
4b749c |
raise util.Abort(_('cannot read tags from %s') % self.path)
|
|
|
4b749c |
@@ -237,8 +218,6 @@ class convert_git(converter_source):
|
|
|
4b749c |
if not tag.startswith(prefix):
|
|
|
4b749c |
continue
|
|
|
4b749c |
alltags[tag[len(prefix):]] = node
|
|
|
4b749c |
- if fh.close():
|
|
|
4b749c |
- raise util.Abort(_('cannot read tags from %s') % self.path)
|
|
|
4b749c |
|
|
|
4b749c |
# Filter out tag objects for annotated tag refs
|
|
|
4b749c |
for tag in alltags:
|
|
|
4b749c |
@@ -255,18 +234,20 @@ class convert_git(converter_source):
|
|
|
4b749c |
def getchangedfiles(self, version, i):
|
|
|
4b749c |
changes = []
|
|
|
4b749c |
if i is None:
|
|
|
4b749c |
- fh = self.gitopen("git diff-tree --root -m -r %s" % version)
|
|
|
4b749c |
- for l in fh:
|
|
|
4b749c |
+ output, status = self.gitrunlines('diff-tree', '--root', '-m',
|
|
|
4b749c |
+ '-r', version)
|
|
|
4b749c |
+ if status:
|
|
|
4b749c |
+ raise error.Abort(_('cannot read changes in %s') % version)
|
|
|
4b749c |
+ for l in output:
|
|
|
4b749c |
if "\t" not in l:
|
|
|
4b749c |
continue
|
|
|
4b749c |
m, f = l[:-1].split("\t")
|
|
|
4b749c |
changes.append(f)
|
|
|
4b749c |
else:
|
|
|
4b749c |
- fh = self.gitopen('git diff-tree --name-only --root -r %s '
|
|
|
4b749c |
- '"%s^%s" --' % (version, version, i + 1))
|
|
|
4b749c |
- changes = [f.rstrip('\n') for f in fh]
|
|
|
4b749c |
- if fh.close():
|
|
|
4b749c |
- raise util.Abort(_('cannot read changes in %s') % version)
|
|
|
4b749c |
+ output, status = self.gitrunlines('diff-tree', '--name-only',
|
|
|
4b749c |
+ '--root', '-r', version,
|
|
|
4b749c |
+ '%s^%s' % (version, i + 1), '--')
|
|
|
4b749c |
+ changes = [f.rstrip('\n') for f in output]
|
|
|
4b749c |
|
|
|
4b749c |
return changes
|
|
|
4b749c |
|
|
|
4b749c |
@@ -278,14 +259,14 @@ class convert_git(converter_source):
|
|
|
4b749c |
prefixlen = len(prefix)
|
|
|
4b749c |
|
|
|
4b749c |
# factor two commands
|
|
|
4b749c |
- gitcmd = { 'remote/': 'git ls-remote --heads origin',
|
|
|
4b749c |
- '': 'git show-ref'}
|
|
|
4b749c |
+ gitcmd = { 'remote/': ['ls-remote', '--heads origin'],
|
|
|
4b749c |
+ '': ['show-ref']}
|
|
|
4b749c |
|
|
|
4b749c |
# Origin heads
|
|
|
4b749c |
for reftype in gitcmd:
|
|
|
4b749c |
try:
|
|
|
4b749c |
- fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
|
|
|
4b749c |
- for line in fh:
|
|
|
4b749c |
+ output, status = self.gitrunlines(*gitcmd[reftype])
|
|
|
4b749c |
+ for line in output:
|
|
|
4b749c |
line = line.strip()
|
|
|
4b749c |
rev, name = line.split(None, 1)
|
|
|
4b749c |
if not name.startswith(prefix):
|
|
|
4b749c |
|
|
|
4b749c |
diff --git a/tests/test-convert-git.t b/tests/test-convert-git.t
|
|
|
4b749c |
index 21f18d2..7eb068b 100644
|
|
|
4b749c |
--- a/tests/test-convert-git.t
|
|
|
4b749c |
+++ b/tests/test-convert-git.t
|
|
|
4b749c |
@@ -341,7 +341,7 @@ damage git repository by renaming a commit object
|
|
|
4b749c |
$ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
|
|
|
4b749c |
$ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
|
|
|
4b749c |
$ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
|
|
|
4b749c |
- abort: cannot read tags from git-repo4/.git
|
|
|
4b749c |
+ abort: cannot retrieve number of commits in git-repo4/.git
|
|
|
4b749c |
$ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
|
|
|
4b749c |
damage git repository by renaming a blob object
|
|
|
4b749c |
|
|
|
4b749c |
@@ -356,3 +356,20 @@ damage git repository by renaming a tree object
|
|
|
4b749c |
$ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
|
|
|
4b749c |
$ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
|
|
|
4b749c |
abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
|
|
|
4b749c |
+
|
|
|
4b749c |
+test for escaping the repo name (CVE-2016-3069)
|
|
|
4b749c |
+
|
|
|
4b749c |
+ $ git init '`echo pwned >COMMAND-INJECTION`'
|
|
|
4b749c |
+ Initialized empty Git repository in $TESTTMP/`echo pwned >COMMAND-INJECTION`/.git/
|
|
|
4b749c |
+ $ cd '`echo pwned >COMMAND-INJECTION`'
|
|
|
4b749c |
+ $ git commit -q --allow-empty -m 'empty'
|
|
|
4b749c |
+ $ cd ..
|
|
|
4b749c |
+ $ hg convert '`echo pwned >COMMAND-INJECTION`' 'converted'
|
|
|
4b749c |
+ initializing destination converted repository
|
|
|
4b749c |
+ scanning source...
|
|
|
4b749c |
+ sorting...
|
|
|
4b749c |
+ converting...
|
|
|
4b749c |
+ 0 empty
|
|
|
4b749c |
+ updating bookmarks
|
|
|
4b749c |
+ $ test -f COMMAND-INJECTION
|
|
|
4b749c |
+ [1]
|