git_rebase_update_test.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. #!/usr/bin/env vpython3
  2. # Copyright 2014 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_rebase_update.py"""
  6. import os
  7. import sys
  8. from unittest import mock
  9. DEPOT_TOOLS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  10. sys.path.insert(0, DEPOT_TOOLS_ROOT)
  11. from testing_support import coverage_utils
  12. from testing_support import git_test_utils
  13. # TODO: Should fix these warnings.
  14. # pylint: disable=line-too-long
  15. class GitRebaseUpdateTest(git_test_utils.GitRepoReadWriteTestBase):
  16. REPO_SCHEMA = """
  17. A B C D E F G
  18. B H I J K
  19. J L
  20. """
  21. @classmethod
  22. def getRepoContent(cls, commit):
  23. # Every commit X gets a file X with the content X
  24. return {commit: {'data': commit.encode('utf-8')}}
  25. @classmethod
  26. def setUpClass(cls):
  27. super(GitRebaseUpdateTest, cls).setUpClass()
  28. import git_rebase_update, git_new_branch, git_reparent_branch, git_common
  29. import git_rename_branch
  30. cls.reup = git_rebase_update
  31. cls.rp = git_reparent_branch
  32. cls.nb = git_new_branch
  33. cls.mv = git_rename_branch
  34. cls.gc = git_common
  35. cls.gc.TEST_MODE = True
  36. def setUp(self):
  37. super(GitRebaseUpdateTest, self).setUp()
  38. # Include branch_K, branch_L to make sure that ABCDEFG all get the
  39. # same commit hashes as self.repo. Otherwise they get committed with the
  40. # wrong timestamps, due to commit ordering.
  41. # TODO(iannucci): Make commit timestamps deterministic in left to right,
  42. # top to bottom order, not in lexi-topographical order.
  43. origin_schema = git_test_utils.GitRepoSchema(
  44. """
  45. A B C D E F G M N O
  46. B H I J K
  47. J L
  48. """, self.getRepoContent)
  49. self.origin = origin_schema.reify()
  50. self.origin.git('checkout', 'main')
  51. self.origin.git('branch', '-d', *['branch_' + l for l in 'KLG'])
  52. self.repo.git('remote', 'add', 'origin', self.origin.repo_path)
  53. self.repo.git('config', '--add', 'remote.origin.fetch',
  54. '+refs/tags/*:refs/tags/*')
  55. self.repo.git('update-ref', 'refs/remotes/origin/main', 'tag_E')
  56. self.repo.git('branch', '--set-upstream-to', 'branch_G', 'branch_K')
  57. self.repo.git('branch', '--set-upstream-to', 'branch_K', 'branch_L')
  58. self.repo.git('branch', '--set-upstream-to', 'origin/main', 'branch_G')
  59. self.repo.to_schema_refs += ['origin/main']
  60. mock.patch('git_rebase_update.RESET', '').start()
  61. mock.patch('git_rebase_update.BRIGHT', '').start()
  62. def tearDown(self):
  63. self.origin.nuke()
  64. super(GitRebaseUpdateTest, self).tearDown()
  65. def testRebaseUpdate(self):
  66. self.repo.git('checkout', 'branch_K')
  67. self.repo.run(self.nb.main, ['foobar'])
  68. self.assertEqual(
  69. self.repo.git('rev-parse', 'HEAD').stdout,
  70. self.repo.git('rev-parse', 'origin/main').stdout)
  71. with self.repo.open('foobar', 'w') as f:
  72. f.write('this is the foobar file')
  73. self.repo.git('add', 'foobar')
  74. self.repo.git_commit('foobar1')
  75. with self.repo.open('foobar', 'w') as f:
  76. f.write('totes the Foobar file')
  77. self.repo.git_commit('foobar2')
  78. self.repo.run(self.nb.main, ['--upstream-current', 'int1_foobar'])
  79. self.repo.run(self.nb.main, ['--upstream-current', 'int2_foobar'])
  80. self.repo.run(self.nb.main, ['--upstream-current', 'sub_foobar'])
  81. with self.repo.open('foobar', 'w') as f:
  82. f.write('some more foobaring')
  83. self.repo.git('add', 'foobar')
  84. self.repo.git_commit('foobar3')
  85. self.repo.git('checkout', 'branch_K')
  86. self.repo.run(self.nb.main, ['--upstream-current', 'sub_K'])
  87. with self.repo.open('K', 'w') as f:
  88. f.write('This depends on K')
  89. self.repo.git_commit('sub_K')
  90. self.repo.run(self.nb.main, ['old_branch'])
  91. self.repo.git('reset', '--hard', self.repo['A'])
  92. with self.repo.open('old_file', 'w') as f:
  93. f.write('old_files we want to keep around')
  94. self.repo.git('add', 'old_file')
  95. self.repo.git_commit('old_file')
  96. self.repo.git('config', 'branch.old_branch.dormant', 'true')
  97. self.repo.git('checkout', 'origin/main')
  98. self.assertSchema("""
  99. A B H I J K sub_K
  100. J L
  101. B C D E foobar1 foobar2 foobar3
  102. E F G
  103. A old_file
  104. """)
  105. self.assertEqual(self.repo['A'], self.origin['A'])
  106. self.assertEqual(self.repo['E'], self.origin['E'])
  107. with self.repo.open('bob', 'wb') as f:
  108. f.write(b'testing auto-freeze/thaw')
  109. output, _ = self.repo.capture_stdio(self.reup.main)
  110. self.assertIn('Cannot rebase-update', output)
  111. self.repo.run(self.nb.main, ['empty_branch'])
  112. self.repo.run(self.nb.main, ['--upstream-current', 'empty_branch2'])
  113. self.repo.git('checkout', 'branch_K')
  114. output, _ = self.repo.capture_stdio(self.reup.main)
  115. self.assertIn('Rebasing: branch_G', output)
  116. self.assertIn('Rebasing: branch_K', output)
  117. self.assertIn('Rebasing: branch_L', output)
  118. self.assertIn('Rebasing: foobar', output)
  119. self.assertIn('Rebasing: sub_K', output)
  120. self.assertIn('Deleted branch branch_G', output)
  121. self.assertIn('Deleted branch empty_branch', output)
  122. self.assertIn('Deleted branch empty_branch2', output)
  123. self.assertIn('Deleted branch int1_foobar', output)
  124. self.assertIn('Deleted branch int2_foobar', output)
  125. self.assertIn('Reparented branch_K to track origin/main', output)
  126. self.assertIn('Reparented sub_foobar to track foobar', output)
  127. self.assertSchema("""
  128. A B C D E F G M N O H I J K sub_K
  129. K L
  130. O foobar1 foobar2 foobar3
  131. A old_file
  132. """)
  133. output, _ = self.repo.capture_stdio(self.reup.main)
  134. self.assertIn('branch_K up-to-date', output)
  135. self.assertIn('branch_L up-to-date', output)
  136. self.assertIn('foobar up-to-date', output)
  137. self.assertIn('sub_K up-to-date', output)
  138. with self.repo.open('bob') as f:
  139. self.assertEqual(b'testing auto-freeze/thaw', f.read())
  140. self.assertEqual(
  141. self.repo.git('status', '--porcelain').stdout, '?? bob\n')
  142. self.repo.git('checkout', 'origin/main')
  143. _, err = self.repo.capture_stdio(self.rp.main, [])
  144. self.assertIn('Must specify new parent somehow', err)
  145. _, err = self.repo.capture_stdio(self.rp.main, ['foobar'])
  146. self.assertIn('Must be on the branch', err)
  147. self.repo.git('checkout', 'branch_K')
  148. _, err = self.repo.capture_stdio(self.rp.main, ['origin/main'])
  149. self.assertIn('Cannot reparent a branch to its existing parent', err)
  150. output, _ = self.repo.capture_stdio(self.rp.main, ['foobar'])
  151. self.assertIn('Rebasing: branch_K', output)
  152. self.assertIn('Rebasing: sub_K', output)
  153. self.assertIn('Rebasing: branch_L', output)
  154. self.assertSchema("""
  155. A B C D E F G M N O foobar1 foobar2 H I J K L
  156. foobar2 foobar3
  157. K sub_K
  158. A old_file
  159. """)
  160. self.repo.git('checkout', 'sub_K')
  161. output, _ = self.repo.capture_stdio(self.rp.main, ['foobar'])
  162. self.assertIn('Squashing failed', output)
  163. self.assertTrue(self.repo.run(self.gc.in_rebase))
  164. self.repo.git('rebase', '--abort')
  165. self.assertIsNone(self.repo.run(self.gc.thaw))
  166. self.assertSchema("""
  167. A B C D E F G M N O foobar1 foobar2 H I J K L
  168. foobar2 foobar3
  169. A old_file
  170. K sub_K
  171. """)
  172. self.assertEqual(
  173. self.repo.git('status', '--porcelain').stdout, '?? bob\n')
  174. branches = self.repo.run(set, self.gc.branches())
  175. self.assertEqual(
  176. branches, {
  177. 'branch_K', 'main', 'sub_K', 'root_A', 'branch_L', 'old_branch',
  178. 'foobar', 'sub_foobar'
  179. })
  180. self.repo.git('checkout', 'branch_K')
  181. self.repo.run(self.mv.main, ['special_K'])
  182. branches = self.repo.run(set, self.gc.branches())
  183. self.assertEqual(
  184. branches, {
  185. 'special_K', 'main', 'sub_K', 'root_A', 'branch_L',
  186. 'old_branch', 'foobar', 'sub_foobar'
  187. })
  188. self.repo.git('checkout', 'origin/main')
  189. _, err = self.repo.capture_stdio(self.mv.main,
  190. ['special_K', 'cool branch'])
  191. self.assertIn('fatal: \'cool branch\' is not a valid branch name', err)
  192. self.repo.run(self.mv.main, ['special_K', 'cool_branch'])
  193. branches = self.repo.run(set, self.gc.branches())
  194. # This check fails with git 2.4 (see crbug.com/487172)
  195. self.assertEqual(
  196. branches, {
  197. 'cool_branch', 'main', 'sub_K', 'root_A', 'branch_L',
  198. 'old_branch', 'foobar', 'sub_foobar'
  199. })
  200. _, branch_tree = self.repo.run(self.gc.get_branch_tree)
  201. self.assertEqual(branch_tree['sub_K'], 'foobar')
  202. def testRebaseConflicts(self):
  203. # Pretend that branch_L landed
  204. self.origin.git('checkout', 'main')
  205. with self.origin.open('L', 'w') as f:
  206. f.write('L')
  207. self.origin.git('add', 'L')
  208. self.origin.git_commit('L')
  209. # Add a commit to branch_K so that things fail
  210. self.repo.git('checkout', 'branch_K')
  211. with self.repo.open('M', 'w') as f:
  212. f.write('NOPE')
  213. self.repo.git('add', 'M')
  214. self.repo.git_commit('K NOPE')
  215. # Add a commits to branch_L which will work when squashed
  216. self.repo.git('checkout', 'branch_L')
  217. self.repo.git('reset', 'branch_L~')
  218. with self.repo.open('L', 'w') as f:
  219. f.write('NOPE')
  220. self.repo.git('add', 'L')
  221. self.repo.git_commit('L NOPE')
  222. with self.repo.open('L', 'w') as f:
  223. f.write('L')
  224. self.repo.git('add', 'L')
  225. self.repo.git_commit('L YUP')
  226. # start on a branch which will be deleted
  227. self.repo.git('checkout', 'branch_G')
  228. output, _ = self.repo.capture_stdio(self.reup.main)
  229. self.assertIn('branch.branch_K.dormant true', output)
  230. output, _ = self.repo.capture_stdio(self.reup.main)
  231. self.assertIn('Rebase in progress', output)
  232. self.repo.git('checkout', '--theirs', 'M')
  233. self.repo.git('rebase', '--skip')
  234. output, _ = self.repo.capture_stdio(self.reup.main)
  235. self.assertIn('Failed! Attempting to squash', output)
  236. self.assertIn('Deleted branch branch_G', output)
  237. self.assertIn('Deleted branch branch_L', output)
  238. self.assertIn('\'branch_G\' was merged', output)
  239. self.assertIn('checking out \'origin/main\'', output)
  240. def testRebaseConflictsKeepGoing(self):
  241. # Pretend that branch_L landed
  242. self.origin.git('checkout', 'main')
  243. with self.origin.open('L', 'w') as f:
  244. f.write('L')
  245. self.origin.git('add', 'L')
  246. self.origin.git_commit('L')
  247. # Add a commit to branch_K so that things fail
  248. self.repo.git('checkout', 'branch_K')
  249. with self.repo.open('M', 'w') as f:
  250. f.write('NOPE')
  251. self.repo.git('add', 'M')
  252. self.repo.git_commit('K NOPE')
  253. # Add a commits to branch_L which will work when squashed
  254. self.repo.git('checkout', 'branch_L')
  255. self.repo.git('reset', 'branch_L~')
  256. with self.repo.open('L', 'w') as f:
  257. f.write('NOPE')
  258. self.repo.git('add', 'L')
  259. self.repo.git_commit('L NOPE')
  260. with self.repo.open('L', 'w') as f:
  261. f.write('L')
  262. self.repo.git('add', 'L')
  263. self.repo.git_commit('L YUP')
  264. # start on a branch which will be deleted
  265. self.repo.git('checkout', 'branch_G')
  266. self.repo.git('config', 'branch.branch_K.dormant', 'false')
  267. output, _ = self.repo.capture_stdio(self.reup.main, ['-k'])
  268. self.assertIn('--keep-going set, continuing with next branch.', output)
  269. self.assertIn('could not be cleanly rebased:', output)
  270. self.assertIn(' branch_K', output)
  271. def testTrackTag(self):
  272. self.origin.git('tag', 'tag-to-track', self.origin['M'])
  273. self.repo.git('tag', 'tag-to-track', self.repo['D'])
  274. self.repo.git('config', 'branch.branch_G.remote', '.')
  275. self.repo.git('config', 'branch.branch_G.merge',
  276. 'refs/tags/tag-to-track')
  277. self.assertIn(
  278. 'fatal: \'foo bar\' is not a valid branch name',
  279. self.repo.capture_stdio(
  280. self.nb.main,
  281. ['--upstream', 'tags/tag-to-track', 'foo bar'])[1])
  282. self.repo.run(self.nb.main,
  283. ['--upstream', 'tags/tag-to-track', 'foobar'])
  284. with self.repo.open('foobar', 'w') as f:
  285. f.write('this is the foobar file')
  286. self.repo.git('add', 'foobar')
  287. self.repo.git_commit('foobar1')
  288. with self.repo.open('foobar', 'w') as f:
  289. f.write('totes the Foobar file')
  290. self.repo.git_commit('foobar2')
  291. self.assertSchema("""
  292. A B H I J K
  293. J L
  294. B C D E F G
  295. D foobar1 foobar2
  296. """)
  297. self.assertEqual(self.repo['A'], self.origin['A'])
  298. self.assertEqual(self.repo['G'], self.origin['G'])
  299. output, _ = self.repo.capture_stdio(self.reup.main)
  300. self.assertIn('Rebasing: branch_G', output)
  301. self.assertIn('Rebasing: branch_K', output)
  302. self.assertIn('Rebasing: branch_L', output)
  303. self.assertIn('Rebasing: foobar', output)
  304. self.assertEqual(
  305. self.repo.git('rev-parse', 'tags/tag-to-track').stdout.strip(),
  306. self.origin['M'])
  307. self.assertSchema("""
  308. A B C D E F G M N O
  309. M H I J K L
  310. M foobar1 foobar2
  311. """)
  312. _, err = self.repo.capture_stdio(self.rp.main, ['tag F'])
  313. self.assertIn('fatal: invalid reference', err)
  314. output, _ = self.repo.capture_stdio(self.rp.main, ['tag_F'])
  315. self.assertIn('to track tag_F [tag] (was tag-to-track [tag])', output)
  316. self.assertSchema("""
  317. A B C D E F G M N O
  318. M H I J K L
  319. F foobar1 foobar2
  320. """)
  321. output, _ = self.repo.capture_stdio(self.rp.main, ['tag-to-track'])
  322. self.assertIn('to track tag-to-track [tag] (was tag_F [tag])', output)
  323. self.assertSchema("""
  324. A B C D E F G M N O
  325. M H I J K L
  326. M foobar1 foobar2
  327. """)
  328. output, _ = self.repo.capture_stdio(self.rp.main, ['--root'])
  329. self.assertIn('to track origin/main (was tag-to-track [tag])', output)
  330. self.assertSchema("""
  331. A B C D E F G M N O foobar1 foobar2
  332. M H I J K L
  333. """)
  334. def testReparentBranchWithoutUpstream(self):
  335. self.repo.git('branch', 'nerp')
  336. self.repo.git('checkout', 'nerp')
  337. _, err = self.repo.capture_stdio(self.rp.main, ['branch_K'])
  338. self.assertIn('Unable to determine nerp@{upstream}', err)
  339. if __name__ == '__main__':
  340. sys.exit(
  341. coverage_utils.covered_main(
  342. (os.path.join(DEPOT_TOOLS_ROOT, 'git_rebase_update.py'),
  343. os.path.join(DEPOT_TOOLS_ROOT, 'git_new_branch.py'),
  344. os.path.join(DEPOT_TOOLS_ROOT, 'git_reparent_branch.py'),
  345. os.path.join(DEPOT_TOOLS_ROOT, 'git_rename_branch.py'))))