blob: b6fa179902c97c7ea078f035a2656f82fd5f2506 [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')
426 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000427
Mike Frysinger67761632023-09-05 20:24:16 +0000428 def testUpdateMerge(self):
429 if not self.enabled:
430 return
431 options = self.Options()
432 options.merge = True
433 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
434 scm._Run(['checkout', '-q', 'feature'], options)
435 rev = scm.revinfo(options, (), None)
436 file_list = []
437 scm.update(options, (), file_list)
438 self.assertEqual(file_list,
439 [join(self.base_path, x) for x in ['a', 'b', 'c']])
440 # The actual commit that is created is unstable, so we verify its tree
441 # and parents instead.
442 self.assertEqual(scm._Capture(['rev-parse', 'HEAD:']),
443 'd2e35c10ac24d6c621e14a1fcadceb533155627d')
444 parent = 'HEAD^' if sys.platform != 'win32' else 'HEAD^^'
445 self.assertEqual(scm._Capture(['rev-parse', parent + '1']), rev)
446 self.assertEqual(scm._Capture(['rev-parse', parent + '2']),
447 scm._Capture(['rev-parse', 'origin/main']))
448 sys.stdout.close()
Joanna Wang4e6c1072023-08-17 18:46:24 +0000449
Mike Frysinger67761632023-09-05 20:24:16 +0000450 def testUpdateRebase(self):
451 if not self.enabled:
452 return
453 options = self.Options()
454 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
455 scm._Run(['checkout', '-q', 'feature'], options)
456 file_list = []
457 # Fake a 'y' key press.
458 scm._AskForData = self._GetAskForDataCallback(
459 'Cannot fast-forward merge, attempt to rebase? '
460 '(y)es / (q)uit / (s)kip : ', 'y')
461 scm.update(options, (), file_list)
462 self.assertEqual(file_list,
463 [join(self.base_path, x) for x in ['a', 'b', 'c']])
464 # The actual commit that is created is unstable, so we verify its tree
465 # and parent instead.
466 self.assertEqual(scm._Capture(['rev-parse', 'HEAD:']),
467 'd2e35c10ac24d6c621e14a1fcadceb533155627d')
468 parent = 'HEAD^' if sys.platform != 'win32' else 'HEAD^^'
469 self.assertEqual(scm._Capture(['rev-parse', parent + '1']),
470 scm._Capture(['rev-parse', 'origin/main']))
471 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000472
Mike Frysinger67761632023-09-05 20:24:16 +0000473 def testUpdateReset(self):
474 if not self.enabled:
475 return
476 options = self.Options()
477 options.reset = True
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000478
Mike Frysinger67761632023-09-05 20:24:16 +0000479 dir_path = join(self.base_path, 'c')
480 os.mkdir(dir_path)
481 with open(join(dir_path, 'nested'), 'w') as f:
482 f.writelines('new\n')
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000483
Mike Frysinger67761632023-09-05 20:24:16 +0000484 file_path = join(self.base_path, 'file')
485 with open(file_path, 'w') as f:
486 f.writelines('new\n')
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000487
Mike Frysinger67761632023-09-05 20:24:16 +0000488 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
489 file_list = []
490 scm.update(options, (), file_list)
491 self.assert_(gclient_scm.os.path.isdir(dir_path))
492 self.assert_(gclient_scm.os.path.isfile(file_path))
493 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000494
Mike Frysinger67761632023-09-05 20:24:16 +0000495 def testUpdateResetUnsetsFetchConfig(self):
496 if not self.enabled:
497 return
498 options = self.Options()
499 options.reset = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000500
Mike Frysinger67761632023-09-05 20:24:16 +0000501 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
502 scm._Run([
503 'config', 'remote.origin.fetch',
504 '+refs/heads/bad/ref:refs/remotes/origin/bad/ref'
505 ], options)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000506
Mike Frysinger67761632023-09-05 20:24:16 +0000507 file_list = []
508 scm.update(options, (), file_list)
509 self.assertEqual(scm.revinfo(options, (), None),
510 '069c602044c5388d2d15c3f875b057c852003458')
511 sys.stdout.close()
Edward Lemur579c9862018-07-13 23:17:51 +0000512
Mike Frysinger67761632023-09-05 20:24:16 +0000513 def testUpdateResetDeleteUnversionedTrees(self):
514 if not self.enabled:
515 return
516 options = self.Options()
517 options.reset = True
518 options.delete_unversioned_trees = True
Edward Lemur579c9862018-07-13 23:17:51 +0000519
Mike Frysinger67761632023-09-05 20:24:16 +0000520 dir_path = join(self.base_path, 'dir')
521 os.mkdir(dir_path)
522 with open(join(dir_path, 'nested'), 'w') as f:
523 f.writelines('new\n')
Edward Lemur579c9862018-07-13 23:17:51 +0000524
Mike Frysinger67761632023-09-05 20:24:16 +0000525 file_path = join(self.base_path, 'file')
526 with open(file_path, 'w') as f:
527 f.writelines('new\n')
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000528
Mike Frysinger67761632023-09-05 20:24:16 +0000529 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
530 file_list = []
531 scm.update(options, (), file_list)
532 self.assert_(not gclient_scm.os.path.isdir(dir_path))
533 self.assert_(gclient_scm.os.path.isfile(file_path))
534 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000535
Mike Frysinger67761632023-09-05 20:24:16 +0000536 def testUpdateUnstagedConflict(self):
537 if not self.enabled:
538 return
539 options = self.Options()
540 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
541 file_path = join(self.base_path, 'b')
542 with open(file_path, 'w') as f:
543 f.writelines('conflict\n')
544 try:
545 scm.update(options, (), [])
546 self.fail()
547 except (gclient_scm.gclient_utils.Error,
548 subprocess2.CalledProcessError):
549 # The exact exception text varies across git versions so it's not
550 # worth verifying it. It's fine as long as it throws.
551 pass
552 # Manually flush stdout since we can't verify it's content accurately
553 # across git versions.
554 sys.stdout.getvalue()
555 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000556
Mike Frysinger67761632023-09-05 20:24:16 +0000557 @unittest.skip('Skipping until crbug.com/670884 is resolved.')
558 def testUpdateLocked(self):
559 if not self.enabled:
560 return
561 options = self.Options()
562 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
563 file_path = join(self.base_path, '.git', 'index.lock')
564 with open(file_path, 'w'):
565 pass
566 with self.assertRaises(subprocess2.CalledProcessError):
567 scm.update(options, (), [])
568 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000569
Mike Frysinger67761632023-09-05 20:24:16 +0000570 def testUpdateLockedBreak(self):
571 if not self.enabled:
572 return
573 options = self.Options()
574 options.break_repo_locks = True
575 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
576 file_path = join(self.base_path, '.git', 'index.lock')
577 with open(file_path, 'w'):
578 pass
579 scm.update(options, (), [])
580 self.assertRegexpMatches(sys.stdout.getvalue(),
581 r'breaking lock.*\.git[/|\\]index\.lock')
582 self.assertFalse(os.path.exists(file_path))
583 sys.stdout.close()
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000584
Mike Frysinger67761632023-09-05 20:24:16 +0000585 def testUpdateConflict(self):
586 if not self.enabled:
587 return
588 options = self.Options()
589 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
590 file_path = join(self.base_path, 'b')
591 with open(file_path, 'w') as f:
592 f.writelines('conflict\n')
593 scm._Run(['commit', '-am', 'test'], options)
594 scm._AskForData = self._GetAskForDataCallback(
595 'Cannot fast-forward merge, attempt to rebase? '
596 '(y)es / (q)uit / (s)kip : ', 'y')
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000597
Mike Frysinger67761632023-09-05 20:24:16 +0000598 with self.assertRaises(gclient_scm.gclient_utils.Error) as e:
599 scm.update(options, (), [])
600 self.assertEqual(
601 e.exception.args[0], 'Conflict while rebasing this branch.\n'
602 'Fix the conflict and run gclient again.\n'
603 'See \'man git-rebase\' for details.\n')
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000604
Mike Frysinger67761632023-09-05 20:24:16 +0000605 with self.assertRaises(gclient_scm.gclient_utils.Error) as e:
606 scm.update(options, (), [])
607 self.assertEqual(
608 e.exception.args[0], '\n____ . at refs/remotes/origin/main\n'
609 '\tYou have unstaged changes.\n'
610 '\tcd into ., run git status to see changes,\n'
611 '\tand commit, stash, or reset.\n')
Edward Lemur979fa782019-08-13 22:44:05 +0000612
Mike Frysinger67761632023-09-05 20:24:16 +0000613 sys.stdout.close()
Edward Lemur979fa782019-08-13 22:44:05 +0000614
Mike Frysinger67761632023-09-05 20:24:16 +0000615 def testRevinfo(self):
616 if not self.enabled:
617 return
618 options = self.Options()
619 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
620 rev_info = scm.revinfo(options, (), None)
621 self.assertEqual(rev_info, '069c602044c5388d2d15c3f875b057c852003458')
msb@chromium.org0f282062009-11-06 20:14:02 +0000622
msb@chromium.orge28e4982009-09-25 20:51:45 +0000623
Edward Lemur979fa782019-08-13 22:44:05 +0000624class ManagedGitWrapperTestCaseMock(unittest.TestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000625 class OptionsObject(object):
626 def __init__(self, verbose=False, revision=None, force=False):
627 self.verbose = verbose
628 self.revision = revision
629 self.deps_os = None
630 self.force = force
631 self.reset = False
632 self.nohooks = False
633 self.break_repo_locks = False
634 # TODO(maruel): Test --jobs > 1.
635 self.jobs = 1
636 self.patch_ref = None
637 self.patch_repo = None
638 self.rebase_patch_ref = True
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000639
Mike Frysinger67761632023-09-05 20:24:16 +0000640 def Options(self, *args, **kwargs):
641 return self.OptionsObject(*args, **kwargs)
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000642
Mike Frysinger67761632023-09-05 20:24:16 +0000643 def checkstdout(self, expected):
644 # pylint: disable=no-member
645 value = sys.stdout.getvalue()
646 sys.stdout.close()
647 # Check that the expected output appears.
648 self.assertIn(expected, strip_timestamps(value))
borenet@google.comb09097a2014-04-09 19:09:08 +0000649
Mike Frysinger67761632023-09-05 20:24:16 +0000650 def setUp(self):
651 self.fake_hash_1 = 't0ta11yf4k3'
652 self.fake_hash_2 = '3v3nf4k3r'
653 self.url = 'git://foo'
654 self.root_dir = '/tmp' if sys.platform != 'win32' else 't:\\tmp'
655 self.relpath = 'fake'
656 self.base_path = os.path.join(self.root_dir, self.relpath)
657 self.backup_base_path = os.path.join(self.root_dir,
658 'old_%s.git' % self.relpath)
659 mock.patch('gclient_scm.scm.GIT.ApplyEnvVars').start()
660 mock.patch('gclient_scm.GitWrapper._CheckMinVersion').start()
661 mock.patch('gclient_scm.GitWrapper._Fetch').start()
662 mock.patch('gclient_scm.GitWrapper._DeleteOrMove').start()
663 mock.patch('sys.stdout', StringIO()).start()
664 self.addCleanup(mock.patch.stopall)
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000665
Mike Frysinger67761632023-09-05 20:24:16 +0000666 @mock.patch('scm.GIT.IsValidRevision')
667 @mock.patch('os.path.isdir', lambda _: True)
668 def testGetUsableRevGit(self, mockIsValidRevision):
669 # pylint: disable=no-member
670 options = self.Options(verbose=True)
smutae7ea312016-07-18 11:59:41 -0700671
Mike Frysinger67761632023-09-05 20:24:16 +0000672 mockIsValidRevision.side_effect = lambda cwd, rev: rev != '1'
smutae7ea312016-07-18 11:59:41 -0700673
Mike Frysinger67761632023-09-05 20:24:16 +0000674 git_scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
675 # A [fake] git sha1 with a git repo should work (this is in the case
676 # that the LKGR gets flipped to git sha1's some day).
677 self.assertEqual(git_scm.GetUsableRev(self.fake_hash_1, options),
678 self.fake_hash_1)
679 # An SVN rev with an existing purely git repo should raise an exception.
680 self.assertRaises(gclient_scm.gclient_utils.Error, git_scm.GetUsableRev,
681 '1', options)
smutae7ea312016-07-18 11:59:41 -0700682
Mike Frysinger67761632023-09-05 20:24:16 +0000683 @mock.patch('gclient_scm.GitWrapper._Clone')
684 @mock.patch('os.path.isdir')
685 @mock.patch('os.path.exists')
686 @mock.patch('subprocess2.check_output')
687 def testUpdateNoDotGit(self, mockCheckOutput, mockExists, mockIsdir,
688 mockClone):
689 mockIsdir.side_effect = lambda path: path == self.base_path
690 mockExists.side_effect = lambda path: path == self.base_path
691 mockCheckOutput.side_effect = [b'refs/remotes/origin/main', b'', b'']
Edward Lemur979fa782019-08-13 22:44:05 +0000692
Mike Frysinger67761632023-09-05 20:24:16 +0000693 options = self.Options()
694 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
695 scm.update(options, None, [])
Edward Lemur979fa782019-08-13 22:44:05 +0000696
Mike Frysinger67761632023-09-05 20:24:16 +0000697 env = gclient_scm.scm.GIT.ApplyEnvVars({})
698 self.assertEqual(mockCheckOutput.mock_calls, [
699 mock.call(['git', 'symbolic-ref', 'refs/remotes/origin/HEAD'],
700 cwd=self.base_path,
701 env=env,
702 stderr=-1),
703 mock.call(['git', '-c', 'core.quotePath=false', 'ls-files'],
704 cwd=self.base_path,
705 env=env,
706 stderr=-1),
707 mock.call(['git', 'rev-parse', '--verify', 'HEAD'],
708 cwd=self.base_path,
709 env=env,
710 stderr=-1),
711 ])
712 mockClone.assert_called_with('refs/remotes/origin/main', self.url,
713 options)
714 self.checkstdout('\n')
borenet@google.comb09097a2014-04-09 19:09:08 +0000715
Mike Frysinger67761632023-09-05 20:24:16 +0000716 @mock.patch('gclient_scm.GitWrapper._Clone')
717 @mock.patch('os.path.isdir')
718 @mock.patch('os.path.exists')
719 @mock.patch('subprocess2.check_output')
720 def testUpdateConflict(self, mockCheckOutput, mockExists, mockIsdir,
721 mockClone):
722 mockIsdir.side_effect = lambda path: path == self.base_path
723 mockExists.side_effect = lambda path: path == self.base_path
724 mockCheckOutput.side_effect = [b'refs/remotes/origin/main', b'', b'']
725 mockClone.side_effect = [
726 gclient_scm.subprocess2.CalledProcessError(None, None, None, None,
727 None),
728 None,
729 ]
Edward Lemur979fa782019-08-13 22:44:05 +0000730
Mike Frysinger67761632023-09-05 20:24:16 +0000731 options = self.Options()
732 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
733 scm.update(options, None, [])
Edward Lemur979fa782019-08-13 22:44:05 +0000734
Mike Frysinger67761632023-09-05 20:24:16 +0000735 env = gclient_scm.scm.GIT.ApplyEnvVars({})
736 self.assertEqual(mockCheckOutput.mock_calls, [
737 mock.call(['git', 'symbolic-ref', 'refs/remotes/origin/HEAD'],
738 cwd=self.base_path,
739 env=env,
740 stderr=-1),
741 mock.call(['git', '-c', 'core.quotePath=false', 'ls-files'],
742 cwd=self.base_path,
743 env=env,
744 stderr=-1),
745 mock.call(['git', 'rev-parse', '--verify', 'HEAD'],
746 cwd=self.base_path,
747 env=env,
748 stderr=-1),
749 ])
750 mockClone.assert_called_with('refs/remotes/origin/main', self.url,
751 options)
752 self.checkstdout('\n')
borenet@google.comb09097a2014-04-09 19:09:08 +0000753
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000754
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000755class UnmanagedGitWrapperTestCase(BaseGitWrapperTestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000756 def checkInStdout(self, expected):
757 # pylint: disable=no-member
758 value = sys.stdout.getvalue()
759 sys.stdout.close()
760 self.assertIn(expected, value)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000761
Mike Frysinger67761632023-09-05 20:24:16 +0000762 def checkNotInStdout(self, expected):
763 # pylint: disable=no-member
764 value = sys.stdout.getvalue()
765 sys.stdout.close()
766 self.assertNotIn(expected, value)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000767
Mike Frysinger67761632023-09-05 20:24:16 +0000768 def getCurrentBranch(self):
769 # Returns name of current branch or HEAD for detached HEAD
770 branch = gclient_scm.scm.GIT.Capture(
771 ['rev-parse', '--abbrev-ref', 'HEAD'], cwd=self.base_path)
772 if branch == 'HEAD':
773 return None
774 return branch
smut@google.com27c9c8a2014-09-11 19:57:55 +0000775
Mike Frysinger67761632023-09-05 20:24:16 +0000776 def testUpdateClone(self):
777 if not self.enabled:
778 return
779 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000780
Mike Frysinger67761632023-09-05 20:24:16 +0000781 origin_root_dir = self.root_dir
782 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
Edward Lesmese79107e2019-10-25 22:47:33 +0000783
Mike Frysinger67761632023-09-05 20:24:16 +0000784 self.root_dir = tempfile.mkdtemp()
785 self.relpath = '.'
786 self.base_path = join(self.root_dir, self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000787
Mike Frysinger67761632023-09-05 20:24:16 +0000788 scm = gclient_scm.GitWrapper(origin_root_dir, self.root_dir,
789 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000790
Mike Frysinger67761632023-09-05 20:24:16 +0000791 expected_file_list = [
792 join(self.base_path, "a"),
793 join(self.base_path, "b")
794 ]
795 file_list = []
796 options.revision = 'unmanaged'
797 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000798
Mike Frysinger67761632023-09-05 20:24:16 +0000799 self.assertEqual(file_list, expected_file_list)
800 self.assertEqual(scm.revinfo(options, (), None),
801 '069c602044c5388d2d15c3f875b057c852003458')
802 # indicates detached HEAD
803 self.assertEqual(self.getCurrentBranch(), None)
804 self.checkInStdout(
805 'Checked out refs/remotes/origin/main to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000806
Mike Frysinger67761632023-09-05 20:24:16 +0000807 def testUpdateCloneOnCommit(self):
808 if not self.enabled:
809 return
810 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000811
Mike Frysinger67761632023-09-05 20:24:16 +0000812 origin_root_dir = self.root_dir
813 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000814
Mike Frysinger67761632023-09-05 20:24:16 +0000815 self.root_dir = tempfile.mkdtemp()
816 self.relpath = '.'
817 self.base_path = join(self.root_dir, self.relpath)
818 url_with_commit_ref = origin_root_dir +\
819 '@a7142dc9f0009350b96a11f372b6ea658592aa95'
Edward Lesmese79107e2019-10-25 22:47:33 +0000820
Mike Frysinger67761632023-09-05 20:24:16 +0000821 scm = gclient_scm.GitWrapper(url_with_commit_ref, self.root_dir,
822 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000823
Mike Frysinger67761632023-09-05 20:24:16 +0000824 expected_file_list = [
825 join(self.base_path, "a"),
826 join(self.base_path, "b")
827 ]
828 file_list = []
829 options.revision = 'unmanaged'
830 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000831
Mike Frysinger67761632023-09-05 20:24:16 +0000832 self.assertEqual(file_list, expected_file_list)
833 self.assertEqual(scm.revinfo(options, (), None),
834 'a7142dc9f0009350b96a11f372b6ea658592aa95')
835 # indicates detached HEAD
836 self.assertEqual(self.getCurrentBranch(), None)
837 self.checkInStdout(
838 'Checked out a7142dc9f0009350b96a11f372b6ea658592aa95 to a detached HEAD'
839 )
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000840
Mike Frysinger67761632023-09-05 20:24:16 +0000841 def testUpdateCloneOnBranch(self):
842 if not self.enabled:
843 return
844 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000845
Mike Frysinger67761632023-09-05 20:24:16 +0000846 origin_root_dir = self.root_dir
847 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000848
Mike Frysinger67761632023-09-05 20:24:16 +0000849 self.root_dir = tempfile.mkdtemp()
850 self.relpath = '.'
851 self.base_path = join(self.root_dir, self.relpath)
852 url_with_branch_ref = origin_root_dir + '@feature'
Edward Lesmese79107e2019-10-25 22:47:33 +0000853
Mike Frysinger67761632023-09-05 20:24:16 +0000854 scm = gclient_scm.GitWrapper(url_with_branch_ref, self.root_dir,
855 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000856
Mike Frysinger67761632023-09-05 20:24:16 +0000857 expected_file_list = [
858 join(self.base_path, "a"),
859 join(self.base_path, "b"),
860 join(self.base_path, "c")
861 ]
862 file_list = []
863 options.revision = 'unmanaged'
864 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000865
Mike Frysinger67761632023-09-05 20:24:16 +0000866 self.assertEqual(file_list, expected_file_list)
867 self.assertEqual(scm.revinfo(options, (), None),
868 '9a51244740b25fa2ded5252ca00a3178d3f665a9')
869 # indicates detached HEAD
870 self.assertEqual(self.getCurrentBranch(), None)
871 self.checkInStdout(
872 'Checked out 9a51244740b25fa2ded5252ca00a3178d3f665a9 '
873 'to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000874
Mike Frysinger67761632023-09-05 20:24:16 +0000875 def testUpdateCloneOnFetchedRemoteBranch(self):
876 if not self.enabled:
877 return
878 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000879
Mike Frysinger67761632023-09-05 20:24:16 +0000880 origin_root_dir = self.root_dir
881 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000882
Mike Frysinger67761632023-09-05 20:24:16 +0000883 self.root_dir = tempfile.mkdtemp()
884 self.relpath = '.'
885 self.base_path = join(self.root_dir, self.relpath)
886 url_with_branch_ref = origin_root_dir + '@refs/remotes/origin/feature'
Edward Lesmese79107e2019-10-25 22:47:33 +0000887
Mike Frysinger67761632023-09-05 20:24:16 +0000888 scm = gclient_scm.GitWrapper(url_with_branch_ref, self.root_dir,
889 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000890
Mike Frysinger67761632023-09-05 20:24:16 +0000891 expected_file_list = [
892 join(self.base_path, "a"),
893 join(self.base_path, "b"),
894 join(self.base_path, "c")
895 ]
896 file_list = []
897 options.revision = 'unmanaged'
898 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000899
Mike Frysinger67761632023-09-05 20:24:16 +0000900 self.assertEqual(file_list, expected_file_list)
901 self.assertEqual(scm.revinfo(options, (), None),
902 '9a51244740b25fa2ded5252ca00a3178d3f665a9')
903 # indicates detached HEAD
904 self.assertEqual(self.getCurrentBranch(), None)
905 self.checkInStdout(
906 'Checked out refs/remotes/origin/feature to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000907
Mike Frysinger67761632023-09-05 20:24:16 +0000908 def testUpdateCloneOnTrueRemoteBranch(self):
909 if not self.enabled:
910 return
911 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000912
Mike Frysinger67761632023-09-05 20:24:16 +0000913 origin_root_dir = self.root_dir
914 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000915
Mike Frysinger67761632023-09-05 20:24:16 +0000916 self.root_dir = tempfile.mkdtemp()
917 self.relpath = '.'
918 self.base_path = join(self.root_dir, self.relpath)
919 url_with_branch_ref = origin_root_dir + '@refs/heads/feature'
Edward Lesmese79107e2019-10-25 22:47:33 +0000920
Mike Frysinger67761632023-09-05 20:24:16 +0000921 scm = gclient_scm.GitWrapper(url_with_branch_ref, self.root_dir,
922 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000923
Mike Frysinger67761632023-09-05 20:24:16 +0000924 expected_file_list = [
925 join(self.base_path, "a"),
926 join(self.base_path, "b"),
927 join(self.base_path, "c")
928 ]
929 file_list = []
930 options.revision = 'unmanaged'
931 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000932
Mike Frysinger67761632023-09-05 20:24:16 +0000933 self.assertEqual(file_list, expected_file_list)
934 self.assertEqual(scm.revinfo(options, (), None),
935 '9a51244740b25fa2ded5252ca00a3178d3f665a9')
936 # @refs/heads/feature is AKA @refs/remotes/origin/feature in the clone,
937 # so should be treated as such by gclient. TODO(mmoss): Though really,
938 # we should only allow DEPS to specify branches as they are known in the
939 # upstream repo, since the mapping into the local repo can be modified
940 # by users (or we might even want to change the gclient defaults at some
941 # point). But that will take more work to stop using refs/remotes/
942 # everywhere that we do (and to stop assuming a DEPS ref will always
943 # resolve locally, like when passing them to show-ref or rev-list).
944 self.assertEqual(self.getCurrentBranch(), None)
945 self.checkInStdout(
946 'Checked out refs/remotes/origin/feature to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000947
Mike Frysinger67761632023-09-05 20:24:16 +0000948 def testUpdateUpdate(self):
949 if not self.enabled:
950 return
951 options = self.Options()
952 expected_file_list = []
953 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
954 file_list = []
955 options.revision = 'unmanaged'
956 scm.update(options, (), file_list)
957 self.assertEqual(file_list, expected_file_list)
958 self.assertEqual(scm.revinfo(options, (), None),
959 '069c602044c5388d2d15c3f875b057c852003458')
960 self.checkstdout('________ unmanaged solution; skipping .\n')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000961
962
Edward Lemur979fa782019-08-13 22:44:05 +0000963class CipdWrapperTestCase(unittest.TestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000964 def setUp(self):
965 # Create this before setting up mocks.
966 self._cipd_root_dir = tempfile.mkdtemp()
967 self._workdir = tempfile.mkdtemp()
John Budorick0f7b2002018-01-19 15:46:17 -0800968
Mike Frysinger67761632023-09-05 20:24:16 +0000969 self._cipd_instance_url = 'https://chrome-infra-packages.appspot.com'
970 self._cipd_root = gclient_scm.CipdRoot(self._cipd_root_dir,
971 self._cipd_instance_url)
972 self._cipd_packages = [
973 self._cipd_root.add_package('f', 'foo_package', 'foo_version'),
974 self._cipd_root.add_package('b', 'bar_package', 'bar_version'),
975 self._cipd_root.add_package('b', 'baz_package', 'baz_version'),
976 ]
977 mock.patch('tempfile.mkdtemp', lambda: self._workdir).start()
978 mock.patch('gclient_scm.CipdRoot.add_package').start()
979 mock.patch('gclient_scm.CipdRoot.clobber').start()
980 mock.patch('gclient_scm.CipdRoot.ensure_file_resolve').start()
981 mock.patch('gclient_scm.CipdRoot.ensure').start()
982 self.addCleanup(mock.patch.stopall)
983 self.addCleanup(gclient_utils.rmtree, self._cipd_root_dir)
984 self.addCleanup(gclient_utils.rmtree, self._workdir)
John Budorick0f7b2002018-01-19 15:46:17 -0800985
Mike Frysinger67761632023-09-05 20:24:16 +0000986 def createScmWithPackageThatSatisfies(self, condition):
987 return gclient_scm.CipdWrapper(
988 url=self._cipd_instance_url,
989 root_dir=self._cipd_root_dir,
990 relpath='fake_relpath',
991 root=self._cipd_root,
992 package=self.getPackageThatSatisfies(condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800993
Mike Frysinger67761632023-09-05 20:24:16 +0000994 def getPackageThatSatisfies(self, condition):
995 for p in self._cipd_packages:
996 if condition(p):
997 return p
John Budorick0f7b2002018-01-19 15:46:17 -0800998
Mike Frysinger67761632023-09-05 20:24:16 +0000999 self.fail('Unable to find a satisfactory package.')
John Budorick0f7b2002018-01-19 15:46:17 -08001000
Mike Frysinger67761632023-09-05 20:24:16 +00001001 def testRevert(self):
1002 """Checks that revert does nothing."""
1003 scm = self.createScmWithPackageThatSatisfies(lambda _: True)
1004 scm.revert(None, (), [])
John Budorick0f7b2002018-01-19 15:46:17 -08001005
Mike Frysinger67761632023-09-05 20:24:16 +00001006 @mock.patch('gclient_scm.gclient_utils.CheckCallAndFilter')
1007 @mock.patch('gclient_scm.gclient_utils.rmtree')
1008 def testRevinfo(self, mockRmtree, mockCheckCallAndFilter):
1009 """Checks that revinfo uses the JSON from cipd describe."""
1010 scm = self.createScmWithPackageThatSatisfies(lambda _: True)
John Budorick0f7b2002018-01-19 15:46:17 -08001011
Mike Frysinger67761632023-09-05 20:24:16 +00001012 expected_revinfo = '0123456789abcdef0123456789abcdef01234567'
1013 json_contents = {
1014 'result': {
1015 'pin': {
1016 'instance_id': expected_revinfo,
1017 }
John Budorick0f7b2002018-01-19 15:46:17 -08001018 }
1019 }
Mike Frysinger67761632023-09-05 20:24:16 +00001020 describe_json_path = join(self._workdir, 'describe.json')
1021 with open(describe_json_path, 'w') as describe_json:
1022 json.dump(json_contents, describe_json)
John Budorick0f7b2002018-01-19 15:46:17 -08001023
Mike Frysinger67761632023-09-05 20:24:16 +00001024 revinfo = scm.revinfo(None, (), [])
1025 self.assertEqual(revinfo, expected_revinfo)
Edward Lemur979fa782019-08-13 22:44:05 +00001026
Mike Frysinger67761632023-09-05 20:24:16 +00001027 mockRmtree.assert_called_with(self._workdir)
1028 mockCheckCallAndFilter.assert_called_with([
1029 'cipd',
1030 'describe',
1031 'foo_package',
1032 '-log-level',
1033 'error',
1034 '-version',
1035 'foo_version',
1036 '-json-output',
1037 describe_json_path,
1038 ])
John Budorick0f7b2002018-01-19 15:46:17 -08001039
Mike Frysinger67761632023-09-05 20:24:16 +00001040 def testUpdate(self):
1041 """Checks that update does nothing."""
1042 scm = self.createScmWithPackageThatSatisfies(lambda _: True)
1043 scm.update(None, (), [])
John Budorick0f7b2002018-01-19 15:46:17 -08001044
1045
Edward Lesmes8073a502020-04-15 02:11:14 +00001046class BranchHeadsFakeRepo(fake_repos.FakeReposBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001047 def populateGit(self):
1048 # Creates a tree that looks like this:
1049 #
1050 # 5 refs/branch-heads/5
1051 # |
1052 # 4
1053 # |
1054 # 1--2--3 refs/heads/main
1055 self._commit_git('repo_1', {'commit 1': 'touched'})
1056 self._commit_git('repo_1', {'commit 2': 'touched'})
1057 self._commit_git('repo_1', {'commit 3': 'touched'})
1058 self._create_ref('repo_1', 'refs/heads/main', 3)
Edward Lesmes8073a502020-04-15 02:11:14 +00001059
Mike Frysinger67761632023-09-05 20:24:16 +00001060 self._commit_git('repo_1', {'commit 4': 'touched'}, base=2)
1061 self._commit_git('repo_1', {'commit 5': 'touched'}, base=2)
1062 self._create_ref('repo_1', 'refs/branch-heads/5', 5)
Edward Lesmes8073a502020-04-15 02:11:14 +00001063
1064
1065class BranchHeadsTest(fake_repos.FakeReposTestBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001066 FAKE_REPOS_CLASS = BranchHeadsFakeRepo
Edward Lesmes8073a502020-04-15 02:11:14 +00001067
Mike Frysinger67761632023-09-05 20:24:16 +00001068 def setUp(self):
1069 super(BranchHeadsTest, self).setUp()
1070 self.enabled = self.FAKE_REPOS.set_up_git()
1071 self.options = BaseGitWrapperTestCase.OptionsObject()
1072 self.url = self.git_base + 'repo_1'
1073 self.mirror = None
1074 mock.patch('sys.stdout', StringIO()).start()
1075 self.addCleanup(mock.patch.stopall)
Edward Lesmes8073a502020-04-15 02:11:14 +00001076
Mike Frysinger67761632023-09-05 20:24:16 +00001077 def setUpMirror(self):
1078 self.mirror = tempfile.mkdtemp('mirror')
1079 git_cache.Mirror.SetCachePath(self.mirror)
1080 self.addCleanup(gclient_utils.rmtree, self.mirror)
1081 self.addCleanup(git_cache.Mirror.SetCachePath, None)
Edward Lesmes8073a502020-04-15 02:11:14 +00001082
Mike Frysinger67761632023-09-05 20:24:16 +00001083 def testCheckoutBranchHeads(self):
1084 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1085 file_list = []
Edward Lesmes8073a502020-04-15 02:11:14 +00001086
Mike Frysinger67761632023-09-05 20:24:16 +00001087 self.options.revision = 'refs/branch-heads/5'
1088 scm.update(self.options, None, file_list)
1089 self.assertEqual(self.githash('repo_1', 5),
1090 self.gitrevparse(self.root_dir))
Edward Lesmes8073a502020-04-15 02:11:14 +00001091
Mike Frysinger67761632023-09-05 20:24:16 +00001092 def testCheckoutUpdatedBranchHeads(self):
1093 # Travel back in time, and set refs/branch-heads/5 to its parent.
1094 subprocess2.check_call([
1095 'git', 'update-ref', 'refs/branch-heads/5',
1096 self.githash('repo_1', 4)
1097 ],
1098 cwd=self.url)
Edward Lesmes8073a502020-04-15 02:11:14 +00001099
Mike Frysinger67761632023-09-05 20:24:16 +00001100 # Sync to refs/branch-heads/5
1101 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1102 self.options.revision = 'refs/branch-heads/5'
1103 scm.update(self.options, None, [])
Edward Lesmes8073a502020-04-15 02:11:14 +00001104
Mike Frysinger67761632023-09-05 20:24:16 +00001105 # Set refs/branch-heads/5 back to its original value.
1106 subprocess2.check_call([
1107 'git', 'update-ref', 'refs/branch-heads/5',
1108 self.githash('repo_1', 5)
1109 ],
1110 cwd=self.url)
Edward Lesmes8073a502020-04-15 02:11:14 +00001111
Mike Frysinger67761632023-09-05 20:24:16 +00001112 # Attempt to sync to refs/branch-heads/5 again.
1113 self.testCheckoutBranchHeads()
Edward Lesmes8073a502020-04-15 02:11:14 +00001114
Mike Frysinger67761632023-09-05 20:24:16 +00001115 def testCheckoutBranchHeadsMirror(self):
1116 self.setUpMirror()
1117 self.testCheckoutBranchHeads()
Edward Lesmes8073a502020-04-15 02:11:14 +00001118
Mike Frysinger67761632023-09-05 20:24:16 +00001119 def testCheckoutUpdatedBranchHeadsMirror(self):
1120 self.setUpMirror()
1121 self.testCheckoutUpdatedBranchHeads()
Edward Lesmes8073a502020-04-15 02:11:14 +00001122
1123
Edward Lemurd64781e2018-07-11 23:09:55 +00001124class GerritChangesFakeRepo(fake_repos.FakeReposBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001125 def populateGit(self):
1126 # Creates a tree that looks like this:
1127 #
1128 # 6 refs/changes/35/1235/1
1129 # |
1130 # 5 refs/changes/34/1234/1
1131 # |
1132 # 1--2--3--4 refs/heads/main
1133 # | |
1134 # | 11(5)--12 refs/heads/main-with-5
1135 # |
1136 # 7--8--9 refs/heads/feature
1137 # |
1138 # 10 refs/changes/36/1236/1
1139 #
Edward Lemurd64781e2018-07-11 23:09:55 +00001140
Mike Frysinger67761632023-09-05 20:24:16 +00001141 self._commit_git('repo_1', {'commit 1': 'touched'})
1142 self._commit_git('repo_1', {'commit 2': 'touched'})
1143 self._commit_git('repo_1', {'commit 3': 'touched'})
1144 self._commit_git('repo_1', {'commit 4': 'touched'})
1145 self._create_ref('repo_1', 'refs/heads/main', 4)
Edward Lemurd64781e2018-07-11 23:09:55 +00001146
Mike Frysinger67761632023-09-05 20:24:16 +00001147 # Create a change on top of commit 3 that consists of two commits.
1148 self._commit_git('repo_1', {
1149 'commit 5': 'touched',
1150 'change': '1234'
1151 },
1152 base=3)
1153 self._create_ref('repo_1', 'refs/changes/34/1234/1', 5)
1154 self._commit_git('repo_1', {'commit 6': 'touched', 'change': '1235'})
1155 self._create_ref('repo_1', 'refs/changes/35/1235/1', 6)
Edward Lemurd64781e2018-07-11 23:09:55 +00001156
Mike Frysinger67761632023-09-05 20:24:16 +00001157 # Create a refs/heads/feature branch on top of commit 2, consisting of
1158 # three commits.
1159 self._commit_git('repo_1', {'commit 7': 'touched'}, base=2)
1160 self._commit_git('repo_1', {'commit 8': 'touched'})
1161 self._commit_git('repo_1', {'commit 9': 'touched'})
1162 self._create_ref('repo_1', 'refs/heads/feature', 9)
Edward Lemurca7d8812018-07-24 17:42:45 +00001163
Mike Frysinger67761632023-09-05 20:24:16 +00001164 # Create a change of top of commit 8.
1165 self._commit_git('repo_1', {
1166 'commit 10': 'touched',
1167 'change': '1236'
1168 },
1169 base=8)
1170 self._create_ref('repo_1', 'refs/changes/36/1236/1', 10)
Edward Lemurca7d8812018-07-24 17:42:45 +00001171
Mike Frysinger67761632023-09-05 20:24:16 +00001172 # Create a refs/heads/main-with-5 on top of commit 3 which is a branch
1173 # where refs/changes/34/1234/1 (commit 5) has already landed as commit
1174 # 11.
1175 self._commit_git(
1176 'repo_1',
1177 # This is really commit 11, but has the changes of commit 5
1178 {
1179 'commit 5': 'touched',
1180 'change': '1234'
1181 },
1182 base=3)
1183 self._commit_git('repo_1', {'commit 12': 'touched'})
1184 self._create_ref('repo_1', 'refs/heads/main-with-5', 12)
Edward Lemurca7d8812018-07-24 17:42:45 +00001185
Edward Lemurd64781e2018-07-11 23:09:55 +00001186
1187class GerritChangesTest(fake_repos.FakeReposTestBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001188 FAKE_REPOS_CLASS = GerritChangesFakeRepo
Edward Lemurd64781e2018-07-11 23:09:55 +00001189
Mike Frysinger67761632023-09-05 20:24:16 +00001190 def setUp(self):
1191 super(GerritChangesTest, self).setUp()
1192 self.enabled = self.FAKE_REPOS.set_up_git()
1193 self.options = BaseGitWrapperTestCase.OptionsObject()
1194 self.url = self.git_base + 'repo_1'
1195 self.mirror = None
1196 mock.patch('sys.stdout', StringIO()).start()
1197 self.addCleanup(mock.patch.stopall)
Edward Lemurd64781e2018-07-11 23:09:55 +00001198
Mike Frysinger67761632023-09-05 20:24:16 +00001199 def setUpMirror(self):
1200 self.mirror = tempfile.mkdtemp()
1201 git_cache.Mirror.SetCachePath(self.mirror)
1202 self.addCleanup(gclient_utils.rmtree, self.mirror)
1203 self.addCleanup(git_cache.Mirror.SetCachePath, None)
Edward Lemurd64781e2018-07-11 23:09:55 +00001204
Mike Frysinger67761632023-09-05 20:24:16 +00001205 def assertCommits(self, commits):
1206 """Check that all, and only |commits| are present in the current checkout.
Edward Lemurca7d8812018-07-24 17:42:45 +00001207 """
Mike Frysinger67761632023-09-05 20:24:16 +00001208 for i in commits:
1209 name = os.path.join(self.root_dir, 'commit ' + str(i))
1210 self.assertTrue(os.path.exists(name), 'Commit not found: %s' % name)
Edward Lemurca7d8812018-07-24 17:42:45 +00001211
Mike Frysinger67761632023-09-05 20:24:16 +00001212 all_commits = set(range(1, len(self.FAKE_REPOS.git_hashes['repo_1'])))
1213 for i in all_commits - set(commits):
1214 name = os.path.join(self.root_dir, 'commit ' + str(i))
1215 self.assertFalse(os.path.exists(name),
1216 'Unexpected commit: %s' % name)
Edward Lemurca7d8812018-07-24 17:42:45 +00001217
Mike Frysinger67761632023-09-05 20:24:16 +00001218 def testCanCloneGerritChange(self):
1219 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1220 file_list = []
Edward Lemurd64781e2018-07-11 23:09:55 +00001221
Mike Frysinger67761632023-09-05 20:24:16 +00001222 self.options.revision = 'refs/changes/35/1235/1'
1223 scm.update(self.options, None, file_list)
1224 self.assertEqual(self.githash('repo_1', 6),
1225 self.gitrevparse(self.root_dir))
Edward Lemurd64781e2018-07-11 23:09:55 +00001226
Mike Frysinger67761632023-09-05 20:24:16 +00001227 def testCanSyncToGerritChange(self):
1228 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1229 file_list = []
Edward Lemurd64781e2018-07-11 23:09:55 +00001230
Mike Frysinger67761632023-09-05 20:24:16 +00001231 self.options.revision = self.githash('repo_1', 1)
1232 scm.update(self.options, None, file_list)
1233 self.assertEqual(self.githash('repo_1', 1),
1234 self.gitrevparse(self.root_dir))
Edward Lemurd64781e2018-07-11 23:09:55 +00001235
Mike Frysinger67761632023-09-05 20:24:16 +00001236 self.options.revision = 'refs/changes/35/1235/1'
1237 scm.update(self.options, None, file_list)
1238 self.assertEqual(self.githash('repo_1', 6),
1239 self.gitrevparse(self.root_dir))
Edward Lemurd64781e2018-07-11 23:09:55 +00001240
Mike Frysinger67761632023-09-05 20:24:16 +00001241 def testCanCloneGerritChangeMirror(self):
1242 self.setUpMirror()
1243 self.testCanCloneGerritChange()
Edward Lemurd64781e2018-07-11 23:09:55 +00001244
Mike Frysinger67761632023-09-05 20:24:16 +00001245 def testCanSyncToGerritChangeMirror(self):
1246 self.setUpMirror()
1247 self.testCanSyncToGerritChange()
Edward Lemurd64781e2018-07-11 23:09:55 +00001248
Mike Frysinger67761632023-09-05 20:24:16 +00001249 def testMirrorPushUrl(self):
1250 self.setUpMirror()
Edward Lesmese79107e2019-10-25 22:47:33 +00001251
Mike Frysinger67761632023-09-05 20:24:16 +00001252 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1253 file_list = []
1254 self.assertIsNotNone(scm._GetMirror(self.url, self.options))
Edward Lesmese79107e2019-10-25 22:47:33 +00001255
Mike Frysinger67761632023-09-05 20:24:16 +00001256 scm.update(self.options, None, file_list)
Edward Lesmese79107e2019-10-25 22:47:33 +00001257
Mike Frysinger67761632023-09-05 20:24:16 +00001258 fetch_url = scm._Capture(['remote', 'get-url', 'origin'])
1259 self.assertTrue(
1260 fetch_url.startswith(self.mirror),
1261 msg='\n'.join([
1262 'Repository fetch url should be in the git cache mirror directory.',
1263 ' fetch_url: %s' % fetch_url,
1264 ' mirror: %s' % self.mirror
1265 ]))
1266 push_url = scm._Capture(['remote', 'get-url', '--push', 'origin'])
1267 self.assertEqual(push_url, self.url)
Edward Lesmese79107e2019-10-25 22:47:33 +00001268
Mike Frysinger67761632023-09-05 20:24:16 +00001269 def testAppliesPatchOnTopOfMasterByDefault(self):
1270 """Test the default case, where we apply a patch on top of main."""
1271 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1272 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001273
Mike Frysinger67761632023-09-05 20:24:16 +00001274 # Make sure we don't specify a revision.
1275 self.options.revision = None
1276 scm.update(self.options, None, file_list)
1277 self.assertEqual(self.githash('repo_1', 4),
1278 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001279
Mike Frysinger67761632023-09-05 20:24:16 +00001280 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1281 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001282
Mike Frysinger67761632023-09-05 20:24:16 +00001283 self.assertCommits([1, 2, 3, 4, 5, 6])
1284 self.assertEqual(self.githash('repo_1', 4),
1285 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001286
Mike Frysinger67761632023-09-05 20:24:16 +00001287 def testCheckoutOlderThanPatchBase(self):
1288 """Test applying a patch on an old checkout.
Edward Lemurca7d8812018-07-24 17:42:45 +00001289
1290 We first checkout commit 1, and try to patch refs/changes/35/1235/1, which
1291 contains commits 5 and 6, and is based on top of commit 3.
1292 The final result should contain commits 1, 5 and 6, but not commits 2 or 3.
1293 """
Mike Frysinger67761632023-09-05 20:24:16 +00001294 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1295 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001296
Mike Frysinger67761632023-09-05 20:24:16 +00001297 # Sync to commit 1
1298 self.options.revision = self.githash('repo_1', 1)
1299 scm.update(self.options, None, file_list)
1300 self.assertEqual(self.githash('repo_1', 1),
1301 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001302
Mike Frysinger67761632023-09-05 20:24:16 +00001303 # Apply the change on top of that.
1304 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1305 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001306
Mike Frysinger67761632023-09-05 20:24:16 +00001307 self.assertCommits([1, 5, 6])
1308 self.assertEqual(self.githash('repo_1', 1),
1309 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001310
Mike Frysinger67761632023-09-05 20:24:16 +00001311 def testCheckoutOriginFeature(self):
1312 """Tests that we can apply a patch on a branch other than main."""
1313 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1314 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001315
Mike Frysinger67761632023-09-05 20:24:16 +00001316 # Sync to remote's refs/heads/feature
1317 self.options.revision = 'refs/heads/feature'
1318 scm.update(self.options, None, file_list)
1319 self.assertEqual(self.githash('repo_1', 9),
1320 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001321
Mike Frysinger67761632023-09-05 20:24:16 +00001322 # Apply the change on top of that.
1323 scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1',
1324 'refs/heads/feature', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001325
Mike Frysinger67761632023-09-05 20:24:16 +00001326 self.assertCommits([1, 2, 7, 8, 9, 10])
1327 self.assertEqual(self.githash('repo_1', 9),
1328 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001329
Mike Frysinger67761632023-09-05 20:24:16 +00001330 def testCheckoutOriginFeatureOnOldRevision(self):
1331 """Tests that we can apply a patch on an old checkout, on a branch other
Josip Sokcevic7e133ff2021-07-13 17:44:53 +00001332 than main."""
Mike Frysinger67761632023-09-05 20:24:16 +00001333 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1334 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001335
Mike Frysinger67761632023-09-05 20:24:16 +00001336 # Sync to remote's refs/heads/feature on an old revision
1337 self.options.revision = self.githash('repo_1', 7)
1338 scm.update(self.options, None, file_list)
1339 self.assertEqual(self.githash('repo_1', 7),
1340 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001341
Mike Frysinger67761632023-09-05 20:24:16 +00001342 # Apply the change on top of that.
1343 scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1',
1344 'refs/heads/feature', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001345
Mike Frysinger67761632023-09-05 20:24:16 +00001346 # We shouldn't have rebased on top of 2 (which is the merge base between
1347 # remote's main branch and the change) but on top of 7 (which is the
1348 # merge base between remote's feature branch and the change).
1349 self.assertCommits([1, 2, 7, 10])
1350 self.assertEqual(self.githash('repo_1', 7),
1351 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001352
Mike Frysinger67761632023-09-05 20:24:16 +00001353 def testCheckoutOriginFeaturePatchBranch(self):
1354 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1355 file_list = []
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001356
Mike Frysinger67761632023-09-05 20:24:16 +00001357 # Sync to the hash instead of remote's refs/heads/feature.
1358 self.options.revision = self.githash('repo_1', 9)
1359 scm.update(self.options, None, file_list)
1360 self.assertEqual(self.githash('repo_1', 9),
1361 self.gitrevparse(self.root_dir))
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001362
Mike Frysinger67761632023-09-05 20:24:16 +00001363 # Apply refs/changes/34/1234/1, created for remote's main branch on top
1364 # of remote's feature branch.
1365 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1366 'refs/heads/main', self.options, file_list)
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001367
Mike Frysinger67761632023-09-05 20:24:16 +00001368 # Commits 5 and 6 are part of the patch, and commits 1, 2, 7, 8 and 9
1369 # are part of remote's feature branch.
1370 self.assertCommits([1, 2, 5, 6, 7, 8, 9])
1371 self.assertEqual(self.githash('repo_1', 9),
1372 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001373
Mike Frysinger67761632023-09-05 20:24:16 +00001374 def testDoesntRebasePatchMaster(self):
1375 """Tests that we can apply a patch without rebasing it.
Edward Lemurca7d8812018-07-24 17:42:45 +00001376 """
Mike Frysinger67761632023-09-05 20:24:16 +00001377 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1378 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001379
Mike Frysinger67761632023-09-05 20:24:16 +00001380 self.options.rebase_patch_ref = False
1381 scm.update(self.options, None, file_list)
1382 self.assertEqual(self.githash('repo_1', 4),
1383 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001384
Mike Frysinger67761632023-09-05 20:24:16 +00001385 # Apply the change on top of that.
1386 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1387 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001388
Mike Frysinger67761632023-09-05 20:24:16 +00001389 self.assertCommits([1, 2, 3, 5, 6])
1390 self.assertEqual(self.githash('repo_1', 5),
1391 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001392
Mike Frysinger67761632023-09-05 20:24:16 +00001393 def testDoesntRebasePatchOldCheckout(self):
1394 """Tests that we can apply a patch without rebasing it on an old checkout.
Edward Lemurca7d8812018-07-24 17:42:45 +00001395 """
Mike Frysinger67761632023-09-05 20:24:16 +00001396 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1397 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001398
Mike Frysinger67761632023-09-05 20:24:16 +00001399 # Sync to commit 1
1400 self.options.revision = self.githash('repo_1', 1)
1401 self.options.rebase_patch_ref = False
1402 scm.update(self.options, None, file_list)
1403 self.assertEqual(self.githash('repo_1', 1),
1404 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001405
Mike Frysinger67761632023-09-05 20:24:16 +00001406 # Apply the change on top of that.
1407 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1408 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001409
Mike Frysinger67761632023-09-05 20:24:16 +00001410 self.assertCommits([1, 2, 3, 5, 6])
1411 self.assertEqual(self.githash('repo_1', 5),
1412 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001413
Mike Frysinger67761632023-09-05 20:24:16 +00001414 def testDoesntSoftResetIfNotAskedTo(self):
1415 """Test that we can apply a patch without doing a soft reset."""
1416 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1417 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001418
Mike Frysinger67761632023-09-05 20:24:16 +00001419 self.options.reset_patch_ref = False
1420 scm.update(self.options, None, file_list)
1421 self.assertEqual(self.githash('repo_1', 4),
1422 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001423
Mike Frysinger67761632023-09-05 20:24:16 +00001424 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1425 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001426
Mike Frysinger67761632023-09-05 20:24:16 +00001427 self.assertCommits([1, 2, 3, 4, 5, 6])
1428 # The commit hash after cherry-picking is not known, but it must be
1429 # different from what the repo was synced at before patching.
1430 self.assertNotEqual(self.githash('repo_1', 4),
1431 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001432
Mike Frysinger67761632023-09-05 20:24:16 +00001433 @mock.patch('gerrit_util.GetChange', return_value={'topic': 'test_topic'})
1434 @mock.patch('gerrit_util.QueryChanges',
1435 return_value=[{
1436 '_number': 1234
1437 }, {
1438 '_number': 1235,
1439 'current_revision': 'abc',
1440 'revisions': {
1441 'abc': {
1442 'ref': 'refs/changes/35/1235/1'
1443 }
1444 }
1445 }])
1446 def testDownloadTopics(self, query_changes_mock, get_change_mock):
1447 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1448 file_list = []
Ravi Mistryecda7822022-02-28 16:22:20 +00001449
Mike Frysinger67761632023-09-05 20:24:16 +00001450 self.options.revision = 'refs/changes/34/1234/1'
1451 scm.update(self.options, None, file_list)
1452 self.assertEqual(self.githash('repo_1', 5),
1453 self.gitrevparse(self.root_dir))
Ravi Mistryecda7822022-02-28 16:22:20 +00001454
Mike Frysinger67761632023-09-05 20:24:16 +00001455 # pylint: disable=attribute-defined-outside-init
1456 self.options.download_topics = True
1457 scm.url = 'https://test-repo.googlesource.com/repo_1.git'
1458 scm.apply_patch_ref(self.url, 'refs/changes/34/1234/1',
1459 'refs/heads/main', self.options, file_list)
Ravi Mistryecda7822022-02-28 16:22:20 +00001460
Mike Frysinger67761632023-09-05 20:24:16 +00001461 get_change_mock.assert_called_once_with(mock.ANY, '1234')
1462 query_changes_mock.assert_called_once_with(mock.ANY,
1463 [('topic', 'test_topic'),
1464 ('status', 'open'),
1465 ('repo', 'repo_1')],
1466 o_params=['ALL_REVISIONS'])
Ravi Mistryecda7822022-02-28 16:22:20 +00001467
Mike Frysinger67761632023-09-05 20:24:16 +00001468 self.assertCommits([1, 2, 3, 5, 6])
1469 # The commit hash after the two cherry-picks is not known, but it must
1470 # be different from what the repo was synced at before patching.
1471 self.assertNotEqual(self.githash('repo_1', 4),
1472 self.gitrevparse(self.root_dir))
Ravi Mistryecda7822022-02-28 16:22:20 +00001473
Mike Frysinger67761632023-09-05 20:24:16 +00001474 def testRecoversAfterPatchFailure(self):
1475 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1476 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001477
Mike Frysinger67761632023-09-05 20:24:16 +00001478 self.options.revision = 'refs/changes/34/1234/1'
1479 scm.update(self.options, None, file_list)
1480 self.assertEqual(self.githash('repo_1', 5),
1481 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001482
Mike Frysinger67761632023-09-05 20:24:16 +00001483 # Checkout 'refs/changes/34/1234/1' modifies the 'change' file, so
1484 # trying to patch 'refs/changes/36/1236/1' creates a patch failure.
1485 with self.assertRaises(subprocess2.CalledProcessError) as cm:
1486 scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1',
1487 'refs/heads/main', self.options, file_list)
1488 self.assertEqual(cm.exception.cmd[:2], ['git', 'cherry-pick'])
1489 self.assertIn(b'error: could not apply', cm.exception.stderr)
Edward Lemurca7d8812018-07-24 17:42:45 +00001490
Mike Frysinger67761632023-09-05 20:24:16 +00001491 # Try to apply 'refs/changes/35/1235/1', which doesn't have a merge
1492 # conflict.
1493 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1494 'refs/heads/main', self.options, file_list)
1495 self.assertCommits([1, 2, 3, 5, 6])
1496 self.assertEqual(self.githash('repo_1', 5),
1497 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001498
Mike Frysinger67761632023-09-05 20:24:16 +00001499 def testIgnoresAlreadyMergedCommits(self):
1500 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1501 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001502
Mike Frysinger67761632023-09-05 20:24:16 +00001503 self.options.revision = 'refs/heads/main-with-5'
1504 scm.update(self.options, None, file_list)
1505 self.assertEqual(self.githash('repo_1', 12),
1506 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001507
Mike Frysinger67761632023-09-05 20:24:16 +00001508 # When we try 'refs/changes/35/1235/1' on top of 'refs/heads/feature',
1509 # 'refs/changes/34/1234/1' will be an empty commit, since the changes
1510 # were already present in the tree as commit 11. Make sure we deal with
1511 # this gracefully.
1512 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1513 'refs/heads/feature', self.options, file_list)
1514 self.assertCommits([1, 2, 3, 5, 6, 12])
1515 self.assertEqual(self.githash('repo_1', 12),
1516 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001517
Mike Frysinger67761632023-09-05 20:24:16 +00001518 def testRecoversFromExistingCherryPick(self):
1519 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1520 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001521
Mike Frysinger67761632023-09-05 20:24:16 +00001522 self.options.revision = 'refs/changes/34/1234/1'
1523 scm.update(self.options, None, file_list)
1524 self.assertEqual(self.githash('repo_1', 5),
1525 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001526
Mike Frysinger67761632023-09-05 20:24:16 +00001527 # Checkout 'refs/changes/34/1234/1' modifies the 'change' file, so
1528 # trying to cherry-pick 'refs/changes/36/1236/1' raises an error.
1529 scm._Run(['fetch', 'origin', 'refs/changes/36/1236/1'], self.options)
1530 with self.assertRaises(subprocess2.CalledProcessError) as cm:
1531 scm._Run(['cherry-pick', 'FETCH_HEAD'], self.options)
1532 self.assertEqual(cm.exception.cmd[:2], ['git', 'cherry-pick'])
Edward Lemurca7d8812018-07-24 17:42:45 +00001533
Mike Frysinger67761632023-09-05 20:24:16 +00001534 # Try to apply 'refs/changes/35/1235/1', which doesn't have a merge
1535 # conflict.
1536 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1537 'refs/heads/main', self.options, file_list)
1538 self.assertCommits([1, 2, 3, 5, 6])
1539 self.assertEqual(self.githash('repo_1', 5),
1540 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001541
Edward Lemurd64781e2018-07-11 23:09:55 +00001542
Joanna Wang5a7c8242022-07-01 19:09:00 +00001543class DepsChangesFakeRepo(fake_repos.FakeReposBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001544 def populateGit(self):
1545 self._commit_git('repo_1', {'DEPS': 'versionA', 'doesnotmatter': 'B'})
1546 self._commit_git('repo_1', {'DEPS': 'versionA', 'doesnotmatter': 'C'})
Joanna Wang5a7c8242022-07-01 19:09:00 +00001547
Mike Frysinger67761632023-09-05 20:24:16 +00001548 self._commit_git('repo_1', {'DEPS': 'versionB'})
1549 self._commit_git('repo_1', {'DEPS': 'versionA', 'doesnotmatter': 'C'})
1550 self._create_ref('repo_1', 'refs/heads/main', 4)
Joanna Wang5a7c8242022-07-01 19:09:00 +00001551
1552
1553class CheckDiffTest(fake_repos.FakeReposTestBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001554 FAKE_REPOS_CLASS = DepsChangesFakeRepo
Joanna Wang5a7c8242022-07-01 19:09:00 +00001555
Mike Frysinger67761632023-09-05 20:24:16 +00001556 def setUp(self):
1557 super(CheckDiffTest, self).setUp()
1558 self.enabled = self.FAKE_REPOS.set_up_git()
1559 self.options = BaseGitWrapperTestCase.OptionsObject()
1560 self.url = self.git_base + 'repo_1'
1561 self.mirror = None
1562 mock.patch('sys.stdout', StringIO()).start()
1563 self.addCleanup(mock.patch.stopall)
Joanna Wang5a7c8242022-07-01 19:09:00 +00001564
Mike Frysinger67761632023-09-05 20:24:16 +00001565 def setUpMirror(self):
1566 self.mirror = tempfile.mkdtemp()
1567 git_cache.Mirror.SetCachePath(self.mirror)
1568 self.addCleanup(gclient_utils.rmtree, self.mirror)
1569 self.addCleanup(git_cache.Mirror.SetCachePath, None)
Joanna Wang5a7c8242022-07-01 19:09:00 +00001570
Mike Frysinger67761632023-09-05 20:24:16 +00001571 def testCheckDiff(self):
1572 """Correctly check for diffs."""
1573 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1574 file_list = []
Joanna Wang5a7c8242022-07-01 19:09:00 +00001575
Mike Frysinger67761632023-09-05 20:24:16 +00001576 # Make sure we don't specify a revision.
1577 self.options.revision = None
1578 scm.update(self.options, None, file_list)
1579 self.assertEqual(self.githash('repo_1', 4),
1580 self.gitrevparse(self.root_dir))
Joanna Wang5a7c8242022-07-01 19:09:00 +00001581
Mike Frysinger67761632023-09-05 20:24:16 +00001582 self.assertFalse(
1583 scm.check_diff(self.githash('repo_1', 1), files=['DEPS']))
1584 self.assertTrue(scm.check_diff(self.githash('repo_1', 1)))
1585 self.assertTrue(
1586 scm.check_diff(self.githash('repo_1', 3), files=['DEPS']))
Joanna Wang5a7c8242022-07-01 19:09:00 +00001587
Mike Frysinger67761632023-09-05 20:24:16 +00001588 self.assertFalse(
1589 scm.check_diff(self.githash('repo_1', 2),
1590 files=['DEPS', 'doesnotmatter']))
1591 self.assertFalse(scm.check_diff(self.githash('repo_1', 2)))
Joanna Wang5a7c8242022-07-01 19:09:00 +00001592
1593
Joanna Wang1a977bd2022-06-02 21:51:17 +00001594if 'unittest.util' in __import__('sys').modules:
Mike Frysinger67761632023-09-05 20:24:16 +00001595 # Show full diff in self.assertEqual.
1596 __import__('sys').modules['unittest.util']._MAX_LENGTH = 999999999
Joanna Wang1a977bd2022-06-02 21:51:17 +00001597
msb@chromium.orge28e4982009-09-25 20:51:45 +00001598if __name__ == '__main__':
Mike Frysinger67761632023-09-05 20:24:16 +00001599 level = logging.DEBUG if '-v' in sys.argv else logging.FATAL
1600 logging.basicConfig(level=level,
1601 format='%(asctime).19s %(levelname)s %(filename)s:'
1602 '%(lineno)s %(message)s')
1603 unittest.main()
msb@chromium.orge28e4982009-09-25 20:51:45 +00001604
1605# vim: ts=2:sw=2:tw=80:et: