Remove SVN support from checkout.py

R=hinoka@chromium.org
BUG=475320

Review-Url: https://codereview.chromium.org/2398603003
changes/96/408096/1
agable 9 years ago committed by Commit bot
parent 1a8439a7d9
commit c972d189fa

@ -265,8 +265,6 @@ def main():
scm_type = scm.determine_scm(full_dir)
if scm_type == 'git':
scm_obj = checkout.GitCheckout(full_dir, None, None, None, None)
elif scm_type == None:
scm_obj = checkout.RawCheckout(full_dir, None, None)
else:
parser.error('Couldn\'t determine the scm')

@ -4,7 +4,7 @@
# found in the LICENSE file.
"""Manages a project checkout.
Includes support for svn, git-svn and git.
Includes support only for git.
"""
import fnmatch
@ -161,403 +161,6 @@ class CheckoutBase(object):
raise NotImplementedError()
class RawCheckout(CheckoutBase):
"""Used to apply a patch locally without any intent to commit it.
To be used by the try server.
"""
def prepare(self, revision):
"""Stubbed out."""
pass
def apply_patch(self, patches, post_processors=None, verbose=False):
"""Ignores svn properties."""
post_processors = post_processors or self.post_processors or []
for p in patches:
stdout = []
try:
filepath = os.path.join(self.project_path, p.filename)
if p.is_delete:
os.remove(filepath)
assert(not os.path.exists(filepath))
stdout.append('Deleted.')
else:
dirname = os.path.dirname(p.filename)
full_dir = os.path.join(self.project_path, dirname)
if dirname and not os.path.isdir(full_dir):
os.makedirs(full_dir)
stdout.append('Created missing directory %s.' % dirname)
if p.is_binary:
content = p.get()
with open(filepath, 'wb') as f:
f.write(content)
stdout.append('Added binary file %d bytes.' % len(content))
else:
if p.source_filename:
if not p.is_new:
raise PatchApplicationFailed(
p,
'File has a source filename specified but is not new')
# Copy the file first.
if os.path.isfile(filepath):
raise PatchApplicationFailed(
p, 'File exist but was about to be overwriten')
shutil.copy2(
os.path.join(self.project_path, p.source_filename), filepath)
stdout.append('Copied %s -> %s' % (p.source_filename, p.filename))
if p.diff_hunks:
cmd = ['patch', '-u', '--binary', '-p%s' % p.patchlevel]
if verbose:
cmd.append('--verbose')
env = os.environ.copy()
env['TMPDIR'] = tempfile.mkdtemp(prefix='crpatch')
try:
stdout.append(
subprocess2.check_output(
cmd,
stdin=p.get(False),
stderr=subprocess2.STDOUT,
cwd=self.project_path,
timeout=GLOBAL_TIMEOUT,
env=env))
finally:
shutil.rmtree(env['TMPDIR'])
elif p.is_new and not os.path.exists(filepath):
# There is only a header. Just create the file.
open(filepath, 'w').close()
stdout.append('Created an empty file.')
for post in post_processors:
post(self, p)
if verbose:
print p.filename
print align_stdout(stdout)
except OSError, e:
raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e))
except subprocess.CalledProcessError, e:
raise PatchApplicationFailed(
p,
'While running %s;\n%s%s' % (
' '.join(e.cmd),
align_stdout(stdout),
align_stdout([getattr(e, 'stdout', '')])))
def commit(self, commit_message, user):
"""Stubbed out."""
raise NotImplementedError('RawCheckout can\'t commit')
def revisions(self, _rev1, _rev2):
return None
class SvnConfig(object):
"""Parses a svn configuration file."""
def __init__(self, svn_config_dir=None):
super(SvnConfig, self).__init__()
self.svn_config_dir = svn_config_dir
self.default = not bool(self.svn_config_dir)
if not self.svn_config_dir:
if sys.platform == 'win32':
self.svn_config_dir = os.path.join(os.environ['APPDATA'], 'Subversion')
else:
self.svn_config_dir = os.path.expanduser(
os.path.join('~', '.subversion'))
svn_config_file = os.path.join(self.svn_config_dir, 'config')
parser = configparser.SafeConfigParser()
if os.path.isfile(svn_config_file):
parser.read(svn_config_file)
else:
parser.add_section('auto-props')
self.auto_props = dict(parser.items('auto-props'))
class SvnMixIn(object):
"""MixIn class to add svn commands common to both svn and git-svn clients."""
# These members need to be set by the subclass.
commit_user = None
commit_pwd = None
svn_url = None
project_path = None
# Override at class level when necessary. If used, --non-interactive is
# implied.
svn_config = SvnConfig()
# Set to True when non-interactivity is necessary but a custom subversion
# configuration directory is not necessary.
non_interactive = False
def _add_svn_flags(self, args, non_interactive, credentials=True):
args = ['svn'] + args
if not self.svn_config.default:
args.extend(['--config-dir', self.svn_config.svn_config_dir])
if not self.svn_config.default or self.non_interactive or non_interactive:
args.append('--non-interactive')
if credentials:
if self.commit_user:
args.extend(['--username', self.commit_user])
if self.commit_pwd:
args.extend(['--password', self.commit_pwd])
return args
def _check_call_svn(self, args, **kwargs):
"""Runs svn and throws an exception if the command failed."""
kwargs.setdefault('cwd', self.project_path)
kwargs.setdefault('stdout', self.VOID)
kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
return subprocess2.check_call_out(
self._add_svn_flags(args, False), **kwargs)
def _check_output_svn(self, args, credentials=True, **kwargs):
"""Runs svn and throws an exception if the command failed.
Returns the output.
"""
kwargs.setdefault('cwd', self.project_path)
return subprocess2.check_output(
self._add_svn_flags(args, True, credentials),
stderr=subprocess2.STDOUT,
timeout=GLOBAL_TIMEOUT,
**kwargs)
@staticmethod
def _parse_svn_info(output, key):
"""Returns value for key from svn info output.
Case insensitive.
"""
values = {}
key = key.lower()
for line in output.splitlines(False):
if not line:
continue
k, v = line.split(':', 1)
k = k.strip().lower()
v = v.strip()
assert not k in values
values[k] = v
return values.get(key, None)
class SvnCheckout(CheckoutBase, SvnMixIn):
"""Manages a subversion checkout."""
def __init__(self, root_dir, project_name, commit_user, commit_pwd, svn_url,
post_processors=None):
CheckoutBase.__init__(self, root_dir, project_name, post_processors)
SvnMixIn.__init__(self)
self.commit_user = commit_user
self.commit_pwd = commit_pwd
self.svn_url = svn_url
assert bool(self.commit_user) >= bool(self.commit_pwd)
def prepare(self, revision):
# Will checkout if the directory is not present.
assert self.svn_url
if not os.path.isdir(self.project_path):
logging.info('Checking out %s in %s' %
(self.project_name, self.project_path))
return self._revert(revision)
def apply_patch(self, patches, post_processors=None, verbose=False):
post_processors = post_processors or self.post_processors or []
for p in patches:
stdout = []
try:
filepath = os.path.join(self.project_path, p.filename)
# It is important to use credentials=False otherwise credentials could
# leak in the error message. Credentials are not necessary here for the
# following commands anyway.
if p.is_delete:
stdout.append(self._check_output_svn(
['delete', p.filename, '--force'], credentials=False))
assert(not os.path.exists(filepath))
stdout.append('Deleted.')
else:
# svn add while creating directories otherwise svn add on the
# contained files will silently fail.
# First, find the root directory that exists.
dirname = os.path.dirname(p.filename)
dirs_to_create = []
while (dirname and
not os.path.isdir(os.path.join(self.project_path, dirname))):
dirs_to_create.append(dirname)
dirname = os.path.dirname(dirname)
for dir_to_create in reversed(dirs_to_create):
os.mkdir(os.path.join(self.project_path, dir_to_create))
stdout.append(
self._check_output_svn(
['add', dir_to_create, '--force'], credentials=False))
stdout.append('Created missing directory %s.' % dir_to_create)
if p.is_binary:
content = p.get()
with open(filepath, 'wb') as f:
f.write(content)
stdout.append('Added binary file %d bytes.' % len(content))
else:
if p.source_filename:
if not p.is_new:
raise PatchApplicationFailed(
p,
'File has a source filename specified but is not new')
# Copy the file first.
if os.path.isfile(filepath):
raise PatchApplicationFailed(
p, 'File exist but was about to be overwriten')
stdout.append(
self._check_output_svn(
['copy', p.source_filename, p.filename]))
stdout.append('Copied %s -> %s' % (p.source_filename, p.filename))
if p.diff_hunks:
cmd = [
'patch',
'-p%s' % p.patchlevel,
'--forward',
'--force',
'--no-backup-if-mismatch',
]
env = os.environ.copy()
env['TMPDIR'] = tempfile.mkdtemp(prefix='crpatch')
try:
stdout.append(
subprocess2.check_output(
cmd,
stdin=p.get(False),
cwd=self.project_path,
timeout=GLOBAL_TIMEOUT,
env=env))
finally:
shutil.rmtree(env['TMPDIR'])
elif p.is_new and not os.path.exists(filepath):
# There is only a header. Just create the file if it doesn't
# exist.
open(filepath, 'w').close()
stdout.append('Created an empty file.')
if p.is_new and not p.source_filename:
# Do not run it if p.source_filename is defined, since svn copy was
# using above.
stdout.append(
self._check_output_svn(
['add', p.filename, '--force'], credentials=False))
for name, value in p.svn_properties:
if value is None:
stdout.append(
self._check_output_svn(
['propdel', '--quiet', name, p.filename],
credentials=False))
stdout.append('Property %s deleted.' % name)
else:
stdout.append(
self._check_output_svn(
['propset', name, value, p.filename], credentials=False))
stdout.append('Property %s=%s' % (name, value))
for prop, values in self.svn_config.auto_props.iteritems():
if fnmatch.fnmatch(p.filename, prop):
for value in values.split(';'):
if '=' not in value:
params = [value, '.']
else:
params = value.split('=', 1)
if params[1] == '*':
# Works around crbug.com/150960 on Windows.
params[1] = '.'
stdout.append(
self._check_output_svn(
['propset'] + params + [p.filename], credentials=False))
stdout.append('Property (auto) %s' % '='.join(params))
for post in post_processors:
post(self, p)
if verbose:
print p.filename
print align_stdout(stdout)
except OSError, e:
raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e))
except subprocess.CalledProcessError, e:
raise PatchApplicationFailed(
p,
'While running %s;\n%s%s' % (
' '.join(e.cmd),
align_stdout(stdout),
align_stdout([getattr(e, 'stdout', '')])))
def commit(self, commit_message, user):
logging.info('Committing patch for %s' % user)
assert self.commit_user
assert isinstance(commit_message, unicode)
handle, commit_filename = tempfile.mkstemp(text=True)
try:
# Shouldn't assume default encoding is UTF-8. But really, if you are using
# anything else, you are living in another world.
os.write(handle, commit_message.encode('utf-8'))
os.close(handle)
# When committing, svn won't update the Revision metadata of the checkout,
# so if svn commit returns "Committed revision 3.", svn info will still
# return "Revision: 2". Since running svn update right after svn commit
# creates a race condition with other committers, this code _must_ parse
# the output of svn commit and use a regexp to grab the revision number.
# Note that "Committed revision N." is localized but subprocess2 forces
# LANGUAGE=en.
args = ['commit', '--file', commit_filename]
# realauthor is parsed by a server-side hook.
if user and user != self.commit_user:
args.extend(['--with-revprop', 'realauthor=%s' % user])
out = self._check_output_svn(args)
finally:
os.remove(commit_filename)
lines = filter(None, out.splitlines())
match = re.match(r'^Committed revision (\d+).$', lines[-1])
if not match:
raise PatchApplicationFailed(
None,
'Couldn\'t make sense out of svn commit message:\n' + out)
return int(match.group(1))
def _revert(self, revision):
"""Reverts local modifications or checks out if the directory is not
present. Use depot_tools's functionality to do this.
"""
flags = ['--ignore-externals']
if revision:
flags.extend(['--revision', str(revision)])
if os.path.isdir(self.project_path):
# This may remove any part (or all) of the checkout.
scm.SVN.Revert(self.project_path, no_ignore=True)
if os.path.isdir(self.project_path):
# Revive files that were deleted in scm.SVN.Revert().
self._check_call_svn(['update', '--force'] + flags,
timeout=FETCH_TIMEOUT)
else:
logging.info(
'Directory %s is not present, checking it out.' % self.project_path)
self._check_call_svn(
['checkout', self.svn_url, self.project_path] + flags, cwd=None,
timeout=FETCH_TIMEOUT)
return self._get_revision()
def _get_revision(self):
out = self._check_output_svn(['info', '.'])
revision = int(self._parse_svn_info(out, 'revision'))
if revision != self._last_seen_revision:
logging.info('Updated to revision %d' % revision)
self._last_seen_revision = revision
return revision
def revisions(self, rev1, rev2):
"""Returns the number of actual commits, not just the difference between
numbers.
"""
rev2 = rev2 or 'HEAD'
# Revision range is inclusive and ordering doesn't matter, they'll appear in
# the order specified.
try:
out = self._check_output_svn(
['log', '-q', self.svn_url, '-r', '%s:%s' % (rev1, rev2)])
except subprocess.CalledProcessError:
return None
# Ignore the '----' lines.
return len([l for l in out.splitlines() if l.startswith('r')]) - 1
class GitCheckout(CheckoutBase):
"""Manages a git checkout."""
def __init__(self, root_dir, project_name, remote_branch, git_url,
@ -639,8 +242,6 @@ class GitCheckout(CheckoutBase):
"""Applies a patch on 'working_branch' and switches to it.
The changes remain staged on the current branch.
Ignores svn properties and raise an exception on unexpected ones.
"""
post_processors = post_processors or self.post_processors or []
# It this throws, the checkout is corrupted. Maybe worth deleting it and
@ -686,19 +287,6 @@ class GitCheckout(CheckoutBase):
if verbose:
cmd.append('--verbose')
stdout.append(self._check_output_git(cmd, stdin=p.get(True)))
for key, value in p.svn_properties:
# Ignore some known auto-props flags through .subversion/config,
# bails out on the other ones.
# TODO(maruel): Read ~/.subversion/config and detect the rules that
# applies here to figure out if the property will be correctly
# handled.
stdout.append('Property %s=%s' % (key, value))
if not key in (
'svn:eol-style', 'svn:executable', 'svn:mime-type'):
raise patch.UnsupportedPatchFormat(
p.filename,
'Cannot apply svn property %s to file %s.' % (
key, p.filename))
for post in post_processors:
post(self, p)
if verbose:

Loading…
Cancel
Save