git_common_test.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. #!/usr/bin/env python
  2. # Copyright 2013 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Unit tests for git_common.py"""
  6. import binascii
  7. import collections
  8. import os
  9. import shutil
  10. import signal
  11. import sys
  12. import tempfile
  13. import time
  14. import unittest
  15. DEPOT_TOOLS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  16. sys.path.insert(0, DEPOT_TOOLS_ROOT)
  17. from testing_support import coverage_utils
  18. from testing_support import git_test_utils
  19. class GitCommonTestBase(unittest.TestCase):
  20. @classmethod
  21. def setUpClass(cls):
  22. super(GitCommonTestBase, cls).setUpClass()
  23. import git_common
  24. cls.gc = git_common
  25. cls.gc.TEST_MODE = True
  26. class Support(GitCommonTestBase):
  27. def _testMemoizeOneBody(self, threadsafe):
  28. calls = collections.defaultdict(int)
  29. def double_if_even(val):
  30. calls[val] += 1
  31. return val * 2 if val % 2 == 0 else None
  32. # Use this explicitly as a wrapper fn instead of a decorator. Otherwise
  33. # pylint crashes (!!)
  34. double_if_even = self.gc.memoize_one(threadsafe=threadsafe)(double_if_even)
  35. self.assertEqual(4, double_if_even(2))
  36. self.assertEqual(4, double_if_even(2))
  37. self.assertEqual(None, double_if_even(1))
  38. self.assertEqual(None, double_if_even(1))
  39. self.assertDictEqual({1: 2, 2: 1}, calls)
  40. double_if_even.set(10, 20)
  41. self.assertEqual(20, double_if_even(10))
  42. self.assertDictEqual({1: 2, 2: 1}, calls)
  43. double_if_even.clear()
  44. self.assertEqual(4, double_if_even(2))
  45. self.assertEqual(4, double_if_even(2))
  46. self.assertEqual(None, double_if_even(1))
  47. self.assertEqual(None, double_if_even(1))
  48. self.assertEqual(20, double_if_even(10))
  49. self.assertDictEqual({1: 4, 2: 2, 10: 1}, calls)
  50. def testMemoizeOne(self):
  51. self._testMemoizeOneBody(threadsafe=False)
  52. def testMemoizeOneThreadsafe(self):
  53. self._testMemoizeOneBody(threadsafe=True)
  54. def testOnce(self):
  55. testlist = []
  56. # This works around a bug in pylint
  57. once = self.gc.once
  58. @once
  59. def add_to_list():
  60. testlist.append('dog')
  61. add_to_list()
  62. add_to_list()
  63. add_to_list()
  64. add_to_list()
  65. self.assertEquals(testlist, ['dog'])
  66. def slow_square(i):
  67. """Helper for ScopedPoolTest.
  68. Must be global because non top-level functions aren't pickleable.
  69. """
  70. return i ** 2
  71. class ScopedPoolTest(GitCommonTestBase):
  72. CTRL_C = signal.CTRL_C_EVENT if sys.platform == 'win32' else signal.SIGINT
  73. def testThreads(self):
  74. result = []
  75. with self.gc.ScopedPool(kind='threads') as pool:
  76. result = list(pool.imap(slow_square, xrange(10)))
  77. self.assertEqual([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], result)
  78. def testThreadsCtrlC(self):
  79. result = []
  80. with self.assertRaises(KeyboardInterrupt):
  81. with self.gc.ScopedPool(kind='threads') as pool:
  82. # Make sure this pool is interrupted in mid-swing
  83. for i in pool.imap(slow_square, xrange(20)):
  84. if i > 32:
  85. os.kill(os.getpid(), self.CTRL_C)
  86. result.append(i)
  87. self.assertEqual([0, 1, 4, 9, 16, 25], result)
  88. def testProcs(self):
  89. result = []
  90. with self.gc.ScopedPool() as pool:
  91. result = list(pool.imap(slow_square, xrange(10)))
  92. self.assertEqual([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], result)
  93. def testProcsCtrlC(self):
  94. result = []
  95. with self.assertRaises(KeyboardInterrupt):
  96. with self.gc.ScopedPool() as pool:
  97. # Make sure this pool is interrupted in mid-swing
  98. for i in pool.imap(slow_square, xrange(20)):
  99. if i > 32:
  100. os.kill(os.getpid(), self.CTRL_C)
  101. result.append(i)
  102. self.assertEqual([0, 1, 4, 9, 16, 25], result)
  103. class ProgressPrinterTest(GitCommonTestBase):
  104. class FakeStream(object):
  105. def __init__(self):
  106. self.data = set()
  107. self.count = 0
  108. def write(self, line):
  109. self.data.add(line)
  110. def flush(self):
  111. self.count += 1
  112. @unittest.expectedFailure
  113. def testBasic(self):
  114. """This test is probably racy, but I don't have a better alternative."""
  115. fmt = '%(count)d/10'
  116. stream = self.FakeStream()
  117. pp = self.gc.ProgressPrinter(fmt, enabled=True, fout=stream, period=0.01)
  118. with pp as inc:
  119. for _ in xrange(10):
  120. time.sleep(0.02)
  121. inc()
  122. filtered = {x.strip() for x in stream.data}
  123. rslt = {fmt % {'count': i} for i in xrange(11)}
  124. self.assertSetEqual(filtered, rslt)
  125. self.assertGreaterEqual(stream.count, 10)
  126. class GitReadOnlyFunctionsTest(git_test_utils.GitRepoReadOnlyTestBase,
  127. GitCommonTestBase):
  128. REPO_SCHEMA = """
  129. A B C D
  130. B E D
  131. """
  132. COMMIT_A = {
  133. 'some/files/file1': {'data': 'file1'},
  134. 'some/files/file2': {'data': 'file2'},
  135. 'some/files/file3': {'data': 'file3'},
  136. 'some/other/file': {'data': 'otherfile'},
  137. }
  138. COMMIT_C = {
  139. 'some/files/file2': {
  140. 'mode': 0755,
  141. 'data': 'file2 - vanilla\n'},
  142. }
  143. COMMIT_E = {
  144. 'some/files/file2': {'data': 'file2 - merged\n'},
  145. }
  146. COMMIT_D = {
  147. 'some/files/file2': {'data': 'file2 - vanilla\nfile2 - merged\n'},
  148. }
  149. def testHashes(self):
  150. ret = self.repo.run(
  151. self.gc.hash_multi, *[
  152. 'master',
  153. 'master~3',
  154. self.repo['E']+'~',
  155. self.repo['D']+'^2',
  156. 'tag_C^{}',
  157. ]
  158. )
  159. self.assertEqual([
  160. self.repo['D'],
  161. self.repo['A'],
  162. self.repo['B'],
  163. self.repo['E'],
  164. self.repo['C'],
  165. ], ret)
  166. self.assertEquals(
  167. self.repo.run(self.gc.hash_one, 'branch_D'),
  168. self.repo['D']
  169. )
  170. self.assertTrue(self.repo['D'].startswith(
  171. self.repo.run(self.gc.hash_one, 'branch_D', short=True)))
  172. def testStream(self):
  173. items = set(self.repo.commit_map.itervalues())
  174. def testfn():
  175. for line in self.gc.run_stream('log', '--format=%H').xreadlines():
  176. line = line.strip()
  177. self.assertIn(line, items)
  178. items.remove(line)
  179. self.repo.run(testfn)
  180. def testStreamWithRetcode(self):
  181. items = set(self.repo.commit_map.itervalues())
  182. def testfn():
  183. with self.gc.run_stream_with_retcode('log', '--format=%H') as stdout:
  184. for line in stdout.xreadlines():
  185. line = line.strip()
  186. self.assertIn(line, items)
  187. items.remove(line)
  188. self.repo.run(testfn)
  189. def testStreamWithRetcodeException(self):
  190. import subprocess2
  191. with self.assertRaises(subprocess2.CalledProcessError):
  192. with self.gc.run_stream_with_retcode('checkout', 'unknown-branch'):
  193. pass
  194. def testCurrentBranch(self):
  195. def cur_branch_out_of_git():
  196. os.chdir('..')
  197. return self.gc.current_branch()
  198. self.assertIsNone(self.repo.run(cur_branch_out_of_git))
  199. self.repo.git('checkout', 'branch_D')
  200. self.assertEqual(self.repo.run(self.gc.current_branch), 'branch_D')
  201. def testBranches(self):
  202. # This check fails with git 2.4 (see crbug.com/487172)
  203. self.assertEqual(self.repo.run(set, self.gc.branches()),
  204. {'master', 'branch_D', 'root_A'})
  205. def testDiff(self):
  206. # Get the names of the blobs being compared (to avoid hard-coding).
  207. c_blob_short = self.repo.git('rev-parse', '--short',
  208. 'tag_C:some/files/file2').stdout.strip()
  209. d_blob_short = self.repo.git('rev-parse', '--short',
  210. 'tag_D:some/files/file2').stdout.strip()
  211. expected_output = [
  212. 'diff --git a/some/files/file2 b/some/files/file2',
  213. 'index %s..%s 100755' % (c_blob_short, d_blob_short),
  214. '--- a/some/files/file2',
  215. '+++ b/some/files/file2',
  216. '@@ -1 +1,2 @@',
  217. ' file2 - vanilla',
  218. '+file2 - merged']
  219. self.assertEqual(expected_output,
  220. self.repo.run(self.gc.diff, 'tag_C', 'tag_D').split('\n'))
  221. def testDormant(self):
  222. self.assertFalse(self.repo.run(self.gc.is_dormant, 'master'))
  223. self.repo.git('config', 'branch.master.dormant', 'true')
  224. self.assertTrue(self.repo.run(self.gc.is_dormant, 'master'))
  225. def testBlame(self):
  226. def get_porcelain_for_commit(commit_name, lines):
  227. format_string = ('%H {}\nauthor %an\nauthor-mail <%ae>\nauthor-time %at\n'
  228. 'author-tz +0000\ncommitter %cn\ncommitter-mail <%ce>\n'
  229. 'committer-time %ct\ncommitter-tz +0000\nsummary {}')
  230. format_string = format_string.format(lines, commit_name)
  231. info = self.repo.show_commit(commit_name, format_string=format_string)
  232. return info.split('\n')
  233. # Expect to blame line 1 on C, line 2 on E.
  234. c_short = self.repo['C'][:8]
  235. c_author = self.repo.show_commit('C', format_string='%an %ai')
  236. e_short = self.repo['E'][:8]
  237. e_author = self.repo.show_commit('E', format_string='%an %ai')
  238. expected_output = ['%s (%s 1) file2 - vanilla' % (c_short, c_author),
  239. '%s (%s 2) file2 - merged' % (e_short, e_author)]
  240. self.assertEqual(expected_output,
  241. self.repo.run(self.gc.blame, 'some/files/file2',
  242. 'tag_D').split('\n'))
  243. # Test porcelain.
  244. expected_output = []
  245. expected_output.extend(get_porcelain_for_commit('C', '1 1 1'))
  246. expected_output.append('previous %s some/files/file2' % self.repo['B'])
  247. expected_output.append('filename some/files/file2')
  248. expected_output.append('\tfile2 - vanilla')
  249. expected_output.extend(get_porcelain_for_commit('E', '1 2 1'))
  250. expected_output.append('previous %s some/files/file2' % self.repo['B'])
  251. expected_output.append('filename some/files/file2')
  252. expected_output.append('\tfile2 - merged')
  253. self.assertEqual(expected_output,
  254. self.repo.run(self.gc.blame, 'some/files/file2',
  255. 'tag_D', porcelain=True).split('\n'))
  256. def testParseCommitrefs(self):
  257. ret = self.repo.run(
  258. self.gc.parse_commitrefs, *[
  259. 'master',
  260. 'master~3',
  261. self.repo['E']+'~',
  262. self.repo['D']+'^2',
  263. 'tag_C^{}',
  264. ]
  265. )
  266. self.assertEqual(ret, map(binascii.unhexlify, [
  267. self.repo['D'],
  268. self.repo['A'],
  269. self.repo['B'],
  270. self.repo['E'],
  271. self.repo['C'],
  272. ]))
  273. with self.assertRaisesRegexp(Exception, r"one of \('master', 'bananas'\)"):
  274. self.repo.run(self.gc.parse_commitrefs, 'master', 'bananas')
  275. def testRepoRoot(self):
  276. def cd_and_repo_root(path):
  277. print(os.getcwd())
  278. os.chdir(path)
  279. return self.gc.repo_root()
  280. self.assertEqual(self.repo.repo_path, self.repo.run(self.gc.repo_root))
  281. # cd to a subdirectory; repo_root should still return the root dir.
  282. self.assertEqual(self.repo.repo_path,
  283. self.repo.run(cd_and_repo_root, 'some/files'))
  284. def testTags(self):
  285. self.assertEqual(set(self.repo.run(self.gc.tags)),
  286. {'tag_'+l for l in 'ABCDE'})
  287. def testTree(self):
  288. tree = self.repo.run(self.gc.tree, 'master:some/files')
  289. file1 = self.COMMIT_A['some/files/file1']['data']
  290. file2 = self.COMMIT_D['some/files/file2']['data']
  291. file3 = self.COMMIT_A['some/files/file3']['data']
  292. self.assertEquals(
  293. tree['file1'],
  294. ('100644', 'blob', git_test_utils.git_hash_data(file1)))
  295. self.assertEquals(
  296. tree['file2'],
  297. ('100755', 'blob', git_test_utils.git_hash_data(file2)))
  298. self.assertEquals(
  299. tree['file3'],
  300. ('100644', 'blob', git_test_utils.git_hash_data(file3)))
  301. tree = self.repo.run(self.gc.tree, 'master:some')
  302. self.assertEquals(len(tree), 2)
  303. # Don't check the tree hash because we're lazy :)
  304. self.assertEquals(tree['files'][:2], ('040000', 'tree'))
  305. tree = self.repo.run(self.gc.tree, 'master:wat')
  306. self.assertEqual(tree, None)
  307. def testTreeRecursive(self):
  308. tree = self.repo.run(self.gc.tree, 'master:some', recurse=True)
  309. file1 = self.COMMIT_A['some/files/file1']['data']
  310. file2 = self.COMMIT_D['some/files/file2']['data']
  311. file3 = self.COMMIT_A['some/files/file3']['data']
  312. other = self.COMMIT_A['some/other/file']['data']
  313. self.assertEquals(
  314. tree['files/file1'],
  315. ('100644', 'blob', git_test_utils.git_hash_data(file1)))
  316. self.assertEquals(
  317. tree['files/file2'],
  318. ('100755', 'blob', git_test_utils.git_hash_data(file2)))
  319. self.assertEquals(
  320. tree['files/file3'],
  321. ('100644', 'blob', git_test_utils.git_hash_data(file3)))
  322. self.assertEquals(
  323. tree['other/file'],
  324. ('100644', 'blob', git_test_utils.git_hash_data(other)))
  325. class GitMutableFunctionsTest(git_test_utils.GitRepoReadWriteTestBase,
  326. GitCommonTestBase):
  327. REPO_SCHEMA = ''
  328. def _intern_data(self, data):
  329. with tempfile.TemporaryFile() as f:
  330. f.write(data)
  331. f.seek(0)
  332. return self.repo.run(self.gc.intern_f, f)
  333. def testInternF(self):
  334. data = 'CoolBobcatsBro'
  335. data_hash = self._intern_data(data)
  336. self.assertEquals(git_test_utils.git_hash_data(data), data_hash)
  337. self.assertEquals(data, self.repo.git('cat-file', 'blob', data_hash).stdout)
  338. def testMkTree(self):
  339. tree = {}
  340. for i in 1, 2, 3:
  341. name = 'file%d' % i
  342. tree[name] = ('100644', 'blob', self._intern_data(name))
  343. tree_hash = self.repo.run(self.gc.mktree, tree)
  344. self.assertEquals('37b61866d6e061c4ba478e7eb525be7b5752737d', tree_hash)
  345. def testConfig(self):
  346. self.repo.git('config', '--add', 'happy.derpies', 'food')
  347. self.assertEquals(self.repo.run(self.gc.config_list, 'happy.derpies'),
  348. ['food'])
  349. self.assertEquals(self.repo.run(self.gc.config_list, 'sad.derpies'), [])
  350. self.repo.git('config', '--add', 'happy.derpies', 'cat')
  351. self.assertEquals(self.repo.run(self.gc.config_list, 'happy.derpies'),
  352. ['food', 'cat'])
  353. self.assertEquals('cat', self.repo.run(self.gc.config, 'dude.bob', 'cat'))
  354. self.repo.run(self.gc.set_config, 'dude.bob', 'dog')
  355. self.assertEquals('dog', self.repo.run(self.gc.config, 'dude.bob', 'cat'))
  356. self.repo.run(self.gc.del_config, 'dude.bob')
  357. # This should work without raising an exception
  358. self.repo.run(self.gc.del_config, 'dude.bob')
  359. self.assertEquals('cat', self.repo.run(self.gc.config, 'dude.bob', 'cat'))
  360. self.assertEquals('origin/master', self.repo.run(self.gc.root))
  361. self.repo.git('config', 'depot-tools.upstream', 'catfood')
  362. self.assertEquals('catfood', self.repo.run(self.gc.root))
  363. def testUpstream(self):
  364. self.repo.git('commit', '--allow-empty', '-am', 'foooooo')
  365. self.assertEquals(self.repo.run(self.gc.upstream, 'bobly'), None)
  366. self.assertEquals(self.repo.run(self.gc.upstream, 'master'), None)
  367. self.repo.git('checkout', '-tb', 'happybranch', 'master')
  368. self.assertEquals(self.repo.run(self.gc.upstream, 'happybranch'),
  369. 'master')
  370. def testNormalizedVersion(self):
  371. self.assertTrue(all(
  372. isinstance(x, int) for x in self.repo.run(self.gc.get_git_version)))
  373. def testGetBranchesInfo(self):
  374. self.repo.git('commit', '--allow-empty', '-am', 'foooooo')
  375. self.repo.git('checkout', '-tb', 'happybranch', 'master')
  376. self.repo.git('commit', '--allow-empty', '-am', 'foooooo')
  377. self.repo.git('checkout', '-tb', 'child', 'happybranch')
  378. self.repo.git('checkout', '-tb', 'to_delete', 'master')
  379. self.repo.git('checkout', '-tb', 'parent_gone', 'to_delete')
  380. self.repo.git('branch', '-D', 'to_delete')
  381. supports_track = (
  382. self.repo.run(self.gc.get_git_version)
  383. >= self.gc.MIN_UPSTREAM_TRACK_GIT_VERSION)
  384. actual = self.repo.run(self.gc.get_branches_info, supports_track)
  385. expected = {
  386. 'happybranch': (
  387. self.repo.run(self.gc.hash_one, 'happybranch', short=True),
  388. 'master',
  389. 1 if supports_track else None,
  390. None
  391. ),
  392. 'child': (
  393. self.repo.run(self.gc.hash_one, 'child', short=True),
  394. 'happybranch',
  395. None,
  396. None
  397. ),
  398. 'master': (
  399. self.repo.run(self.gc.hash_one, 'master', short=True),
  400. '',
  401. None,
  402. None
  403. ),
  404. '': None,
  405. 'parent_gone': (
  406. self.repo.run(self.gc.hash_one, 'parent_gone', short=True),
  407. 'to_delete',
  408. None,
  409. None
  410. ),
  411. 'to_delete': None
  412. }
  413. self.assertEquals(expected, actual)
  414. class GitMutableStructuredTest(git_test_utils.GitRepoReadWriteTestBase,
  415. GitCommonTestBase):
  416. REPO_SCHEMA = """
  417. A B C D E F G
  418. B H I J K
  419. J L
  420. X Y Z
  421. CAT DOG
  422. """
  423. COMMIT_B = {'file': {'data': 'B'}}
  424. COMMIT_H = {'file': {'data': 'H'}}
  425. COMMIT_I = {'file': {'data': 'I'}}
  426. COMMIT_J = {'file': {'data': 'J'}}
  427. COMMIT_K = {'file': {'data': 'K'}}
  428. COMMIT_L = {'file': {'data': 'L'}}
  429. def setUp(self):
  430. super(GitMutableStructuredTest, self).setUp()
  431. self.repo.git('branch', '--set-upstream-to', 'root_X', 'branch_Z')
  432. self.repo.git('branch', '--set-upstream-to', 'branch_G', 'branch_K')
  433. self.repo.git('branch', '--set-upstream-to', 'branch_K', 'branch_L')
  434. self.repo.git('branch', '--set-upstream-to', 'root_A', 'branch_G')
  435. self.repo.git('branch', '--set-upstream-to', 'root_X', 'root_A')
  436. def testTooManyBranches(self):
  437. for i in xrange(30):
  438. self.repo.git('branch', 'a'*i)
  439. _, rslt = self.repo.capture_stdio(list, self.gc.branches())
  440. self.assertIn('too many branches (39/20)', rslt)
  441. self.repo.git('config', 'depot-tools.branch-limit', 'cat')
  442. _, rslt = self.repo.capture_stdio(list, self.gc.branches())
  443. self.assertIn('too many branches (39/20)', rslt)
  444. self.repo.git('config', 'depot-tools.branch-limit', '100')
  445. # should not raise
  446. # This check fails with git 2.4 (see crbug.com/487172)
  447. self.assertEqual(38, len(self.repo.run(list, self.gc.branches())))
  448. def testMergeBase(self):
  449. self.repo.git('checkout', 'branch_K')
  450. self.assertEqual(
  451. self.repo['B'],
  452. self.repo.run(self.gc.get_or_create_merge_base, 'branch_K', 'branch_G')
  453. )
  454. self.assertEqual(
  455. self.repo['J'],
  456. self.repo.run(self.gc.get_or_create_merge_base, 'branch_L', 'branch_K')
  457. )
  458. self.assertEqual(
  459. self.repo['B'], self.repo.run(self.gc.config, 'branch.branch_K.base')
  460. )
  461. self.assertEqual(
  462. 'branch_G', self.repo.run(self.gc.config, 'branch.branch_K.base-upstream')
  463. )
  464. # deadbeef is a bad hash, so this will result in repo['B']
  465. self.repo.run(self.gc.manual_merge_base, 'branch_K', 'deadbeef', 'branch_G')
  466. self.assertEqual(
  467. self.repo['B'],
  468. self.repo.run(self.gc.get_or_create_merge_base, 'branch_K', 'branch_G')
  469. )
  470. # but if we pick a real ancestor, then it'll work
  471. self.repo.run(self.gc.manual_merge_base, 'branch_K', self.repo['I'],
  472. 'branch_G')
  473. self.assertEqual(
  474. self.repo['I'],
  475. self.repo.run(self.gc.get_or_create_merge_base, 'branch_K', 'branch_G')
  476. )
  477. self.assertEqual({'branch_K': self.repo['I'], 'branch_L': self.repo['J']},
  478. self.repo.run(self.gc.branch_config_map, 'base'))
  479. self.repo.run(self.gc.remove_merge_base, 'branch_K')
  480. self.repo.run(self.gc.remove_merge_base, 'branch_L')
  481. self.assertEqual(None,
  482. self.repo.run(self.gc.config, 'branch.branch_K.base'))
  483. self.assertEqual({}, self.repo.run(self.gc.branch_config_map, 'base'))
  484. # if it's too old, then it caps at merge-base
  485. self.repo.run(self.gc.manual_merge_base, 'branch_K', self.repo['A'],
  486. 'branch_G')
  487. self.assertEqual(
  488. self.repo['B'],
  489. self.repo.run(self.gc.get_or_create_merge_base, 'branch_K', 'branch_G')
  490. )
  491. # If the user does --set-upstream-to something else, then we discard the
  492. # base and recompute it.
  493. self.repo.run(self.gc.run, 'branch', '-u', 'root_A')
  494. self.assertEqual(
  495. self.repo['A'],
  496. self.repo.run(self.gc.get_or_create_merge_base, 'branch_K')
  497. )
  498. self.assertIsNone(
  499. self.repo.run(self.gc.get_or_create_merge_base, 'branch_DOG'))
  500. def testGetBranchTree(self):
  501. skipped, tree = self.repo.run(self.gc.get_branch_tree)
  502. # This check fails with git 2.4 (see crbug.com/487172)
  503. self.assertEqual(skipped, {'master', 'root_X', 'branch_DOG', 'root_CAT'})
  504. self.assertEqual(tree, {
  505. 'branch_G': 'root_A',
  506. 'root_A': 'root_X',
  507. 'branch_K': 'branch_G',
  508. 'branch_L': 'branch_K',
  509. 'branch_Z': 'root_X'
  510. })
  511. topdown = list(self.gc.topo_iter(tree))
  512. bottomup = list(self.gc.topo_iter(tree, top_down=False))
  513. self.assertEqual(topdown, [
  514. ('branch_Z', 'root_X'),
  515. ('root_A', 'root_X'),
  516. ('branch_G', 'root_A'),
  517. ('branch_K', 'branch_G'),
  518. ('branch_L', 'branch_K'),
  519. ])
  520. self.assertEqual(bottomup, [
  521. ('branch_L', 'branch_K'),
  522. ('branch_Z', 'root_X'),
  523. ('branch_K', 'branch_G'),
  524. ('branch_G', 'root_A'),
  525. ('root_A', 'root_X'),
  526. ])
  527. def testIsGitTreeDirty(self):
  528. self.assertEquals(False, self.repo.run(self.gc.is_dirty_git_tree, 'foo'))
  529. self.repo.open('test.file', 'w').write('test data')
  530. self.repo.git('add', 'test.file')
  531. self.assertEquals(True, self.repo.run(self.gc.is_dirty_git_tree, 'foo'))
  532. def testSquashBranch(self):
  533. self.repo.git('checkout', 'branch_K')
  534. self.assertEquals(True, self.repo.run(self.gc.squash_current_branch,
  535. 'cool message'))
  536. lines = ['cool message', '']
  537. for l in 'HIJK':
  538. lines.extend((self.repo[l], l, ''))
  539. lines.pop()
  540. msg = '\n'.join(lines)
  541. self.assertEquals(self.repo.run(self.gc.run, 'log', '-n1', '--format=%B'),
  542. msg)
  543. self.assertEquals(
  544. self.repo.git('cat-file', 'blob', 'branch_K:file').stdout,
  545. 'K'
  546. )
  547. def testSquashBranchEmpty(self):
  548. self.repo.git('checkout', 'branch_K')
  549. self.repo.git('checkout', 'branch_G', '.')
  550. self.repo.git('commit', '-m', 'revert all changes no branch')
  551. # Should return False since the quash would result in an empty commit
  552. stdout = self.repo.capture_stdio(self.gc.squash_current_branch)[0]
  553. self.assertEquals(stdout, 'Nothing to commit; squashed branch is empty\n')
  554. def testRebase(self):
  555. self.assertSchema("""
  556. A B C D E F G
  557. B H I J K
  558. J L
  559. X Y Z
  560. CAT DOG
  561. """)
  562. rslt = self.repo.run(
  563. self.gc.rebase, 'branch_G', 'branch_K~4', 'branch_K')
  564. self.assertTrue(rslt.success)
  565. self.assertSchema("""
  566. A B C D E F G H I J K
  567. B H I J L
  568. X Y Z
  569. CAT DOG
  570. """)
  571. rslt = self.repo.run(
  572. self.gc.rebase, 'branch_K', 'branch_L~1', 'branch_L', abort=True)
  573. self.assertFalse(rslt.success)
  574. self.assertFalse(self.repo.run(self.gc.in_rebase))
  575. rslt = self.repo.run(
  576. self.gc.rebase, 'branch_K', 'branch_L~1', 'branch_L', abort=False)
  577. self.assertFalse(rslt.success)
  578. self.assertTrue(self.repo.run(self.gc.in_rebase))
  579. self.assertEqual(self.repo.git('status', '--porcelain').stdout, 'UU file\n')
  580. self.repo.git('checkout', '--theirs', 'file')
  581. self.repo.git('add', 'file')
  582. self.repo.git('rebase', '--continue')
  583. self.assertSchema("""
  584. A B C D E F G H I J K L
  585. X Y Z
  586. CAT DOG
  587. """)
  588. class GitFreezeThaw(git_test_utils.GitRepoReadWriteTestBase):
  589. @classmethod
  590. def setUpClass(cls):
  591. super(GitFreezeThaw, cls).setUpClass()
  592. import git_common
  593. cls.gc = git_common
  594. cls.gc.TEST_MODE = True
  595. REPO_SCHEMA = """
  596. A B C D
  597. B E D
  598. """
  599. COMMIT_A = {
  600. 'some/files/file1': {'data': 'file1'},
  601. 'some/files/file2': {'data': 'file2'},
  602. 'some/files/file3': {'data': 'file3'},
  603. 'some/other/file': {'data': 'otherfile'},
  604. }
  605. COMMIT_C = {
  606. 'some/files/file2': {
  607. 'mode': 0755,
  608. 'data': 'file2 - vanilla'},
  609. }
  610. COMMIT_E = {
  611. 'some/files/file2': {'data': 'file2 - merged'},
  612. }
  613. COMMIT_D = {
  614. 'some/files/file2': {'data': 'file2 - vanilla\nfile2 - merged'},
  615. }
  616. def testNothing(self):
  617. self.assertIsNotNone(self.repo.run(self.gc.thaw)) # 'Nothing to thaw'
  618. self.assertIsNotNone(self.repo.run(self.gc.freeze)) # 'Nothing to freeze'
  619. def testAll(self):
  620. def inner():
  621. with open('some/files/file2', 'a') as f2:
  622. print >> f2, 'cool appended line'
  623. os.mkdir('some/other_files')
  624. with open('some/other_files/subdir_file', 'w') as f3:
  625. print >> f3, 'new file!'
  626. with open('some/files/file5', 'w') as f5:
  627. print >> f5, 'New file!1!one!'
  628. STATUS_1 = '\n'.join((
  629. ' M some/files/file2',
  630. 'A some/files/file5',
  631. '?? some/other_files/'
  632. )) + '\n'
  633. self.repo.git('add', 'some/files/file5')
  634. # Freeze group 1
  635. self.assertEquals(self.repo.git('status', '--porcelain').stdout, STATUS_1)
  636. self.assertIsNone(self.gc.freeze())
  637. self.assertEquals(self.repo.git('status', '--porcelain').stdout, '')
  638. # Freeze group 2
  639. with open('some/files/file2', 'a') as f2:
  640. print >> f2, 'new! appended line!'
  641. self.assertEquals(self.repo.git('status', '--porcelain').stdout,
  642. ' M some/files/file2\n')
  643. self.assertIsNone(self.gc.freeze())
  644. self.assertEquals(self.repo.git('status', '--porcelain').stdout, '')
  645. # Thaw it out!
  646. self.assertIsNone(self.gc.thaw())
  647. self.assertIsNotNone(self.gc.thaw()) # One thaw should thaw everything
  648. self.assertEquals(self.repo.git('status', '--porcelain').stdout, STATUS_1)
  649. self.repo.run(inner)
  650. class GitMakeWorkdir(git_test_utils.GitRepoReadOnlyTestBase, GitCommonTestBase):
  651. def setUp(self):
  652. self._tempdir = tempfile.mkdtemp()
  653. def tearDown(self):
  654. shutil.rmtree(self._tempdir)
  655. REPO_SCHEMA = """
  656. A
  657. """
  658. def testMakeWorkdir(self):
  659. if not hasattr(os, 'symlink'):
  660. return
  661. workdir = os.path.join(self._tempdir, 'workdir')
  662. self.gc.make_workdir(os.path.join(self.repo.repo_path, '.git'),
  663. os.path.join(workdir, '.git'))
  664. EXPECTED_LINKS = [
  665. 'config', 'info', 'hooks', 'logs/refs', 'objects', 'refs',
  666. ]
  667. for path in EXPECTED_LINKS:
  668. self.assertTrue(os.path.islink(os.path.join(workdir, '.git', path)))
  669. self.assertEqual(os.path.realpath(os.path.join(workdir, '.git', path)),
  670. os.path.join(self.repo.repo_path, '.git', path))
  671. self.assertFalse(os.path.islink(os.path.join(workdir, '.git', 'HEAD')))
  672. class GitTestUtilsTest(git_test_utils.GitRepoReadOnlyTestBase):
  673. REPO_SCHEMA = """
  674. A B
  675. """
  676. COMMIT_A = {
  677. 'file1': {'data': 'file1'},
  678. }
  679. COMMIT_B = {
  680. 'file1': {'data': 'file1 changed'},
  681. }
  682. def testAutomaticCommitDates(self):
  683. # The dates should start from 1970-01-01 and automatically increment. They
  684. # must be in UTC (otherwise the tests are system-dependent, and if your
  685. # local timezone is positive, timestamps will be <0 which causes bizarre
  686. # behaviour in Git; http://crbug.com/581895).
  687. self.assertEquals('Author McAuthorly 1970-01-01 00:00:00 +0000',
  688. self.repo.show_commit('A', format_string='%an %ai'))
  689. self.assertEquals('Charles Committish 1970-01-02 00:00:00 +0000',
  690. self.repo.show_commit('A', format_string='%cn %ci'))
  691. self.assertEquals('Author McAuthorly 1970-01-03 00:00:00 +0000',
  692. self.repo.show_commit('B', format_string='%an %ai'))
  693. self.assertEquals('Charles Committish 1970-01-04 00:00:00 +0000',
  694. self.repo.show_commit('B', format_string='%cn %ci'))
  695. if __name__ == '__main__':
  696. sys.exit(coverage_utils.covered_main(
  697. os.path.join(DEPOT_TOOLS_ROOT, 'git_common.py')))