Commit 4b5b1779 authored by tony@chromium.org's avatar tony@chromium.org

Add the ability to check out a single file from a repo.

The syntax is:
deps {
  'path': File('http://svn..../path/file@42')
}

This will checkout a single file and use scm.update to
keep it up to date.

See https://bugs.webkit.org/show_bug.cgi?id=36578#c7 for a
description of why I want to add this.

Review URL: http://codereview.chromium.org/1356005

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@43911 0039d316-1c4b-4281-b951-d872f2087c98
parent 5e9f2ed7
......@@ -429,6 +429,29 @@ class GClient(object):
def __str__(self):
return 'From("%s")' % self.module_name
class FileImpl:
"""Used to implement the File('') syntax which lets you sync a single file
from an SVN repo."""
def __init__(self, file_location):
self.file_location = file_location
def __str__(self):
return 'File("%s")' % self.file_location
def GetPath(self):
return os.path.split(self.file_location)[0]
def GetFilename(self):
rev_tokens = self.file_location.split('@')
return os.path.split(rev_tokens[0])[1]
def GetRevision(self):
rev_tokens = self.file_location.split('@')
if len(rev_tokens) > 1:
return rev_tokens[1]
return None
class _VarImpl:
def __init__(self, custom_vars, local_scope):
self._custom_vars = custom_vars
......@@ -461,7 +484,12 @@ class GClient(object):
# Eval the content
local_scope = {}
var = self._VarImpl(custom_vars, local_scope)
global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
global_scope = {
"File": self.FileImpl,
"From": self.FromImpl,
"Var": var.Lookup,
"deps_os": {},
}
exec(solution_deps_content, global_scope, local_scope)
deps = local_scope.get("deps", {})
......@@ -560,14 +588,14 @@ class GClient(object):
#
# If multiple solutions all have the same From reference, then we
# should only add one to our list of dependencies.
if type(url) != str:
if isinstance(url, self.FromImpl):
if url.module_name in solution_urls:
# Already parsed.
continue
if d in deps and type(deps[d]) != str:
if url.module_name == deps[d].module_name:
continue
else:
elif isinstance(url, str):
parsed_url = urlparse.urlparse(url)
scheme = parsed_url[0]
if not scheme:
......@@ -728,12 +756,20 @@ class GClient(object):
scm = gclient_scm.CreateSCM(url, self._root_dir, d)
scm.RunCommand(command, self._options, args, file_list)
self._options.revision = None
elif isinstance(deps[d], self.FileImpl):
file = deps[d]
self._options.revision = file.GetRevision()
if run_scm:
scm = gclient_scm.CreateSCM(file.GetPath(), self._root_dir, d)
scm.RunCommand("updatesingle", self._options,
args + [file.GetFilename()], file_list)
if command == 'update' and not self._options.verbose:
pm.end()
# Second pass for inherited deps (via the From keyword)
for d in deps_to_process:
if type(deps[d]) != str:
if isinstance(deps[d], self.FromImpl):
filename = os.path.join(self._root_dir,
deps[d].module_name,
self._options.deps_file)
......@@ -885,7 +921,7 @@ class GClient(object):
# Second pass for inherited deps (via the From keyword)
for d in deps_to_process:
if type(deps[d]) != str:
if isinstance(deps[d], self.FromImpl):
deps_parent_url = entries[deps[d].module_name]
if deps_parent_url.find("@") < 0:
raise gclient_utils.Error("From %s missing revisioned url" %
......
......@@ -97,8 +97,8 @@ class SCMWrapper(object):
if file_list is None:
file_list = []
commands = ['cleanup', 'export', 'update', 'revert', 'revinfo',
'status', 'diff', 'pack', 'runhooks']
commands = ['cleanup', 'export', 'update', 'updatesingle', 'revert',
'revinfo', 'status', 'diff', 'pack', 'runhooks']
if not command in commands:
raise gclient_utils.Error('Unknown command %s' % command)
......@@ -746,6 +746,20 @@ class SVNWrapper(SCMWrapper):
command.extend(['--revision', str(revision)])
scm.SVN.RunAndGetFileList(options, command, self._root_dir, file_list)
def updatesingle(self, options, args, file_list):
checkout_path = os.path.join(self._root_dir, self.relpath)
filename = args.pop()
if not os.path.exists(checkout_path):
# Create an empty checkout and then update the one file we want. Future
# operations will only apply to the one file we checked out.
command = ["checkout", "--depth", "empty", self.url, checkout_path]
scm.SVN.Run(command, self._root_dir)
command = ["update", filename]
scm.SVN.RunAndGetFileList(options, command, checkout_path, file_list)
# After the initial checkout, we can use update as if it were any other
# dep.
self.update(options, args, file_list)
def revert(self, options, args, file_list):
"""Reverts local modifications. Subversion specific.
......
......@@ -60,7 +60,8 @@ class SVNWrapperTestCase(BaseTestCase):
members = [
'FullUrlForRelativeUrl', 'RunCommand',
'cleanup', 'diff', 'export', 'pack', 'relpath', 'revert',
'revinfo', 'runhooks', 'scm_name', 'status', 'update', 'url',
'revinfo', 'runhooks', 'scm_name', 'status', 'update',
'updatesingle', 'url',
]
# If you add a member, be sure to add the relevant test!
......@@ -266,6 +267,61 @@ class SVNWrapperTestCase(BaseTestCase):
relpath=self.relpath)
scm.update(options, (), files_list)
def testUpdateSingleCheckout(self):
options = self.Options(verbose=True)
base_path = gclient_scm.os.path.join(self.root_dir, self.relpath)
file_info = {
'URL': self.url,
'Revision': 42,
}
# When checking out a single file, we issue an svn checkout and svn update.
gclient_scm.os.path.exists(base_path).AndReturn(False)
files_list = self.mox.CreateMockAnything()
gclient_scm.scm.SVN.Run(
['checkout', '--depth', 'empty', self.url, base_path], self.root_dir)
gclient_scm.scm.SVN.RunAndGetFileList(options, ['update', 'DEPS'],
gclient_scm.os.path.join(self.root_dir, self.relpath), files_list)
# Now we fall back on scm.update().
gclient_scm.os.path.exists(gclient_scm.os.path.join(base_path, '.git')
).AndReturn(False)
gclient_scm.os.path.exists(base_path).AndReturn(True)
gclient_scm.scm.SVN.CaptureInfo(
gclient_scm.os.path.join(base_path, "."), '.'
).AndReturn(file_info)
gclient_scm.scm.SVN.CaptureInfo(file_info['URL'], '.').AndReturn(file_info)
print("\n_____ %s at 42" % self.relpath)
self.mox.ReplayAll()
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
relpath=self.relpath)
scm.updatesingle(options, ['DEPS'], files_list)
def testUpdateSingleUpdate(self):
options = self.Options(verbose=True)
base_path = gclient_scm.os.path.join(self.root_dir, self.relpath)
file_info = {
'URL': self.url,
'Revision': 42,
}
gclient_scm.os.path.exists(base_path).AndReturn(True)
# Now we fall back on scm.update().
files_list = self.mox.CreateMockAnything()
gclient_scm.os.path.exists(gclient_scm.os.path.join(base_path, '.git')
).AndReturn(False)
gclient_scm.os.path.exists(base_path).AndReturn(True)
gclient_scm.scm.SVN.CaptureInfo(
gclient_scm.os.path.join(base_path, "."), '.'
).AndReturn(file_info)
gclient_scm.scm.SVN.CaptureInfo(file_info['URL'], '.').AndReturn(file_info)
print("\n_____ %s at 42" % self.relpath)
self.mox.ReplayAll()
scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir,
relpath=self.relpath)
scm.updatesingle(options, ['DEPS'], files_list)
def testUpdateGit(self):
options = self.Options(verbose=True)
file_path = gclient_scm.os.path.join(self.root_dir, self.relpath, '.git')
......
......@@ -213,9 +213,6 @@ class TestDoRunHooks(GenericCommandTestCase):
class TestDoUpdate(GenericCommandTestCase):
def Options(self, verbose=False, *args, **kwargs):
return self.OptionsObject(self, verbose=verbose, *args, **kwargs)
def ReturnValue(self, command, function, return_value):
options = self.Options()
gclient.GClient.LoadCurrentConfig(options).AndReturn(gclient.GClient)
......@@ -302,7 +299,7 @@ class TestDoRevert(GenericCommandTestCase):
class GClientClassTestCase(GclientTestCase):
def testDir(self):
members = [
'ConfigContent', 'FromImpl', 'GetVar', 'LoadCurrentConfig',
'ConfigContent', 'FileImpl', 'FromImpl', 'GetVar', 'LoadCurrentConfig',
'RunOnDeps', 'SaveConfig', 'SetConfig', 'SetDefaultConfig',
'supported_commands', 'PrintRevInfo',
]
......@@ -1033,6 +1030,48 @@ deps = {
# TODO(maruel): Test me!
pass
def testFileImpl(self):
# Fake .gclient file.
name = "testFileImpl"
gclient_config = (
"solutions = [ { 'name': '%s',"
"'url': '%s', } ]" % (name, self.url)
)
# Fake DEPS file.
target = "chromium_deps"
deps_content = (
"deps = {"
" '%s': File('%s/DEPS') }" % (target, self.url)
)
gclient.gclient_scm.CreateSCM(self.url, self.root_dir, name).AndReturn(
gclient.gclient_scm.CreateSCM)
options = self.Options()
gclient.gclient_scm.CreateSCM.RunCommand('update', options, self.args, [])
gclient.gclient_utils.FileRead(
gclient.os.path.join(self.root_dir, name, options.deps_file)
).AndReturn(deps_content)
gclient.os.path.exists(
gclient.os.path.join(self.root_dir, name, '.git')
).AndReturn(False)
gclient.os.path.exists(
gclient.os.path.join(self.root_dir, options.entries_filename)
).AndReturn(False)
# This is where gclient tries to do the initial checkout.
gclient.gclient_scm.CreateSCM(self.url, self.root_dir, target).AndReturn(
gclient.gclient_scm.CreateSCM)
gclient.gclient_scm.CreateSCM.RunCommand('updatesingle', options,
self.args + ["DEPS"], [])
gclient.gclient_utils.FileWrite(
gclient.os.path.join(self.root_dir, options.entries_filename),
"entries = \\\n{'%s': '%s'}\n" % (name, self.url))
self.mox.ReplayAll()
client = self._gclient_gclient(self.root_dir, options)
client.SetConfig(gclient_config)
client.RunOnDeps('update', self.args)
def test_PrintRevInfo(self):
# TODO(aharper): no test yet for revinfo, lock it down once we've verified
# implementation for Pulse plugin
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment