blob: c2004e3d57097cb9688948c0f22adaefd2020a11 [file] [log] [blame]
Edward Lemura877ee62019-09-03 20:23:17 +00001#!/usr/bin/env vpython3
steveblock@chromium.org93567042012-02-15 01:02:26 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
msb@chromium.orge28e4982009-09-25 20:51:45 +00005"""Unit tests for gclient_scm.py."""
6
maruel@chromium.org428342a2011-11-10 15:46:33 +00007# pylint: disable=E1103
maruel@chromium.orgbf38a7e2010-12-14 18:15:54 +00008
Edward Lesmese79107e2019-10-25 22:47:33 +00009from __future__ import unicode_literals
10
Gavin Mak65c49b12023-08-24 18:06:42 +000011from io import StringIO
John Budorick0f7b2002018-01-19 15:46:17 -080012import json
maruel@chromium.org428342a2011-11-10 15:46:33 +000013import logging
14import os
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000015import re
Gavin Mak65c49b12023-08-24 18:06:42 +000016from subprocess import Popen, PIPE, STDOUT
maruel@chromium.org428342a2011-11-10 15:46:33 +000017import sys
msb@chromium.orge28e4982009-09-25 20:51:45 +000018import tempfile
maruel@chromium.org389d6de2010-09-09 14:14:37 +000019import unittest
Gavin Mak65c49b12023-08-24 18:06:42 +000020from unittest import mock
msb@chromium.orge28e4982009-09-25 20:51:45 +000021
maruel@chromium.org428342a2011-11-10 15:46:33 +000022sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
maruel@chromium.orgba551772010-02-03 18:21:42 +000023
msb@chromium.orge28e4982009-09-25 20:51:45 +000024import gclient_scm
Edward Lesmese79107e2019-10-25 22:47:33 +000025import gclient_utils
szager@chromium.orgb0a13a22014-06-18 00:52:25 +000026import git_cache
maruel@chromium.orgfae707b2011-09-15 18:57:58 +000027import subprocess2
Gavin Mak65c49b12023-08-24 18:06:42 +000028from testing_support import fake_repos
29from testing_support import test_case_utils
maruel@chromium.org96913eb2010-06-01 16:22:47 +000030
Mike Frysinger67761632023-09-05 20:24:16 +000031# TODO: Should fix these warnings.
32# pylint: disable=line-too-long
Edward Lesmese79107e2019-10-25 22:47:33 +000033
34GIT = 'git' if sys.platform != 'win32' else 'git.bat'
35
szager@chromium.orgb0a13a22014-06-18 00:52:25 +000036# Disable global git cache
37git_cache.Mirror.SetCachePath(None)
38
maruel@chromium.org795a8c12010-10-05 19:54:29 +000039# Shortcut since this function is used often
40join = gclient_scm.os.path.join
41
Raul Tambrea79f0e52019-09-21 07:27:39 +000042TIMESTAMP_RE = re.compile(r'\[[0-9]{1,2}:[0-9]{2}:[0-9]{2}\] (.*)', re.DOTALL)
Mike Frysinger67761632023-09-05 20:24:16 +000043
44
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000045def strip_timestamps(value):
Mike Frysinger67761632023-09-05 20:24:16 +000046 lines = value.splitlines(True)
47 for i in range(len(lines)):
48 m = TIMESTAMP_RE.match(lines[i])
49 if m:
50 lines[i] = m.group(1)
51 return ''.join(lines)
maruel@chromium.org96913eb2010-06-01 16:22:47 +000052
maruel@chromium.orgd579fcf2011-12-13 20:36:03 +000053
Edward Lemur979fa782019-08-13 22:44:05 +000054class BasicTests(unittest.TestCase):
Mike Frysinger67761632023-09-05 20:24:16 +000055 @mock.patch('gclient_scm.scm.GIT.Capture')
56 def testGetFirstRemoteUrl(self, mockCapture):
57 REMOTE_STRINGS = [
58 ('remote.origin.url E:\\foo\\bar', 'E:\\foo\\bar'),
59 ('remote.origin.url /b/foo/bar', '/b/foo/bar'),
60 ('remote.origin.url https://foo/bar', 'https://foo/bar'),
61 ('remote.origin.url E:\\Fo Bar\\bax', 'E:\\Fo Bar\\bax'),
62 ('remote.origin.url git://what/"do', 'git://what/"do')
63 ]
64 FAKE_PATH = '/fake/path'
65 mockCapture.side_effect = [question for question, _ in REMOTE_STRINGS]
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +000066
Mike Frysinger67761632023-09-05 20:24:16 +000067 for _, answer in REMOTE_STRINGS:
68 self.assertEqual(
69 gclient_scm.SCMWrapper._get_first_remote_url(FAKE_PATH), answer)
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +000070
Mike Frysinger67761632023-09-05 20:24:16 +000071 expected_calls = [
72 mock.call(['config', '--local', '--get-regexp', r'remote.*.url'],
73 cwd=FAKE_PATH) for _ in REMOTE_STRINGS
74 ]
75 self.assertEqual(mockCapture.mock_calls, expected_calls)
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +000076
77
Edward Lemur9cafbf42019-08-15 22:03:35 +000078class BaseGitWrapperTestCase(unittest.TestCase, test_case_utils.TestCaseUtils):
Mike Frysinger67761632023-09-05 20:24:16 +000079 """This class doesn't use pymox."""
80 class OptionsObject(object):
81 def __init__(self, verbose=False, revision=None):
82 self.auto_rebase = False
83 self.verbose = verbose
84 self.revision = revision
85 self.deps_os = None
86 self.force = False
87 self.reset = False
88 self.nohooks = False
89 self.no_history = False
90 self.upstream = False
91 self.cache_dir = None
92 self.merge = False
93 self.jobs = 1
94 self.break_repo_locks = False
95 self.delete_unversioned_trees = False
96 self.patch_ref = None
97 self.patch_repo = None
98 self.rebase_patch_ref = True
99 self.reset_patch_ref = True
msb@chromium.orge28e4982009-09-25 20:51:45 +0000100
Mike Frysinger67761632023-09-05 20:24:16 +0000101 sample_git_import = """blob
msb@chromium.orge28e4982009-09-25 20:51:45 +0000102mark :1
103data 6
104Hello
105
106blob
107mark :2
108data 4
109Bye
110
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000111reset refs/heads/main
112commit refs/heads/main
msb@chromium.orge28e4982009-09-25 20:51:45 +0000113mark :3
114author Bob <bob@example.com> 1253744361 -0700
115committer Bob <bob@example.com> 1253744361 -0700
116data 8
117A and B
118M 100644 :1 a
119M 100644 :2 b
120
121blob
122mark :4
123data 10
124Hello
125You
126
127blob
128mark :5
129data 8
130Bye
131You
132
133commit refs/heads/origin
134mark :6
135author Alice <alice@example.com> 1253744424 -0700
136committer Alice <alice@example.com> 1253744424 -0700
137data 13
138Personalized
139from :3
140M 100644 :4 a
141M 100644 :5 b
142
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000143blob
144mark :7
145data 5
146Mooh
147
148commit refs/heads/feature
149mark :8
150author Bob <bob@example.com> 1390311986 -0000
151committer Bob <bob@example.com> 1390311986 -0000
152data 6
153Add C
154from :3
155M 100644 :7 c
156
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000157reset refs/heads/main
msb@chromium.orge28e4982009-09-25 20:51:45 +0000158from :3
159"""
msb@chromium.orge28e4982009-09-25 20:51:45 +0000160
Mike Frysinger67761632023-09-05 20:24:16 +0000161 def Options(self, *args, **kwargs):
162 return self.OptionsObject(*args, **kwargs)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000163
Mike Frysinger67761632023-09-05 20:24:16 +0000164 def checkstdout(self, expected):
165 # pylint: disable=no-member
166 value = sys.stdout.getvalue()
167 sys.stdout.close()
168 # Check that the expected output appears.
169 self.assertIn(expected, strip_timestamps(value))
msb@chromium.orge28e4982009-09-25 20:51:45 +0000170
Mike Frysinger67761632023-09-05 20:24:16 +0000171 @staticmethod
172 def CreateGitRepo(git_import, path):
173 """Do it for real."""
174 try:
175 Popen([GIT, 'init', '-q'], stdout=PIPE, stderr=STDOUT,
176 cwd=path).communicate()
177 except OSError:
178 # git is not available, skip this test.
179 return False
180 Popen([GIT, 'fast-import', '--quiet'],
181 stdin=PIPE,
182 stdout=PIPE,
183 stderr=STDOUT,
184 cwd=path).communicate(input=git_import.encode())
185 Popen([GIT, 'checkout', '-q'], stdout=PIPE, stderr=STDOUT,
186 cwd=path).communicate()
187 Popen([GIT, 'remote', 'add', '-f', 'origin', '.'],
188 stdout=PIPE,
189 stderr=STDOUT,
190 cwd=path).communicate()
191 Popen([GIT, 'checkout', '-b', 'new', 'origin/main', '-q'],
192 stdout=PIPE,
193 stderr=STDOUT,
194 cwd=path).communicate()
195 Popen([GIT, 'push', 'origin', 'origin/origin:origin/main', '-q'],
196 stdout=PIPE,
197 stderr=STDOUT,
198 cwd=path).communicate()
199 Popen([GIT, 'config', '--unset', 'remote.origin.fetch'],
200 stdout=PIPE,
201 stderr=STDOUT,
202 cwd=path).communicate()
203 Popen([GIT, 'config', 'user.email', 'someuser@chromium.org'],
204 stdout=PIPE,
205 stderr=STDOUT,
206 cwd=path).communicate()
207 Popen([GIT, 'config', 'user.name', 'Some User'],
208 stdout=PIPE,
209 stderr=STDOUT,
210 cwd=path).communicate()
211 # Set HEAD back to main
212 Popen([GIT, 'checkout', 'main', '-q'],
213 stdout=PIPE,
214 stderr=STDOUT,
215 cwd=path).communicate()
216 return True
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000217
Mike Frysinger67761632023-09-05 20:24:16 +0000218 def _GetAskForDataCallback(self, expected_prompt, return_value):
219 def AskForData(prompt, options):
220 self.assertEqual(prompt, expected_prompt)
221 return return_value
222
223 return AskForData
224
225 def setUp(self):
226 unittest.TestCase.setUp(self)
227 test_case_utils.TestCaseUtils.setUp(self)
228 self.url = 'git://foo'
229 # The .git suffix allows gclient_scm to recognize the dir as a git repo
230 # when cloning it locally
231 self.root_dir = tempfile.mkdtemp('.git')
232 self.relpath = '.'
233 self.base_path = join(self.root_dir, self.relpath)
234 self.enabled = self.CreateGitRepo(self.sample_git_import,
235 self.base_path)
236 mock.patch('sys.stdout', StringIO()).start()
237 self.addCleanup(mock.patch.stopall)
238 self.addCleanup(gclient_utils.rmtree, self.root_dir)
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000239
msb@chromium.orge28e4982009-09-25 20:51:45 +0000240
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000241class ManagedGitWrapperTestCase(BaseGitWrapperTestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000242 @mock.patch('gclient_scm.GitWrapper._IsCog')
243 @mock.patch('gclient_scm.GitWrapper._Run', return_value=True)
244 @mock.patch('gclient_scm.GitWrapper._SetFetchConfig')
245 @mock.patch('gclient_scm.GitWrapper._GetCurrentBranch')
246 def testCloneInCog(self, mockGetCurrentBranch, mockSetFetchConfig, mockRun,
247 _mockIsCog):
248 """Test that we call the correct commands when in a cog workspace."""
249 if not self.enabled:
250 return
251 options = self.Options()
252 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
253 scm._Clone('123123ab', self.url, options)
254 mockRun.assert_called_once_with(
255 ['citc', 'clone-repo', self.url, scm.checkout_path, '123123ab'],
256 options,
257 cwd=scm._root_dir,
258 retry=True,
259 print_stdout=False,
260 filter_fn=scm.filter)
261 mockSetFetchConfig.assert_called_once()
262 mockGetCurrentBranch.assert_called_once()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000263
Mike Frysinger67761632023-09-05 20:24:16 +0000264 def testRevertMissing(self):
265 if not self.enabled:
266 return
267 options = self.Options()
268 file_path = join(self.base_path, 'a')
269 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
270 file_list = []
271 scm.update(options, None, file_list)
272 gclient_scm.os.remove(file_path)
273 file_list = []
274 scm.revert(options, self.args, file_list)
275 self.assertEqual(file_list, [file_path])
276 file_list = []
277 scm.diff(options, self.args, file_list)
278 self.assertEqual(file_list, [])
279 sys.stdout.close()
Joanna Wang1a977bd2022-06-02 21:51:17 +0000280
Mike Frysinger67761632023-09-05 20:24:16 +0000281 def testRevertNone(self):
282 if not self.enabled:
283 return
284 options = self.Options()
285 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
286 file_list = []
287 scm.update(options, None, file_list)
288 file_list = []
289 scm.revert(options, self.args, file_list)
290 self.assertEqual(file_list, [])
291 self.assertEqual(scm.revinfo(options, self.args, None),
292 'a7142dc9f0009350b96a11f372b6ea658592aa95')
293 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000294
Mike Frysinger67761632023-09-05 20:24:16 +0000295 def testRevertModified(self):
296 if not self.enabled:
297 return
298 options = self.Options()
299 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
300 file_list = []
301 scm.update(options, None, file_list)
302 file_path = join(self.base_path, 'a')
303 with open(file_path, 'a') as f:
304 f.writelines('touched\n')
305 file_list = []
306 scm.revert(options, self.args, file_list)
307 self.assertEqual(file_list, [file_path])
308 file_list = []
309 scm.diff(options, self.args, file_list)
310 self.assertEqual(file_list, [])
311 self.assertEqual(scm.revinfo(options, self.args, None),
312 'a7142dc9f0009350b96a11f372b6ea658592aa95')
313 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000314
Mike Frysinger67761632023-09-05 20:24:16 +0000315 def testRevertNew(self):
316 if not self.enabled:
317 return
318 options = self.Options()
319 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
320 file_list = []
321 scm.update(options, None, file_list)
322 file_path = join(self.base_path, 'c')
323 with open(file_path, 'w') as f:
324 f.writelines('new\n')
325 Popen([GIT, 'add', 'c'], stdout=PIPE, stderr=STDOUT,
326 cwd=self.base_path).communicate()
327 file_list = []
328 scm.revert(options, self.args, file_list)
329 self.assertEqual(file_list, [file_path])
330 file_list = []
331 scm.diff(options, self.args, file_list)
332 self.assertEqual(file_list, [])
333 self.assertEqual(scm.revinfo(options, self.args, None),
334 'a7142dc9f0009350b96a11f372b6ea658592aa95')
335 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000336
Mike Frysinger67761632023-09-05 20:24:16 +0000337 def testStatusRef(self):
338 if not self.enabled:
339 return
340 options = self.Options()
341 file_paths = [join(self.base_path, 'a')]
342 with open(file_paths[0], 'a') as f:
343 f.writelines('touched\n')
344 scm = gclient_scm.GitWrapper(self.url + '@refs/heads/feature',
345 self.root_dir, self.relpath)
346 file_paths.append(join(self.base_path, 'c')) # feature branch touches c
347 file_list = []
348 scm.status(options, self.args, file_list)
349 self.assertEqual(file_list, file_paths)
350 self.checkstdout((
351 '\n________ running \'git -c core.quotePath=false diff --name-status '
352 'refs/remotes/origin/feature\' in \'%s\'\n\nM\ta\n') %
353 join(self.root_dir, '.'))
msb@chromium.orge28e4982009-09-25 20:51:45 +0000354
Mike Frysinger67761632023-09-05 20:24:16 +0000355 def testStatusNew(self):
356 if not self.enabled:
357 return
358 options = self.Options()
359 file_path = join(self.base_path, 'a')
360 with open(file_path, 'a') as f:
361 f.writelines('touched\n')
362 scm = gclient_scm.GitWrapper(
363 self.url + '@069c602044c5388d2d15c3f875b057c852003458',
364 self.root_dir, self.relpath)
365 file_list = []
366 scm.status(options, self.args, file_list)
367 self.assertEqual(file_list, [file_path])
368 self.checkstdout((
369 '\n________ running \'git -c core.quotePath=false diff --name-status '
370 '069c602044c5388d2d15c3f875b057c852003458\' in \'%s\'\n\nM\ta\n') %
371 join(self.root_dir, '.'))
Joanna Wanga654ff32023-07-18 23:25:19 +0000372
Mike Frysinger67761632023-09-05 20:24:16 +0000373 def testStatusNewNoBaseRev(self):
374 if not self.enabled:
375 return
376 options = self.Options()
377 file_path = join(self.base_path, 'a')
378 with open(file_path, 'a') as f:
379 f.writelines('touched\n')
380 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
381 file_list = []
382 scm.status(options, self.args, file_list)
383 self.assertEqual(file_list, [file_path])
384 self.checkstdout((
385 '\n________ running \'git -c core.quotePath=false diff --name-status'
386 '\' in \'%s\'\n\nM\ta\n') % join(self.root_dir, '.'))
msb@chromium.orge28e4982009-09-25 20:51:45 +0000387
Mike Frysinger67761632023-09-05 20:24:16 +0000388 def testStatus2New(self):
389 if not self.enabled:
390 return
391 options = self.Options()
392 expected_file_list = []
393 for f in ['a', 'b']:
394 file_path = join(self.base_path, f)
395 with open(file_path, 'a') as f:
396 f.writelines('touched\n')
397 expected_file_list.extend([file_path])
398 scm = gclient_scm.GitWrapper(
399 self.url + '@069c602044c5388d2d15c3f875b057c852003458',
400 self.root_dir, self.relpath)
401 file_list = []
402 scm.status(options, self.args, file_list)
403 expected_file_list = [join(self.base_path, x) for x in ['a', 'b']]
404 self.assertEqual(sorted(file_list), expected_file_list)
405 self.checkstdout((
406 '\n________ running \'git -c core.quotePath=false diff --name-status '
407 '069c602044c5388d2d15c3f875b057c852003458\' in \'%s\'\n\nM\ta\nM\tb\n'
408 ) % join(self.root_dir, '.'))
Anthony Politobb457342019-11-15 22:26:01 +0000409
Mike Frysinger67761632023-09-05 20:24:16 +0000410 def testUpdateUpdate(self):
411 if not self.enabled:
412 return
413 options = self.Options()
414 expected_file_list = [join(self.base_path, x) for x in ['a', 'b']]
415 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
416 file_list = []
Anthony Politobb457342019-11-15 22:26:01 +0000417
Mike Frysinger67761632023-09-05 20:24:16 +0000418 scm.update(options, (), file_list)
419 self.assertEqual(file_list, expected_file_list)
420 self.assertEqual(scm.revinfo(options, (), None),
421 'a7142dc9f0009350b96a11f372b6ea658592aa95')
422 self.assertEqual(
423 scm._Capture(['config', '--get', 'diff.ignoreSubmodules']), 'dirty')
424 self.assertEqual(
425 scm._Capture(['config', '--get', 'fetch.recurseSubmodules']), 'off')
Josip Sokcevic3b9212b2023-09-18 19:26:26 +0000426 self.assertEqual(
427 scm._Capture(['config', '--get', 'push.recurseSubmodules']), 'off')
Mike Frysinger67761632023-09-05 20:24:16 +0000428 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000429
Mike Frysinger67761632023-09-05 20:24:16 +0000430 def testUpdateMerge(self):
431 if not self.enabled:
432 return
433 options = self.Options()
434 options.merge = True
435 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
436 scm._Run(['checkout', '-q', 'feature'], options)
437 rev = scm.revinfo(options, (), None)
438 file_list = []
439 scm.update(options, (), file_list)
440 self.assertEqual(file_list,
441 [join(self.base_path, x) for x in ['a', 'b', 'c']])
442 # The actual commit that is created is unstable, so we verify its tree
443 # and parents instead.
444 self.assertEqual(scm._Capture(['rev-parse', 'HEAD:']),
445 'd2e35c10ac24d6c621e14a1fcadceb533155627d')
446 parent = 'HEAD^' if sys.platform != 'win32' else 'HEAD^^'
447 self.assertEqual(scm._Capture(['rev-parse', parent + '1']), rev)
448 self.assertEqual(scm._Capture(['rev-parse', parent + '2']),
449 scm._Capture(['rev-parse', 'origin/main']))
450 sys.stdout.close()
Joanna Wang4e6c1072023-08-17 18:46:24 +0000451
Mike Frysinger67761632023-09-05 20:24:16 +0000452 def testUpdateRebase(self):
453 if not self.enabled:
454 return
455 options = self.Options()
456 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
457 scm._Run(['checkout', '-q', 'feature'], options)
458 file_list = []
459 # Fake a 'y' key press.
460 scm._AskForData = self._GetAskForDataCallback(
461 'Cannot fast-forward merge, attempt to rebase? '
462 '(y)es / (q)uit / (s)kip : ', 'y')
463 scm.update(options, (), file_list)
464 self.assertEqual(file_list,
465 [join(self.base_path, x) for x in ['a', 'b', 'c']])
466 # The actual commit that is created is unstable, so we verify its tree
467 # and parent instead.
468 self.assertEqual(scm._Capture(['rev-parse', 'HEAD:']),
469 'd2e35c10ac24d6c621e14a1fcadceb533155627d')
470 parent = 'HEAD^' if sys.platform != 'win32' else 'HEAD^^'
471 self.assertEqual(scm._Capture(['rev-parse', parent + '1']),
472 scm._Capture(['rev-parse', 'origin/main']))
473 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000474
Mike Frysinger67761632023-09-05 20:24:16 +0000475 def testUpdateReset(self):
476 if not self.enabled:
477 return
478 options = self.Options()
479 options.reset = True
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000480
Mike Frysinger67761632023-09-05 20:24:16 +0000481 dir_path = join(self.base_path, 'c')
482 os.mkdir(dir_path)
483 with open(join(dir_path, 'nested'), 'w') as f:
484 f.writelines('new\n')
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000485
Mike Frysinger67761632023-09-05 20:24:16 +0000486 file_path = join(self.base_path, 'file')
487 with open(file_path, 'w') as f:
488 f.writelines('new\n')
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000489
Mike Frysinger67761632023-09-05 20:24:16 +0000490 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
491 file_list = []
492 scm.update(options, (), file_list)
493 self.assert_(gclient_scm.os.path.isdir(dir_path))
494 self.assert_(gclient_scm.os.path.isfile(file_path))
495 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000496
Mike Frysinger67761632023-09-05 20:24:16 +0000497 def testUpdateResetUnsetsFetchConfig(self):
498 if not self.enabled:
499 return
500 options = self.Options()
501 options.reset = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000502
Mike Frysinger67761632023-09-05 20:24:16 +0000503 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
504 scm._Run([
505 'config', 'remote.origin.fetch',
506 '+refs/heads/bad/ref:refs/remotes/origin/bad/ref'
507 ], options)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000508
Mike Frysinger67761632023-09-05 20:24:16 +0000509 file_list = []
510 scm.update(options, (), file_list)
511 self.assertEqual(scm.revinfo(options, (), None),
512 '069c602044c5388d2d15c3f875b057c852003458')
513 sys.stdout.close()
Edward Lemur579c9862018-07-13 23:17:51 +0000514
Mike Frysinger67761632023-09-05 20:24:16 +0000515 def testUpdateResetDeleteUnversionedTrees(self):
516 if not self.enabled:
517 return
518 options = self.Options()
519 options.reset = True
520 options.delete_unversioned_trees = True
Edward Lemur579c9862018-07-13 23:17:51 +0000521
Mike Frysinger67761632023-09-05 20:24:16 +0000522 dir_path = join(self.base_path, 'dir')
523 os.mkdir(dir_path)
524 with open(join(dir_path, 'nested'), 'w') as f:
525 f.writelines('new\n')
Edward Lemur579c9862018-07-13 23:17:51 +0000526
Mike Frysinger67761632023-09-05 20:24:16 +0000527 file_path = join(self.base_path, 'file')
528 with open(file_path, 'w') as f:
529 f.writelines('new\n')
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000530
Mike Frysinger67761632023-09-05 20:24:16 +0000531 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
532 file_list = []
533 scm.update(options, (), file_list)
534 self.assert_(not gclient_scm.os.path.isdir(dir_path))
535 self.assert_(gclient_scm.os.path.isfile(file_path))
536 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000537
Mike Frysinger67761632023-09-05 20:24:16 +0000538 def testUpdateUnstagedConflict(self):
539 if not self.enabled:
540 return
541 options = self.Options()
542 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
543 file_path = join(self.base_path, 'b')
544 with open(file_path, 'w') as f:
545 f.writelines('conflict\n')
546 try:
547 scm.update(options, (), [])
548 self.fail()
549 except (gclient_scm.gclient_utils.Error,
550 subprocess2.CalledProcessError):
551 # The exact exception text varies across git versions so it's not
552 # worth verifying it. It's fine as long as it throws.
553 pass
554 # Manually flush stdout since we can't verify it's content accurately
555 # across git versions.
556 sys.stdout.getvalue()
557 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000558
Mike Frysinger67761632023-09-05 20:24:16 +0000559 @unittest.skip('Skipping until crbug.com/670884 is resolved.')
560 def testUpdateLocked(self):
561 if not self.enabled:
562 return
563 options = self.Options()
564 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
565 file_path = join(self.base_path, '.git', 'index.lock')
566 with open(file_path, 'w'):
567 pass
568 with self.assertRaises(subprocess2.CalledProcessError):
569 scm.update(options, (), [])
570 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000571
Mike Frysinger67761632023-09-05 20:24:16 +0000572 def testUpdateLockedBreak(self):
573 if not self.enabled:
574 return
575 options = self.Options()
576 options.break_repo_locks = True
577 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
578 file_path = join(self.base_path, '.git', 'index.lock')
579 with open(file_path, 'w'):
580 pass
581 scm.update(options, (), [])
582 self.assertRegexpMatches(sys.stdout.getvalue(),
583 r'breaking lock.*\.git[/|\\]index\.lock')
584 self.assertFalse(os.path.exists(file_path))
585 sys.stdout.close()
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000586
Mike Frysinger67761632023-09-05 20:24:16 +0000587 def testUpdateConflict(self):
588 if not self.enabled:
589 return
590 options = self.Options()
591 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
592 file_path = join(self.base_path, 'b')
593 with open(file_path, 'w') as f:
594 f.writelines('conflict\n')
595 scm._Run(['commit', '-am', 'test'], options)
596 scm._AskForData = self._GetAskForDataCallback(
597 'Cannot fast-forward merge, attempt to rebase? '
598 '(y)es / (q)uit / (s)kip : ', 'y')
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000599
Mike Frysinger67761632023-09-05 20:24:16 +0000600 with self.assertRaises(gclient_scm.gclient_utils.Error) as e:
601 scm.update(options, (), [])
602 self.assertEqual(
603 e.exception.args[0], 'Conflict while rebasing this branch.\n'
604 'Fix the conflict and run gclient again.\n'
605 'See \'man git-rebase\' for details.\n')
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000606
Mike Frysinger67761632023-09-05 20:24:16 +0000607 with self.assertRaises(gclient_scm.gclient_utils.Error) as e:
608 scm.update(options, (), [])
609 self.assertEqual(
610 e.exception.args[0], '\n____ . at refs/remotes/origin/main\n'
611 '\tYou have unstaged changes.\n'
612 '\tcd into ., run git status to see changes,\n'
613 '\tand commit, stash, or reset.\n')
Edward Lemur979fa782019-08-13 22:44:05 +0000614
Mike Frysinger67761632023-09-05 20:24:16 +0000615 sys.stdout.close()
Edward Lemur979fa782019-08-13 22:44:05 +0000616
Mike Frysinger67761632023-09-05 20:24:16 +0000617 def testRevinfo(self):
618 if not self.enabled:
619 return
620 options = self.Options()
621 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
622 rev_info = scm.revinfo(options, (), None)
623 self.assertEqual(rev_info, '069c602044c5388d2d15c3f875b057c852003458')
msb@chromium.org0f282062009-11-06 20:14:02 +0000624
msb@chromium.orge28e4982009-09-25 20:51:45 +0000625
Edward Lemur979fa782019-08-13 22:44:05 +0000626class ManagedGitWrapperTestCaseMock(unittest.TestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000627 class OptionsObject(object):
628 def __init__(self, verbose=False, revision=None, force=False):
629 self.verbose = verbose
630 self.revision = revision
631 self.deps_os = None
632 self.force = force
633 self.reset = False
634 self.nohooks = False
635 self.break_repo_locks = False
636 # TODO(maruel): Test --jobs > 1.
637 self.jobs = 1
638 self.patch_ref = None
639 self.patch_repo = None
640 self.rebase_patch_ref = True
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000641
Mike Frysinger67761632023-09-05 20:24:16 +0000642 def Options(self, *args, **kwargs):
643 return self.OptionsObject(*args, **kwargs)
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000644
Mike Frysinger67761632023-09-05 20:24:16 +0000645 def checkstdout(self, expected):
646 # pylint: disable=no-member
647 value = sys.stdout.getvalue()
648 sys.stdout.close()
649 # Check that the expected output appears.
650 self.assertIn(expected, strip_timestamps(value))
borenet@google.comb09097a2014-04-09 19:09:08 +0000651
Mike Frysinger67761632023-09-05 20:24:16 +0000652 def setUp(self):
653 self.fake_hash_1 = 't0ta11yf4k3'
654 self.fake_hash_2 = '3v3nf4k3r'
655 self.url = 'git://foo'
656 self.root_dir = '/tmp' if sys.platform != 'win32' else 't:\\tmp'
657 self.relpath = 'fake'
658 self.base_path = os.path.join(self.root_dir, self.relpath)
659 self.backup_base_path = os.path.join(self.root_dir,
660 'old_%s.git' % self.relpath)
661 mock.patch('gclient_scm.scm.GIT.ApplyEnvVars').start()
662 mock.patch('gclient_scm.GitWrapper._CheckMinVersion').start()
663 mock.patch('gclient_scm.GitWrapper._Fetch').start()
664 mock.patch('gclient_scm.GitWrapper._DeleteOrMove').start()
665 mock.patch('sys.stdout', StringIO()).start()
666 self.addCleanup(mock.patch.stopall)
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000667
Mike Frysinger67761632023-09-05 20:24:16 +0000668 @mock.patch('scm.GIT.IsValidRevision')
669 @mock.patch('os.path.isdir', lambda _: True)
670 def testGetUsableRevGit(self, mockIsValidRevision):
671 # pylint: disable=no-member
672 options = self.Options(verbose=True)
smutae7ea312016-07-18 11:59:41 -0700673
Mike Frysinger67761632023-09-05 20:24:16 +0000674 mockIsValidRevision.side_effect = lambda cwd, rev: rev != '1'
smutae7ea312016-07-18 11:59:41 -0700675
Mike Frysinger67761632023-09-05 20:24:16 +0000676 git_scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
677 # A [fake] git sha1 with a git repo should work (this is in the case
678 # that the LKGR gets flipped to git sha1's some day).
679 self.assertEqual(git_scm.GetUsableRev(self.fake_hash_1, options),
680 self.fake_hash_1)
681 # An SVN rev with an existing purely git repo should raise an exception.
682 self.assertRaises(gclient_scm.gclient_utils.Error, git_scm.GetUsableRev,
683 '1', options)
smutae7ea312016-07-18 11:59:41 -0700684
Mike Frysinger67761632023-09-05 20:24:16 +0000685 @mock.patch('gclient_scm.GitWrapper._Clone')
686 @mock.patch('os.path.isdir')
687 @mock.patch('os.path.exists')
688 @mock.patch('subprocess2.check_output')
689 def testUpdateNoDotGit(self, mockCheckOutput, mockExists, mockIsdir,
690 mockClone):
691 mockIsdir.side_effect = lambda path: path == self.base_path
692 mockExists.side_effect = lambda path: path == self.base_path
693 mockCheckOutput.side_effect = [b'refs/remotes/origin/main', b'', b'']
Edward Lemur979fa782019-08-13 22:44:05 +0000694
Mike Frysinger67761632023-09-05 20:24:16 +0000695 options = self.Options()
696 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
697 scm.update(options, None, [])
Edward Lemur979fa782019-08-13 22:44:05 +0000698
Mike Frysinger67761632023-09-05 20:24:16 +0000699 env = gclient_scm.scm.GIT.ApplyEnvVars({})
700 self.assertEqual(mockCheckOutput.mock_calls, [
701 mock.call(['git', 'symbolic-ref', 'refs/remotes/origin/HEAD'],
702 cwd=self.base_path,
703 env=env,
704 stderr=-1),
705 mock.call(['git', '-c', 'core.quotePath=false', 'ls-files'],
706 cwd=self.base_path,
707 env=env,
708 stderr=-1),
709 mock.call(['git', 'rev-parse', '--verify', 'HEAD'],
710 cwd=self.base_path,
711 env=env,
712 stderr=-1),
713 ])
714 mockClone.assert_called_with('refs/remotes/origin/main', self.url,
715 options)
716 self.checkstdout('\n')
borenet@google.comb09097a2014-04-09 19:09:08 +0000717
Mike Frysinger67761632023-09-05 20:24:16 +0000718 @mock.patch('gclient_scm.GitWrapper._Clone')
719 @mock.patch('os.path.isdir')
720 @mock.patch('os.path.exists')
721 @mock.patch('subprocess2.check_output')
722 def testUpdateConflict(self, mockCheckOutput, mockExists, mockIsdir,
723 mockClone):
724 mockIsdir.side_effect = lambda path: path == self.base_path
725 mockExists.side_effect = lambda path: path == self.base_path
726 mockCheckOutput.side_effect = [b'refs/remotes/origin/main', b'', b'']
727 mockClone.side_effect = [
728 gclient_scm.subprocess2.CalledProcessError(None, None, None, None,
729 None),
730 None,
731 ]
Edward Lemur979fa782019-08-13 22:44:05 +0000732
Mike Frysinger67761632023-09-05 20:24:16 +0000733 options = self.Options()
734 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
735 scm.update(options, None, [])
Edward Lemur979fa782019-08-13 22:44:05 +0000736
Mike Frysinger67761632023-09-05 20:24:16 +0000737 env = gclient_scm.scm.GIT.ApplyEnvVars({})
738 self.assertEqual(mockCheckOutput.mock_calls, [
739 mock.call(['git', 'symbolic-ref', 'refs/remotes/origin/HEAD'],
740 cwd=self.base_path,
741 env=env,
742 stderr=-1),
743 mock.call(['git', '-c', 'core.quotePath=false', 'ls-files'],
744 cwd=self.base_path,
745 env=env,
746 stderr=-1),
747 mock.call(['git', 'rev-parse', '--verify', 'HEAD'],
748 cwd=self.base_path,
749 env=env,
750 stderr=-1),
751 ])
752 mockClone.assert_called_with('refs/remotes/origin/main', self.url,
753 options)
754 self.checkstdout('\n')
borenet@google.comb09097a2014-04-09 19:09:08 +0000755
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000756
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000757class UnmanagedGitWrapperTestCase(BaseGitWrapperTestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000758 def checkInStdout(self, expected):
759 # pylint: disable=no-member
760 value = sys.stdout.getvalue()
761 sys.stdout.close()
762 self.assertIn(expected, value)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000763
Mike Frysinger67761632023-09-05 20:24:16 +0000764 def checkNotInStdout(self, expected):
765 # pylint: disable=no-member
766 value = sys.stdout.getvalue()
767 sys.stdout.close()
768 self.assertNotIn(expected, value)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000769
Mike Frysinger67761632023-09-05 20:24:16 +0000770 def getCurrentBranch(self):
771 # Returns name of current branch or HEAD for detached HEAD
772 branch = gclient_scm.scm.GIT.Capture(
773 ['rev-parse', '--abbrev-ref', 'HEAD'], cwd=self.base_path)
774 if branch == 'HEAD':
775 return None
776 return branch
smut@google.com27c9c8a2014-09-11 19:57:55 +0000777
Mike Frysinger67761632023-09-05 20:24:16 +0000778 def testUpdateClone(self):
779 if not self.enabled:
780 return
781 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000782
Mike Frysinger67761632023-09-05 20:24:16 +0000783 origin_root_dir = self.root_dir
784 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
Edward Lesmese79107e2019-10-25 22:47:33 +0000785
Mike Frysinger67761632023-09-05 20:24:16 +0000786 self.root_dir = tempfile.mkdtemp()
787 self.relpath = '.'
788 self.base_path = join(self.root_dir, self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000789
Mike Frysinger67761632023-09-05 20:24:16 +0000790 scm = gclient_scm.GitWrapper(origin_root_dir, self.root_dir,
791 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000792
Mike Frysinger67761632023-09-05 20:24:16 +0000793 expected_file_list = [
794 join(self.base_path, "a"),
795 join(self.base_path, "b")
796 ]
797 file_list = []
798 options.revision = 'unmanaged'
799 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000800
Mike Frysinger67761632023-09-05 20:24:16 +0000801 self.assertEqual(file_list, expected_file_list)
802 self.assertEqual(scm.revinfo(options, (), None),
803 '069c602044c5388d2d15c3f875b057c852003458')
804 # indicates detached HEAD
805 self.assertEqual(self.getCurrentBranch(), None)
806 self.checkInStdout(
807 'Checked out refs/remotes/origin/main to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000808
Mike Frysinger67761632023-09-05 20:24:16 +0000809 def testUpdateCloneOnCommit(self):
810 if not self.enabled:
811 return
812 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000813
Mike Frysinger67761632023-09-05 20:24:16 +0000814 origin_root_dir = self.root_dir
815 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000816
Mike Frysinger67761632023-09-05 20:24:16 +0000817 self.root_dir = tempfile.mkdtemp()
818 self.relpath = '.'
819 self.base_path = join(self.root_dir, self.relpath)
820 url_with_commit_ref = origin_root_dir +\
821 '@a7142dc9f0009350b96a11f372b6ea658592aa95'
Edward Lesmese79107e2019-10-25 22:47:33 +0000822
Mike Frysinger67761632023-09-05 20:24:16 +0000823 scm = gclient_scm.GitWrapper(url_with_commit_ref, self.root_dir,
824 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000825
Mike Frysinger67761632023-09-05 20:24:16 +0000826 expected_file_list = [
827 join(self.base_path, "a"),
828 join(self.base_path, "b")
829 ]
830 file_list = []
831 options.revision = 'unmanaged'
832 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000833
Mike Frysinger67761632023-09-05 20:24:16 +0000834 self.assertEqual(file_list, expected_file_list)
835 self.assertEqual(scm.revinfo(options, (), None),
836 'a7142dc9f0009350b96a11f372b6ea658592aa95')
837 # indicates detached HEAD
838 self.assertEqual(self.getCurrentBranch(), None)
839 self.checkInStdout(
840 'Checked out a7142dc9f0009350b96a11f372b6ea658592aa95 to a detached HEAD'
841 )
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000842
Mike Frysinger67761632023-09-05 20:24:16 +0000843 def testUpdateCloneOnBranch(self):
844 if not self.enabled:
845 return
846 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000847
Mike Frysinger67761632023-09-05 20:24:16 +0000848 origin_root_dir = self.root_dir
849 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000850
Mike Frysinger67761632023-09-05 20:24:16 +0000851 self.root_dir = tempfile.mkdtemp()
852 self.relpath = '.'
853 self.base_path = join(self.root_dir, self.relpath)
854 url_with_branch_ref = origin_root_dir + '@feature'
Edward Lesmese79107e2019-10-25 22:47:33 +0000855
Mike Frysinger67761632023-09-05 20:24:16 +0000856 scm = gclient_scm.GitWrapper(url_with_branch_ref, self.root_dir,
857 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000858
Mike Frysinger67761632023-09-05 20:24:16 +0000859 expected_file_list = [
860 join(self.base_path, "a"),
861 join(self.base_path, "b"),
862 join(self.base_path, "c")
863 ]
864 file_list = []
865 options.revision = 'unmanaged'
866 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000867
Mike Frysinger67761632023-09-05 20:24:16 +0000868 self.assertEqual(file_list, expected_file_list)
869 self.assertEqual(scm.revinfo(options, (), None),
870 '9a51244740b25fa2ded5252ca00a3178d3f665a9')
871 # indicates detached HEAD
872 self.assertEqual(self.getCurrentBranch(), None)
873 self.checkInStdout(
874 'Checked out 9a51244740b25fa2ded5252ca00a3178d3f665a9 '
875 'to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000876
Mike Frysinger67761632023-09-05 20:24:16 +0000877 def testUpdateCloneOnFetchedRemoteBranch(self):
878 if not self.enabled:
879 return
880 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000881
Mike Frysinger67761632023-09-05 20:24:16 +0000882 origin_root_dir = self.root_dir
883 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000884
Mike Frysinger67761632023-09-05 20:24:16 +0000885 self.root_dir = tempfile.mkdtemp()
886 self.relpath = '.'
887 self.base_path = join(self.root_dir, self.relpath)
888 url_with_branch_ref = origin_root_dir + '@refs/remotes/origin/feature'
Edward Lesmese79107e2019-10-25 22:47:33 +0000889
Mike Frysinger67761632023-09-05 20:24:16 +0000890 scm = gclient_scm.GitWrapper(url_with_branch_ref, self.root_dir,
891 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000892
Mike Frysinger67761632023-09-05 20:24:16 +0000893 expected_file_list = [
894 join(self.base_path, "a"),
895 join(self.base_path, "b"),
896 join(self.base_path, "c")
897 ]
898 file_list = []
899 options.revision = 'unmanaged'
900 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000901
Mike Frysinger67761632023-09-05 20:24:16 +0000902 self.assertEqual(file_list, expected_file_list)
903 self.assertEqual(scm.revinfo(options, (), None),
904 '9a51244740b25fa2ded5252ca00a3178d3f665a9')
905 # indicates detached HEAD
906 self.assertEqual(self.getCurrentBranch(), None)
907 self.checkInStdout(
908 'Checked out refs/remotes/origin/feature to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000909
Mike Frysinger67761632023-09-05 20:24:16 +0000910 def testUpdateCloneOnTrueRemoteBranch(self):
911 if not self.enabled:
912 return
913 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000914
Mike Frysinger67761632023-09-05 20:24:16 +0000915 origin_root_dir = self.root_dir
916 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000917
Mike Frysinger67761632023-09-05 20:24:16 +0000918 self.root_dir = tempfile.mkdtemp()
919 self.relpath = '.'
920 self.base_path = join(self.root_dir, self.relpath)
921 url_with_branch_ref = origin_root_dir + '@refs/heads/feature'
Edward Lesmese79107e2019-10-25 22:47:33 +0000922
Mike Frysinger67761632023-09-05 20:24:16 +0000923 scm = gclient_scm.GitWrapper(url_with_branch_ref, self.root_dir,
924 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000925
Mike Frysinger67761632023-09-05 20:24:16 +0000926 expected_file_list = [
927 join(self.base_path, "a"),
928 join(self.base_path, "b"),
929 join(self.base_path, "c")
930 ]
931 file_list = []
932 options.revision = 'unmanaged'
933 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000934
Mike Frysinger67761632023-09-05 20:24:16 +0000935 self.assertEqual(file_list, expected_file_list)
936 self.assertEqual(scm.revinfo(options, (), None),
937 '9a51244740b25fa2ded5252ca00a3178d3f665a9')
938 # @refs/heads/feature is AKA @refs/remotes/origin/feature in the clone,
939 # so should be treated as such by gclient. TODO(mmoss): Though really,
940 # we should only allow DEPS to specify branches as they are known in the
941 # upstream repo, since the mapping into the local repo can be modified
942 # by users (or we might even want to change the gclient defaults at some
943 # point). But that will take more work to stop using refs/remotes/
944 # everywhere that we do (and to stop assuming a DEPS ref will always
945 # resolve locally, like when passing them to show-ref or rev-list).
946 self.assertEqual(self.getCurrentBranch(), None)
947 self.checkInStdout(
948 'Checked out refs/remotes/origin/feature to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000949
Mike Frysinger67761632023-09-05 20:24:16 +0000950 def testUpdateUpdate(self):
951 if not self.enabled:
952 return
953 options = self.Options()
954 expected_file_list = []
955 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
956 file_list = []
957 options.revision = 'unmanaged'
958 scm.update(options, (), file_list)
959 self.assertEqual(file_list, expected_file_list)
960 self.assertEqual(scm.revinfo(options, (), None),
961 '069c602044c5388d2d15c3f875b057c852003458')
962 self.checkstdout('________ unmanaged solution; skipping .\n')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000963
964
Edward Lemur979fa782019-08-13 22:44:05 +0000965class CipdWrapperTestCase(unittest.TestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000966 def setUp(self):
967 # Create this before setting up mocks.
968 self._cipd_root_dir = tempfile.mkdtemp()
969 self._workdir = tempfile.mkdtemp()
John Budorick0f7b2002018-01-19 15:46:17 -0800970
Mike Frysinger67761632023-09-05 20:24:16 +0000971 self._cipd_instance_url = 'https://chrome-infra-packages.appspot.com'
972 self._cipd_root = gclient_scm.CipdRoot(self._cipd_root_dir,
973 self._cipd_instance_url)
974 self._cipd_packages = [
975 self._cipd_root.add_package('f', 'foo_package', 'foo_version'),
976 self._cipd_root.add_package('b', 'bar_package', 'bar_version'),
977 self._cipd_root.add_package('b', 'baz_package', 'baz_version'),
978 ]
979 mock.patch('tempfile.mkdtemp', lambda: self._workdir).start()
980 mock.patch('gclient_scm.CipdRoot.add_package').start()
981 mock.patch('gclient_scm.CipdRoot.clobber').start()
982 mock.patch('gclient_scm.CipdRoot.ensure_file_resolve').start()
983 mock.patch('gclient_scm.CipdRoot.ensure').start()
984 self.addCleanup(mock.patch.stopall)
985 self.addCleanup(gclient_utils.rmtree, self._cipd_root_dir)
986 self.addCleanup(gclient_utils.rmtree, self._workdir)
John Budorick0f7b2002018-01-19 15:46:17 -0800987
Mike Frysinger67761632023-09-05 20:24:16 +0000988 def createScmWithPackageThatSatisfies(self, condition):
989 return gclient_scm.CipdWrapper(
990 url=self._cipd_instance_url,
991 root_dir=self._cipd_root_dir,
992 relpath='fake_relpath',
993 root=self._cipd_root,
994 package=self.getPackageThatSatisfies(condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800995
Mike Frysinger67761632023-09-05 20:24:16 +0000996 def getPackageThatSatisfies(self, condition):
997 for p in self._cipd_packages:
998 if condition(p):
999 return p
John Budorick0f7b2002018-01-19 15:46:17 -08001000
Mike Frysinger67761632023-09-05 20:24:16 +00001001 self.fail('Unable to find a satisfactory package.')
John Budorick0f7b2002018-01-19 15:46:17 -08001002
Mike Frysinger67761632023-09-05 20:24:16 +00001003 def testRevert(self):
1004 """Checks that revert does nothing."""
1005 scm = self.createScmWithPackageThatSatisfies(lambda _: True)
1006 scm.revert(None, (), [])
John Budorick0f7b2002018-01-19 15:46:17 -08001007
Mike Frysinger67761632023-09-05 20:24:16 +00001008 @mock.patch('gclient_scm.gclient_utils.CheckCallAndFilter')
1009 @mock.patch('gclient_scm.gclient_utils.rmtree')
1010 def testRevinfo(self, mockRmtree, mockCheckCallAndFilter):
1011 """Checks that revinfo uses the JSON from cipd describe."""
1012 scm = self.createScmWithPackageThatSatisfies(lambda _: True)
John Budorick0f7b2002018-01-19 15:46:17 -08001013
Mike Frysinger67761632023-09-05 20:24:16 +00001014 expected_revinfo = '0123456789abcdef0123456789abcdef01234567'
1015 json_contents = {
1016 'result': {
1017 'pin': {
1018 'instance_id': expected_revinfo,
1019 }
John Budorick0f7b2002018-01-19 15:46:17 -08001020 }
1021 }
Mike Frysinger67761632023-09-05 20:24:16 +00001022 describe_json_path = join(self._workdir, 'describe.json')
1023 with open(describe_json_path, 'w') as describe_json:
1024 json.dump(json_contents, describe_json)
John Budorick0f7b2002018-01-19 15:46:17 -08001025
Mike Frysinger67761632023-09-05 20:24:16 +00001026 revinfo = scm.revinfo(None, (), [])
1027 self.assertEqual(revinfo, expected_revinfo)
Edward Lemur979fa782019-08-13 22:44:05 +00001028
Mike Frysinger67761632023-09-05 20:24:16 +00001029 mockRmtree.assert_called_with(self._workdir)
1030 mockCheckCallAndFilter.assert_called_with([
1031 'cipd',
1032 'describe',
1033 'foo_package',
1034 '-log-level',
1035 'error',
1036 '-version',
1037 'foo_version',
1038 '-json-output',
1039 describe_json_path,
1040 ])
John Budorick0f7b2002018-01-19 15:46:17 -08001041
Mike Frysinger67761632023-09-05 20:24:16 +00001042 def testUpdate(self):
1043 """Checks that update does nothing."""
1044 scm = self.createScmWithPackageThatSatisfies(lambda _: True)
1045 scm.update(None, (), [])
John Budorick0f7b2002018-01-19 15:46:17 -08001046
1047
Edward Lesmes8073a502020-04-15 02:11:14 +00001048class BranchHeadsFakeRepo(fake_repos.FakeReposBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001049 def populateGit(self):
1050 # Creates a tree that looks like this:
1051 #
1052 # 5 refs/branch-heads/5
1053 # |
1054 # 4
1055 # |
1056 # 1--2--3 refs/heads/main
1057 self._commit_git('repo_1', {'commit 1': 'touched'})
1058 self._commit_git('repo_1', {'commit 2': 'touched'})
1059 self._commit_git('repo_1', {'commit 3': 'touched'})
1060 self._create_ref('repo_1', 'refs/heads/main', 3)
Edward Lesmes8073a502020-04-15 02:11:14 +00001061
Mike Frysinger67761632023-09-05 20:24:16 +00001062 self._commit_git('repo_1', {'commit 4': 'touched'}, base=2)
1063 self._commit_git('repo_1', {'commit 5': 'touched'}, base=2)
1064 self._create_ref('repo_1', 'refs/branch-heads/5', 5)
Edward Lesmes8073a502020-04-15 02:11:14 +00001065
1066
1067class BranchHeadsTest(fake_repos.FakeReposTestBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001068 FAKE_REPOS_CLASS = BranchHeadsFakeRepo
Edward Lesmes8073a502020-04-15 02:11:14 +00001069
Mike Frysinger67761632023-09-05 20:24:16 +00001070 def setUp(self):
1071 super(BranchHeadsTest, self).setUp()
1072 self.enabled = self.FAKE_REPOS.set_up_git()
1073 self.options = BaseGitWrapperTestCase.OptionsObject()
1074 self.url = self.git_base + 'repo_1'
1075 self.mirror = None
1076 mock.patch('sys.stdout', StringIO()).start()
1077 self.addCleanup(mock.patch.stopall)
Edward Lesmes8073a502020-04-15 02:11:14 +00001078
Mike Frysinger67761632023-09-05 20:24:16 +00001079 def setUpMirror(self):
1080 self.mirror = tempfile.mkdtemp('mirror')
1081 git_cache.Mirror.SetCachePath(self.mirror)
1082 self.addCleanup(gclient_utils.rmtree, self.mirror)
1083 self.addCleanup(git_cache.Mirror.SetCachePath, None)
Edward Lesmes8073a502020-04-15 02:11:14 +00001084
Mike Frysinger67761632023-09-05 20:24:16 +00001085 def testCheckoutBranchHeads(self):
1086 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1087 file_list = []
Edward Lesmes8073a502020-04-15 02:11:14 +00001088
Mike Frysinger67761632023-09-05 20:24:16 +00001089 self.options.revision = 'refs/branch-heads/5'
1090 scm.update(self.options, None, file_list)
1091 self.assertEqual(self.githash('repo_1', 5),
1092 self.gitrevparse(self.root_dir))
Edward Lesmes8073a502020-04-15 02:11:14 +00001093
Mike Frysinger67761632023-09-05 20:24:16 +00001094 def testCheckoutUpdatedBranchHeads(self):
1095 # Travel back in time, and set refs/branch-heads/5 to its parent.
1096 subprocess2.check_call([
1097 'git', 'update-ref', 'refs/branch-heads/5',
1098 self.githash('repo_1', 4)
1099 ],
1100 cwd=self.url)
Edward Lesmes8073a502020-04-15 02:11:14 +00001101
Mike Frysinger67761632023-09-05 20:24:16 +00001102 # Sync to refs/branch-heads/5
1103 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1104 self.options.revision = 'refs/branch-heads/5'
1105 scm.update(self.options, None, [])
Edward Lesmes8073a502020-04-15 02:11:14 +00001106
Mike Frysinger67761632023-09-05 20:24:16 +00001107 # Set refs/branch-heads/5 back to its original value.
1108 subprocess2.check_call([
1109 'git', 'update-ref', 'refs/branch-heads/5',
1110 self.githash('repo_1', 5)
1111 ],
1112 cwd=self.url)
Edward Lesmes8073a502020-04-15 02:11:14 +00001113
Mike Frysinger67761632023-09-05 20:24:16 +00001114 # Attempt to sync to refs/branch-heads/5 again.
1115 self.testCheckoutBranchHeads()
Edward Lesmes8073a502020-04-15 02:11:14 +00001116
Mike Frysinger67761632023-09-05 20:24:16 +00001117 def testCheckoutBranchHeadsMirror(self):
1118 self.setUpMirror()
1119 self.testCheckoutBranchHeads()
Edward Lesmes8073a502020-04-15 02:11:14 +00001120
Mike Frysinger67761632023-09-05 20:24:16 +00001121 def testCheckoutUpdatedBranchHeadsMirror(self):
1122 self.setUpMirror()
1123 self.testCheckoutUpdatedBranchHeads()
Edward Lesmes8073a502020-04-15 02:11:14 +00001124
1125
Edward Lemurd64781e2018-07-11 23:09:55 +00001126class GerritChangesFakeRepo(fake_repos.FakeReposBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001127 def populateGit(self):
1128 # Creates a tree that looks like this:
1129 #
1130 # 6 refs/changes/35/1235/1
1131 # |
1132 # 5 refs/changes/34/1234/1
1133 # |
1134 # 1--2--3--4 refs/heads/main
1135 # | |
1136 # | 11(5)--12 refs/heads/main-with-5
1137 # |
1138 # 7--8--9 refs/heads/feature
1139 # |
1140 # 10 refs/changes/36/1236/1
1141 #
Edward Lemurd64781e2018-07-11 23:09:55 +00001142
Mike Frysinger67761632023-09-05 20:24:16 +00001143 self._commit_git('repo_1', {'commit 1': 'touched'})
1144 self._commit_git('repo_1', {'commit 2': 'touched'})
1145 self._commit_git('repo_1', {'commit 3': 'touched'})
1146 self._commit_git('repo_1', {'commit 4': 'touched'})
1147 self._create_ref('repo_1', 'refs/heads/main', 4)
Edward Lemurd64781e2018-07-11 23:09:55 +00001148
Mike Frysinger67761632023-09-05 20:24:16 +00001149 # Create a change on top of commit 3 that consists of two commits.
1150 self._commit_git('repo_1', {
1151 'commit 5': 'touched',
1152 'change': '1234'
1153 },
1154 base=3)
1155 self._create_ref('repo_1', 'refs/changes/34/1234/1', 5)
1156 self._commit_git('repo_1', {'commit 6': 'touched', 'change': '1235'})
1157 self._create_ref('repo_1', 'refs/changes/35/1235/1', 6)
Edward Lemurd64781e2018-07-11 23:09:55 +00001158
Mike Frysinger67761632023-09-05 20:24:16 +00001159 # Create a refs/heads/feature branch on top of commit 2, consisting of
1160 # three commits.
1161 self._commit_git('repo_1', {'commit 7': 'touched'}, base=2)
1162 self._commit_git('repo_1', {'commit 8': 'touched'})
1163 self._commit_git('repo_1', {'commit 9': 'touched'})
1164 self._create_ref('repo_1', 'refs/heads/feature', 9)
Edward Lemurca7d8812018-07-24 17:42:45 +00001165
Mike Frysinger67761632023-09-05 20:24:16 +00001166 # Create a change of top of commit 8.
1167 self._commit_git('repo_1', {
1168 'commit 10': 'touched',
1169 'change': '1236'
1170 },
1171 base=8)
1172 self._create_ref('repo_1', 'refs/changes/36/1236/1', 10)
Edward Lemurca7d8812018-07-24 17:42:45 +00001173
Mike Frysinger67761632023-09-05 20:24:16 +00001174 # Create a refs/heads/main-with-5 on top of commit 3 which is a branch
1175 # where refs/changes/34/1234/1 (commit 5) has already landed as commit
1176 # 11.
1177 self._commit_git(
1178 'repo_1',
1179 # This is really commit 11, but has the changes of commit 5
1180 {
1181 'commit 5': 'touched',
1182 'change': '1234'
1183 },
1184 base=3)
1185 self._commit_git('repo_1', {'commit 12': 'touched'})
1186 self._create_ref('repo_1', 'refs/heads/main-with-5', 12)
Edward Lemurca7d8812018-07-24 17:42:45 +00001187
Edward Lemurd64781e2018-07-11 23:09:55 +00001188
1189class GerritChangesTest(fake_repos.FakeReposTestBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001190 FAKE_REPOS_CLASS = GerritChangesFakeRepo
Edward Lemurd64781e2018-07-11 23:09:55 +00001191
Mike Frysinger67761632023-09-05 20:24:16 +00001192 def setUp(self):
1193 super(GerritChangesTest, self).setUp()
1194 self.enabled = self.FAKE_REPOS.set_up_git()
1195 self.options = BaseGitWrapperTestCase.OptionsObject()
1196 self.url = self.git_base + 'repo_1'
1197 self.mirror = None
1198 mock.patch('sys.stdout', StringIO()).start()
1199 self.addCleanup(mock.patch.stopall)
Edward Lemurd64781e2018-07-11 23:09:55 +00001200
Mike Frysinger67761632023-09-05 20:24:16 +00001201 def setUpMirror(self):
1202 self.mirror = tempfile.mkdtemp()
1203 git_cache.Mirror.SetCachePath(self.mirror)
1204 self.addCleanup(gclient_utils.rmtree, self.mirror)
1205 self.addCleanup(git_cache.Mirror.SetCachePath, None)
Edward Lemurd64781e2018-07-11 23:09:55 +00001206
Mike Frysinger67761632023-09-05 20:24:16 +00001207 def assertCommits(self, commits):
1208 """Check that all, and only |commits| are present in the current checkout.
Edward Lemurca7d8812018-07-24 17:42:45 +00001209 """
Mike Frysinger67761632023-09-05 20:24:16 +00001210 for i in commits:
1211 name = os.path.join(self.root_dir, 'commit ' + str(i))
1212 self.assertTrue(os.path.exists(name), 'Commit not found: %s' % name)
Edward Lemurca7d8812018-07-24 17:42:45 +00001213
Mike Frysinger67761632023-09-05 20:24:16 +00001214 all_commits = set(range(1, len(self.FAKE_REPOS.git_hashes['repo_1'])))
1215 for i in all_commits - set(commits):
1216 name = os.path.join(self.root_dir, 'commit ' + str(i))
1217 self.assertFalse(os.path.exists(name),
1218 'Unexpected commit: %s' % name)
Edward Lemurca7d8812018-07-24 17:42:45 +00001219
Mike Frysinger67761632023-09-05 20:24:16 +00001220 def testCanCloneGerritChange(self):
1221 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1222 file_list = []
Edward Lemurd64781e2018-07-11 23:09:55 +00001223
Mike Frysinger67761632023-09-05 20:24:16 +00001224 self.options.revision = 'refs/changes/35/1235/1'
1225 scm.update(self.options, None, file_list)
1226 self.assertEqual(self.githash('repo_1', 6),
1227 self.gitrevparse(self.root_dir))
Edward Lemurd64781e2018-07-11 23:09:55 +00001228
Mike Frysinger67761632023-09-05 20:24:16 +00001229 def testCanSyncToGerritChange(self):
1230 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1231 file_list = []
Edward Lemurd64781e2018-07-11 23:09:55 +00001232
Mike Frysinger67761632023-09-05 20:24:16 +00001233 self.options.revision = self.githash('repo_1', 1)
1234 scm.update(self.options, None, file_list)
1235 self.assertEqual(self.githash('repo_1', 1),
1236 self.gitrevparse(self.root_dir))
Edward Lemurd64781e2018-07-11 23:09:55 +00001237
Mike Frysinger67761632023-09-05 20:24:16 +00001238 self.options.revision = 'refs/changes/35/1235/1'
1239 scm.update(self.options, None, file_list)
1240 self.assertEqual(self.githash('repo_1', 6),
1241 self.gitrevparse(self.root_dir))
Edward Lemurd64781e2018-07-11 23:09:55 +00001242
Mike Frysinger67761632023-09-05 20:24:16 +00001243 def testCanCloneGerritChangeMirror(self):
1244 self.setUpMirror()
1245 self.testCanCloneGerritChange()
Edward Lemurd64781e2018-07-11 23:09:55 +00001246
Mike Frysinger67761632023-09-05 20:24:16 +00001247 def testCanSyncToGerritChangeMirror(self):
1248 self.setUpMirror()
1249 self.testCanSyncToGerritChange()
Edward Lemurd64781e2018-07-11 23:09:55 +00001250
Mike Frysinger67761632023-09-05 20:24:16 +00001251 def testMirrorPushUrl(self):
1252 self.setUpMirror()
Edward Lesmese79107e2019-10-25 22:47:33 +00001253
Mike Frysinger67761632023-09-05 20:24:16 +00001254 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1255 file_list = []
1256 self.assertIsNotNone(scm._GetMirror(self.url, self.options))
Edward Lesmese79107e2019-10-25 22:47:33 +00001257
Mike Frysinger67761632023-09-05 20:24:16 +00001258 scm.update(self.options, None, file_list)
Edward Lesmese79107e2019-10-25 22:47:33 +00001259
Mike Frysinger67761632023-09-05 20:24:16 +00001260 fetch_url = scm._Capture(['remote', 'get-url', 'origin'])
1261 self.assertTrue(
1262 fetch_url.startswith(self.mirror),
1263 msg='\n'.join([
1264 'Repository fetch url should be in the git cache mirror directory.',
1265 ' fetch_url: %s' % fetch_url,
1266 ' mirror: %s' % self.mirror
1267 ]))
1268 push_url = scm._Capture(['remote', 'get-url', '--push', 'origin'])
1269 self.assertEqual(push_url, self.url)
Edward Lesmese79107e2019-10-25 22:47:33 +00001270
Mike Frysinger67761632023-09-05 20:24:16 +00001271 def testAppliesPatchOnTopOfMasterByDefault(self):
1272 """Test the default case, where we apply a patch on top of main."""
1273 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1274 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001275
Mike Frysinger67761632023-09-05 20:24:16 +00001276 # Make sure we don't specify a revision.
1277 self.options.revision = None
1278 scm.update(self.options, None, file_list)
1279 self.assertEqual(self.githash('repo_1', 4),
1280 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001281
Mike Frysinger67761632023-09-05 20:24:16 +00001282 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1283 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001284
Mike Frysinger67761632023-09-05 20:24:16 +00001285 self.assertCommits([1, 2, 3, 4, 5, 6])
1286 self.assertEqual(self.githash('repo_1', 4),
1287 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001288
Mike Frysinger67761632023-09-05 20:24:16 +00001289 def testCheckoutOlderThanPatchBase(self):
1290 """Test applying a patch on an old checkout.
Edward Lemurca7d8812018-07-24 17:42:45 +00001291
1292 We first checkout commit 1, and try to patch refs/changes/35/1235/1, which
1293 contains commits 5 and 6, and is based on top of commit 3.
1294 The final result should contain commits 1, 5 and 6, but not commits 2 or 3.
1295 """
Mike Frysinger67761632023-09-05 20:24:16 +00001296 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1297 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001298
Mike Frysinger67761632023-09-05 20:24:16 +00001299 # Sync to commit 1
1300 self.options.revision = self.githash('repo_1', 1)
1301 scm.update(self.options, None, file_list)
1302 self.assertEqual(self.githash('repo_1', 1),
1303 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001304
Mike Frysinger67761632023-09-05 20:24:16 +00001305 # Apply the change on top of that.
1306 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1307 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001308
Mike Frysinger67761632023-09-05 20:24:16 +00001309 self.assertCommits([1, 5, 6])
1310 self.assertEqual(self.githash('repo_1', 1),
1311 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001312
Mike Frysinger67761632023-09-05 20:24:16 +00001313 def testCheckoutOriginFeature(self):
1314 """Tests that we can apply a patch on a branch other than main."""
1315 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1316 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001317
Mike Frysinger67761632023-09-05 20:24:16 +00001318 # Sync to remote's refs/heads/feature
1319 self.options.revision = 'refs/heads/feature'
1320 scm.update(self.options, None, file_list)
1321 self.assertEqual(self.githash('repo_1', 9),
1322 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001323
Mike Frysinger67761632023-09-05 20:24:16 +00001324 # Apply the change on top of that.
1325 scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1',
1326 'refs/heads/feature', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001327
Mike Frysinger67761632023-09-05 20:24:16 +00001328 self.assertCommits([1, 2, 7, 8, 9, 10])
1329 self.assertEqual(self.githash('repo_1', 9),
1330 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001331
Mike Frysinger67761632023-09-05 20:24:16 +00001332 def testCheckoutOriginFeatureOnOldRevision(self):
1333 """Tests that we can apply a patch on an old checkout, on a branch other
Josip Sokcevic7e133ff2021-07-13 17:44:53 +00001334 than main."""
Mike Frysinger67761632023-09-05 20:24:16 +00001335 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1336 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001337
Mike Frysinger67761632023-09-05 20:24:16 +00001338 # Sync to remote's refs/heads/feature on an old revision
1339 self.options.revision = self.githash('repo_1', 7)
1340 scm.update(self.options, None, file_list)
1341 self.assertEqual(self.githash('repo_1', 7),
1342 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001343
Mike Frysinger67761632023-09-05 20:24:16 +00001344 # Apply the change on top of that.
1345 scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1',
1346 'refs/heads/feature', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001347
Mike Frysinger67761632023-09-05 20:24:16 +00001348 # We shouldn't have rebased on top of 2 (which is the merge base between
1349 # remote's main branch and the change) but on top of 7 (which is the
1350 # merge base between remote's feature branch and the change).
1351 self.assertCommits([1, 2, 7, 10])
1352 self.assertEqual(self.githash('repo_1', 7),
1353 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001354
Mike Frysinger67761632023-09-05 20:24:16 +00001355 def testCheckoutOriginFeaturePatchBranch(self):
1356 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1357 file_list = []
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001358
Mike Frysinger67761632023-09-05 20:24:16 +00001359 # Sync to the hash instead of remote's refs/heads/feature.
1360 self.options.revision = self.githash('repo_1', 9)
1361 scm.update(self.options, None, file_list)
1362 self.assertEqual(self.githash('repo_1', 9),
1363 self.gitrevparse(self.root_dir))
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001364
Mike Frysinger67761632023-09-05 20:24:16 +00001365 # Apply refs/changes/34/1234/1, created for remote's main branch on top
1366 # of remote's feature branch.
1367 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1368 'refs/heads/main', self.options, file_list)
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001369
Mike Frysinger67761632023-09-05 20:24:16 +00001370 # Commits 5 and 6 are part of the patch, and commits 1, 2, 7, 8 and 9
1371 # are part of remote's feature branch.
1372 self.assertCommits([1, 2, 5, 6, 7, 8, 9])
1373 self.assertEqual(self.githash('repo_1', 9),
1374 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001375
Mike Frysinger67761632023-09-05 20:24:16 +00001376 def testDoesntRebasePatchMaster(self):
1377 """Tests that we can apply a patch without rebasing it.
Edward Lemurca7d8812018-07-24 17:42:45 +00001378 """
Mike Frysinger67761632023-09-05 20:24:16 +00001379 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1380 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001381
Mike Frysinger67761632023-09-05 20:24:16 +00001382 self.options.rebase_patch_ref = False
1383 scm.update(self.options, None, file_list)
1384 self.assertEqual(self.githash('repo_1', 4),
1385 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001386
Mike Frysinger67761632023-09-05 20:24:16 +00001387 # Apply the change on top of that.
1388 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1389 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001390
Mike Frysinger67761632023-09-05 20:24:16 +00001391 self.assertCommits([1, 2, 3, 5, 6])
1392 self.assertEqual(self.githash('repo_1', 5),
1393 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001394
Mike Frysinger67761632023-09-05 20:24:16 +00001395 def testDoesntRebasePatchOldCheckout(self):
1396 """Tests that we can apply a patch without rebasing it on an old checkout.
Edward Lemurca7d8812018-07-24 17:42:45 +00001397 """
Mike Frysinger67761632023-09-05 20:24:16 +00001398 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1399 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001400
Mike Frysinger67761632023-09-05 20:24:16 +00001401 # Sync to commit 1
1402 self.options.revision = self.githash('repo_1', 1)
1403 self.options.rebase_patch_ref = False
1404 scm.update(self.options, None, file_list)
1405 self.assertEqual(self.githash('repo_1', 1),
1406 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001407
Mike Frysinger67761632023-09-05 20:24:16 +00001408 # Apply the change on top of that.
1409 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1410 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001411
Mike Frysinger67761632023-09-05 20:24:16 +00001412 self.assertCommits([1, 2, 3, 5, 6])
1413 self.assertEqual(self.githash('repo_1', 5),
1414 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001415
Mike Frysinger67761632023-09-05 20:24:16 +00001416 def testDoesntSoftResetIfNotAskedTo(self):
1417 """Test that we can apply a patch without doing a soft reset."""
1418 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1419 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001420
Mike Frysinger67761632023-09-05 20:24:16 +00001421 self.options.reset_patch_ref = False
1422 scm.update(self.options, None, file_list)
1423 self.assertEqual(self.githash('repo_1', 4),
1424 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001425
Mike Frysinger67761632023-09-05 20:24:16 +00001426 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1427 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001428
Mike Frysinger67761632023-09-05 20:24:16 +00001429 self.assertCommits([1, 2, 3, 4, 5, 6])
1430 # The commit hash after cherry-picking is not known, but it must be
1431 # different from what the repo was synced at before patching.
1432 self.assertNotEqual(self.githash('repo_1', 4),
1433 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001434
Mike Frysinger67761632023-09-05 20:24:16 +00001435 @mock.patch('gerrit_util.GetChange', return_value={'topic': 'test_topic'})
1436 @mock.patch('gerrit_util.QueryChanges',
1437 return_value=[{
1438 '_number': 1234
1439 }, {
1440 '_number': 1235,
1441 'current_revision': 'abc',
1442 'revisions': {
1443 'abc': {
1444 'ref': 'refs/changes/35/1235/1'
1445 }
1446 }
1447 }])
1448 def testDownloadTopics(self, query_changes_mock, get_change_mock):
1449 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1450 file_list = []
Ravi Mistryecda7822022-02-28 16:22:20 +00001451
Mike Frysinger67761632023-09-05 20:24:16 +00001452 self.options.revision = 'refs/changes/34/1234/1'
1453 scm.update(self.options, None, file_list)
1454 self.assertEqual(self.githash('repo_1', 5),
1455 self.gitrevparse(self.root_dir))
Ravi Mistryecda7822022-02-28 16:22:20 +00001456
Mike Frysinger67761632023-09-05 20:24:16 +00001457 # pylint: disable=attribute-defined-outside-init
1458 self.options.download_topics = True
1459 scm.url = 'https://test-repo.googlesource.com/repo_1.git'
1460 scm.apply_patch_ref(self.url, 'refs/changes/34/1234/1',
1461 'refs/heads/main', self.options, file_list)
Ravi Mistryecda7822022-02-28 16:22:20 +00001462
Mike Frysinger67761632023-09-05 20:24:16 +00001463 get_change_mock.assert_called_once_with(mock.ANY, '1234')
1464 query_changes_mock.assert_called_once_with(mock.ANY,
1465 [('topic', 'test_topic'),
1466 ('status', 'open'),
1467 ('repo', 'repo_1')],
1468 o_params=['ALL_REVISIONS'])
Ravi Mistryecda7822022-02-28 16:22:20 +00001469
Mike Frysinger67761632023-09-05 20:24:16 +00001470 self.assertCommits([1, 2, 3, 5, 6])
1471 # The commit hash after the two cherry-picks is not known, but it must
1472 # be different from what the repo was synced at before patching.
1473 self.assertNotEqual(self.githash('repo_1', 4),
1474 self.gitrevparse(self.root_dir))
Ravi Mistryecda7822022-02-28 16:22:20 +00001475
Mike Frysinger67761632023-09-05 20:24:16 +00001476 def testRecoversAfterPatchFailure(self):
1477 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1478 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001479
Mike Frysinger67761632023-09-05 20:24:16 +00001480 self.options.revision = 'refs/changes/34/1234/1'
1481 scm.update(self.options, None, file_list)
1482 self.assertEqual(self.githash('repo_1', 5),
1483 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001484
Mike Frysinger67761632023-09-05 20:24:16 +00001485 # Checkout 'refs/changes/34/1234/1' modifies the 'change' file, so
1486 # trying to patch 'refs/changes/36/1236/1' creates a patch failure.
1487 with self.assertRaises(subprocess2.CalledProcessError) as cm:
1488 scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1',
1489 'refs/heads/main', self.options, file_list)
1490 self.assertEqual(cm.exception.cmd[:2], ['git', 'cherry-pick'])
1491 self.assertIn(b'error: could not apply', cm.exception.stderr)
Edward Lemurca7d8812018-07-24 17:42:45 +00001492
Mike Frysinger67761632023-09-05 20:24:16 +00001493 # Try to apply 'refs/changes/35/1235/1', which doesn't have a merge
1494 # conflict.
1495 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1496 'refs/heads/main', self.options, file_list)
1497 self.assertCommits([1, 2, 3, 5, 6])
1498 self.assertEqual(self.githash('repo_1', 5),
1499 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001500
Mike Frysinger67761632023-09-05 20:24:16 +00001501 def testIgnoresAlreadyMergedCommits(self):
1502 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1503 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001504
Mike Frysinger67761632023-09-05 20:24:16 +00001505 self.options.revision = 'refs/heads/main-with-5'
1506 scm.update(self.options, None, file_list)
1507 self.assertEqual(self.githash('repo_1', 12),
1508 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001509
Mike Frysinger67761632023-09-05 20:24:16 +00001510 # When we try 'refs/changes/35/1235/1' on top of 'refs/heads/feature',
1511 # 'refs/changes/34/1234/1' will be an empty commit, since the changes
1512 # were already present in the tree as commit 11. Make sure we deal with
1513 # this gracefully.
1514 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1515 'refs/heads/feature', self.options, file_list)
1516 self.assertCommits([1, 2, 3, 5, 6, 12])
1517 self.assertEqual(self.githash('repo_1', 12),
1518 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001519
Mike Frysinger67761632023-09-05 20:24:16 +00001520 def testRecoversFromExistingCherryPick(self):
1521 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1522 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001523
Mike Frysinger67761632023-09-05 20:24:16 +00001524 self.options.revision = 'refs/changes/34/1234/1'
1525 scm.update(self.options, None, file_list)
1526 self.assertEqual(self.githash('repo_1', 5),
1527 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001528
Mike Frysinger67761632023-09-05 20:24:16 +00001529 # Checkout 'refs/changes/34/1234/1' modifies the 'change' file, so
1530 # trying to cherry-pick 'refs/changes/36/1236/1' raises an error.
1531 scm._Run(['fetch', 'origin', 'refs/changes/36/1236/1'], self.options)
1532 with self.assertRaises(subprocess2.CalledProcessError) as cm:
1533 scm._Run(['cherry-pick', 'FETCH_HEAD'], self.options)
1534 self.assertEqual(cm.exception.cmd[:2], ['git', 'cherry-pick'])
Edward Lemurca7d8812018-07-24 17:42:45 +00001535
Mike Frysinger67761632023-09-05 20:24:16 +00001536 # Try to apply 'refs/changes/35/1235/1', which doesn't have a merge
1537 # conflict.
1538 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1539 'refs/heads/main', self.options, file_list)
1540 self.assertCommits([1, 2, 3, 5, 6])
1541 self.assertEqual(self.githash('repo_1', 5),
1542 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001543
Edward Lemurd64781e2018-07-11 23:09:55 +00001544
Joanna Wang5a7c8242022-07-01 19:09:00 +00001545class DepsChangesFakeRepo(fake_repos.FakeReposBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001546 def populateGit(self):
1547 self._commit_git('repo_1', {'DEPS': 'versionA', 'doesnotmatter': 'B'})
1548 self._commit_git('repo_1', {'DEPS': 'versionA', 'doesnotmatter': 'C'})
Joanna Wang5a7c8242022-07-01 19:09:00 +00001549
Mike Frysinger67761632023-09-05 20:24:16 +00001550 self._commit_git('repo_1', {'DEPS': 'versionB'})
1551 self._commit_git('repo_1', {'DEPS': 'versionA', 'doesnotmatter': 'C'})
1552 self._create_ref('repo_1', 'refs/heads/main', 4)
Joanna Wang5a7c8242022-07-01 19:09:00 +00001553
1554
1555class CheckDiffTest(fake_repos.FakeReposTestBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001556 FAKE_REPOS_CLASS = DepsChangesFakeRepo
Joanna Wang5a7c8242022-07-01 19:09:00 +00001557
Mike Frysinger67761632023-09-05 20:24:16 +00001558 def setUp(self):
1559 super(CheckDiffTest, self).setUp()
1560 self.enabled = self.FAKE_REPOS.set_up_git()
1561 self.options = BaseGitWrapperTestCase.OptionsObject()
1562 self.url = self.git_base + 'repo_1'
1563 self.mirror = None
1564 mock.patch('sys.stdout', StringIO()).start()
1565 self.addCleanup(mock.patch.stopall)
Joanna Wang5a7c8242022-07-01 19:09:00 +00001566
Mike Frysinger67761632023-09-05 20:24:16 +00001567 def setUpMirror(self):
1568 self.mirror = tempfile.mkdtemp()
1569 git_cache.Mirror.SetCachePath(self.mirror)
1570 self.addCleanup(gclient_utils.rmtree, self.mirror)
1571 self.addCleanup(git_cache.Mirror.SetCachePath, None)
Joanna Wang5a7c8242022-07-01 19:09:00 +00001572
Mike Frysinger67761632023-09-05 20:24:16 +00001573 def testCheckDiff(self):
1574 """Correctly check for diffs."""
1575 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1576 file_list = []
Joanna Wang5a7c8242022-07-01 19:09:00 +00001577
Mike Frysinger67761632023-09-05 20:24:16 +00001578 # Make sure we don't specify a revision.
1579 self.options.revision = None
1580 scm.update(self.options, None, file_list)
1581 self.assertEqual(self.githash('repo_1', 4),
1582 self.gitrevparse(self.root_dir))
Joanna Wang5a7c8242022-07-01 19:09:00 +00001583
Mike Frysinger67761632023-09-05 20:24:16 +00001584 self.assertFalse(
1585 scm.check_diff(self.githash('repo_1', 1), files=['DEPS']))
1586 self.assertTrue(scm.check_diff(self.githash('repo_1', 1)))
1587 self.assertTrue(
1588 scm.check_diff(self.githash('repo_1', 3), files=['DEPS']))
Joanna Wang5a7c8242022-07-01 19:09:00 +00001589
Mike Frysinger67761632023-09-05 20:24:16 +00001590 self.assertFalse(
1591 scm.check_diff(self.githash('repo_1', 2),
1592 files=['DEPS', 'doesnotmatter']))
1593 self.assertFalse(scm.check_diff(self.githash('repo_1', 2)))
Joanna Wang5a7c8242022-07-01 19:09:00 +00001594
1595
Joanna Wang1a977bd2022-06-02 21:51:17 +00001596if 'unittest.util' in __import__('sys').modules:
Mike Frysinger67761632023-09-05 20:24:16 +00001597 # Show full diff in self.assertEqual.
1598 __import__('sys').modules['unittest.util']._MAX_LENGTH = 999999999
Joanna Wang1a977bd2022-06-02 21:51:17 +00001599
msb@chromium.orge28e4982009-09-25 20:51:45 +00001600if __name__ == '__main__':
Mike Frysinger67761632023-09-05 20:24:16 +00001601 level = logging.DEBUG if '-v' in sys.argv else logging.FATAL
1602 logging.basicConfig(level=level,
1603 format='%(asctime).19s %(levelname)s %(filename)s:'
1604 '%(lineno)s %(message)s')
1605 unittest.main()
msb@chromium.orge28e4982009-09-25 20:51:45 +00001606
1607# vim: ts=2:sw=2:tw=80:et: