blob: c5e61dd9aadcbd4d42393f5943bc02bb8ae3f4a8 [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:
Edward Lemur5908f992017-09-12 23:49:41 +0200255 filepath = os.path.join(self.project_path,
256 p.filename_after_patchlevel())
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000257 if p.is_delete:
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000258 if (not os.path.exists(filepath) and
maruel@chromium.org5e975632011-09-29 18:07:06 +0000259 any(p1.source_filename == p.filename for p1 in patches[0:index])):
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000260 # The file was already deleted if a prior patch with file rename
261 # was already processed because 'git apply' did it for us.
maruel@chromium.org5e975632011-09-29 18:07:06 +0000262 pass
263 else:
Edward Lemur5908f992017-09-12 23:49:41 +0200264 stdout.append(self._check_output_git(
265 ['rm', p.filename_after_patchlevel()]))
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:
Edward Lemur5908f992017-09-12 23:49:41 +0200269 dirname = os.path.dirname(p.filename_after_patchlevel())
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000270 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))
Edward Lemur5908f992017-09-12 23:49:41 +0200279 cmd = ['add', p.filename_after_patchlevel()]
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000280 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:
skobes2f3f1372016-10-25 08:08:27 -0700296 errors.append((p, '%s%s' % (align_stdout(stdout), e)))
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000297 except subprocess.CalledProcessError, e:
skobes2f3f1372016-10-25 08:08:27 -0700298 errors.append((p,
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000299 'While running %s;\n%s%s' % (
300 ' '.join(e.cmd),
301 align_stdout(stdout),
skobes2f3f1372016-10-25 08:08:27 -0700302 align_stdout([getattr(e, 'stdout', '')]))))
303 if errors:
304 raise PatchApplicationFailed(errors, verbose)
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000305 found_files = self._check_output_git(
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000306 ['diff', '--ignore-submodules',
307 '--name-only', '--staged']).splitlines(False)
hinoka@chromium.orgdc6a1d02014-05-10 04:42:48 +0000308 if sorted(patches.filenames) != sorted(found_files):
309 extra_files = sorted(set(found_files) - set(patches.filenames))
310 unpatched_files = sorted(set(patches.filenames) - set(found_files))
311 if extra_files:
312 print 'Found extra files: %r' % (extra_files,)
313 if unpatched_files:
314 print 'Found unpatched files: %r' % (unpatched_files,)
315
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000316
317 def commit(self, commit_message, user):
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000318 """Commits, updates the commit message and pushes."""
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000319 # TODO(hinoka): CQ no longer uses this, I think its deprecated.
320 # Delete this.
rmistry@google.combb050f62013-10-03 16:53:54 +0000321 assert self.commit_user
maruel@chromium.org1bf50972011-05-05 19:57:21 +0000322 assert isinstance(commit_message, unicode)
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000323 current_branch = self._check_output_git(
324 ['rev-parse', '--abbrev-ref', 'HEAD']).strip()
325 assert current_branch == self.working_branch
hinoka@google.comdabbea22014-04-21 23:58:11 +0000326
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000327 commit_cmd = ['commit', '-m', commit_message]
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000328 if user and user != self.commit_user:
329 # We do not have the first or last name of the user, grab the username
330 # from the email and call it the original author's name.
331 # TODO(rmistry): Do not need the below if user is already in
332 # "Name <email>" format.
333 name = user.split('@')[0]
334 commit_cmd.extend(['--author', '%s <%s>' % (name, user)])
335 self._check_call_git(commit_cmd)
336
337 # Push to the remote repository.
338 self._check_call_git(
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000339 ['push', 'origin', '%s:%s' % (self.working_branch, self.remote_branch),
agable@chromium.org39262282014-03-19 21:07:38 +0000340 '--quiet'])
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000341 # Get the revision after the push.
342 revision = self._get_head_commit_hash()
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000343 # Switch back to the remote_branch and sync it.
344 self._check_call_git(['checkout', self.remote_branch])
345 self._sync_remote_branch()
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000346 # Delete the working branch since we are done with it.
347 self._check_call_git(['branch', '-D', self.working_branch])
348
349 return revision
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000350
351 def _check_call_git(self, args, **kwargs):
352 kwargs.setdefault('cwd', self.project_path)
353 kwargs.setdefault('stdout', self.VOID)
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000354 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
maruel@chromium.org44b21b92012-11-08 19:37:08 +0000355 return subprocess2.check_call_out(['git'] + args, **kwargs)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000356
357 def _call_git(self, args, **kwargs):
358 """Like check_call but doesn't throw on failure."""
359 kwargs.setdefault('cwd', self.project_path)
360 kwargs.setdefault('stdout', self.VOID)
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000361 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
maruel@chromium.org44b21b92012-11-08 19:37:08 +0000362 return subprocess2.call(['git'] + args, **kwargs)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000363
364 def _check_output_git(self, args, **kwargs):
365 kwargs.setdefault('cwd', self.project_path)
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000366 kwargs.setdefault('timeout', GLOBAL_TIMEOUT)
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000367 return subprocess2.check_output(
maruel@chromium.org44b21b92012-11-08 19:37:08 +0000368 ['git'] + args, stderr=subprocess2.STDOUT, **kwargs)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000369
370 def _branches(self):
371 """Returns the list of branches and the active one."""
372 out = self._check_output_git(['branch']).splitlines(False)
373 branches = [l[2:] for l in out]
374 active = None
375 for l in out:
376 if l.startswith('*'):
377 active = l[2:]
378 break
379 return branches, active
380
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000381 def revisions(self, rev1, rev2):
382 """Returns the number of actual commits between both hash."""
383 self._fetch_remote()
384
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000385 rev2 = rev2 or '%s/%s' % (self.remote, self.remote_branch)
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000386 # Revision range is ]rev1, rev2] and ordering matters.
387 try:
388 out = self._check_output_git(
389 ['log', '--format="%H"' , '%s..%s' % (rev1, rev2)])
390 except subprocess.CalledProcessError:
391 return None
392 return len(out.splitlines())
393
394 def _fetch_remote(self):
395 """Fetches the remote without rebasing."""
rmistry@google.com3b5efdf2013-09-05 11:59:40 +0000396 # git fetch is always verbose even with -q, so redirect its output.
agable@chromium.org7e8c19d2014-03-19 16:47:37 +0000397 self._check_output_git(['fetch', self.remote, self.remote_branch],
csharp@chromium.org9af0a112013-03-20 20:21:35 +0000398 timeout=FETCH_TIMEOUT)
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000399
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000400
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000401class ReadOnlyCheckout(object):
402 """Converts a checkout into a read-only one."""
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000403 def __init__(self, checkout, post_processors=None):
maruel@chromium.orga5129fb2011-06-20 18:36:25 +0000404 super(ReadOnlyCheckout, self).__init__()
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000405 self.checkout = checkout
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000406 self.post_processors = (post_processors or []) + (
407 self.checkout.post_processors or [])
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000408
maruel@chromium.org51919772011-06-12 01:27:42 +0000409 def prepare(self, revision):
410 return self.checkout.prepare(revision)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000411
412 def get_settings(self, key):
413 return self.checkout.get_settings(key)
414
hinoka@chromium.orgc4396a12014-05-10 02:19:27 +0000415 def apply_patch(self, patches, post_processors=None, verbose=False):
maruel@chromium.orgb1d1a782011-09-29 14:13:55 +0000416 return self.checkout.apply_patch(
maruel@chromium.org4dd9f722012-10-01 16:23:03 +0000417 patches, post_processors or self.post_processors, verbose)
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000418
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800419 def commit(self, message, user): # pylint: disable=no-self-use
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000420 logging.info('Would have committed for %s with message: %s' % (
421 user, message))
422 return 'FAKE'
423
maruel@chromium.orgbc32ad12012-07-26 13:22:47 +0000424 def revisions(self, rev1, rev2):
425 return self.checkout.revisions(rev1, rev2)
426
maruel@chromium.orgdfaecd22011-04-21 00:33:31 +0000427 @property
428 def project_name(self):
429 return self.checkout.project_name
430
431 @property
432 def project_path(self):
433 return self.checkout.project_path