blob: 005518b1b5b2bd56eac87c97400e5f2b4eac519f [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
Gavin Mak65c49b12023-08-24 18:06:42 +00009from io import StringIO
John Budorick0f7b2002018-01-19 15:46:17 -080010import json
maruel@chromium.org428342a2011-11-10 15:46:33 +000011import logging
12import os
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000013import re
Gavin Mak65c49b12023-08-24 18:06:42 +000014from subprocess import Popen, PIPE, STDOUT
maruel@chromium.org428342a2011-11-10 15:46:33 +000015import sys
msb@chromium.orge28e4982009-09-25 20:51:45 +000016import tempfile
maruel@chromium.org389d6de2010-09-09 14:14:37 +000017import unittest
Gavin Mak65c49b12023-08-24 18:06:42 +000018from unittest import mock
msb@chromium.orge28e4982009-09-25 20:51:45 +000019
maruel@chromium.org428342a2011-11-10 15:46:33 +000020sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
maruel@chromium.orgba551772010-02-03 18:21:42 +000021
msb@chromium.orge28e4982009-09-25 20:51:45 +000022import gclient_scm
Edward Lesmese79107e2019-10-25 22:47:33 +000023import gclient_utils
szager@chromium.orgb0a13a22014-06-18 00:52:25 +000024import git_cache
maruel@chromium.orgfae707b2011-09-15 18:57:58 +000025import subprocess2
Gavin Mak65c49b12023-08-24 18:06:42 +000026from testing_support import fake_repos
27from testing_support import test_case_utils
maruel@chromium.org96913eb2010-06-01 16:22:47 +000028
Mike Frysinger67761632023-09-05 20:24:16 +000029# TODO: Should fix these warnings.
30# pylint: disable=line-too-long
Edward Lesmese79107e2019-10-25 22:47:33 +000031
32GIT = 'git' if sys.platform != 'win32' else 'git.bat'
33
szager@chromium.orgb0a13a22014-06-18 00:52:25 +000034# Disable global git cache
35git_cache.Mirror.SetCachePath(None)
36
maruel@chromium.org795a8c12010-10-05 19:54:29 +000037# Shortcut since this function is used often
38join = gclient_scm.os.path.join
39
Raul Tambrea79f0e52019-09-21 07:27:39 +000040TIMESTAMP_RE = re.compile(r'\[[0-9]{1,2}:[0-9]{2}:[0-9]{2}\] (.*)', re.DOTALL)
Mike Frysinger67761632023-09-05 20:24:16 +000041
42
szager@chromium.orgfe0d1902014-04-08 20:50:44 +000043def strip_timestamps(value):
Mike Frysinger67761632023-09-05 20:24:16 +000044 lines = value.splitlines(True)
45 for i in range(len(lines)):
46 m = TIMESTAMP_RE.match(lines[i])
47 if m:
48 lines[i] = m.group(1)
49 return ''.join(lines)
maruel@chromium.org96913eb2010-06-01 16:22:47 +000050
maruel@chromium.orgd579fcf2011-12-13 20:36:03 +000051
Edward Lemur979fa782019-08-13 22:44:05 +000052class BasicTests(unittest.TestCase):
Mike Frysinger67761632023-09-05 20:24:16 +000053 @mock.patch('gclient_scm.scm.GIT.Capture')
54 def testGetFirstRemoteUrl(self, mockCapture):
55 REMOTE_STRINGS = [
56 ('remote.origin.url E:\\foo\\bar', 'E:\\foo\\bar'),
57 ('remote.origin.url /b/foo/bar', '/b/foo/bar'),
58 ('remote.origin.url https://foo/bar', 'https://foo/bar'),
59 ('remote.origin.url E:\\Fo Bar\\bax', 'E:\\Fo Bar\\bax'),
60 ('remote.origin.url git://what/"do', 'git://what/"do')
61 ]
62 FAKE_PATH = '/fake/path'
63 mockCapture.side_effect = [question for question, _ in REMOTE_STRINGS]
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +000064
Mike Frysinger67761632023-09-05 20:24:16 +000065 for _, answer in REMOTE_STRINGS:
66 self.assertEqual(
67 gclient_scm.SCMWrapper._get_first_remote_url(FAKE_PATH), answer)
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +000068
Mike Frysinger67761632023-09-05 20:24:16 +000069 expected_calls = [
70 mock.call(['config', '--local', '--get-regexp', r'remote.*.url'],
71 cwd=FAKE_PATH) for _ in REMOTE_STRINGS
72 ]
73 self.assertEqual(mockCapture.mock_calls, expected_calls)
hinoka@chromium.orgfa2b9b42014-08-22 18:08:53 +000074
75
Edward Lemur9cafbf42019-08-15 22:03:35 +000076class BaseGitWrapperTestCase(unittest.TestCase, test_case_utils.TestCaseUtils):
Mike Frysinger67761632023-09-05 20:24:16 +000077 """This class doesn't use pymox."""
78 class OptionsObject(object):
79 def __init__(self, verbose=False, revision=None):
80 self.auto_rebase = False
81 self.verbose = verbose
82 self.revision = revision
83 self.deps_os = None
84 self.force = False
85 self.reset = False
86 self.nohooks = False
87 self.no_history = False
88 self.upstream = False
89 self.cache_dir = None
90 self.merge = False
91 self.jobs = 1
92 self.break_repo_locks = False
93 self.delete_unversioned_trees = False
94 self.patch_ref = None
95 self.patch_repo = None
96 self.rebase_patch_ref = True
97 self.reset_patch_ref = True
msb@chromium.orge28e4982009-09-25 20:51:45 +000098
Mike Frysinger67761632023-09-05 20:24:16 +000099 sample_git_import = """blob
msb@chromium.orge28e4982009-09-25 20:51:45 +0000100mark :1
101data 6
102Hello
103
104blob
105mark :2
106data 4
107Bye
108
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000109reset refs/heads/main
110commit refs/heads/main
msb@chromium.orge28e4982009-09-25 20:51:45 +0000111mark :3
112author Bob <bob@example.com> 1253744361 -0700
113committer Bob <bob@example.com> 1253744361 -0700
114data 8
115A and B
116M 100644 :1 a
117M 100644 :2 b
118
119blob
120mark :4
121data 10
122Hello
123You
124
125blob
126mark :5
127data 8
128Bye
129You
130
131commit refs/heads/origin
132mark :6
133author Alice <alice@example.com> 1253744424 -0700
134committer Alice <alice@example.com> 1253744424 -0700
135data 13
136Personalized
137from :3
138M 100644 :4 a
139M 100644 :5 b
140
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000141blob
142mark :7
143data 5
144Mooh
145
146commit refs/heads/feature
147mark :8
148author Bob <bob@example.com> 1390311986 -0000
149committer Bob <bob@example.com> 1390311986 -0000
150data 6
151Add C
152from :3
153M 100644 :7 c
154
Josip Sokcevic7e133ff2021-07-13 17:44:53 +0000155reset refs/heads/main
msb@chromium.orge28e4982009-09-25 20:51:45 +0000156from :3
157"""
msb@chromium.orge28e4982009-09-25 20:51:45 +0000158
Mike Frysinger67761632023-09-05 20:24:16 +0000159 def Options(self, *args, **kwargs):
160 return self.OptionsObject(*args, **kwargs)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000161
Mike Frysinger67761632023-09-05 20:24:16 +0000162 def checkstdout(self, expected):
163 # pylint: disable=no-member
164 value = sys.stdout.getvalue()
165 sys.stdout.close()
166 # Check that the expected output appears.
167 self.assertIn(expected, strip_timestamps(value))
msb@chromium.orge28e4982009-09-25 20:51:45 +0000168
Mike Frysinger67761632023-09-05 20:24:16 +0000169 @staticmethod
170 def CreateGitRepo(git_import, path):
171 """Do it for real."""
172 try:
173 Popen([GIT, 'init', '-q'], stdout=PIPE, stderr=STDOUT,
174 cwd=path).communicate()
175 except OSError:
176 # git is not available, skip this test.
177 return False
178 Popen([GIT, 'fast-import', '--quiet'],
179 stdin=PIPE,
180 stdout=PIPE,
181 stderr=STDOUT,
182 cwd=path).communicate(input=git_import.encode())
183 Popen([GIT, 'checkout', '-q'], stdout=PIPE, stderr=STDOUT,
184 cwd=path).communicate()
185 Popen([GIT, 'remote', 'add', '-f', 'origin', '.'],
186 stdout=PIPE,
187 stderr=STDOUT,
188 cwd=path).communicate()
189 Popen([GIT, 'checkout', '-b', 'new', 'origin/main', '-q'],
190 stdout=PIPE,
191 stderr=STDOUT,
192 cwd=path).communicate()
193 Popen([GIT, 'push', 'origin', 'origin/origin:origin/main', '-q'],
194 stdout=PIPE,
195 stderr=STDOUT,
196 cwd=path).communicate()
197 Popen([GIT, 'config', '--unset', 'remote.origin.fetch'],
198 stdout=PIPE,
199 stderr=STDOUT,
200 cwd=path).communicate()
201 Popen([GIT, 'config', 'user.email', 'someuser@chromium.org'],
202 stdout=PIPE,
203 stderr=STDOUT,
204 cwd=path).communicate()
205 Popen([GIT, 'config', 'user.name', 'Some User'],
206 stdout=PIPE,
207 stderr=STDOUT,
208 cwd=path).communicate()
209 # Set HEAD back to main
210 Popen([GIT, 'checkout', 'main', '-q'],
211 stdout=PIPE,
212 stderr=STDOUT,
213 cwd=path).communicate()
214 return True
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000215
Mike Frysinger67761632023-09-05 20:24:16 +0000216 def _GetAskForDataCallback(self, expected_prompt, return_value):
217 def AskForData(prompt, options):
218 self.assertEqual(prompt, expected_prompt)
219 return return_value
220
221 return AskForData
222
223 def setUp(self):
224 unittest.TestCase.setUp(self)
225 test_case_utils.TestCaseUtils.setUp(self)
226 self.url = 'git://foo'
227 # The .git suffix allows gclient_scm to recognize the dir as a git repo
228 # when cloning it locally
229 self.root_dir = tempfile.mkdtemp('.git')
230 self.relpath = '.'
231 self.base_path = join(self.root_dir, self.relpath)
232 self.enabled = self.CreateGitRepo(self.sample_git_import,
233 self.base_path)
234 mock.patch('sys.stdout', StringIO()).start()
235 self.addCleanup(mock.patch.stopall)
236 self.addCleanup(gclient_utils.rmtree, self.root_dir)
maruel@chromium.org1a60dca2013-11-26 14:06:26 +0000237
msb@chromium.orge28e4982009-09-25 20:51:45 +0000238
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000239class ManagedGitWrapperTestCase(BaseGitWrapperTestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000240 def testRevertMissing(self):
241 if not self.enabled:
242 return
243 options = self.Options()
244 file_path = join(self.base_path, 'a')
245 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
246 file_list = []
247 scm.update(options, None, file_list)
248 gclient_scm.os.remove(file_path)
249 file_list = []
250 scm.revert(options, self.args, file_list)
251 self.assertEqual(file_list, [file_path])
252 file_list = []
253 scm.diff(options, self.args, file_list)
254 self.assertEqual(file_list, [])
255 sys.stdout.close()
Joanna Wang1a977bd2022-06-02 21:51:17 +0000256
Mike Frysinger67761632023-09-05 20:24:16 +0000257 def testRevertNone(self):
258 if not self.enabled:
259 return
260 options = self.Options()
261 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
262 file_list = []
263 scm.update(options, None, file_list)
264 file_list = []
265 scm.revert(options, self.args, file_list)
266 self.assertEqual(file_list, [])
267 self.assertEqual(scm.revinfo(options, self.args, None),
268 'a7142dc9f0009350b96a11f372b6ea658592aa95')
269 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000270
Mike Frysinger67761632023-09-05 20:24:16 +0000271 def testRevertModified(self):
272 if not self.enabled:
273 return
274 options = self.Options()
275 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
276 file_list = []
277 scm.update(options, None, file_list)
278 file_path = join(self.base_path, 'a')
279 with open(file_path, 'a') as f:
280 f.writelines('touched\n')
281 file_list = []
282 scm.revert(options, self.args, file_list)
283 self.assertEqual(file_list, [file_path])
284 file_list = []
285 scm.diff(options, self.args, file_list)
286 self.assertEqual(file_list, [])
287 self.assertEqual(scm.revinfo(options, self.args, None),
288 'a7142dc9f0009350b96a11f372b6ea658592aa95')
289 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000290
Mike Frysinger67761632023-09-05 20:24:16 +0000291 def testRevertNew(self):
292 if not self.enabled:
293 return
294 options = self.Options()
295 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
296 file_list = []
297 scm.update(options, None, file_list)
298 file_path = join(self.base_path, 'c')
299 with open(file_path, 'w') as f:
300 f.writelines('new\n')
301 Popen([GIT, 'add', 'c'], stdout=PIPE, stderr=STDOUT,
302 cwd=self.base_path).communicate()
303 file_list = []
304 scm.revert(options, self.args, file_list)
305 self.assertEqual(file_list, [file_path])
306 file_list = []
307 scm.diff(options, self.args, file_list)
308 self.assertEqual(file_list, [])
309 self.assertEqual(scm.revinfo(options, self.args, None),
310 'a7142dc9f0009350b96a11f372b6ea658592aa95')
311 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000312
Mike Frysinger67761632023-09-05 20:24:16 +0000313 def testStatusRef(self):
314 if not self.enabled:
315 return
316 options = self.Options()
317 file_paths = [join(self.base_path, 'a')]
318 with open(file_paths[0], 'a') as f:
319 f.writelines('touched\n')
320 scm = gclient_scm.GitWrapper(self.url + '@refs/heads/feature',
321 self.root_dir, self.relpath)
322 file_paths.append(join(self.base_path, 'c')) # feature branch touches c
323 file_list = []
324 scm.status(options, self.args, file_list)
325 self.assertEqual(file_list, file_paths)
326 self.checkstdout((
327 '\n________ running \'git -c core.quotePath=false diff --name-status '
328 'refs/remotes/origin/feature\' in \'%s\'\n\nM\ta\n') %
329 join(self.root_dir, '.'))
msb@chromium.orge28e4982009-09-25 20:51:45 +0000330
Mike Frysinger67761632023-09-05 20:24:16 +0000331 def testStatusNew(self):
332 if not self.enabled:
333 return
334 options = self.Options()
335 file_path = join(self.base_path, 'a')
336 with open(file_path, 'a') as f:
337 f.writelines('touched\n')
338 scm = gclient_scm.GitWrapper(
339 self.url + '@069c602044c5388d2d15c3f875b057c852003458',
340 self.root_dir, self.relpath)
341 file_list = []
342 scm.status(options, self.args, file_list)
343 self.assertEqual(file_list, [file_path])
344 self.checkstdout((
345 '\n________ running \'git -c core.quotePath=false diff --name-status '
346 '069c602044c5388d2d15c3f875b057c852003458\' in \'%s\'\n\nM\ta\n') %
347 join(self.root_dir, '.'))
Joanna Wanga654ff32023-07-18 23:25:19 +0000348
Mike Frysinger67761632023-09-05 20:24:16 +0000349 def testStatusNewNoBaseRev(self):
350 if not self.enabled:
351 return
352 options = self.Options()
353 file_path = join(self.base_path, 'a')
354 with open(file_path, 'a') as f:
355 f.writelines('touched\n')
356 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
357 file_list = []
358 scm.status(options, self.args, file_list)
359 self.assertEqual(file_list, [file_path])
360 self.checkstdout((
361 '\n________ running \'git -c core.quotePath=false diff --name-status'
362 '\' in \'%s\'\n\nM\ta\n') % join(self.root_dir, '.'))
msb@chromium.orge28e4982009-09-25 20:51:45 +0000363
Mike Frysinger67761632023-09-05 20:24:16 +0000364 def testStatus2New(self):
365 if not self.enabled:
366 return
367 options = self.Options()
368 expected_file_list = []
369 for f in ['a', 'b']:
370 file_path = join(self.base_path, f)
371 with open(file_path, 'a') as f:
372 f.writelines('touched\n')
373 expected_file_list.extend([file_path])
374 scm = gclient_scm.GitWrapper(
375 self.url + '@069c602044c5388d2d15c3f875b057c852003458',
376 self.root_dir, self.relpath)
377 file_list = []
378 scm.status(options, self.args, file_list)
379 expected_file_list = [join(self.base_path, x) for x in ['a', 'b']]
380 self.assertEqual(sorted(file_list), expected_file_list)
381 self.checkstdout((
382 '\n________ running \'git -c core.quotePath=false diff --name-status '
383 '069c602044c5388d2d15c3f875b057c852003458\' in \'%s\'\n\nM\ta\nM\tb\n'
384 ) % join(self.root_dir, '.'))
Anthony Politobb457342019-11-15 22:26:01 +0000385
Mike Frysinger67761632023-09-05 20:24:16 +0000386 def testUpdateUpdate(self):
387 if not self.enabled:
388 return
389 options = self.Options()
390 expected_file_list = [join(self.base_path, x) for x in ['a', 'b']]
391 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
392 file_list = []
Anthony Politobb457342019-11-15 22:26:01 +0000393
Mike Frysinger67761632023-09-05 20:24:16 +0000394 scm.update(options, (), file_list)
395 self.assertEqual(file_list, expected_file_list)
396 self.assertEqual(scm.revinfo(options, (), None),
397 'a7142dc9f0009350b96a11f372b6ea658592aa95')
398 self.assertEqual(
399 scm._Capture(['config', '--get', 'diff.ignoreSubmodules']), 'dirty')
400 self.assertEqual(
401 scm._Capture(['config', '--get', 'fetch.recurseSubmodules']), 'off')
Josip Sokcevic3b9212b2023-09-18 19:26:26 +0000402 self.assertEqual(
403 scm._Capture(['config', '--get', 'push.recurseSubmodules']), 'off')
Mike Frysinger67761632023-09-05 20:24:16 +0000404 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000405
Mike Frysinger67761632023-09-05 20:24:16 +0000406 def testUpdateMerge(self):
407 if not self.enabled:
408 return
409 options = self.Options()
410 options.merge = True
411 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
412 scm._Run(['checkout', '-q', 'feature'], options)
413 rev = scm.revinfo(options, (), None)
414 file_list = []
415 scm.update(options, (), file_list)
416 self.assertEqual(file_list,
417 [join(self.base_path, x) for x in ['a', 'b', 'c']])
418 # The actual commit that is created is unstable, so we verify its tree
419 # and parents instead.
420 self.assertEqual(scm._Capture(['rev-parse', 'HEAD:']),
421 'd2e35c10ac24d6c621e14a1fcadceb533155627d')
422 parent = 'HEAD^' if sys.platform != 'win32' else 'HEAD^^'
423 self.assertEqual(scm._Capture(['rev-parse', parent + '1']), rev)
424 self.assertEqual(scm._Capture(['rev-parse', parent + '2']),
425 scm._Capture(['rev-parse', 'origin/main']))
426 sys.stdout.close()
Joanna Wang4e6c1072023-08-17 18:46:24 +0000427
Mike Frysinger67761632023-09-05 20:24:16 +0000428 def testUpdateRebase(self):
429 if not self.enabled:
430 return
431 options = self.Options()
432 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
433 scm._Run(['checkout', '-q', 'feature'], options)
434 file_list = []
435 # Fake a 'y' key press.
436 scm._AskForData = self._GetAskForDataCallback(
437 'Cannot fast-forward merge, attempt to rebase? '
438 '(y)es / (q)uit / (s)kip : ', 'y')
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 parent 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']),
448 scm._Capture(['rev-parse', 'origin/main']))
449 sys.stdout.close()
msb@chromium.orge28e4982009-09-25 20:51:45 +0000450
Mike Frysinger67761632023-09-05 20:24:16 +0000451 def testUpdateReset(self):
452 if not self.enabled:
453 return
454 options = self.Options()
455 options.reset = True
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000456
Mike Frysinger67761632023-09-05 20:24:16 +0000457 dir_path = join(self.base_path, 'c')
458 os.mkdir(dir_path)
459 with open(join(dir_path, 'nested'), 'w') as f:
460 f.writelines('new\n')
bauerb@chromium.org30c46d62014-01-23 12:11:56 +0000461
Mike Frysinger67761632023-09-05 20:24:16 +0000462 file_path = join(self.base_path, 'file')
463 with open(file_path, 'w') as f:
464 f.writelines('new\n')
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000465
Mike Frysinger67761632023-09-05 20:24:16 +0000466 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
467 file_list = []
468 scm.update(options, (), file_list)
469 self.assert_(gclient_scm.os.path.isdir(dir_path))
470 self.assert_(gclient_scm.os.path.isfile(file_path))
471 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000472
Mike Frysinger67761632023-09-05 20:24:16 +0000473 def testUpdateResetUnsetsFetchConfig(self):
474 if not self.enabled:
475 return
476 options = self.Options()
477 options.reset = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000478
Mike Frysinger67761632023-09-05 20:24:16 +0000479 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
480 scm._Run([
481 'config', 'remote.origin.fetch',
482 '+refs/heads/bad/ref:refs/remotes/origin/bad/ref'
483 ], options)
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000484
Mike Frysinger67761632023-09-05 20:24:16 +0000485 file_list = []
486 scm.update(options, (), file_list)
487 self.assertEqual(scm.revinfo(options, (), None),
488 '069c602044c5388d2d15c3f875b057c852003458')
489 sys.stdout.close()
Edward Lemur579c9862018-07-13 23:17:51 +0000490
Mike Frysinger67761632023-09-05 20:24:16 +0000491 def testUpdateResetDeleteUnversionedTrees(self):
492 if not self.enabled:
493 return
494 options = self.Options()
495 options.reset = True
496 options.delete_unversioned_trees = True
Edward Lemur579c9862018-07-13 23:17:51 +0000497
Mike Frysinger67761632023-09-05 20:24:16 +0000498 dir_path = join(self.base_path, 'dir')
499 os.mkdir(dir_path)
500 with open(join(dir_path, 'nested'), 'w') as f:
501 f.writelines('new\n')
Edward Lemur579c9862018-07-13 23:17:51 +0000502
Mike Frysinger67761632023-09-05 20:24:16 +0000503 file_path = join(self.base_path, 'file')
504 with open(file_path, 'w') as f:
505 f.writelines('new\n')
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000506
Mike Frysinger67761632023-09-05 20:24:16 +0000507 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
508 file_list = []
509 scm.update(options, (), file_list)
510 self.assert_(not gclient_scm.os.path.isdir(dir_path))
511 self.assert_(gclient_scm.os.path.isfile(file_path))
512 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000513
Mike Frysinger67761632023-09-05 20:24:16 +0000514 def testUpdateUnstagedConflict(self):
515 if not self.enabled:
516 return
517 options = self.Options()
518 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
519 file_path = join(self.base_path, 'b')
520 with open(file_path, 'w') as f:
521 f.writelines('conflict\n')
522 try:
523 scm.update(options, (), [])
524 self.fail()
525 except (gclient_scm.gclient_utils.Error,
526 subprocess2.CalledProcessError):
527 # The exact exception text varies across git versions so it's not
528 # worth verifying it. It's fine as long as it throws.
529 pass
530 # Manually flush stdout since we can't verify it's content accurately
531 # across git versions.
532 sys.stdout.getvalue()
533 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000534
Mike Frysinger67761632023-09-05 20:24:16 +0000535 @unittest.skip('Skipping until crbug.com/670884 is resolved.')
536 def testUpdateLocked(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, '.git', 'index.lock')
542 with open(file_path, 'w'):
543 pass
544 with self.assertRaises(subprocess2.CalledProcessError):
545 scm.update(options, (), [])
546 sys.stdout.close()
steveblock@chromium.org98e69452012-02-16 16:36:43 +0000547
Mike Frysinger67761632023-09-05 20:24:16 +0000548 def testUpdateLockedBreak(self):
549 if not self.enabled:
550 return
551 options = self.Options()
552 options.break_repo_locks = True
553 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
554 file_path = join(self.base_path, '.git', 'index.lock')
555 with open(file_path, 'w'):
556 pass
557 scm.update(options, (), [])
558 self.assertRegexpMatches(sys.stdout.getvalue(),
559 r'breaking lock.*\.git[/|\\]index\.lock')
560 self.assertFalse(os.path.exists(file_path))
561 sys.stdout.close()
nasser@codeaurora.orgd90ba3f2010-02-23 14:42:57 +0000562
Mike Frysinger67761632023-09-05 20:24:16 +0000563 def testUpdateConflict(self):
564 if not self.enabled:
565 return
566 options = self.Options()
567 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
568 file_path = join(self.base_path, 'b')
569 with open(file_path, 'w') as f:
570 f.writelines('conflict\n')
571 scm._Run(['commit', '-am', 'test'], options)
572 scm._AskForData = self._GetAskForDataCallback(
573 'Cannot fast-forward merge, attempt to rebase? '
574 '(y)es / (q)uit / (s)kip : ', 'y')
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000575
Mike Frysinger67761632023-09-05 20:24:16 +0000576 with self.assertRaises(gclient_scm.gclient_utils.Error) as e:
577 scm.update(options, (), [])
578 self.assertEqual(
579 e.exception.args[0], 'Conflict while rebasing this branch.\n'
580 'Fix the conflict and run gclient again.\n'
581 'See \'man git-rebase\' for details.\n')
iannucci@chromium.org30a07982016-04-07 21:35:19 +0000582
Mike Frysinger67761632023-09-05 20:24:16 +0000583 with self.assertRaises(gclient_scm.gclient_utils.Error) as e:
584 scm.update(options, (), [])
585 self.assertEqual(
586 e.exception.args[0], '\n____ . at refs/remotes/origin/main\n'
Aravind Vasudevan259774c2023-12-05 00:50:49 +0000587 '\tYou have uncommitted changes.\n'
Mike Frysinger67761632023-09-05 20:24:16 +0000588 '\tcd into ., run git status to see changes,\n'
589 '\tand commit, stash, or reset.\n')
Edward Lemur979fa782019-08-13 22:44:05 +0000590
Mike Frysinger67761632023-09-05 20:24:16 +0000591 sys.stdout.close()
Edward Lemur979fa782019-08-13 22:44:05 +0000592
Mike Frysinger67761632023-09-05 20:24:16 +0000593 def testRevinfo(self):
594 if not self.enabled:
595 return
596 options = self.Options()
597 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
598 rev_info = scm.revinfo(options, (), None)
599 self.assertEqual(rev_info, '069c602044c5388d2d15c3f875b057c852003458')
msb@chromium.org0f282062009-11-06 20:14:02 +0000600
msb@chromium.orge28e4982009-09-25 20:51:45 +0000601
Edward Lemur979fa782019-08-13 22:44:05 +0000602class ManagedGitWrapperTestCaseMock(unittest.TestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000603 class OptionsObject(object):
604 def __init__(self, verbose=False, revision=None, force=False):
605 self.verbose = verbose
606 self.revision = revision
607 self.deps_os = None
608 self.force = force
609 self.reset = False
610 self.nohooks = False
611 self.break_repo_locks = False
612 # TODO(maruel): Test --jobs > 1.
613 self.jobs = 1
614 self.patch_ref = None
615 self.patch_repo = None
616 self.rebase_patch_ref = True
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000617
Mike Frysinger67761632023-09-05 20:24:16 +0000618 def Options(self, *args, **kwargs):
619 return self.OptionsObject(*args, **kwargs)
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000620
Mike Frysinger67761632023-09-05 20:24:16 +0000621 def checkstdout(self, expected):
622 # pylint: disable=no-member
623 value = sys.stdout.getvalue()
624 sys.stdout.close()
625 # Check that the expected output appears.
626 self.assertIn(expected, strip_timestamps(value))
borenet@google.comb09097a2014-04-09 19:09:08 +0000627
Mike Frysinger67761632023-09-05 20:24:16 +0000628 def setUp(self):
629 self.fake_hash_1 = 't0ta11yf4k3'
630 self.fake_hash_2 = '3v3nf4k3r'
631 self.url = 'git://foo'
632 self.root_dir = '/tmp' if sys.platform != 'win32' else 't:\\tmp'
633 self.relpath = 'fake'
634 self.base_path = os.path.join(self.root_dir, self.relpath)
635 self.backup_base_path = os.path.join(self.root_dir,
636 'old_%s.git' % self.relpath)
637 mock.patch('gclient_scm.scm.GIT.ApplyEnvVars').start()
Mike Frysinger67761632023-09-05 20:24:16 +0000638 mock.patch('gclient_scm.GitWrapper._Fetch').start()
639 mock.patch('gclient_scm.GitWrapper._DeleteOrMove').start()
640 mock.patch('sys.stdout', StringIO()).start()
641 self.addCleanup(mock.patch.stopall)
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000642
Mike Frysinger67761632023-09-05 20:24:16 +0000643 @mock.patch('scm.GIT.IsValidRevision')
644 @mock.patch('os.path.isdir', lambda _: True)
645 def testGetUsableRevGit(self, mockIsValidRevision):
646 # pylint: disable=no-member
647 options = self.Options(verbose=True)
smutae7ea312016-07-18 11:59:41 -0700648
Mike Frysinger67761632023-09-05 20:24:16 +0000649 mockIsValidRevision.side_effect = lambda cwd, rev: rev != '1'
smutae7ea312016-07-18 11:59:41 -0700650
Mike Frysinger67761632023-09-05 20:24:16 +0000651 git_scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
652 # A [fake] git sha1 with a git repo should work (this is in the case
653 # that the LKGR gets flipped to git sha1's some day).
654 self.assertEqual(git_scm.GetUsableRev(self.fake_hash_1, options),
655 self.fake_hash_1)
656 # An SVN rev with an existing purely git repo should raise an exception.
657 self.assertRaises(gclient_scm.gclient_utils.Error, git_scm.GetUsableRev,
658 '1', options)
smutae7ea312016-07-18 11:59:41 -0700659
Mike Frysinger67761632023-09-05 20:24:16 +0000660 @mock.patch('gclient_scm.GitWrapper._Clone')
661 @mock.patch('os.path.isdir')
662 @mock.patch('os.path.exists')
663 @mock.patch('subprocess2.check_output')
664 def testUpdateNoDotGit(self, mockCheckOutput, mockExists, mockIsdir,
665 mockClone):
666 mockIsdir.side_effect = lambda path: path == self.base_path
667 mockExists.side_effect = lambda path: path == self.base_path
668 mockCheckOutput.side_effect = [b'refs/remotes/origin/main', b'', b'']
Edward Lemur979fa782019-08-13 22:44:05 +0000669
Mike Frysinger67761632023-09-05 20:24:16 +0000670 options = self.Options()
671 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
672 scm.update(options, None, [])
Edward Lemur979fa782019-08-13 22:44:05 +0000673
Mike Frysinger67761632023-09-05 20:24:16 +0000674 env = gclient_scm.scm.GIT.ApplyEnvVars({})
675 self.assertEqual(mockCheckOutput.mock_calls, [
676 mock.call(['git', 'symbolic-ref', 'refs/remotes/origin/HEAD'],
677 cwd=self.base_path,
678 env=env,
679 stderr=-1),
680 mock.call(['git', '-c', 'core.quotePath=false', 'ls-files'],
681 cwd=self.base_path,
682 env=env,
683 stderr=-1),
684 mock.call(['git', 'rev-parse', '--verify', 'HEAD'],
685 cwd=self.base_path,
686 env=env,
687 stderr=-1),
688 ])
689 mockClone.assert_called_with('refs/remotes/origin/main', self.url,
690 options)
691 self.checkstdout('\n')
borenet@google.comb09097a2014-04-09 19:09:08 +0000692
Mike Frysinger67761632023-09-05 20:24:16 +0000693 @mock.patch('gclient_scm.GitWrapper._Clone')
694 @mock.patch('os.path.isdir')
695 @mock.patch('os.path.exists')
696 @mock.patch('subprocess2.check_output')
697 def testUpdateConflict(self, mockCheckOutput, mockExists, mockIsdir,
698 mockClone):
699 mockIsdir.side_effect = lambda path: path == self.base_path
700 mockExists.side_effect = lambda path: path == self.base_path
701 mockCheckOutput.side_effect = [b'refs/remotes/origin/main', b'', b'']
702 mockClone.side_effect = [
703 gclient_scm.subprocess2.CalledProcessError(None, None, None, None,
704 None),
705 None,
706 ]
Edward Lemur979fa782019-08-13 22:44:05 +0000707
Mike Frysinger67761632023-09-05 20:24:16 +0000708 options = self.Options()
709 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
710 scm.update(options, None, [])
Edward Lemur979fa782019-08-13 22:44:05 +0000711
Mike Frysinger67761632023-09-05 20:24:16 +0000712 env = gclient_scm.scm.GIT.ApplyEnvVars({})
713 self.assertEqual(mockCheckOutput.mock_calls, [
714 mock.call(['git', 'symbolic-ref', 'refs/remotes/origin/HEAD'],
715 cwd=self.base_path,
716 env=env,
717 stderr=-1),
718 mock.call(['git', '-c', 'core.quotePath=false', 'ls-files'],
719 cwd=self.base_path,
720 env=env,
721 stderr=-1),
722 mock.call(['git', 'rev-parse', '--verify', 'HEAD'],
723 cwd=self.base_path,
724 env=env,
725 stderr=-1),
726 ])
727 mockClone.assert_called_with('refs/remotes/origin/main', self.url,
728 options)
729 self.checkstdout('\n')
borenet@google.comb09097a2014-04-09 19:09:08 +0000730
dbeam@chromium.orge5d1e612011-12-19 19:49:19 +0000731
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000732class UnmanagedGitWrapperTestCase(BaseGitWrapperTestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000733 def checkInStdout(self, expected):
734 # pylint: disable=no-member
735 value = sys.stdout.getvalue()
736 sys.stdout.close()
737 self.assertIn(expected, value)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000738
Mike Frysinger67761632023-09-05 20:24:16 +0000739 def checkNotInStdout(self, expected):
740 # pylint: disable=no-member
741 value = sys.stdout.getvalue()
742 sys.stdout.close()
743 self.assertNotIn(expected, value)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000744
Mike Frysinger67761632023-09-05 20:24:16 +0000745 def getCurrentBranch(self):
746 # Returns name of current branch or HEAD for detached HEAD
747 branch = gclient_scm.scm.GIT.Capture(
748 ['rev-parse', '--abbrev-ref', 'HEAD'], cwd=self.base_path)
749 if branch == 'HEAD':
750 return None
751 return branch
smut@google.com27c9c8a2014-09-11 19:57:55 +0000752
Mike Frysinger67761632023-09-05 20:24:16 +0000753 def testUpdateClone(self):
754 if not self.enabled:
755 return
756 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000757
Mike Frysinger67761632023-09-05 20:24:16 +0000758 origin_root_dir = self.root_dir
759 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
Edward Lesmese79107e2019-10-25 22:47:33 +0000760
Mike Frysinger67761632023-09-05 20:24:16 +0000761 self.root_dir = tempfile.mkdtemp()
762 self.relpath = '.'
763 self.base_path = join(self.root_dir, self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000764
Mike Frysinger67761632023-09-05 20:24:16 +0000765 scm = gclient_scm.GitWrapper(origin_root_dir, self.root_dir,
766 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000767
Mike Frysinger67761632023-09-05 20:24:16 +0000768 expected_file_list = [
769 join(self.base_path, "a"),
770 join(self.base_path, "b")
771 ]
772 file_list = []
773 options.revision = 'unmanaged'
774 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000775
Mike Frysinger67761632023-09-05 20:24:16 +0000776 self.assertEqual(file_list, expected_file_list)
777 self.assertEqual(scm.revinfo(options, (), None),
778 '069c602044c5388d2d15c3f875b057c852003458')
779 # indicates detached HEAD
780 self.assertEqual(self.getCurrentBranch(), None)
781 self.checkInStdout(
782 'Checked out refs/remotes/origin/main to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000783
Mike Frysinger67761632023-09-05 20:24:16 +0000784 def testUpdateCloneOnCommit(self):
785 if not self.enabled:
786 return
787 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000788
Mike Frysinger67761632023-09-05 20:24:16 +0000789 origin_root_dir = self.root_dir
790 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000791
Mike Frysinger67761632023-09-05 20:24:16 +0000792 self.root_dir = tempfile.mkdtemp()
793 self.relpath = '.'
794 self.base_path = join(self.root_dir, self.relpath)
795 url_with_commit_ref = origin_root_dir +\
796 '@a7142dc9f0009350b96a11f372b6ea658592aa95'
Edward Lesmese79107e2019-10-25 22:47:33 +0000797
Mike Frysinger67761632023-09-05 20:24:16 +0000798 scm = gclient_scm.GitWrapper(url_with_commit_ref, self.root_dir,
799 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000800
Mike Frysinger67761632023-09-05 20:24:16 +0000801 expected_file_list = [
802 join(self.base_path, "a"),
803 join(self.base_path, "b")
804 ]
805 file_list = []
806 options.revision = 'unmanaged'
807 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000808
Mike Frysinger67761632023-09-05 20:24:16 +0000809 self.assertEqual(file_list, expected_file_list)
810 self.assertEqual(scm.revinfo(options, (), None),
811 'a7142dc9f0009350b96a11f372b6ea658592aa95')
812 # indicates detached HEAD
813 self.assertEqual(self.getCurrentBranch(), None)
814 self.checkInStdout(
815 'Checked out a7142dc9f0009350b96a11f372b6ea658592aa95 to a detached HEAD'
816 )
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000817
Mike Frysinger67761632023-09-05 20:24:16 +0000818 def testUpdateCloneOnBranch(self):
819 if not self.enabled:
820 return
821 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000822
Mike Frysinger67761632023-09-05 20:24:16 +0000823 origin_root_dir = self.root_dir
824 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000825
Mike Frysinger67761632023-09-05 20:24:16 +0000826 self.root_dir = tempfile.mkdtemp()
827 self.relpath = '.'
828 self.base_path = join(self.root_dir, self.relpath)
829 url_with_branch_ref = origin_root_dir + '@feature'
Edward Lesmese79107e2019-10-25 22:47:33 +0000830
Mike Frysinger67761632023-09-05 20:24:16 +0000831 scm = gclient_scm.GitWrapper(url_with_branch_ref, self.root_dir,
832 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000833
Mike Frysinger67761632023-09-05 20:24:16 +0000834 expected_file_list = [
835 join(self.base_path, "a"),
836 join(self.base_path, "b"),
837 join(self.base_path, "c")
838 ]
839 file_list = []
840 options.revision = 'unmanaged'
841 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000842
Mike Frysinger67761632023-09-05 20:24:16 +0000843 self.assertEqual(file_list, expected_file_list)
844 self.assertEqual(scm.revinfo(options, (), None),
845 '9a51244740b25fa2ded5252ca00a3178d3f665a9')
846 # indicates detached HEAD
847 self.assertEqual(self.getCurrentBranch(), None)
848 self.checkInStdout(
849 'Checked out 9a51244740b25fa2ded5252ca00a3178d3f665a9 '
850 'to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000851
Mike Frysinger67761632023-09-05 20:24:16 +0000852 def testUpdateCloneOnFetchedRemoteBranch(self):
853 if not self.enabled:
854 return
855 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000856
Mike Frysinger67761632023-09-05 20:24:16 +0000857 origin_root_dir = self.root_dir
858 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000859
Mike Frysinger67761632023-09-05 20:24:16 +0000860 self.root_dir = tempfile.mkdtemp()
861 self.relpath = '.'
862 self.base_path = join(self.root_dir, self.relpath)
863 url_with_branch_ref = origin_root_dir + '@refs/remotes/origin/feature'
Edward Lesmese79107e2019-10-25 22:47:33 +0000864
Mike Frysinger67761632023-09-05 20:24:16 +0000865 scm = gclient_scm.GitWrapper(url_with_branch_ref, self.root_dir,
866 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000867
Mike Frysinger67761632023-09-05 20:24:16 +0000868 expected_file_list = [
869 join(self.base_path, "a"),
870 join(self.base_path, "b"),
871 join(self.base_path, "c")
872 ]
873 file_list = []
874 options.revision = 'unmanaged'
875 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000876
Mike Frysinger67761632023-09-05 20:24:16 +0000877 self.assertEqual(file_list, expected_file_list)
878 self.assertEqual(scm.revinfo(options, (), None),
879 '9a51244740b25fa2ded5252ca00a3178d3f665a9')
880 # indicates detached HEAD
881 self.assertEqual(self.getCurrentBranch(), None)
882 self.checkInStdout(
883 'Checked out refs/remotes/origin/feature to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000884
Mike Frysinger67761632023-09-05 20:24:16 +0000885 def testUpdateCloneOnTrueRemoteBranch(self):
886 if not self.enabled:
887 return
888 options = self.Options()
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000889
Mike Frysinger67761632023-09-05 20:24:16 +0000890 origin_root_dir = self.root_dir
891 self.addCleanup(gclient_utils.rmtree, origin_root_dir)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000892
Mike Frysinger67761632023-09-05 20:24:16 +0000893 self.root_dir = tempfile.mkdtemp()
894 self.relpath = '.'
895 self.base_path = join(self.root_dir, self.relpath)
896 url_with_branch_ref = origin_root_dir + '@refs/heads/feature'
Edward Lesmese79107e2019-10-25 22:47:33 +0000897
Mike Frysinger67761632023-09-05 20:24:16 +0000898 scm = gclient_scm.GitWrapper(url_with_branch_ref, self.root_dir,
899 self.relpath)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000900
Mike Frysinger67761632023-09-05 20:24:16 +0000901 expected_file_list = [
902 join(self.base_path, "a"),
903 join(self.base_path, "b"),
904 join(self.base_path, "c")
905 ]
906 file_list = []
907 options.revision = 'unmanaged'
908 scm.update(options, (), file_list)
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000909
Mike Frysinger67761632023-09-05 20:24:16 +0000910 self.assertEqual(file_list, expected_file_list)
911 self.assertEqual(scm.revinfo(options, (), None),
912 '9a51244740b25fa2ded5252ca00a3178d3f665a9')
913 # @refs/heads/feature is AKA @refs/remotes/origin/feature in the clone,
914 # so should be treated as such by gclient. TODO(mmoss): Though really,
915 # we should only allow DEPS to specify branches as they are known in the
916 # upstream repo, since the mapping into the local repo can be modified
917 # by users (or we might even want to change the gclient defaults at some
918 # point). But that will take more work to stop using refs/remotes/
919 # everywhere that we do (and to stop assuming a DEPS ref will always
920 # resolve locally, like when passing them to show-ref or rev-list).
921 self.assertEqual(self.getCurrentBranch(), None)
922 self.checkInStdout(
923 'Checked out refs/remotes/origin/feature to a detached HEAD')
romain.pokrzywka@gmail.com483a0ba2014-05-30 00:06:07 +0000924
Mike Frysinger67761632023-09-05 20:24:16 +0000925 def testUpdateUpdate(self):
926 if not self.enabled:
927 return
928 options = self.Options()
929 expected_file_list = []
930 scm = gclient_scm.GitWrapper(self.url, self.root_dir, self.relpath)
931 file_list = []
932 options.revision = 'unmanaged'
933 scm.update(options, (), file_list)
934 self.assertEqual(file_list, expected_file_list)
935 self.assertEqual(scm.revinfo(options, (), None),
936 '069c602044c5388d2d15c3f875b057c852003458')
937 self.checkstdout('________ unmanaged solution; skipping .\n')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000938
939
Edward Lemur979fa782019-08-13 22:44:05 +0000940class CipdWrapperTestCase(unittest.TestCase):
Mike Frysinger67761632023-09-05 20:24:16 +0000941 def setUp(self):
942 # Create this before setting up mocks.
943 self._cipd_root_dir = tempfile.mkdtemp()
944 self._workdir = tempfile.mkdtemp()
John Budorick0f7b2002018-01-19 15:46:17 -0800945
Mike Frysinger67761632023-09-05 20:24:16 +0000946 self._cipd_instance_url = 'https://chrome-infra-packages.appspot.com'
947 self._cipd_root = gclient_scm.CipdRoot(self._cipd_root_dir,
948 self._cipd_instance_url)
949 self._cipd_packages = [
950 self._cipd_root.add_package('f', 'foo_package', 'foo_version'),
951 self._cipd_root.add_package('b', 'bar_package', 'bar_version'),
952 self._cipd_root.add_package('b', 'baz_package', 'baz_version'),
953 ]
954 mock.patch('tempfile.mkdtemp', lambda: self._workdir).start()
955 mock.patch('gclient_scm.CipdRoot.add_package').start()
956 mock.patch('gclient_scm.CipdRoot.clobber').start()
957 mock.patch('gclient_scm.CipdRoot.ensure_file_resolve').start()
958 mock.patch('gclient_scm.CipdRoot.ensure').start()
959 self.addCleanup(mock.patch.stopall)
960 self.addCleanup(gclient_utils.rmtree, self._cipd_root_dir)
961 self.addCleanup(gclient_utils.rmtree, self._workdir)
John Budorick0f7b2002018-01-19 15:46:17 -0800962
Mike Frysinger67761632023-09-05 20:24:16 +0000963 def createScmWithPackageThatSatisfies(self, condition):
964 return gclient_scm.CipdWrapper(
965 url=self._cipd_instance_url,
966 root_dir=self._cipd_root_dir,
967 relpath='fake_relpath',
968 root=self._cipd_root,
969 package=self.getPackageThatSatisfies(condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800970
Mike Frysinger67761632023-09-05 20:24:16 +0000971 def getPackageThatSatisfies(self, condition):
972 for p in self._cipd_packages:
973 if condition(p):
974 return p
John Budorick0f7b2002018-01-19 15:46:17 -0800975
Mike Frysinger67761632023-09-05 20:24:16 +0000976 self.fail('Unable to find a satisfactory package.')
John Budorick0f7b2002018-01-19 15:46:17 -0800977
Mike Frysinger67761632023-09-05 20:24:16 +0000978 def testRevert(self):
979 """Checks that revert does nothing."""
980 scm = self.createScmWithPackageThatSatisfies(lambda _: True)
981 scm.revert(None, (), [])
John Budorick0f7b2002018-01-19 15:46:17 -0800982
Mike Frysinger67761632023-09-05 20:24:16 +0000983 @mock.patch('gclient_scm.gclient_utils.CheckCallAndFilter')
984 @mock.patch('gclient_scm.gclient_utils.rmtree')
985 def testRevinfo(self, mockRmtree, mockCheckCallAndFilter):
986 """Checks that revinfo uses the JSON from cipd describe."""
987 scm = self.createScmWithPackageThatSatisfies(lambda _: True)
John Budorick0f7b2002018-01-19 15:46:17 -0800988
Mike Frysinger67761632023-09-05 20:24:16 +0000989 expected_revinfo = '0123456789abcdef0123456789abcdef01234567'
990 json_contents = {
991 'result': {
992 'pin': {
993 'instance_id': expected_revinfo,
994 }
John Budorick0f7b2002018-01-19 15:46:17 -0800995 }
996 }
Mike Frysinger67761632023-09-05 20:24:16 +0000997 describe_json_path = join(self._workdir, 'describe.json')
998 with open(describe_json_path, 'w') as describe_json:
999 json.dump(json_contents, describe_json)
John Budorick0f7b2002018-01-19 15:46:17 -08001000
Mike Frysinger67761632023-09-05 20:24:16 +00001001 revinfo = scm.revinfo(None, (), [])
1002 self.assertEqual(revinfo, expected_revinfo)
Edward Lemur979fa782019-08-13 22:44:05 +00001003
Mike Frysinger67761632023-09-05 20:24:16 +00001004 mockRmtree.assert_called_with(self._workdir)
1005 mockCheckCallAndFilter.assert_called_with([
1006 'cipd',
1007 'describe',
1008 'foo_package',
1009 '-log-level',
1010 'error',
1011 '-version',
1012 'foo_version',
1013 '-json-output',
1014 describe_json_path,
1015 ])
John Budorick0f7b2002018-01-19 15:46:17 -08001016
Mike Frysinger67761632023-09-05 20:24:16 +00001017 def testUpdate(self):
1018 """Checks that update does nothing."""
1019 scm = self.createScmWithPackageThatSatisfies(lambda _: True)
1020 scm.update(None, (), [])
John Budorick0f7b2002018-01-19 15:46:17 -08001021
1022
Edward Lesmes8073a502020-04-15 02:11:14 +00001023class BranchHeadsFakeRepo(fake_repos.FakeReposBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001024 def populateGit(self):
1025 # Creates a tree that looks like this:
1026 #
1027 # 5 refs/branch-heads/5
1028 # |
1029 # 4
1030 # |
1031 # 1--2--3 refs/heads/main
1032 self._commit_git('repo_1', {'commit 1': 'touched'})
1033 self._commit_git('repo_1', {'commit 2': 'touched'})
1034 self._commit_git('repo_1', {'commit 3': 'touched'})
1035 self._create_ref('repo_1', 'refs/heads/main', 3)
Edward Lesmes8073a502020-04-15 02:11:14 +00001036
Mike Frysinger67761632023-09-05 20:24:16 +00001037 self._commit_git('repo_1', {'commit 4': 'touched'}, base=2)
1038 self._commit_git('repo_1', {'commit 5': 'touched'}, base=2)
1039 self._create_ref('repo_1', 'refs/branch-heads/5', 5)
Edward Lesmes8073a502020-04-15 02:11:14 +00001040
1041
1042class BranchHeadsTest(fake_repos.FakeReposTestBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001043 FAKE_REPOS_CLASS = BranchHeadsFakeRepo
Edward Lesmes8073a502020-04-15 02:11:14 +00001044
Mike Frysinger67761632023-09-05 20:24:16 +00001045 def setUp(self):
1046 super(BranchHeadsTest, self).setUp()
1047 self.enabled = self.FAKE_REPOS.set_up_git()
1048 self.options = BaseGitWrapperTestCase.OptionsObject()
1049 self.url = self.git_base + 'repo_1'
1050 self.mirror = None
1051 mock.patch('sys.stdout', StringIO()).start()
1052 self.addCleanup(mock.patch.stopall)
Edward Lesmes8073a502020-04-15 02:11:14 +00001053
Mike Frysinger67761632023-09-05 20:24:16 +00001054 def setUpMirror(self):
1055 self.mirror = tempfile.mkdtemp('mirror')
1056 git_cache.Mirror.SetCachePath(self.mirror)
1057 self.addCleanup(gclient_utils.rmtree, self.mirror)
1058 self.addCleanup(git_cache.Mirror.SetCachePath, None)
Edward Lesmes8073a502020-04-15 02:11:14 +00001059
Mike Frysinger67761632023-09-05 20:24:16 +00001060 def testCheckoutBranchHeads(self):
1061 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1062 file_list = []
Edward Lesmes8073a502020-04-15 02:11:14 +00001063
Mike Frysinger67761632023-09-05 20:24:16 +00001064 self.options.revision = 'refs/branch-heads/5'
1065 scm.update(self.options, None, file_list)
1066 self.assertEqual(self.githash('repo_1', 5),
1067 self.gitrevparse(self.root_dir))
Edward Lesmes8073a502020-04-15 02:11:14 +00001068
Mike Frysinger67761632023-09-05 20:24:16 +00001069 def testCheckoutUpdatedBranchHeads(self):
1070 # Travel back in time, and set refs/branch-heads/5 to its parent.
1071 subprocess2.check_call([
1072 'git', 'update-ref', 'refs/branch-heads/5',
1073 self.githash('repo_1', 4)
1074 ],
1075 cwd=self.url)
Edward Lesmes8073a502020-04-15 02:11:14 +00001076
Mike Frysinger67761632023-09-05 20:24:16 +00001077 # Sync to refs/branch-heads/5
1078 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1079 self.options.revision = 'refs/branch-heads/5'
1080 scm.update(self.options, None, [])
Edward Lesmes8073a502020-04-15 02:11:14 +00001081
Mike Frysinger67761632023-09-05 20:24:16 +00001082 # Set refs/branch-heads/5 back to its original value.
1083 subprocess2.check_call([
1084 'git', 'update-ref', 'refs/branch-heads/5',
1085 self.githash('repo_1', 5)
1086 ],
1087 cwd=self.url)
Edward Lesmes8073a502020-04-15 02:11:14 +00001088
Mike Frysinger67761632023-09-05 20:24:16 +00001089 # Attempt to sync to refs/branch-heads/5 again.
1090 self.testCheckoutBranchHeads()
Edward Lesmes8073a502020-04-15 02:11:14 +00001091
Mike Frysinger67761632023-09-05 20:24:16 +00001092 def testCheckoutBranchHeadsMirror(self):
1093 self.setUpMirror()
1094 self.testCheckoutBranchHeads()
Edward Lesmes8073a502020-04-15 02:11:14 +00001095
Mike Frysinger67761632023-09-05 20:24:16 +00001096 def testCheckoutUpdatedBranchHeadsMirror(self):
1097 self.setUpMirror()
1098 self.testCheckoutUpdatedBranchHeads()
Edward Lesmes8073a502020-04-15 02:11:14 +00001099
1100
Edward Lemurd64781e2018-07-11 23:09:55 +00001101class GerritChangesFakeRepo(fake_repos.FakeReposBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001102 def populateGit(self):
1103 # Creates a tree that looks like this:
1104 #
1105 # 6 refs/changes/35/1235/1
1106 # |
1107 # 5 refs/changes/34/1234/1
1108 # |
1109 # 1--2--3--4 refs/heads/main
1110 # | |
1111 # | 11(5)--12 refs/heads/main-with-5
1112 # |
1113 # 7--8--9 refs/heads/feature
1114 # |
1115 # 10 refs/changes/36/1236/1
1116 #
Edward Lemurd64781e2018-07-11 23:09:55 +00001117
Mike Frysinger67761632023-09-05 20:24:16 +00001118 self._commit_git('repo_1', {'commit 1': 'touched'})
1119 self._commit_git('repo_1', {'commit 2': 'touched'})
1120 self._commit_git('repo_1', {'commit 3': 'touched'})
1121 self._commit_git('repo_1', {'commit 4': 'touched'})
1122 self._create_ref('repo_1', 'refs/heads/main', 4)
Edward Lemurd64781e2018-07-11 23:09:55 +00001123
Mike Frysinger67761632023-09-05 20:24:16 +00001124 # Create a change on top of commit 3 that consists of two commits.
1125 self._commit_git('repo_1', {
1126 'commit 5': 'touched',
1127 'change': '1234'
1128 },
1129 base=3)
1130 self._create_ref('repo_1', 'refs/changes/34/1234/1', 5)
1131 self._commit_git('repo_1', {'commit 6': 'touched', 'change': '1235'})
1132 self._create_ref('repo_1', 'refs/changes/35/1235/1', 6)
Edward Lemurd64781e2018-07-11 23:09:55 +00001133
Mike Frysinger67761632023-09-05 20:24:16 +00001134 # Create a refs/heads/feature branch on top of commit 2, consisting of
1135 # three commits.
1136 self._commit_git('repo_1', {'commit 7': 'touched'}, base=2)
1137 self._commit_git('repo_1', {'commit 8': 'touched'})
1138 self._commit_git('repo_1', {'commit 9': 'touched'})
1139 self._create_ref('repo_1', 'refs/heads/feature', 9)
Edward Lemurca7d8812018-07-24 17:42:45 +00001140
Mike Frysinger67761632023-09-05 20:24:16 +00001141 # Create a change of top of commit 8.
1142 self._commit_git('repo_1', {
1143 'commit 10': 'touched',
1144 'change': '1236'
1145 },
1146 base=8)
1147 self._create_ref('repo_1', 'refs/changes/36/1236/1', 10)
Edward Lemurca7d8812018-07-24 17:42:45 +00001148
Mike Frysinger67761632023-09-05 20:24:16 +00001149 # Create a refs/heads/main-with-5 on top of commit 3 which is a branch
1150 # where refs/changes/34/1234/1 (commit 5) has already landed as commit
1151 # 11.
1152 self._commit_git(
1153 'repo_1',
1154 # This is really commit 11, but has the changes of commit 5
1155 {
1156 'commit 5': 'touched',
1157 'change': '1234'
1158 },
1159 base=3)
1160 self._commit_git('repo_1', {'commit 12': 'touched'})
1161 self._create_ref('repo_1', 'refs/heads/main-with-5', 12)
Edward Lemurca7d8812018-07-24 17:42:45 +00001162
Edward Lemurd64781e2018-07-11 23:09:55 +00001163
1164class GerritChangesTest(fake_repos.FakeReposTestBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001165 FAKE_REPOS_CLASS = GerritChangesFakeRepo
Edward Lemurd64781e2018-07-11 23:09:55 +00001166
Mike Frysinger67761632023-09-05 20:24:16 +00001167 def setUp(self):
1168 super(GerritChangesTest, self).setUp()
1169 self.enabled = self.FAKE_REPOS.set_up_git()
1170 self.options = BaseGitWrapperTestCase.OptionsObject()
1171 self.url = self.git_base + 'repo_1'
1172 self.mirror = None
1173 mock.patch('sys.stdout', StringIO()).start()
1174 self.addCleanup(mock.patch.stopall)
Edward Lemurd64781e2018-07-11 23:09:55 +00001175
Mike Frysinger67761632023-09-05 20:24:16 +00001176 def setUpMirror(self):
1177 self.mirror = tempfile.mkdtemp()
1178 git_cache.Mirror.SetCachePath(self.mirror)
1179 self.addCleanup(gclient_utils.rmtree, self.mirror)
1180 self.addCleanup(git_cache.Mirror.SetCachePath, None)
Edward Lemurd64781e2018-07-11 23:09:55 +00001181
Mike Frysinger67761632023-09-05 20:24:16 +00001182 def assertCommits(self, commits):
1183 """Check that all, and only |commits| are present in the current checkout.
Edward Lemurca7d8812018-07-24 17:42:45 +00001184 """
Mike Frysinger67761632023-09-05 20:24:16 +00001185 for i in commits:
1186 name = os.path.join(self.root_dir, 'commit ' + str(i))
1187 self.assertTrue(os.path.exists(name), 'Commit not found: %s' % name)
Edward Lemurca7d8812018-07-24 17:42:45 +00001188
Mike Frysinger67761632023-09-05 20:24:16 +00001189 all_commits = set(range(1, len(self.FAKE_REPOS.git_hashes['repo_1'])))
1190 for i in all_commits - set(commits):
1191 name = os.path.join(self.root_dir, 'commit ' + str(i))
1192 self.assertFalse(os.path.exists(name),
1193 'Unexpected commit: %s' % name)
Edward Lemurca7d8812018-07-24 17:42:45 +00001194
Mike Frysinger67761632023-09-05 20:24:16 +00001195 def testCanCloneGerritChange(self):
1196 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1197 file_list = []
Edward Lemurd64781e2018-07-11 23:09:55 +00001198
Mike Frysinger67761632023-09-05 20:24:16 +00001199 self.options.revision = 'refs/changes/35/1235/1'
1200 scm.update(self.options, None, file_list)
1201 self.assertEqual(self.githash('repo_1', 6),
1202 self.gitrevparse(self.root_dir))
Edward Lemurd64781e2018-07-11 23:09:55 +00001203
Mike Frysinger67761632023-09-05 20:24:16 +00001204 def testCanSyncToGerritChange(self):
1205 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1206 file_list = []
Edward Lemurd64781e2018-07-11 23:09:55 +00001207
Mike Frysinger67761632023-09-05 20:24:16 +00001208 self.options.revision = self.githash('repo_1', 1)
1209 scm.update(self.options, None, file_list)
1210 self.assertEqual(self.githash('repo_1', 1),
1211 self.gitrevparse(self.root_dir))
Edward Lemurd64781e2018-07-11 23:09:55 +00001212
Mike Frysinger67761632023-09-05 20:24:16 +00001213 self.options.revision = 'refs/changes/35/1235/1'
1214 scm.update(self.options, None, file_list)
1215 self.assertEqual(self.githash('repo_1', 6),
1216 self.gitrevparse(self.root_dir))
Edward Lemurd64781e2018-07-11 23:09:55 +00001217
Mike Frysinger67761632023-09-05 20:24:16 +00001218 def testCanCloneGerritChangeMirror(self):
1219 self.setUpMirror()
1220 self.testCanCloneGerritChange()
Edward Lemurd64781e2018-07-11 23:09:55 +00001221
Mike Frysinger67761632023-09-05 20:24:16 +00001222 def testCanSyncToGerritChangeMirror(self):
1223 self.setUpMirror()
1224 self.testCanSyncToGerritChange()
Edward Lemurd64781e2018-07-11 23:09:55 +00001225
Mike Frysinger67761632023-09-05 20:24:16 +00001226 def testMirrorPushUrl(self):
1227 self.setUpMirror()
Edward Lesmese79107e2019-10-25 22:47:33 +00001228
Mike Frysinger67761632023-09-05 20:24:16 +00001229 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1230 file_list = []
1231 self.assertIsNotNone(scm._GetMirror(self.url, self.options))
Edward Lesmese79107e2019-10-25 22:47:33 +00001232
Mike Frysinger67761632023-09-05 20:24:16 +00001233 scm.update(self.options, None, file_list)
Edward Lesmese79107e2019-10-25 22:47:33 +00001234
Mike Frysinger67761632023-09-05 20:24:16 +00001235 fetch_url = scm._Capture(['remote', 'get-url', 'origin'])
1236 self.assertTrue(
1237 fetch_url.startswith(self.mirror),
1238 msg='\n'.join([
1239 'Repository fetch url should be in the git cache mirror directory.',
1240 ' fetch_url: %s' % fetch_url,
1241 ' mirror: %s' % self.mirror
1242 ]))
1243 push_url = scm._Capture(['remote', 'get-url', '--push', 'origin'])
1244 self.assertEqual(push_url, self.url)
Edward Lesmese79107e2019-10-25 22:47:33 +00001245
Mike Frysinger67761632023-09-05 20:24:16 +00001246 def testAppliesPatchOnTopOfMasterByDefault(self):
1247 """Test the default case, where we apply a patch on top of main."""
1248 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1249 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001250
Mike Frysinger67761632023-09-05 20:24:16 +00001251 # Make sure we don't specify a revision.
1252 self.options.revision = None
1253 scm.update(self.options, None, file_list)
1254 self.assertEqual(self.githash('repo_1', 4),
1255 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001256
Mike Frysinger67761632023-09-05 20:24:16 +00001257 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1258 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001259
Mike Frysinger67761632023-09-05 20:24:16 +00001260 self.assertCommits([1, 2, 3, 4, 5, 6])
1261 self.assertEqual(self.githash('repo_1', 4),
1262 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001263
Mike Frysinger67761632023-09-05 20:24:16 +00001264 def testCheckoutOlderThanPatchBase(self):
1265 """Test applying a patch on an old checkout.
Edward Lemurca7d8812018-07-24 17:42:45 +00001266
1267 We first checkout commit 1, and try to patch refs/changes/35/1235/1, which
1268 contains commits 5 and 6, and is based on top of commit 3.
1269 The final result should contain commits 1, 5 and 6, but not commits 2 or 3.
1270 """
Mike Frysinger67761632023-09-05 20:24:16 +00001271 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 # Sync to commit 1
1275 self.options.revision = self.githash('repo_1', 1)
1276 scm.update(self.options, None, file_list)
1277 self.assertEqual(self.githash('repo_1', 1),
1278 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001279
Mike Frysinger67761632023-09-05 20:24:16 +00001280 # Apply the change on top of that.
1281 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1282 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001283
Mike Frysinger67761632023-09-05 20:24:16 +00001284 self.assertCommits([1, 5, 6])
1285 self.assertEqual(self.githash('repo_1', 1),
1286 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001287
Mike Frysinger67761632023-09-05 20:24:16 +00001288 def testCheckoutOriginFeature(self):
1289 """Tests that we can apply a patch on a branch other than main."""
1290 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1291 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001292
Mike Frysinger67761632023-09-05 20:24:16 +00001293 # Sync to remote's refs/heads/feature
1294 self.options.revision = 'refs/heads/feature'
1295 scm.update(self.options, None, file_list)
1296 self.assertEqual(self.githash('repo_1', 9),
1297 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001298
Mike Frysinger67761632023-09-05 20:24:16 +00001299 # Apply the change on top of that.
1300 scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1',
1301 'refs/heads/feature', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001302
Mike Frysinger67761632023-09-05 20:24:16 +00001303 self.assertCommits([1, 2, 7, 8, 9, 10])
1304 self.assertEqual(self.githash('repo_1', 9),
1305 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001306
Mike Frysinger67761632023-09-05 20:24:16 +00001307 def testCheckoutOriginFeatureOnOldRevision(self):
1308 """Tests that we can apply a patch on an old checkout, on a branch other
Josip Sokcevic7e133ff2021-07-13 17:44:53 +00001309 than main."""
Mike Frysinger67761632023-09-05 20:24:16 +00001310 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1311 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001312
Mike Frysinger67761632023-09-05 20:24:16 +00001313 # Sync to remote's refs/heads/feature on an old revision
1314 self.options.revision = self.githash('repo_1', 7)
1315 scm.update(self.options, None, file_list)
1316 self.assertEqual(self.githash('repo_1', 7),
1317 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001318
Mike Frysinger67761632023-09-05 20:24:16 +00001319 # Apply the change on top of that.
1320 scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1',
1321 'refs/heads/feature', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001322
Mike Frysinger67761632023-09-05 20:24:16 +00001323 # We shouldn't have rebased on top of 2 (which is the merge base between
1324 # remote's main branch and the change) but on top of 7 (which is the
1325 # merge base between remote's feature branch and the change).
1326 self.assertCommits([1, 2, 7, 10])
1327 self.assertEqual(self.githash('repo_1', 7),
1328 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001329
Mike Frysinger67761632023-09-05 20:24:16 +00001330 def testCheckoutOriginFeaturePatchBranch(self):
1331 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1332 file_list = []
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001333
Mike Frysinger67761632023-09-05 20:24:16 +00001334 # Sync to the hash instead of remote's refs/heads/feature.
1335 self.options.revision = self.githash('repo_1', 9)
1336 scm.update(self.options, None, file_list)
1337 self.assertEqual(self.githash('repo_1', 9),
1338 self.gitrevparse(self.root_dir))
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001339
Mike Frysinger67761632023-09-05 20:24:16 +00001340 # Apply refs/changes/34/1234/1, created for remote's main branch on top
1341 # of remote's feature branch.
1342 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1343 'refs/heads/main', self.options, file_list)
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001344
Mike Frysinger67761632023-09-05 20:24:16 +00001345 # Commits 5 and 6 are part of the patch, and commits 1, 2, 7, 8 and 9
1346 # are part of remote's feature branch.
1347 self.assertCommits([1, 2, 5, 6, 7, 8, 9])
1348 self.assertEqual(self.githash('repo_1', 9),
1349 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001350
Mike Frysinger67761632023-09-05 20:24:16 +00001351 def testDoesntRebasePatchMaster(self):
1352 """Tests that we can apply a patch without rebasing it.
Edward Lemurca7d8812018-07-24 17:42:45 +00001353 """
Mike Frysinger67761632023-09-05 20:24:16 +00001354 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1355 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001356
Mike Frysinger67761632023-09-05 20:24:16 +00001357 self.options.rebase_patch_ref = False
1358 scm.update(self.options, None, file_list)
1359 self.assertEqual(self.githash('repo_1', 4),
1360 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001361
Mike Frysinger67761632023-09-05 20:24:16 +00001362 # Apply the change on top of that.
1363 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1364 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001365
Mike Frysinger67761632023-09-05 20:24:16 +00001366 self.assertCommits([1, 2, 3, 5, 6])
1367 self.assertEqual(self.githash('repo_1', 5),
1368 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001369
Mike Frysinger67761632023-09-05 20:24:16 +00001370 def testDoesntRebasePatchOldCheckout(self):
1371 """Tests that we can apply a patch without rebasing it on an old checkout.
Edward Lemurca7d8812018-07-24 17:42:45 +00001372 """
Mike Frysinger67761632023-09-05 20:24:16 +00001373 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1374 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001375
Mike Frysinger67761632023-09-05 20:24:16 +00001376 # Sync to commit 1
1377 self.options.revision = self.githash('repo_1', 1)
1378 self.options.rebase_patch_ref = False
1379 scm.update(self.options, None, file_list)
1380 self.assertEqual(self.githash('repo_1', 1),
1381 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001382
Mike Frysinger67761632023-09-05 20:24:16 +00001383 # Apply the change on top of that.
1384 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1385 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001386
Mike Frysinger67761632023-09-05 20:24:16 +00001387 self.assertCommits([1, 2, 3, 5, 6])
1388 self.assertEqual(self.githash('repo_1', 5),
1389 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001390
Mike Frysinger67761632023-09-05 20:24:16 +00001391 def testDoesntSoftResetIfNotAskedTo(self):
1392 """Test that we can apply a patch without doing a soft reset."""
1393 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1394 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001395
Mike Frysinger67761632023-09-05 20:24:16 +00001396 self.options.reset_patch_ref = False
1397 scm.update(self.options, None, file_list)
1398 self.assertEqual(self.githash('repo_1', 4),
1399 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001400
Mike Frysinger67761632023-09-05 20:24:16 +00001401 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1402 'refs/heads/main', self.options, file_list)
Edward Lemurca7d8812018-07-24 17:42:45 +00001403
Mike Frysinger67761632023-09-05 20:24:16 +00001404 self.assertCommits([1, 2, 3, 4, 5, 6])
1405 # The commit hash after cherry-picking is not known, but it must be
1406 # different from what the repo was synced at before patching.
1407 self.assertNotEqual(self.githash('repo_1', 4),
1408 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001409
Mike Frysinger67761632023-09-05 20:24:16 +00001410 @mock.patch('gerrit_util.GetChange', return_value={'topic': 'test_topic'})
1411 @mock.patch('gerrit_util.QueryChanges',
1412 return_value=[{
1413 '_number': 1234
1414 }, {
1415 '_number': 1235,
1416 'current_revision': 'abc',
1417 'revisions': {
1418 'abc': {
1419 'ref': 'refs/changes/35/1235/1'
1420 }
1421 }
1422 }])
1423 def testDownloadTopics(self, query_changes_mock, get_change_mock):
1424 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1425 file_list = []
Ravi Mistryecda7822022-02-28 16:22:20 +00001426
Mike Frysinger67761632023-09-05 20:24:16 +00001427 self.options.revision = 'refs/changes/34/1234/1'
1428 scm.update(self.options, None, file_list)
1429 self.assertEqual(self.githash('repo_1', 5),
1430 self.gitrevparse(self.root_dir))
Ravi Mistryecda7822022-02-28 16:22:20 +00001431
Mike Frysinger67761632023-09-05 20:24:16 +00001432 # pylint: disable=attribute-defined-outside-init
1433 self.options.download_topics = True
1434 scm.url = 'https://test-repo.googlesource.com/repo_1.git'
1435 scm.apply_patch_ref(self.url, 'refs/changes/34/1234/1',
1436 'refs/heads/main', self.options, file_list)
Ravi Mistryecda7822022-02-28 16:22:20 +00001437
Mike Frysinger67761632023-09-05 20:24:16 +00001438 get_change_mock.assert_called_once_with(mock.ANY, '1234')
1439 query_changes_mock.assert_called_once_with(mock.ANY,
1440 [('topic', 'test_topic'),
1441 ('status', 'open'),
1442 ('repo', 'repo_1')],
1443 o_params=['ALL_REVISIONS'])
Ravi Mistryecda7822022-02-28 16:22:20 +00001444
Mike Frysinger67761632023-09-05 20:24:16 +00001445 self.assertCommits([1, 2, 3, 5, 6])
1446 # The commit hash after the two cherry-picks is not known, but it must
1447 # be different from what the repo was synced at before patching.
1448 self.assertNotEqual(self.githash('repo_1', 4),
1449 self.gitrevparse(self.root_dir))
Ravi Mistryecda7822022-02-28 16:22:20 +00001450
Mike Frysinger67761632023-09-05 20:24:16 +00001451 def testRecoversAfterPatchFailure(self):
1452 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1453 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001454
Mike Frysinger67761632023-09-05 20:24:16 +00001455 self.options.revision = 'refs/changes/34/1234/1'
1456 scm.update(self.options, None, file_list)
1457 self.assertEqual(self.githash('repo_1', 5),
1458 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001459
Mike Frysinger67761632023-09-05 20:24:16 +00001460 # Checkout 'refs/changes/34/1234/1' modifies the 'change' file, so
1461 # trying to patch 'refs/changes/36/1236/1' creates a patch failure.
1462 with self.assertRaises(subprocess2.CalledProcessError) as cm:
1463 scm.apply_patch_ref(self.url, 'refs/changes/36/1236/1',
1464 'refs/heads/main', self.options, file_list)
1465 self.assertEqual(cm.exception.cmd[:2], ['git', 'cherry-pick'])
1466 self.assertIn(b'error: could not apply', cm.exception.stderr)
Edward Lemurca7d8812018-07-24 17:42:45 +00001467
Mike Frysinger67761632023-09-05 20:24:16 +00001468 # Try to apply 'refs/changes/35/1235/1', which doesn't have a merge
1469 # conflict.
1470 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1471 'refs/heads/main', self.options, file_list)
1472 self.assertCommits([1, 2, 3, 5, 6])
1473 self.assertEqual(self.githash('repo_1', 5),
1474 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001475
Mike Frysinger67761632023-09-05 20:24:16 +00001476 def testIgnoresAlreadyMergedCommits(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/heads/main-with-5'
1481 scm.update(self.options, None, file_list)
1482 self.assertEqual(self.githash('repo_1', 12),
1483 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001484
Mike Frysinger67761632023-09-05 20:24:16 +00001485 # When we try 'refs/changes/35/1235/1' on top of 'refs/heads/feature',
1486 # 'refs/changes/34/1234/1' will be an empty commit, since the changes
1487 # were already present in the tree as commit 11. Make sure we deal with
1488 # this gracefully.
1489 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1490 'refs/heads/feature', self.options, file_list)
1491 self.assertCommits([1, 2, 3, 5, 6, 12])
1492 self.assertEqual(self.githash('repo_1', 12),
1493 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001494
Mike Frysinger67761632023-09-05 20:24:16 +00001495 def testRecoversFromExistingCherryPick(self):
1496 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1497 file_list = []
Edward Lemurca7d8812018-07-24 17:42:45 +00001498
Mike Frysinger67761632023-09-05 20:24:16 +00001499 self.options.revision = 'refs/changes/34/1234/1'
1500 scm.update(self.options, None, file_list)
1501 self.assertEqual(self.githash('repo_1', 5),
1502 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001503
Mike Frysinger67761632023-09-05 20:24:16 +00001504 # Checkout 'refs/changes/34/1234/1' modifies the 'change' file, so
1505 # trying to cherry-pick 'refs/changes/36/1236/1' raises an error.
1506 scm._Run(['fetch', 'origin', 'refs/changes/36/1236/1'], self.options)
1507 with self.assertRaises(subprocess2.CalledProcessError) as cm:
1508 scm._Run(['cherry-pick', 'FETCH_HEAD'], self.options)
1509 self.assertEqual(cm.exception.cmd[:2], ['git', 'cherry-pick'])
Edward Lemurca7d8812018-07-24 17:42:45 +00001510
Mike Frysinger67761632023-09-05 20:24:16 +00001511 # Try to apply 'refs/changes/35/1235/1', which doesn't have a merge
1512 # conflict.
1513 scm.apply_patch_ref(self.url, 'refs/changes/35/1235/1',
1514 'refs/heads/main', self.options, file_list)
1515 self.assertCommits([1, 2, 3, 5, 6])
1516 self.assertEqual(self.githash('repo_1', 5),
1517 self.gitrevparse(self.root_dir))
Edward Lemurca7d8812018-07-24 17:42:45 +00001518
Edward Lemurd64781e2018-07-11 23:09:55 +00001519
Joanna Wang5a7c8242022-07-01 19:09:00 +00001520class DepsChangesFakeRepo(fake_repos.FakeReposBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001521 def populateGit(self):
1522 self._commit_git('repo_1', {'DEPS': 'versionA', 'doesnotmatter': 'B'})
1523 self._commit_git('repo_1', {'DEPS': 'versionA', 'doesnotmatter': 'C'})
Joanna Wang5a7c8242022-07-01 19:09:00 +00001524
Mike Frysinger67761632023-09-05 20:24:16 +00001525 self._commit_git('repo_1', {'DEPS': 'versionB'})
1526 self._commit_git('repo_1', {'DEPS': 'versionA', 'doesnotmatter': 'C'})
1527 self._create_ref('repo_1', 'refs/heads/main', 4)
Joanna Wang5a7c8242022-07-01 19:09:00 +00001528
1529
1530class CheckDiffTest(fake_repos.FakeReposTestBase):
Mike Frysinger67761632023-09-05 20:24:16 +00001531 FAKE_REPOS_CLASS = DepsChangesFakeRepo
Joanna Wang5a7c8242022-07-01 19:09:00 +00001532
Mike Frysinger67761632023-09-05 20:24:16 +00001533 def setUp(self):
1534 super(CheckDiffTest, self).setUp()
1535 self.enabled = self.FAKE_REPOS.set_up_git()
1536 self.options = BaseGitWrapperTestCase.OptionsObject()
1537 self.url = self.git_base + 'repo_1'
1538 self.mirror = None
1539 mock.patch('sys.stdout', StringIO()).start()
1540 self.addCleanup(mock.patch.stopall)
Joanna Wang5a7c8242022-07-01 19:09:00 +00001541
Mike Frysinger67761632023-09-05 20:24:16 +00001542 def setUpMirror(self):
1543 self.mirror = tempfile.mkdtemp()
1544 git_cache.Mirror.SetCachePath(self.mirror)
1545 self.addCleanup(gclient_utils.rmtree, self.mirror)
1546 self.addCleanup(git_cache.Mirror.SetCachePath, None)
Joanna Wang5a7c8242022-07-01 19:09:00 +00001547
Mike Frysinger67761632023-09-05 20:24:16 +00001548 def testCheckDiff(self):
1549 """Correctly check for diffs."""
1550 scm = gclient_scm.GitWrapper(self.url, self.root_dir, '.')
1551 file_list = []
Joanna Wang5a7c8242022-07-01 19:09:00 +00001552
Mike Frysinger67761632023-09-05 20:24:16 +00001553 # Make sure we don't specify a revision.
1554 self.options.revision = None
1555 scm.update(self.options, None, file_list)
1556 self.assertEqual(self.githash('repo_1', 4),
1557 self.gitrevparse(self.root_dir))
Joanna Wang5a7c8242022-07-01 19:09:00 +00001558
Mike Frysinger67761632023-09-05 20:24:16 +00001559 self.assertFalse(
1560 scm.check_diff(self.githash('repo_1', 1), files=['DEPS']))
1561 self.assertTrue(scm.check_diff(self.githash('repo_1', 1)))
1562 self.assertTrue(
1563 scm.check_diff(self.githash('repo_1', 3), files=['DEPS']))
Joanna Wang5a7c8242022-07-01 19:09:00 +00001564
Mike Frysinger67761632023-09-05 20:24:16 +00001565 self.assertFalse(
1566 scm.check_diff(self.githash('repo_1', 2),
1567 files=['DEPS', 'doesnotmatter']))
1568 self.assertFalse(scm.check_diff(self.githash('repo_1', 2)))
Joanna Wang5a7c8242022-07-01 19:09:00 +00001569
1570
Joanna Wang1a977bd2022-06-02 21:51:17 +00001571if 'unittest.util' in __import__('sys').modules:
Mike Frysinger67761632023-09-05 20:24:16 +00001572 # Show full diff in self.assertEqual.
1573 __import__('sys').modules['unittest.util']._MAX_LENGTH = 999999999
Joanna Wang1a977bd2022-06-02 21:51:17 +00001574
msb@chromium.orge28e4982009-09-25 20:51:45 +00001575if __name__ == '__main__':
Mike Frysinger67761632023-09-05 20:24:16 +00001576 level = logging.DEBUG if '-v' in sys.argv else logging.FATAL
1577 logging.basicConfig(level=level,
1578 format='%(asctime).19s %(levelname)s %(filename)s:'
1579 '%(lineno)s %(message)s')
1580 unittest.main()
msb@chromium.orge28e4982009-09-25 20:51:45 +00001581
1582# vim: ts=2:sw=2:tw=80:et: