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) |