hgext/convert/git.py
changeset 28670 ff0d3b6b287f
parent 28365 cd599bc179fb
parent 28662 80cac1de6aea
child 28671 96ed01f6514b
equal deleted inserted replaced
28669:c4b727795d6a 28670:ff0d3b6b287f
    30         return "%s = [git]%s" % (self.path, self.url)
    30         return "%s = [git]%s" % (self.path, self.url)
    31 
    31 
    32     def hgsubstate(self):
    32     def hgsubstate(self):
    33         return "%s %s" % (self.node, self.path)
    33         return "%s %s" % (self.node, self.path)
    34 
    34 
    35 class convert_git(common.converter_source):
    35 class convert_git(common.converter_source, common.commandline):
    36     # Windows does not support GIT_DIR= construct while other systems
    36     # Windows does not support GIT_DIR= construct while other systems
    37     # cannot remove environment variable. Just assume none have
    37     # cannot remove environment variable. Just assume none have
    38     # both issues.
    38     # both issues.
    39     if util.safehasattr(os, 'unsetenv'):
    39 
    40         def gitopen(self, s, err=None):
    40     def _gitcmd(self, cmd, *args, **kwargs):
    41             prevgitdir = os.environ.get('GIT_DIR')
    41         return cmd('--git-dir=%s' % self.path, *args, **kwargs)
    42             os.environ['GIT_DIR'] = self.path
    42 
    43             try:
    43     def gitrun0(self, *args, **kwargs):
    44                 if err == subprocess.PIPE:
    44         return self._gitcmd(self.run0, *args, **kwargs)
    45                     (stdin, stdout, stderr) = util.popen3(s)
    45 
    46                     return stdout
    46     def gitrun(self, *args, **kwargs):
    47                 elif err == subprocess.STDOUT:
    47         return self._gitcmd(self.run, *args, **kwargs)
    48                     return self.popen_with_stderr(s)
    48 
    49                 else:
    49     def gitrunlines0(self, *args, **kwargs):
    50                     return util.popen(s, 'rb')
    50         return self._gitcmd(self.runlines0, *args, **kwargs)
    51             finally:
    51 
    52                 if prevgitdir is None:
    52     def gitrunlines(self, *args, **kwargs):
    53                     del os.environ['GIT_DIR']
    53         return self._gitcmd(self.runlines, *args, **kwargs)
    54                 else:
    54 
    55                     os.environ['GIT_DIR'] = prevgitdir
    55     def gitpipe(self, *args, **kwargs):
    56 
    56         return self._gitcmd(self._run3, *args, **kwargs)
    57         def gitpipe(self, s):
       
    58             prevgitdir = os.environ.get('GIT_DIR')
       
    59             os.environ['GIT_DIR'] = self.path
       
    60             try:
       
    61                 return util.popen3(s)
       
    62             finally:
       
    63                 if prevgitdir is None:
       
    64                     del os.environ['GIT_DIR']
       
    65                 else:
       
    66                     os.environ['GIT_DIR'] = prevgitdir
       
    67 
       
    68     else:
       
    69         def gitopen(self, s, err=None):
       
    70             if err == subprocess.PIPE:
       
    71                 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
       
    72                 return so
       
    73             elif err == subprocess.STDOUT:
       
    74                     return self.popen_with_stderr(s)
       
    75             else:
       
    76                 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
       
    77 
       
    78         def gitpipe(self, s):
       
    79             return util.popen3('GIT_DIR=%s %s' % (self.path, s))
       
    80 
       
    81     def popen_with_stderr(self, s):
       
    82         p = subprocess.Popen(s, shell=True, bufsize=-1,
       
    83                              close_fds=util.closefds,
       
    84                              stdin=subprocess.PIPE,
       
    85                              stdout=subprocess.PIPE,
       
    86                              stderr=subprocess.STDOUT,
       
    87                              universal_newlines=False,
       
    88                              env=None)
       
    89         return p.stdout
       
    90 
    57 
    91     def gitread(self, s):
    58     def gitread(self, s):
    92         fh = self.gitopen(s)
    59         fh = self.gitopen(s)
    93         data = fh.read()
    60         data = fh.read()
    94         return data, fh.close()
    61         return data, fh.close()
    95 
    62 
    96     def __init__(self, ui, path, revs=None):
    63     def __init__(self, ui, path, revs=None):
    97         super(convert_git, self).__init__(ui, path, revs=revs)
    64         super(convert_git, self).__init__(ui, path, revs=revs)
       
    65         common.commandline.__init__(self, ui, 'git')
    98 
    66 
    99         if os.path.isdir(path + "/.git"):
    67         if os.path.isdir(path + "/.git"):
   100             path += "/.git"
    68             path += "/.git"
   101         if not os.path.exists(path + "/objects"):
    69         if not os.path.exists(path + "/objects"):
   102             raise common.NoRepo(_("%s does not look like a Git repository") %
    70             raise common.NoRepo(_("%s does not look like a Git repository") %
   105         # The default value (50) is based on the default for 'git diff'.
    73         # The default value (50) is based on the default for 'git diff'.
   106         similarity = ui.configint('convert', 'git.similarity', default=50)
    74         similarity = ui.configint('convert', 'git.similarity', default=50)
   107         if similarity < 0 or similarity > 100:
    75         if similarity < 0 or similarity > 100:
   108             raise error.Abort(_('similarity must be between 0 and 100'))
    76             raise error.Abort(_('similarity must be between 0 and 100'))
   109         if similarity > 0:
    77         if similarity > 0:
   110             self.simopt = '-C%d%%' % similarity
    78             self.simopt = ['-C%d%%' % similarity]
   111             findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
    79             findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
   112                                              False)
    80                                              False)
   113             if findcopiesharder:
    81             if findcopiesharder:
   114                 self.simopt += ' --find-copies-harder'
    82                 self.simopt.append('--find-copies-harder')
   115         else:
    83         else:
   116             self.simopt = ''
    84             self.simopt = []
   117 
    85 
   118         common.checktool('git', 'git')
    86         common.checktool('git', 'git')
   119 
    87 
   120         self.path = path
    88         self.path = path
   121         self.submodules = []
    89         self.submodules = []
   122 
    90 
   123         self.catfilepipe = self.gitpipe('git cat-file --batch')
    91         self.catfilepipe = self.gitpipe('cat-file', '--batch')
   124 
    92 
   125     def after(self):
    93     def after(self):
   126         for f in self.catfilepipe:
    94         for f in self.catfilepipe:
   127             f.close()
    95             f.close()
   128 
    96 
   129     def getheads(self):
    97     def getheads(self):
   130         if not self.revs:
    98         if not self.revs:
   131             heads, ret = self.gitread('git rev-parse --branches --remotes')
    99             output, status = self.gitrun('rev-parse', '--branches', '--remotes')
   132             heads = heads.splitlines()
   100             heads = output.splitlines()
   133             if ret:
   101             if status:
   134                 raise error.Abort(_('cannot retrieve git heads'))
   102                 raise error.Abort(_('cannot retrieve git heads'))
   135         else:
   103         else:
   136             heads = []
   104             heads = []
   137             for rev in self.revs:
   105             for rev in self.revs:
   138                 rawhead, ret = self.gitread("git rev-parse --verify %s" % rev)
   106                 rawhead, ret = self.gitrun('rev-parse', '--verify', rev)
   139                 heads.append(rawhead[:-1])
   107                 heads.append(rawhead[:-1])
   140                 if ret:
   108                 if ret:
   141                     raise error.Abort(_('cannot retrieve git head "%s"') % rev)
   109                     raise error.Abort(_('cannot retrieve git head "%s"') % rev)
   142         return heads
   110         return heads
   143 
   111 
   193             s = c[sec]
   161             s = c[sec]
   194             if 'url' in s and 'path' in s:
   162             if 'url' in s and 'path' in s:
   195                 self.submodules.append(submodule(s['path'], '', s['url']))
   163                 self.submodules.append(submodule(s['path'], '', s['url']))
   196 
   164 
   197     def retrievegitmodules(self, version):
   165     def retrievegitmodules(self, version):
   198         modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
   166         modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules'))
   199         if ret:
   167         if ret:
   200             # This can happen if a file is in the repo that has permissions
   168             # This can happen if a file is in the repo that has permissions
   201             # 160000, but there is no .gitmodules file.
   169             # 160000, but there is no .gitmodules file.
   202             self.ui.warn(_("warning: cannot read submodules config file in "
   170             self.ui.warn(_("warning: cannot read submodules config file in "
   203                            "%s\n") % version)
   171                            "%s\n") % version)
   209             self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
   177             self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
   210                          % version)
   178                          % version)
   211             return
   179             return
   212 
   180 
   213         for m in self.submodules:
   181         for m in self.submodules:
   214             node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
   182             node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path))
   215             if ret:
   183             if ret:
   216                 continue
   184                 continue
   217             m.node = node.strip()
   185             m.node = node.strip()
   218 
   186 
   219     def getchanges(self, version, full):
   187     def getchanges(self, version, full):
   220         if full:
   188         if full:
   221             raise error.Abort(_("convert from git does not support --full"))
   189             raise error.Abort(_("convert from git does not support --full"))
   222         self.modecache = {}
   190         self.modecache = {}
   223         fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % (
   191         cmd = ['diff-tree','-z', '--root', '-m', '-r'] + self.simopt + [version]
   224             self.simopt, version))
   192         output, status = self.gitrun(*cmd)
       
   193         if status:
       
   194             raise error.Abort(_('cannot read changes in %s') % version)
   225         changes = []
   195         changes = []
   226         copies = {}
   196         copies = {}
   227         seen = set()
   197         seen = set()
   228         entry = None
   198         entry = None
   229         subexists = [False]
   199         subexists = [False]
   230         subdeleted = [False]
   200         subdeleted = [False]
   231         difftree = fh.read().split('\x00')
   201         difftree = output.split('\x00')
   232         lcount = len(difftree)
   202         lcount = len(difftree)
   233         i = 0
   203         i = 0
   234 
   204 
   235         skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
   205         skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
   236                                             False)
   206                                             False)
   288                     # .gitmodules isn't imported at all, so it being copied to
   258                     # .gitmodules isn't imported at all, so it being copied to
   289                     # and fro doesn't really make sense
   259                     # and fro doesn't really make sense
   290                     if f != '.gitmodules' and fdest != '.gitmodules':
   260                     if f != '.gitmodules' and fdest != '.gitmodules':
   291                         copies[fdest] = f
   261                         copies[fdest] = f
   292             entry = None
   262             entry = None
   293         if fh.close():
       
   294             raise error.Abort(_('cannot read changes in %s') % version)
       
   295 
   263 
   296         if subexists[0]:
   264         if subexists[0]:
   297             if subdeleted[0]:
   265             if subdeleted[0]:
   298                 changes.append(('.hgsubstate', nodemod.nullhex))
   266                 changes.append(('.hgsubstate', nodemod.nullhex))
   299             else:
   267             else:
   336                           desc=message,
   304                           desc=message,
   337                           rev=version)
   305                           rev=version)
   338         return c
   306         return c
   339 
   307 
   340     def numcommits(self):
   308     def numcommits(self):
   341         return len([None for _ in self.gitopen('git rev-list --all')])
   309         output, ret = self.gitrunlines('rev-list', '--all')
       
   310         if ret:
       
   311             raise error.Abort(_('cannot retrieve number of commits in %s') \
       
   312                               % self.path)
       
   313         return len(output)
   342 
   314 
   343     def gettags(self):
   315     def gettags(self):
   344         tags = {}
   316         tags = {}
   345         alltags = {}
   317         alltags = {}
   346         fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
   318         output, status = self.gitrunlines('ls-remote', '--tags', self.path)
   347                           err=subprocess.STDOUT)
   319 
       
   320         if status:
       
   321             raise error.Abort(_('cannot read tags from %s') % self.path)
   348         prefix = 'refs/tags/'
   322         prefix = 'refs/tags/'
   349 
   323 
   350         # Build complete list of tags, both annotated and bare ones
   324         # Build complete list of tags, both annotated and bare ones
   351         for line in fh:
   325         for line in output:
   352             line = line.strip()
   326             line = line.strip()
   353             if line.startswith("error:") or line.startswith("fatal:"):
   327             if line.startswith("error:") or line.startswith("fatal:"):
   354                 raise error.Abort(_('cannot read tags from %s') % self.path)
   328                 raise error.Abort(_('cannot read tags from %s') % self.path)
   355             node, tag = line.split(None, 1)
   329             node, tag = line.split(None, 1)
   356             if not tag.startswith(prefix):
   330             if not tag.startswith(prefix):
   357                 continue
   331                 continue
   358             alltags[tag[len(prefix):]] = node
   332             alltags[tag[len(prefix):]] = node
   359         if fh.close():
       
   360             raise error.Abort(_('cannot read tags from %s') % self.path)
       
   361 
   333 
   362         # Filter out tag objects for annotated tag refs
   334         # Filter out tag objects for annotated tag refs
   363         for tag in alltags:
   335         for tag in alltags:
   364             if tag.endswith('^{}'):
   336             if tag.endswith('^{}'):
   365                 tags[tag[:-3]] = alltags[tag]
   337                 tags[tag[:-3]] = alltags[tag]
   372         return tags
   344         return tags
   373 
   345 
   374     def getchangedfiles(self, version, i):
   346     def getchangedfiles(self, version, i):
   375         changes = []
   347         changes = []
   376         if i is None:
   348         if i is None:
   377             fh = self.gitopen("git diff-tree --root -m -r %s" % version)
   349             output, status = self.gitrunlines('diff-tree', '--root', '-m',
   378             for l in fh:
   350                                               '-r', version)
       
   351             if status:
       
   352                 raise error.Abort(_('cannot read changes in %s') % version)
       
   353             for l in output:
   379                 if "\t" not in l:
   354                 if "\t" not in l:
   380                     continue
   355                     continue
   381                 m, f = l[:-1].split("\t")
   356                 m, f = l[:-1].split("\t")
   382                 changes.append(f)
   357                 changes.append(f)
   383         else:
   358         else:
   384             fh = self.gitopen('git diff-tree --name-only --root -r %s '
   359             output, status = self.gitrunlines('diff-tree', '--name-only',
   385                               '"%s^%s" --' % (version, version, i + 1))
   360                                               '--root', '-r', version,
   386             changes = [f.rstrip('\n') for f in fh]
   361                                               '%s^%s' % (version, i + 1), '--')
   387         if fh.close():
   362             changes = [f.rstrip('\n') for f in output]
   388             raise error.Abort(_('cannot read changes in %s') % version)
       
   389 
   363 
   390         return changes
   364         return changes
   391 
   365 
   392     def getbookmarks(self):
   366     def getbookmarks(self):
   393         bookmarks = {}
   367         bookmarks = {}
   403         exclude = set([
   377         exclude = set([
   404             'refs/remotes/origin/HEAD',
   378             'refs/remotes/origin/HEAD',
   405         ])
   379         ])
   406 
   380 
   407         try:
   381         try:
   408             fh = self.gitopen('git show-ref', err=subprocess.PIPE)
   382             output, status = self.gitrunlines('show-ref')
   409             for line in fh:
   383             for line in output:
   410                 line = line.strip()
   384                 line = line.strip()
   411                 rev, name = line.split(None, 1)
   385                 rev, name = line.split(None, 1)
   412                 # Process each type of branch
   386                 # Process each type of branch
   413                 for gitprefix, hgprefix in reftypes:
   387                 for gitprefix, hgprefix in reftypes:
   414                     if not name.startswith(gitprefix) or name in exclude:
   388                     if not name.startswith(gitprefix) or name in exclude: