mercurial/subrepo.py
changeset 23572 40e62fbd7356
parent 23571 9626120e017b
child 23573 3fec2a3c768b
equal deleted inserted replaced
23571:9626120e017b 23572:40e62fbd7356
   372 # subrepo classes need to implement the following abstract class:
   372 # subrepo classes need to implement the following abstract class:
   373 
   373 
   374 class abstractsubrepo(object):
   374 class abstractsubrepo(object):
   375 
   375 
   376     def __init__(self, ui):
   376     def __init__(self, ui):
   377         self._ui = ui
   377         self.ui = ui
   378 
   378 
   379     def storeclean(self, path):
   379     def storeclean(self, path):
   380         """
   380         """
   381         returns true if the repository has not changed since it was last
   381         returns true if the repository has not changed since it was last
   382         cloned from or pushed to a given repository.
   382         cloned from or pushed to a given repository.
   437 
   437 
   438     def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
   438     def add(self, ui, match, dryrun, listsubrepos, prefix, explicitonly):
   439         return []
   439         return []
   440 
   440 
   441     def addremove(self, matcher, prefix, opts, dry_run, similarity):
   441     def addremove(self, matcher, prefix, opts, dry_run, similarity):
   442         self._ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
   442         self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
   443         return 1
   443         return 1
   444 
   444 
   445     def cat(self, ui, match, prefix, **opts):
   445     def cat(self, ui, match, prefix, **opts):
   446         return 1
   446         return 1
   447 
   447 
   907                              % self._path)
   907                              % self._path)
   908 
   908 
   909     def _svncommand(self, commands, filename='', failok=False):
   909     def _svncommand(self, commands, filename='', failok=False):
   910         cmd = [self._exe]
   910         cmd = [self._exe]
   911         extrakw = {}
   911         extrakw = {}
   912         if not self._ui.interactive():
   912         if not self.ui.interactive():
   913             # Making stdin be a pipe should prevent svn from behaving
   913             # Making stdin be a pipe should prevent svn from behaving
   914             # interactively even if we can't pass --non-interactive.
   914             # interactively even if we can't pass --non-interactive.
   915             extrakw['stdin'] = subprocess.PIPE
   915             extrakw['stdin'] = subprocess.PIPE
   916             # Starting in svn 1.5 --non-interactive is a global flag
   916             # Starting in svn 1.5 --non-interactive is a global flag
   917             # instead of being per-command, but we need to support 1.4 so
   917             # instead of being per-command, but we need to support 1.4 so
   937         stderr = stderr.strip()
   937         stderr = stderr.strip()
   938         if not failok:
   938         if not failok:
   939             if p.returncode:
   939             if p.returncode:
   940                 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
   940                 raise util.Abort(stderr or 'exited with code %d' % p.returncode)
   941             if stderr:
   941             if stderr:
   942                 self._ui.warn(stderr + '\n')
   942                 self.ui.warn(stderr + '\n')
   943         return stdout, stderr
   943         return stdout, stderr
   944 
   944 
   945     @propertycache
   945     @propertycache
   946     def _svnversion(self):
   946     def _svnversion(self):
   947         output, err = self._svncommand(['--version', '--quiet'], filename=None)
   947         output, err = self._svncommand(['--version', '--quiet'], filename=None)
  1029         if missing:
  1029         if missing:
  1030             # svn can commit with missing entries but aborting like hg
  1030             # svn can commit with missing entries but aborting like hg
  1031             # seems a better approach.
  1031             # seems a better approach.
  1032             raise util.Abort(_('cannot commit missing svn entries'))
  1032             raise util.Abort(_('cannot commit missing svn entries'))
  1033         commitinfo, err = self._svncommand(['commit', '-m', text])
  1033         commitinfo, err = self._svncommand(['commit', '-m', text])
  1034         self._ui.status(commitinfo)
  1034         self.ui.status(commitinfo)
  1035         newrev = re.search('Committed revision ([0-9]+).', commitinfo)
  1035         newrev = re.search('Committed revision ([0-9]+).', commitinfo)
  1036         if not newrev:
  1036         if not newrev:
  1037             if not commitinfo.strip():
  1037             if not commitinfo.strip():
  1038                 # Sometimes, our definition of "changed" differs from
  1038                 # Sometimes, our definition of "changed" differs from
  1039                 # svn one. For instance, svn ignores missing files
  1039                 # svn one. For instance, svn ignores missing files
  1040                 # when committing. If there are only missing files, no
  1040                 # when committing. If there are only missing files, no
  1041                 # commit is made, no output and no error code.
  1041                 # commit is made, no output and no error code.
  1042                 raise util.Abort(_('failed to commit svn changes'))
  1042                 raise util.Abort(_('failed to commit svn changes'))
  1043             raise util.Abort(commitinfo.splitlines()[-1])
  1043             raise util.Abort(commitinfo.splitlines()[-1])
  1044         newrev = newrev.groups()[0]
  1044         newrev = newrev.groups()[0]
  1045         self._ui.status(self._svncommand(['update', '-r', newrev])[0])
  1045         self.ui.status(self._svncommand(['update', '-r', newrev])[0])
  1046         return newrev
  1046         return newrev
  1047 
  1047 
  1048     @annotatesubrepoerror
  1048     @annotatesubrepoerror
  1049     def remove(self):
  1049     def remove(self):
  1050         if self.dirty():
  1050         if self.dirty():
  1051             self._ui.warn(_('not removing repo %s because '
  1051             self.ui.warn(_('not removing repo %s because '
  1052                             'it has changes.\n') % self._path)
  1052                            'it has changes.\n') % self._path)
  1053             return
  1053             return
  1054         self._ui.note(_('removing subrepo %s\n') % self._path)
  1054         self.ui.note(_('removing subrepo %s\n') % self._path)
  1055 
  1055 
  1056         def onerror(function, path, excinfo):
  1056         def onerror(function, path, excinfo):
  1057             if function is not os.remove:
  1057             if function is not os.remove:
  1058                 raise
  1058                 raise
  1059             # read-only files cannot be unlinked under Windows
  1059             # read-only files cannot be unlinked under Windows
  1079             args.append('--force')
  1079             args.append('--force')
  1080         # The revision must be specified at the end of the URL to properly
  1080         # The revision must be specified at the end of the URL to properly
  1081         # update to a directory which has since been deleted and recreated.
  1081         # update to a directory which has since been deleted and recreated.
  1082         args.append('%s@%s' % (state[0], state[1]))
  1082         args.append('%s@%s' % (state[0], state[1]))
  1083         status, err = self._svncommand(args, failok=True)
  1083         status, err = self._svncommand(args, failok=True)
  1084         _sanitize(self._ui, self._ctx._repo.wjoin(self._path), '.svn')
  1084         _sanitize(self.ui, self._ctx._repo.wjoin(self._path), '.svn')
  1085         if not re.search('Checked out revision [0-9]+.', status):
  1085         if not re.search('Checked out revision [0-9]+.', status):
  1086             if ('is already a working copy for a different URL' in err
  1086             if ('is already a working copy for a different URL' in err
  1087                 and (self._wcchanged()[:2] == (False, False))):
  1087                 and (self._wcchanged()[:2] == (False, False))):
  1088                 # obstructed but clean working copy, so just blow it away.
  1088                 # obstructed but clean working copy, so just blow it away.
  1089                 self.remove()
  1089                 self.remove()
  1090                 self.get(state, overwrite=False)
  1090                 self.get(state, overwrite=False)
  1091                 return
  1091                 return
  1092             raise util.Abort((status or err).splitlines()[-1])
  1092             raise util.Abort((status or err).splitlines()[-1])
  1093         self._ui.status(status)
  1093         self.ui.status(status)
  1094 
  1094 
  1095     @annotatesubrepoerror
  1095     @annotatesubrepoerror
  1096     def merge(self, state):
  1096     def merge(self, state):
  1097         old = self._state[1]
  1097         old = self._state[1]
  1098         new = state[1]
  1098         new = state[1]
  1099         wcrev = self._wcrev()
  1099         wcrev = self._wcrev()
  1100         if new != wcrev:
  1100         if new != wcrev:
  1101             dirty = old == wcrev or self._wcchanged()[0]
  1101             dirty = old == wcrev or self._wcchanged()[0]
  1102             if _updateprompt(self._ui, self, dirty, wcrev, new):
  1102             if _updateprompt(self.ui, self, dirty, wcrev, new):
  1103                 self.get(state, False)
  1103                 self.get(state, False)
  1104 
  1104 
  1105     def push(self, opts):
  1105     def push(self, opts):
  1106         # push is a no-op for SVN
  1106         # push is a no-op for SVN
  1107         return True
  1107         return True
  1145                 raise
  1145                 raise
  1146             self._gitexecutable = 'git.cmd'
  1146             self._gitexecutable = 'git.cmd'
  1147             out, err = self._gitnodir(['--version'])
  1147             out, err = self._gitnodir(['--version'])
  1148         versionstatus = self._checkversion(out)
  1148         versionstatus = self._checkversion(out)
  1149         if versionstatus == 'unknown':
  1149         if versionstatus == 'unknown':
  1150             self._ui.warn(_('cannot retrieve git version\n'))
  1150             self.ui.warn(_('cannot retrieve git version\n'))
  1151         elif versionstatus == 'abort':
  1151         elif versionstatus == 'abort':
  1152             raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
  1152             raise util.Abort(_('git subrepo requires at least 1.6.0 or later'))
  1153         elif versionstatus == 'warning':
  1153         elif versionstatus == 'warning':
  1154             self._ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
  1154             self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
  1155 
  1155 
  1156     @staticmethod
  1156     @staticmethod
  1157     def _gitversion(out):
  1157     def _gitversion(out):
  1158         m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
  1158         m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
  1159         if m:
  1159         if m:
  1212         """Calls the git command
  1212         """Calls the git command
  1213 
  1213 
  1214         The methods tries to call the git command. versions prior to 1.6.0
  1214         The methods tries to call the git command. versions prior to 1.6.0
  1215         are not supported and very probably fail.
  1215         are not supported and very probably fail.
  1216         """
  1216         """
  1217         self._ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
  1217         self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
  1218         # unless ui.quiet is set, print git's stderr,
  1218         # unless ui.quiet is set, print git's stderr,
  1219         # which is mostly progress and useful info
  1219         # which is mostly progress and useful info
  1220         errpipe = None
  1220         errpipe = None
  1221         if self._ui.quiet:
  1221         if self.ui.quiet:
  1222             errpipe = open(os.devnull, 'w')
  1222             errpipe = open(os.devnull, 'w')
  1223         p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
  1223         p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
  1224                              cwd=cwd, env=env, close_fds=util.closefds,
  1224                              cwd=cwd, env=env, close_fds=util.closefds,
  1225                              stdout=subprocess.PIPE, stderr=errpipe)
  1225                              stdout=subprocess.PIPE, stderr=errpipe)
  1226         if stream:
  1226         if stream:
  1321         return _abssource(self)
  1321         return _abssource(self)
  1322 
  1322 
  1323     def _fetch(self, source, revision):
  1323     def _fetch(self, source, revision):
  1324         if self._gitmissing():
  1324         if self._gitmissing():
  1325             source = self._abssource(source)
  1325             source = self._abssource(source)
  1326             self._ui.status(_('cloning subrepo %s from %s\n') %
  1326             self.ui.status(_('cloning subrepo %s from %s\n') %
  1327                             (self._relpath, source))
  1327                             (self._relpath, source))
  1328             self._gitnodir(['clone', source, self._abspath])
  1328             self._gitnodir(['clone', source, self._abspath])
  1329         if self._githavelocally(revision):
  1329         if self._githavelocally(revision):
  1330             return
  1330             return
  1331         self._ui.status(_('pulling subrepo %s from %s\n') %
  1331         self.ui.status(_('pulling subrepo %s from %s\n') %
  1332                         (self._relpath, self._gitremote('origin')))
  1332                         (self._relpath, self._gitremote('origin')))
  1333         # try only origin: the originally cloned repo
  1333         # try only origin: the originally cloned repo
  1334         self._gitcommand(['fetch'])
  1334         self._gitcommand(['fetch'])
  1335         if not self._githavelocally(revision):
  1335         if not self._githavelocally(revision):
  1336             raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
  1336             raise util.Abort(_("revision %s does not exist in subrepo %s\n") %
  1383                 # the -f option will otherwise throw away files added for
  1383                 # the -f option will otherwise throw away files added for
  1384                 # commit, not just unmark them.
  1384                 # commit, not just unmark them.
  1385                 self._gitcommand(['reset', 'HEAD'])
  1385                 self._gitcommand(['reset', 'HEAD'])
  1386                 cmd.append('-f')
  1386                 cmd.append('-f')
  1387             self._gitcommand(cmd + args)
  1387             self._gitcommand(cmd + args)
  1388             _sanitize(self._ui, self._abspath, '.git')
  1388             _sanitize(self.ui, self._abspath, '.git')
  1389 
  1389 
  1390         def rawcheckout():
  1390         def rawcheckout():
  1391             # no branch to checkout, check it out with no branch
  1391             # no branch to checkout, check it out with no branch
  1392             self._ui.warn(_('checking out detached HEAD in subrepo %s\n') %
  1392             self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
  1393                           self._relpath)
  1393                           self._relpath)
  1394             self._ui.warn(_('check out a git branch if you intend '
  1394             self.ui.warn(_('check out a git branch if you intend '
  1395                             'to make changes\n'))
  1395                             'to make changes\n'))
  1396             checkout(['-q', revision])
  1396             checkout(['-q', revision])
  1397 
  1397 
  1398         if revision not in rev2branch:
  1398         if revision not in rev2branch:
  1399             rawcheckout()
  1399             rawcheckout()
  1432             # Since we are only looking at branching at update, we need to
  1432             # Since we are only looking at branching at update, we need to
  1433             # detect this situation and perform this action lazily.
  1433             # detect this situation and perform this action lazily.
  1434             if tracking[remote] != self._gitcurrentbranch():
  1434             if tracking[remote] != self._gitcurrentbranch():
  1435                 checkout([tracking[remote]])
  1435                 checkout([tracking[remote]])
  1436             self._gitcommand(['merge', '--ff', remote])
  1436             self._gitcommand(['merge', '--ff', remote])
  1437             _sanitize(self._ui, self._abspath, '.git')
  1437             _sanitize(self.ui, self._abspath, '.git')
  1438         else:
  1438         else:
  1439             # a real merge would be required, just checkout the revision
  1439             # a real merge would be required, just checkout the revision
  1440             rawcheckout()
  1440             rawcheckout()
  1441 
  1441 
  1442     @annotatesubrepoerror
  1442     @annotatesubrepoerror
  1468         def mergefunc():
  1468         def mergefunc():
  1469             if base == revision:
  1469             if base == revision:
  1470                 self.get(state) # fast forward merge
  1470                 self.get(state) # fast forward merge
  1471             elif base != self._state[1]:
  1471             elif base != self._state[1]:
  1472                 self._gitcommand(['merge', '--no-commit', revision])
  1472                 self._gitcommand(['merge', '--no-commit', revision])
  1473             _sanitize(self._ui, self._abspath, '.git')
  1473             _sanitize(self.ui, self._abspath, '.git')
  1474 
  1474 
  1475         if self.dirty():
  1475         if self.dirty():
  1476             if self._gitstate() != revision:
  1476             if self._gitstate() != revision:
  1477                 dirty = self._gitstate() == self._state[1] or code != 0
  1477                 dirty = self._gitstate() == self._state[1] or code != 0
  1478                 if _updateprompt(self._ui, self, dirty,
  1478                 if _updateprompt(self.ui, self, dirty,
  1479                                  self._state[1][:7], revision[:7]):
  1479                                  self._state[1][:7], revision[:7]):
  1480                     mergefunc()
  1480                     mergefunc()
  1481         else:
  1481         else:
  1482             mergefunc()
  1482             mergefunc()
  1483 
  1483 
  1506 
  1506 
  1507         current = self._gitcurrentbranch()
  1507         current = self._gitcurrentbranch()
  1508         if current:
  1508         if current:
  1509             # determine if the current branch is even useful
  1509             # determine if the current branch is even useful
  1510             if not self._gitisancestor(self._state[1], current):
  1510             if not self._gitisancestor(self._state[1], current):
  1511                 self._ui.warn(_('unrelated git branch checked out '
  1511                 self.ui.warn(_('unrelated git branch checked out '
  1512                                 'in subrepo %s\n') % self._relpath)
  1512                                 'in subrepo %s\n') % self._relpath)
  1513                 return False
  1513                 return False
  1514             self._ui.status(_('pushing branch %s of subrepo %s\n') %
  1514             self.ui.status(_('pushing branch %s of subrepo %s\n') %
  1515                             (current.split('/', 2)[2], self._relpath))
  1515                            (current.split('/', 2)[2], self._relpath))
  1516             ret = self._gitdir(cmd + ['origin', current])
  1516             ret = self._gitdir(cmd + ['origin', current])
  1517             return ret[1] == 0
  1517             return ret[1] == 0
  1518         else:
  1518         else:
  1519             self._ui.warn(_('no branch checked out in subrepo %s\n'
  1519             self.ui.warn(_('no branch checked out in subrepo %s\n'
  1520                             'cannot push revision %s\n') %
  1520                            'cannot push revision %s\n') %
  1521                           (self._relpath, self._state[1]))
  1521                           (self._relpath, self._state[1]))
  1522             return False
  1522             return False
  1523 
  1523 
  1524     @annotatesubrepoerror
  1524     @annotatesubrepoerror
  1525     def remove(self):
  1525     def remove(self):
  1526         if self._gitmissing():
  1526         if self._gitmissing():
  1527             return
  1527             return
  1528         if self.dirty():
  1528         if self.dirty():
  1529             self._ui.warn(_('not removing repo %s because '
  1529             self.ui.warn(_('not removing repo %s because '
  1530                             'it has changes.\n') % self._relpath)
  1530                            'it has changes.\n') % self._relpath)
  1531             return
  1531             return
  1532         # we can't fully delete the repository as it may contain
  1532         # we can't fully delete the repository as it may contain
  1533         # local-only history
  1533         # local-only history
  1534         self._ui.note(_('removing subrepo %s\n') % self._relpath)
  1534         self.ui.note(_('removing subrepo %s\n') % self._relpath)
  1535         self._gitcommand(['config', 'core.bare', 'true'])
  1535         self._gitcommand(['config', 'core.bare', 'true'])
  1536         for f in os.listdir(self._abspath):
  1536         for f in os.listdir(self._abspath):
  1537             if f == '.git':
  1537             if f == '.git':
  1538                 continue
  1538                 continue
  1539             path = os.path.join(self._abspath, f)
  1539             path = os.path.join(self._abspath, f)