blob: 4a59000092437c12580bd8ac838cbdaa5cb62ed6 [file] [log] [blame]
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +00001# coding=utf8
maruel@chromium.org9799a072012-01-11 00:26:25 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Manages a project checkout.
6
agablec972d182016-10-24 16:59:13 -07007Includes support only for git.
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +00008"""
9
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +000010import fnmatch
11import logging
12import os
13import re
maruel@chromium.org5e975632011-09-29 18:07:06 +000014import shutil
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +000015import subprocess
16import sys
17import tempfile
18
vapier9f343712016-06-22 07:13:20 -070019# The configparser module was renamed in Python 3.
20try:
21 import configparser
22except ImportError:
23 import ConfigParser as configparser
24
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +000025import patch
26import scm
27import subprocess2
28
29
csharp@chromium.org9af0a112013-03-20 20:21:35 +000030if sys.platform in ('cygwin', 'win32'):
31 # Disable timeouts on Windows since we can't have shells with timeouts.
32 GLOBAL_TIMEOUT = None
33 FETCH_TIMEOUT = None
34else:
35 # Default timeout of 15 minutes.
36 GLOBAL_TIMEOUT = 15*60
37 # Use a larger timeout for checkout since it can be a genuinely slower
38 # operation.
39 FETCH_TIMEOUT = 30*60
40
41
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +000042def get_code_review_setting(path, key,
43 codereview_settings_file='codereview.settings'):
44 """Parses codereview.settings and return the value for the key if present.
45
46 Don't cache the values in case the file is changed."""
47 # TODO(maruel): Do not duplicate code.
48 settings = {}
49 try:
50 settings_file = open(os.path.join(path, codereview_settings_file), 'r')
51 try:
52 for line in settings_file.readlines():
53 if not line or line.startswith('#'):
54 continue
55 if not ':' in line:
56 # Invalid file.
57 return None
58 k, v = line.split(':', 1)
59 settings[k.strip()] = v.strip()
60 finally:
61 settings_file.close()
maruel@chromium.org004fb712011-06-21 20:02:16 +000062 except IOError:
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +000063 return None
64 return settings.get(key, None)
65
66
maruel@chromium.org4dd9f722012-10-01 16:23:03 +000067def align_stdout(stdout):
68 """Returns the aligned output of multiple stdouts."""
69 output = ''
70 for item in stdout:
71 item = item.strip()
72 if not item:
73 continue
74 output += ''.join(' %s\n' % line for line in item.splitlines())
75 return output
76
77
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +000078class PatchApplicationFailed(Exception):
79 """Patch failed to be applied."""
skobes2f3f1372016-10-25 08:08:27 -070080 def __init__(self, errors, verbose):
81 super(PatchApplicationFailed, self).__init__(errors, verbose)
82 self.errors = errors
83 self.verbose = verbose
maruel@chromium.org34f68552012-05-09 19:18:36 +000084
85 def __str__(self):
86 out = []
skobes2f3f1372016-10-25 08:08:27 -070087 for e in self.errors:
88 p, status = e
89 if p and p.filename:
90 out.append('Failed to apply patch for %s:' % p.filename)
91 if status:
92 out.append(status)
93 if p and self.verbose:
94 out.append('Patch: %s' % p.dump())
maruel@chromium.org34f68552012-05-09 19:18:36 +000095 return '\n'.join(out)
96
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +000097
98class CheckoutBase(object):
99 # Set to None to have verbose output.
100 VOID = subprocess2.VOID
101
maruel@chromium.org6ed8b502011-06-12 01:05:35 +0000102 def __init__(self, root_dir, project_name, post_processors):
103 """
104 Args:
105 post_processor: list of lambda(checkout, patches) to call on each of the
106 modified files.
107 """
maruel@chromium.orga5129fb2011-06-20 18:36:25 +0000108 super(CheckoutBase, self).__init__()
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000109 self.root_dir = root_dir
110 self.project_name = project_name
maruel@chromium.org3cdb7f32011-05-05 16:37:24 +0000111 if self.project_name is None:
112 self.project_path = self.root_dir
113 else:
114 self.project_path = os.path.join(self.root_dir, self.project_name)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000115 # Only used for logging purposes.
116 self._last_seen_revision = None
maruel@chromium.orga5129fb2011-06-20 18:36:25 +0000117 self.post_processors = post_processors
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000118 assert self.root_dir
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000119 assert self.project_path
maruel@chromium.org0aca0f92012-10-01 16:39:45 +0000120 assert os.path.isabs(self.project_path)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000121
122 def get_settings(self, key):
123 return get_code_review_setting(self.project_path, key)
124
maruel@chromium.org51919772011-06-12 01:27:42 +0000125 def prepare(self, revision):
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000126 """Checks out a clean copy of the tree and removes any local modification.
127
128 This function shouldn't throw unless the remote repository is inaccessible,
129 there is no free disk space or hard issues like that.
maruel@chromium.org51919772011-06-12 01:27:42 +0000130
131 Args:
132 revision: The revision it should sync to, SCM specific.
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000133 """
134 raise NotImplementedError()
135
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000136 def apply_patch(self, patches, post_processors=None, verbose=False):
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000137 """Applies a patch and returns the list of modified files.
138
139 This function should throw patch.UnsupportedPatchFormat or
140 PatchApplicationFailed when relevant.
maruel@chromium.org8a1396c2011-04-22 00:14:24 +0000141
142 Args:
143 patches: patch.PatchSet object.
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000144 """
145 raise NotImplementedError()
146
147 def commit(self, commit_message, user):
148 """Commits the patch upstream, while impersonating 'user'."""
149 raise NotImplementedError()
150
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000151 def revisions(self, rev1, rev2):
152 """Returns the count of revisions from rev1 to rev2, e.g. len(]rev1, rev2]).
153
154 If rev2 is None, it means 'HEAD'.
155
156 Returns None if there is no link between the two.
157 """
158 raise NotImplementedError()
159
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000160
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000161class GitCheckout(CheckoutBase):
162 """Manages a git checkout."""
163 def __init__(self, root_dir, project_name, remote_branch, git_url,
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000164 commit_user, post_processors=None):
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000165 super(GitCheckout, self).__init__(root_dir, project_name, post_processors)
166 self.git_url = git_url
167 self.commit_user = commit_user
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000168 self.remote_branch = remote_branch
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000169 # The working branch where patches will be applied. It will track the
170 # remote branch.
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000171 self.working_branch = 'working_branch'
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000172 # There is no reason to not hardcode origin.
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000173 self.remote = 'origin'
174 # There is no reason to not hardcode master.
175 self.master_branch = 'master'
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000176
maruel@chromium.org51919772011-06-12 01:27:42 +0000177 def prepare(self, revision):
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000178 """Resets the git repository in a clean state.
179
180 Checks it out if not present and deletes the working branch.
181 """
agable@chromium.org7dc11442014-03-12 22:37:32 +0000182 assert self.remote_branch
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000183 assert self.git_url
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000184
185 if not os.path.isdir(self.project_path):
186 # Clone the repo if the directory is not present.
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000187 logging.info(
188 'Checking out %s in %s', self.project_name, self.project_path)
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000189 self._check_call_git(
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000190 ['clone', self.git_url, '-b', self.remote_branch, self.project_path],
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000191 cwd=None, timeout=FETCH_TIMEOUT)
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000192 else:
193 # Throw away all uncommitted changes in the existing checkout.
194 self._check_call_git(['checkout', self.remote_branch])
195 self._check_call_git(
196 ['reset', '--hard', '--quiet',
197 '%s/%s' % (self.remote, self.remote_branch)])
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000198
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000199 if revision:
200 try:
201 # Look if the commit hash already exist. If so, we can skip a
202 # 'git fetch' call.
halton.huo@intel.com323ec372014-06-17 01:50:37 +0000203 revision = self._check_output_git(['rev-parse', revision]).rstrip()
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000204 except subprocess.CalledProcessError:
205 self._check_call_git(
206 ['fetch', self.remote, self.remote_branch, '--quiet'])
halton.huo@intel.com323ec372014-06-17 01:50:37 +0000207 revision = self._check_output_git(['rev-parse', revision]).rstrip()
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000208 self._check_call_git(['checkout', '--force', '--quiet', revision])
209 else:
210 branches, active = self._branches()
211 if active != self.master_branch:
212 self._check_call_git(
213 ['checkout', '--force', '--quiet', self.master_branch])
214 self._sync_remote_branch()
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000215
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000216 if self.working_branch in branches:
217 self._call_git(['branch', '-D', self.working_branch])
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000218 return self._get_head_commit_hash()
219
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000220 def _sync_remote_branch(self):
221 """Syncs the remote branch."""
222 # We do a 'git pull origin master:refs/remotes/origin/master' instead of
hinoka@google.comdabbea22014-04-21 23:58:11 +0000223 # 'git pull origin master' because from the manpage for git-pull:
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000224 # A parameter <ref> without a colon is equivalent to <ref>: when
225 # pulling/fetching, so it merges <ref> into the current branch without
226 # storing the remote branch anywhere locally.
227 remote_tracked_path = 'refs/remotes/%s/%s' % (
228 self.remote, self.remote_branch)
229 self._check_call_git(
230 ['pull', self.remote,
231 '%s:%s' % (self.remote_branch, remote_tracked_path),
232 '--quiet'])
233
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000234 def _get_head_commit_hash(self):
rmistry@google.com11145db2013-10-03 12:43:40 +0000235 """Gets the current revision (in unicode) from the local branch."""
236 return unicode(self._check_output_git(['rev-parse', 'HEAD']).strip())
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000237
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000238 def apply_patch(self, patches, post_processors=None, verbose=False):
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000239 """Applies a patch on 'working_branch' and switches to it.
maruel@chromium.org8a1396c2011-04-22 00:14:24 +0000240
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000241 The changes remain staged on the current branch.
maruel@chromium.org8a1396c2011-04-22 00:14:24 +0000242 """
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000243 post_processors = post_processors or self.post_processors or []
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000244 # It this throws, the checkout is corrupted. Maybe worth deleting it and
245 # trying again?
maruel@chromium.org3cdb7f32011-05-05 16:37:24 +0000246 if self.remote_branch:
247 self._check_call_git(
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000248 ['checkout', '-b', self.working_branch, '-t', self.remote_branch,
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000249 '--quiet'])
250
skobes2f3f1372016-10-25 08:08:27 -0700251 errors = []
maruel@chromium.org5e975632011-09-29 18:07:06 +0000252 for index, p in enumerate(patches):
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000253 stdout = []
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000254 try:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000255 filepath = os.path.join(self.project_path, p.filename)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000256 if p.is_delete:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000257 if (not os.path.exists(filepath) and
maruel@chromium.org5e975632011-09-29 18:07:06 +0000258 any(p1.source_filename == p.filename for p1 in patches[0:index])):
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000259 # The file was already deleted if a prior patch with file rename
260 # was already processed because 'git apply' did it for us.
maruel@chromium.org5e975632011-09-29 18:07:06 +0000261 pass
262 else:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000263 stdout.append(self._check_output_git(['rm', p.filename]))
phajdan.jr@chromium.orgd9eb69e2014-06-05 20:33:37 +0000264 assert(not os.path.exists(filepath))
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000265 stdout.append('Deleted.')
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000266 else:
267 dirname = os.path.dirname(p.filename)
268 full_dir = os.path.join(self.project_path, dirname)
269 if dirname and not os.path.isdir(full_dir):
270 os.makedirs(full_dir)
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000271 stdout.append('Created missing directory %s.' % dirname)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000272 if p.is_binary:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000273 content = p.get()
274 with open(filepath, 'wb') as f:
275 f.write(content)
276 stdout.append('Added binary file %d bytes' % len(content))
277 cmd = ['add', p.filename]
278 if verbose:
279 cmd.append('--verbose')
280 stdout.append(self._check_output_git(cmd))
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000281 else:
maruel@chromium.org58fe6622011-06-03 20:59:27 +0000282 # No need to do anything special with p.is_new or if not
283 # p.diff_hunks. git apply manages all that already.
primiano@chromium.org49dfcde2014-09-23 08:14:39 +0000284 cmd = ['apply', '--index', '-3', '-p%s' % p.patchlevel]
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000285 if verbose:
286 cmd.append('--verbose')
287 stdout.append(self._check_output_git(cmd, stdin=p.get(True)))
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000288 for post in post_processors:
maruel@chromium.org8a1396c2011-04-22 00:14:24 +0000289 post(self, p)
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000290 if verbose:
291 print p.filename
292 print align_stdout(stdout)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000293 except OSError, e:
skobes2f3f1372016-10-25 08:08:27 -0700294 errors.append((p, '%s%s' % (align_stdout(stdout), e)))
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000295 except subprocess.CalledProcessError, e:
skobes2f3f1372016-10-25 08:08:27 -0700296 errors.append((p,
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000297 'While running %s;\n%s%s' % (
298 ' '.join(e.cmd),
299 align_stdout(stdout),
skobes2f3f1372016-10-25 08:08:27 -0700300 align_stdout([getattr(e, 'stdout', '')]))))
301 if errors:
302 raise PatchApplicationFailed(errors, verbose)
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000303 found_files = self._check_output_git(
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000304 ['diff', '--ignore-submodules',
305 '--name-only', '--staged']).splitlines(False)
hinoka@chromium.orgdc6a1d02014-05-10 04:42:48 +0000306 if sorted(patches.filenames) != sorted(found_files):
307 extra_files = sorted(set(found_files) - set(patches.filenames))
308 unpatched_files = sorted(set(patches.filenames) - set(found_files))
309 if extra_files:
310 print 'Found extra files: %r' % (extra_files,)
311 if unpatched_files:
312 print 'Found unpatched files: %r' % (unpatched_files,)
313
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000314
315 def commit(self, commit_message, user):
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000316 """Commits, updates the commit message and pushes."""
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000317 # TODO(hinoka): CQ no longer uses this, I think its deprecated.
318 # Delete this.
rmistry@google.combb050f62013-10-03 16:53:54 +0000319 assert self.commit_user
maruel@chromium.org1bf50972011-05-05 19:57:21 +0000320 assert isinstance(commit_message, unicode)
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000321 current_branch = self._check_output_git(
322 ['rev-parse', '--abbrev-ref', 'HEAD']).strip()
323 assert current_branch == self.working_branch
hinoka@google.comdabbea22014-04-21 23:58:11 +0000324
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000325 commit_cmd = ['commit', '-m', commit_message]
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000326 if user and user != self.commit_user:
327 # We do not have the first or last name of the user, grab the username
328 # from the email and call it the original author's name.
329 # TODO(rmistry): Do not need the below if user is already in
330 # "Name <email>" format.
331 name = user.split('@')[0]
332 commit_cmd.extend(['--author', '%s <%s>' % (name, user)])
333 self._check_call_git(commit_cmd)
334
335 # Push to the remote repository.
336 self._check_call_git(
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000337 ['push', 'origin', '%s:%s' % (self.working_branch, self.remote_branch),
agable@chromium.org39262282014-03-19 21:07:38 +0000338 '--quiet'])
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000339 # Get the revision after the push.
340 revision = self._get_head_commit_hash()
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000341 # Switch back to the remote_branch and sync it.
342 self._check_call_git(['checkout', self.remote_branch])
343 self._sync_remote_branch()
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000344 # Delete the working branch since we are done with it.
345 self._check_call_git(['branch', '-D', self.working_branch])
346
347 return revision
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000348
349 def _check_call_git(self, args, **kwargs):
350 kwargs.setdefault('cwd', self.project_path)
351 kwargs.setdefault('stdout', self.VOID)
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000352 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
maruel@chromium.org44b21b92012-11-08 19:37:08 +0000353 return subprocess2.check_call_out(['git'] + args, **kwargs)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000354
355 def _call_git(self, args, **kwargs):
356 """Like check_call but doesn't throw on failure."""
357 kwargs.setdefault('cwd', self.project_path)
358 kwargs.setdefault('stdout', self.VOID)
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000359 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
maruel@chromium.org44b21b92012-11-08 19:37:08 +0000360 return subprocess2.call(['git'] + args, **kwargs)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000361
362 def _check_output_git(self, args, **kwargs):
363 kwargs.setdefault('cwd', self.project_path)
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000364 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000365 return subprocess2.check_output(
maruel@chromium.org44b21b92012-11-08 19:37:08 +0000366 ['git'] + args, stderr=subprocess2.STDOUT, **kwargs)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000367
368 def _branches(self):
369 """Returns the list of branches and the active one."""
370 out = self._check_output_git(['branch']).splitlines(False)
371 branches = [l[2:] for l in out]
372 active = None
373 for l in out:
374 if l.startswith('*'):
375 active = l[2:]
376 break
377 return branches, active
378
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000379 def revisions(self, rev1, rev2):
380 """Returns the number of actual commits between both hash."""
381 self._fetch_remote()
382
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000383 rev2 = rev2 or '%s/%s' % (self.remote, self.remote_branch)
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000384 # Revision range is ]rev1, rev2] and ordering matters.
385 try:
386 out = self._check_output_git(
387 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)])
388 except subprocess.CalledProcessError:
389 return None
390 return len(out.splitlines())
391
392 def _fetch_remote(self):
393 """Fetches the remote without rebasing."""
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000394 # git fetch is always verbose even with -q, so redirect its output.
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000395 self._check_output_git(['fetch', self.remote, self.remote_branch],
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000396 timeout=FETCH_TIMEOUT)
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000397
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000398
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000399class ReadOnlyCheckout(object):
400 """Converts a checkout into a read-only one."""
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000401 def __init__(self, checkout, post_processors=None):
maruel@chromium.orga5129fb2011-06-20 18:36:25 +0000402 super(ReadOnlyCheckout, self).__init__()
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000403 self.checkout = checkout
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000404 self.post_processors = (post_processors or []) + (
405 self.checkout.post_processors or [])
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000406
maruel@chromium.org51919772011-06-12 01:27:42 +0000407 def prepare(self, revision):
408 return self.checkout.prepare(revision)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000409
410 def get_settings(self, key):
411 return self.checkout.get_settings(key)
412
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000413 def apply_patch(self, patches, post_processors=None, verbose=False):
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000414 return self.checkout.apply_patch(
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000415 patches, post_processors or self.post_processors, verbose)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000416
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800417 def commit(self, message, user): # pylint: disable=no-self-use
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000418 logging.info('Would have committed for %s with message: %s' % (
419 user, message))
420 return 'FAKE'
421
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000422 def revisions(self, rev1, rev2):
423 return self.checkout.revisions(rev1, rev2)
424
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000425 @property
426 def project_name(self):
427 return self.checkout.project_name
428
429 @property
430 def project_path(self):
431 return self.checkout.project_path