blob: 7c497612e16d5e9e6cb4f9807f4972a8c6a78c4b [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."""
maruel@chromium.org34f68552012-05-09 19:18:36 +000080 def __init__(self, p, status):
81 super(PatchApplicationFailed, self).__init__(p, status)
82 self.patch = p
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +000083 self.status = status
84
maruel@chromium.org34f68552012-05-09 19:18:36 +000085 @property
86 def filename(self):
87 if self.patch:
88 return self.patch.filename
89
90 def __str__(self):
91 out = []
92 if self.filename:
93 out.append('Failed to apply patch for %s:' % self.filename)
94 if self.status:
95 out.append(self.status)
maruel@chromium.orgcb5667a2012-10-23 19:42:10 +000096 if self.patch:
97 out.append('Patch: %s' % self.patch.dump())
maruel@chromium.org34f68552012-05-09 19:18:36 +000098 return '\n'.join(out)
99
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000100
101class CheckoutBase(object):
102 # Set to None to have verbose output.
103 VOID = subprocess2.VOID
104
maruel@chromium.org6ed8b502011-06-12 01:05:35 +0000105 def __init__(self, root_dir, project_name, post_processors):
106 """
107 Args:
108 post_processor: list of lambda(checkout, patches) to call on each of the
109 modified files.
110 """
maruel@chromium.orga5129fb2011-06-20 18:36:25 +0000111 super(CheckoutBase, self).__init__()
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000112 self.root_dir = root_dir
113 self.project_name = project_name
maruel@chromium.org3cdb7f32011-05-05 16:37:24 +0000114 if self.project_name is None:
115 self.project_path = self.root_dir
116 else:
117 self.project_path = os.path.join(self.root_dir, self.project_name)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000118 # Only used for logging purposes.
119 self._last_seen_revision = None
maruel@chromium.orga5129fb2011-06-20 18:36:25 +0000120 self.post_processors = post_processors
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000121 assert self.root_dir
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000122 assert self.project_path
maruel@chromium.org0aca0f92012-10-01 16:39:45 +0000123 assert os.path.isabs(self.project_path)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000124
125 def get_settings(self, key):
126 return get_code_review_setting(self.project_path, key)
127
maruel@chromium.org51919772011-06-12 01:27:42 +0000128 def prepare(self, revision):
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000129 """Checks out a clean copy of the tree and removes any local modification.
130
131 This function shouldn't throw unless the remote repository is inaccessible,
132 there is no free disk space or hard issues like that.
maruel@chromium.org51919772011-06-12 01:27:42 +0000133
134 Args:
135 revision: The revision it should sync to, SCM specific.
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000136 """
137 raise NotImplementedError()
138
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000139 def apply_patch(self, patches, post_processors=None, verbose=False):
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000140 """Applies a patch and returns the list of modified files.
141
142 This function should throw patch.UnsupportedPatchFormat or
143 PatchApplicationFailed when relevant.
maruel@chromium.org8a1396c2011-04-22 00:14:24 +0000144
145 Args:
146 patches: patch.PatchSet object.
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000147 """
148 raise NotImplementedError()
149
150 def commit(self, commit_message, user):
151 """Commits the patch upstream, while impersonating 'user'."""
152 raise NotImplementedError()
153
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000154 def revisions(self, rev1, rev2):
155 """Returns the count of revisions from rev1 to rev2, e.g. len(]rev1, rev2]).
156
157 If rev2 is None, it means 'HEAD'.
158
159 Returns None if there is no link between the two.
160 """
161 raise NotImplementedError()
162
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000163
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000164class GitCheckout(CheckoutBase):
165 """Manages a git checkout."""
166 def __init__(self, root_dir, project_name, remote_branch, git_url,
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000167 commit_user, post_processors=None):
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000168 super(GitCheckout, self).__init__(root_dir, project_name, post_processors)
169 self.git_url = git_url
170 self.commit_user = commit_user
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000171 self.remote_branch = remote_branch
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000172 # The working branch where patches will be applied. It will track the
173 # remote branch.
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000174 self.working_branch = 'working_branch'
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000175 # There is no reason to not hardcode origin.
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000176 self.remote = 'origin'
177 # There is no reason to not hardcode master.
178 self.master_branch = 'master'
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000179
maruel@chromium.org51919772011-06-12 01:27:42 +0000180 def prepare(self, revision):
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000181 """Resets the git repository in a clean state.
182
183 Checks it out if not present and deletes the working branch.
184 """
agable@chromium.org7dc11442014-03-12 22:37:32 +0000185 assert self.remote_branch
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000186 assert self.git_url
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000187
188 if not os.path.isdir(self.project_path):
189 # Clone the repo if the directory is not present.
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000190 logging.info(
191 'Checking out %s in %s', self.project_name, self.project_path)
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000192 self._check_call_git(
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000193 ['clone', self.git_url, '-b', self.remote_branch, self.project_path],
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000194 cwd=None, timeout=FETCH_TIMEOUT)
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000195 else:
196 # Throw away all uncommitted changes in the existing checkout.
197 self._check_call_git(['checkout', self.remote_branch])
198 self._check_call_git(
199 ['reset', '--hard', '--quiet',
200 '%s/%s' % (self.remote, self.remote_branch)])
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000201
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000202 if revision:
203 try:
204 # Look if the commit hash already exist. If so, we can skip a
205 # 'git fetch' call.
halton.huo@intel.com323ec372014-06-17 01:50:37 +0000206 revision = self._check_output_git(['rev-parse', revision]).rstrip()
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000207 except subprocess.CalledProcessError:
208 self._check_call_git(
209 ['fetch', self.remote, self.remote_branch, '--quiet'])
halton.huo@intel.com323ec372014-06-17 01:50:37 +0000210 revision = self._check_output_git(['rev-parse', revision]).rstrip()
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000211 self._check_call_git(['checkout', '--force', '--quiet', revision])
212 else:
213 branches, active = self._branches()
214 if active != self.master_branch:
215 self._check_call_git(
216 ['checkout', '--force', '--quiet', self.master_branch])
217 self._sync_remote_branch()
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000218
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000219 if self.working_branch in branches:
220 self._call_git(['branch', '-D', self.working_branch])
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000221 return self._get_head_commit_hash()
222
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000223 def _sync_remote_branch(self):
224 """Syncs the remote branch."""
225 # We do a 'git pull origin master:refs/remotes/origin/master' instead of
hinoka@google.comdabbea22014-04-21 23:58:11 +0000226 # 'git pull origin master' because from the manpage for git-pull:
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000227 # A parameter <ref> without a colon is equivalent to <ref>: when
228 # pulling/fetching, so it merges <ref> into the current branch without
229 # storing the remote branch anywhere locally.
230 remote_tracked_path = 'refs/remotes/%s/%s' % (
231 self.remote, self.remote_branch)
232 self._check_call_git(
233 ['pull', self.remote,
234 '%s:%s' % (self.remote_branch, remote_tracked_path),
235 '--quiet'])
236
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000237 def _get_head_commit_hash(self):
rmistry@google.com11145db2013-10-03 12:43:40 +0000238 """Gets the current revision (in unicode) from the local branch."""
239 return unicode(self._check_output_git(['rev-parse', 'HEAD']).strip())
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000240
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000241 def apply_patch(self, patches, post_processors=None, verbose=False):
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000242 """Applies a patch on 'working_branch' and switches to it.
maruel@chromium.org8a1396c2011-04-22 00:14:24 +0000243
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000244 The changes remain staged on the current branch.
maruel@chromium.org8a1396c2011-04-22 00:14:24 +0000245 """
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000246 post_processors = post_processors or self.post_processors or []
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000247 # It this throws, the checkout is corrupted. Maybe worth deleting it and
248 # trying again?
maruel@chromium.org3cdb7f32011-05-05 16:37:24 +0000249 if self.remote_branch:
250 self._check_call_git(
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000251 ['checkout', '-b', self.working_branch, '-t', self.remote_branch,
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000252 '--quiet'])
253
maruel@chromium.org5e975632011-09-29 18:07:06 +0000254 for index, p in enumerate(patches):
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000255 stdout = []
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000256 try:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000257 filepath = os.path.join(self.project_path, p.filename)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000258 if p.is_delete:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000259 if (not os.path.exists(filepath) and
maruel@chromium.org5e975632011-09-29 18:07:06 +0000260 any(p1.source_filename == p.filename for p1 in patches[0:index])):
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000261 # The file was already deleted if a prior patch with file rename
262 # was already processed because 'git apply' did it for us.
maruel@chromium.org5e975632011-09-29 18:07:06 +0000263 pass
264 else:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000265 stdout.append(self._check_output_git(['rm', p.filename]))
phajdan.jr@chromium.orgd9eb69e2014-06-05 20:33:37 +0000266 assert(not os.path.exists(filepath))
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000267 stdout.append('Deleted.')
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000268 else:
269 dirname = os.path.dirname(p.filename)
270 full_dir = os.path.join(self.project_path, dirname)
271 if dirname and not os.path.isdir(full_dir):
272 os.makedirs(full_dir)
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000273 stdout.append('Created missing directory %s.' % dirname)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000274 if p.is_binary:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000275 content = p.get()
276 with open(filepath, 'wb') as f:
277 f.write(content)
278 stdout.append('Added binary file %d bytes' % len(content))
279 cmd = ['add', p.filename]
280 if verbose:
281 cmd.append('--verbose')
282 stdout.append(self._check_output_git(cmd))
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000283 else:
maruel@chromium.org58fe6622011-06-03 20:59:27 +0000284 # No need to do anything special with p.is_new or if not
285 # p.diff_hunks. git apply manages all that already.
primiano@chromium.org49dfcde2014-09-23 08:14:39 +0000286 cmd = ['apply', '--index', '-3', '-p%s' % p.patchlevel]
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000287 if verbose:
288 cmd.append('--verbose')
289 stdout.append(self._check_output_git(cmd, stdin=p.get(True)))
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000290 for post in post_processors:
maruel@chromium.org8a1396c2011-04-22 00:14:24 +0000291 post(self, p)
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000292 if verbose:
293 print p.filename
294 print align_stdout(stdout)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000295 except OSError, e:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000296 raise PatchApplicationFailed(p, '%s%s' % (align_stdout(stdout), e))
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000297 except subprocess.CalledProcessError, e:
298 raise PatchApplicationFailed(
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000299 p,
300 'While running %s;\n%s%s' % (
301 ' '.join(e.cmd),
302 align_stdout(stdout),
303 align_stdout([getattr(e, 'stdout', '')])))
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000304 found_files = self._check_output_git(
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000305 ['diff', '--ignore-submodules',
306 '--name-only', '--staged']).splitlines(False)
hinoka@chromium.orgdc6a1d02014-05-10 04:42:48 +0000307 if sorted(patches.filenames) != sorted(found_files):
308 extra_files = sorted(set(found_files) - set(patches.filenames))
309 unpatched_files = sorted(set(patches.filenames) - set(found_files))
310 if extra_files:
311 print 'Found extra files: %r' % (extra_files,)
312 if unpatched_files:
313 print 'Found unpatched files: %r' % (unpatched_files,)
314
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000315
316 def commit(self, commit_message, user):
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000317 """Commits, updates the commit message and pushes."""
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000318 # TODO(hinoka): CQ no longer uses this, I think its deprecated.
319 # Delete this.
rmistry@google.combb050f62013-10-03 16:53:54 +0000320 assert self.commit_user
maruel@chromium.org1bf50972011-05-05 19:57:21 +0000321 assert isinstance(commit_message, unicode)
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000322 current_branch = self._check_output_git(
323 ['rev-parse', '--abbrev-ref', 'HEAD']).strip()
324 assert current_branch == self.working_branch
hinoka@google.comdabbea22014-04-21 23:58:11 +0000325
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000326 commit_cmd = ['commit', '-m', commit_message]
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000327 if user and user != self.commit_user:
328 # We do not have the first or last name of the user, grab the username
329 # from the email and call it the original author's name.
330 # TODO(rmistry): Do not need the below if user is already in
331 # "Name <email>" format.
332 name = user.split('@')[0]
333 commit_cmd.extend(['--author', '%s <%s>' % (name, user)])
334 self._check_call_git(commit_cmd)
335
336 # Push to the remote repository.
337 self._check_call_git(
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000338 ['push', 'origin', '%s:%s' % (self.working_branch, self.remote_branch),
agable@chromium.org39262282014-03-19 21:07:38 +0000339 '--quiet'])
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000340 # Get the revision after the push.
341 revision = self._get_head_commit_hash()
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000342 # Switch back to the remote_branch and sync it.
343 self._check_call_git(['checkout', self.remote_branch])
344 self._sync_remote_branch()
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000345 # Delete the working branch since we are done with it.
346 self._check_call_git(['branch', '-D', self.working_branch])
347
348 return revision
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000349
350 def _check_call_git(self, args, **kwargs):
351 kwargs.setdefault('cwd', self.project_path)
352 kwargs.setdefault('stdout', self.VOID)
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000353 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
maruel@chromium.org44b21b92012-11-08 19:37:08 +0000354 return subprocess2.check_call_out(['git'] + args, **kwargs)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000355
356 def _call_git(self, args, **kwargs):
357 """Like check_call but doesn't throw on failure."""
358 kwargs.setdefault('cwd', self.project_path)
359 kwargs.setdefault('stdout', self.VOID)
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000360 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
maruel@chromium.org44b21b92012-11-08 19:37:08 +0000361 return subprocess2.call(['git'] + args, **kwargs)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000362
363 def _check_output_git(self, args, **kwargs):
364 kwargs.setdefault('cwd', self.project_path)
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000365 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000366 return subprocess2.check_output(
maruel@chromium.org44b21b92012-11-08 19:37:08 +0000367 ['git'] + args, stderr=subprocess2.STDOUT, **kwargs)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000368
369 def _branches(self):
370 """Returns the list of branches and the active one."""
371 out = self._check_output_git(['branch']).splitlines(False)
372 branches = [l[2:] for l in out]
373 active = None
374 for l in out:
375 if l.startswith('*'):
376 active = l[2:]
377 break
378 return branches, active
379
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000380 def revisions(self, rev1, rev2):
381 """Returns the number of actual commits between both hash."""
382 self._fetch_remote()
383
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000384 rev2 = rev2 or '%s/%s' % (self.remote, self.remote_branch)
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000385 # Revision range is ]rev1, rev2] and ordering matters.
386 try:
387 out = self._check_output_git(
388 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)])
389 except subprocess.CalledProcessError:
390 return None
391 return len(out.splitlines())
392
393 def _fetch_remote(self):
394 """Fetches the remote without rebasing."""
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000395 # git fetch is always verbose even with -q, so redirect its output.
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000396 self._check_output_git(['fetch', self.remote, self.remote_branch],
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000397 timeout=FETCH_TIMEOUT)
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000398
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000399
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000400class ReadOnlyCheckout(object):
401 """Converts a checkout into a read-only one."""
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000402 def __init__(self, checkout, post_processors=None):
maruel@chromium.orga5129fb2011-06-20 18:36:25 +0000403 super(ReadOnlyCheckout, self).__init__()
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000404 self.checkout = checkout
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000405 self.post_processors = (post_processors or []) + (
406 self.checkout.post_processors or [])
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000407
maruel@chromium.org51919772011-06-12 01:27:42 +0000408 def prepare(self, revision):
409 return self.checkout.prepare(revision)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000410
411 def get_settings(self, key):
412 return self.checkout.get_settings(key)
413
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000414 def apply_patch(self, patches, post_processors=None, verbose=False):
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000415 return self.checkout.apply_patch(
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000416 patches, post_processors or self.post_processors, verbose)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000417
418 def commit(self, message, user): # pylint: disable=R0201
419 logging.info('Would have committed for %s with message: %s' % (
420 user, message))
421 return 'FAKE'
422
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000423 def revisions(self, rev1, rev2):
424 return self.checkout.revisions(rev1, rev2)
425
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000426 @property
427 def project_name(self):
428 return self.checkout.project_name
429
430 @property
431 def project_path(self):
432 return self.checkout.project_path