| Index: scm.py
|
| diff --git a/scm.py b/scm.py
|
| index 261cb64917ba5912138dc0bae5154677d8408da0..56e795fcfecbd9f35fbcc9d67f24eed2f6cb9477 100644
|
| --- a/scm.py
|
| +++ b/scm.py
|
| @@ -398,13 +398,13 @@ class SVN(object):
|
| current_version = None
|
|
|
| @staticmethod
|
| - def Capture(args, **kwargs):
|
| + def Capture(args, cwd, **kwargs):
|
| """Always redirect stderr.
|
|
|
| Throws an exception if non-0 is returned.
|
| """
|
| return subprocess2.check_output(
|
| - ['svn'] + args, stderr=subprocess2.PIPE, **kwargs)
|
| + ['svn'] + args, stderr=subprocess2.PIPE, cwd=cwd, **kwargs)
|
|
|
| @staticmethod
|
| def RunAndGetFileList(verbose, args, cwd, file_list, stdout=None):
|
| @@ -514,13 +514,30 @@ class SVN(object):
|
| break
|
|
|
| @staticmethod
|
| - def CaptureInfo(cwd):
|
| + def CaptureRemoteInfo(url):
|
| + """Returns a dictionary from the svn info output for the given url.
|
| +
|
| + Throws an exception if svn info fails.
|
| + """
|
| + assert isinstance(url, str)
|
| + return SVN._CaptureInfo([url], None)
|
| +
|
| + @staticmethod
|
| + def CaptureLocalInfo(files, cwd):
|
| + """Returns a dictionary from the svn info output for the given files.
|
| +
|
| + Throws an exception if svn info fails.
|
| + """
|
| + assert isinstance(files, (list, tuple))
|
| + return SVN._CaptureInfo(files, cwd)
|
| +
|
| + @staticmethod
|
| + def _CaptureInfo(files, cwd):
|
| """Returns a dictionary from the svn info output for the given file.
|
|
|
| Throws an exception if svn info fails."""
|
| result = {}
|
| - output = SVN.Capture(['info', '--xml', cwd])
|
| - info = ElementTree.XML(output)
|
| + info = ElementTree.XML(SVN.Capture(['info', '--xml'] + files, cwd))
|
| if info is None:
|
| return result
|
| entry = info.find('entry')
|
| @@ -563,10 +580,10 @@ class SVN(object):
|
| Returns:
|
| Int base revision
|
| """
|
| - return SVN.CaptureInfo(cwd).get('Revision')
|
| + return SVN.CaptureLocalInfo([], cwd).get('Revision')
|
|
|
| @staticmethod
|
| - def CaptureStatus(files):
|
| + def CaptureStatus(files, cwd):
|
| """Returns the svn 1.5 svn status emulated output.
|
|
|
| @files can be a string (one file) or a list of files.
|
| @@ -598,7 +615,7 @@ class SVN(object):
|
| 'replaced': 'R',
|
| 'unversioned': '?',
|
| }
|
| - dom = ElementTree.XML(SVN.Capture(command))
|
| + dom = ElementTree.XML(SVN.Capture(command, cwd))
|
| results = []
|
| if dom is None:
|
| return results
|
| @@ -645,9 +662,10 @@ class SVN(object):
|
| return results
|
|
|
| @staticmethod
|
| - def IsMoved(filename):
|
| + def IsMoved(filename, cwd):
|
| """Determine if a file has been added through svn mv"""
|
| - return SVN.IsMovedInfo(SVN.CaptureInfo(filename))
|
| + assert isinstance(filename, basestring)
|
| + return SVN.IsMovedInfo(SVN.CaptureLocalInfo([filename], cwd))
|
|
|
| @staticmethod
|
| def IsMovedInfo(info):
|
| @@ -657,7 +675,7 @@ class SVN(object):
|
| info.get('Schedule') == 'add')
|
|
|
| @staticmethod
|
| - def GetFileProperty(filename, property_name):
|
| + def GetFileProperty(filename, property_name, cwd):
|
| """Returns the value of an SVN property for the given file.
|
|
|
| Args:
|
| @@ -670,12 +688,12 @@ class SVN(object):
|
| empty string is also returned.
|
| """
|
| try:
|
| - return SVN.Capture(['propget', property_name, filename])
|
| + return SVN.Capture(['propget', property_name, filename], cwd)
|
| except subprocess2.CalledProcessError:
|
| return ''
|
|
|
| @staticmethod
|
| - def DiffItem(filename, full_move=False, revision=None):
|
| + def DiffItem(filename, cwd, full_move, revision):
|
| """Diffs a single file.
|
|
|
| Should be simple, eh? No it isn't.
|
| @@ -693,15 +711,18 @@ class SVN(object):
|
| try:
|
| # Use "svn info" output instead of os.path.isdir because the latter fails
|
| # when the file is deleted.
|
| - return SVN._DiffItemInternal(filename, SVN.CaptureInfo(filename),
|
| - bogus_dir,
|
| - full_move=full_move, revision=revision)
|
| + return SVN._DiffItemInternal(
|
| + filename,
|
| + cwd,
|
| + SVN.CaptureLocalInfo([filename], cwd),
|
| + bogus_dir,
|
| + full_move,
|
| + revision)
|
| finally:
|
| gclient_utils.RemoveDirectory(bogus_dir)
|
|
|
| @staticmethod
|
| - def _DiffItemInternal(filename, info, bogus_dir, full_move=False,
|
| - revision=None):
|
| + def _DiffItemInternal(filename, cwd, info, bogus_dir, full_move, revision):
|
| """Grabs the diff data."""
|
| command = ["diff", "--config-dir", bogus_dir, filename]
|
| if revision:
|
| @@ -737,7 +758,7 @@ class SVN(object):
|
| else:
|
| if info.get("Node Kind") != "directory":
|
| # svn diff on a mv/cp'd file outputs nothing if there was no change.
|
| - data = SVN.Capture(command)
|
| + data = SVN.Capture(command, cwd)
|
| if not data:
|
| # We put in an empty Index entry so upload.py knows about them.
|
| data = "Index: %s\n" % filename.replace(os.sep, '/')
|
| @@ -746,7 +767,7 @@ class SVN(object):
|
| if info.get("Node Kind") != "directory":
|
| # Normal simple case.
|
| try:
|
| - data = SVN.Capture(command)
|
| + data = SVN.Capture(command, cwd)
|
| except subprocess2.CalledProcessError:
|
| if revision:
|
| data = GenFakeDiff(filename)
|
| @@ -756,7 +777,7 @@ class SVN(object):
|
| return data
|
|
|
| @staticmethod
|
| - def GenerateDiff(filenames, root=None, full_move=False, revision=None):
|
| + def GenerateDiff(filenames, cwd, full_move, revision):
|
| """Returns a string containing the diff for the given file list.
|
|
|
| The files in the list should either be absolute paths or relative to the
|
| @@ -765,9 +786,7 @@ class SVN(object):
|
| The diff will always use relative paths.
|
| """
|
| assert isinstance(filenames, (list, tuple))
|
| - previous_cwd = os.getcwd()
|
| - root = root or SVN.GetCheckoutRoot(previous_cwd)
|
| - root = os.path.normcase(os.path.join(root, ''))
|
| + root = os.path.normcase(os.path.join(cwd, ''))
|
| def RelativePath(path, root):
|
| """We must use relative paths."""
|
| if os.path.normcase(path).startswith(root):
|
| @@ -781,11 +800,10 @@ class SVN(object):
|
| # config directory, which gets around these problems.
|
| bogus_dir = tempfile.mkdtemp()
|
| try:
|
| - os.chdir(root)
|
| # Cleanup filenames
|
| filenames = [RelativePath(f, root) for f in filenames]
|
| # Get information about the modified items (files and directories)
|
| - data = dict([(f, SVN.CaptureInfo(f)) for f in filenames])
|
| + data = dict([(f, SVN.CaptureLocalInfo([f], root)) for f in filenames])
|
| diffs = []
|
| if full_move:
|
| # Eliminate modified files inside moved/copied directory.
|
| @@ -805,12 +823,12 @@ class SVN(object):
|
| # for now, the most common case is a head copy,
|
| # so let's just encode that as a straight up cp.
|
| srcurl = info.get('Copied From URL')
|
| - root = info.get('Repository Root')
|
| + file_root = info.get('Repository Root')
|
| rev = int(info.get('Copied From Rev'))
|
| - assert srcurl.startswith(root)
|
| - src = srcurl[len(root)+1:]
|
| + assert srcurl.startswith(file_root)
|
| + src = srcurl[len(file_root)+1:]
|
| try:
|
| - srcinfo = SVN.CaptureInfo(srcurl)
|
| + srcinfo = SVN.CaptureRemoteInfo(srcurl)
|
| except subprocess2.CalledProcessError, e:
|
| if not 'Not a valid URL' in e.stderr:
|
| raise
|
| @@ -818,7 +836,7 @@ class SVN(object):
|
| # revision the file was deleted.
|
| srcinfo = {'Revision': rev}
|
| if (srcinfo.get('Revision') != rev and
|
| - SVN.Capture(['diff', '-r', '%d:head' % rev, srcurl])):
|
| + SVN.Capture(['diff', '-r', '%d:head' % rev, srcurl], cwd)):
|
| metaheaders.append("#$ svn cp -r %d %s %s "
|
| "### WARNING: note non-trunk copy\n" %
|
| (rev, src, filename))
|
| @@ -832,9 +850,8 @@ class SVN(object):
|
| diffs.append("### END SVN COPY METADATA\n")
|
| # Now ready to do the actual diff.
|
| for filename in sorted(data.iterkeys()):
|
| - diffs.append(SVN._DiffItemInternal(filename, data[filename], bogus_dir,
|
| - full_move=full_move,
|
| - revision=revision))
|
| + diffs.append(SVN._DiffItemInternal(
|
| + filename, cwd, data[filename], bogus_dir, full_move, revision))
|
| # Use StringIO since it can be messy when diffing a directory move with
|
| # full_move=True.
|
| buf = cStringIO.StringIO()
|
| @@ -844,14 +861,13 @@ class SVN(object):
|
| buf.close()
|
| return result
|
| finally:
|
| - os.chdir(previous_cwd)
|
| gclient_utils.RemoveDirectory(bogus_dir)
|
|
|
| @staticmethod
|
| - def GetEmail(repo_root):
|
| + def GetEmail(cwd):
|
| """Retrieves the svn account which we assume is an email address."""
|
| try:
|
| - infos = SVN.CaptureInfo(repo_root)
|
| + infos = SVN.CaptureLocalInfo([], cwd)
|
| except subprocess2.CalledProcessError:
|
| return None
|
|
|
| @@ -904,36 +920,36 @@ class SVN(object):
|
| return values
|
|
|
| @staticmethod
|
| - def GetCheckoutRoot(directory):
|
| + def GetCheckoutRoot(cwd):
|
| """Returns the top level directory of the current repository.
|
|
|
| The directory is returned as an absolute path.
|
| """
|
| - directory = os.path.abspath(directory)
|
| + cwd = os.path.abspath(cwd)
|
| try:
|
| - info = SVN.CaptureInfo(directory)
|
| + info = SVN.CaptureLocalInfo([], cwd)
|
| cur_dir_repo_root = info['Repository Root']
|
| url = info['URL']
|
| except subprocess2.CalledProcessError:
|
| return None
|
| while True:
|
| - parent = os.path.dirname(directory)
|
| + parent = os.path.dirname(cwd)
|
| try:
|
| - info = SVN.CaptureInfo(parent)
|
| + info = SVN.CaptureLocalInfo([], parent)
|
| if (info['Repository Root'] != cur_dir_repo_root or
|
| info['URL'] != os.path.dirname(url)):
|
| break
|
| url = info['URL']
|
| except subprocess2.CalledProcessError:
|
| break
|
| - directory = parent
|
| - return GetCasedPath(directory)
|
| + cwd = parent
|
| + return GetCasedPath(cwd)
|
|
|
| @classmethod
|
| def AssertVersion(cls, min_version):
|
| """Asserts svn's version is at least min_version."""
|
| if cls.current_version is None:
|
| - cls.current_version = cls.Capture(['--version']).split()[2]
|
| + cls.current_version = cls.Capture(['--version'], None).split()[2]
|
| current_version_list = map(only_int, cls.current_version.split('.'))
|
| for min_ver in map(int, min_version.split('.')):
|
| ver = current_version_list.pop(0)
|
| @@ -944,16 +960,16 @@ class SVN(object):
|
| return (True, cls.current_version)
|
|
|
| @staticmethod
|
| - def Revert(repo_root, callback=None, ignore_externals=False):
|
| - """Reverts all svn modifications in repo_root, including properties.
|
| + def Revert(cwd, callback=None, ignore_externals=False):
|
| + """Reverts all svn modifications in cwd, including properties.
|
|
|
| Deletes any modified files or directory.
|
|
|
| A "svn update --revision BASE" call is required after to revive deleted
|
| files.
|
| """
|
| - for file_status in SVN.CaptureStatus(repo_root):
|
| - file_path = os.path.join(repo_root, file_status[1])
|
| + for file_status in SVN.CaptureStatus(None, cwd):
|
| + file_path = os.path.join(cwd, file_status[1])
|
| if (ignore_externals and
|
| file_status[0][0] == 'X' and
|
| file_status[0][1:].isspace()):
|
| @@ -985,11 +1001,11 @@ class SVN(object):
|
| not file_status[0][1:].isspace()):
|
| # Added, deleted file requires manual intervention and require calling
|
| # revert, like for properties.
|
| - if not os.path.isdir(repo_root):
|
| + if not os.path.isdir(cwd):
|
| # '.' was deleted. It's not worth continuing.
|
| return
|
| try:
|
| - SVN.Capture(['revert', file_status[1]], cwd=repo_root)
|
| + SVN.Capture(['revert', file_status[1]], cwd=cwd)
|
| except subprocess2.CalledProcessError:
|
| if not os.path.exists(file_path):
|
| continue
|
|
|