test_rerere.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. """Tests for rerere functionality."""
  2. import os
  3. import tempfile
  4. import unittest
  5. from dulwich.rerere import (
  6. RerereCache,
  7. _extract_conflict_regions,
  8. _has_conflict_markers,
  9. _normalize_conflict_markers,
  10. _remove_conflict_markers,
  11. is_rerere_autoupdate,
  12. is_rerere_enabled,
  13. )
  14. class NormalizeConflictMarkersTests(unittest.TestCase):
  15. """Tests for _normalize_conflict_markers function."""
  16. def test_normalize_basic_conflict(self) -> None:
  17. """Test normalizing a basic conflict."""
  18. content = b"""line 1
  19. <<<<<<< ours
  20. our change
  21. =======
  22. their change
  23. >>>>>>> theirs
  24. line 2
  25. """
  26. expected = b"""line 1
  27. <<<<<<<
  28. our change
  29. =======
  30. their change
  31. >>>>>>>
  32. line 2
  33. """
  34. result = _normalize_conflict_markers(content)
  35. self.assertEqual(expected, result)
  36. def test_normalize_with_branch_names(self) -> None:
  37. """Test normalizing conflict with branch names."""
  38. content = b"""<<<<<<< HEAD
  39. content from HEAD
  40. =======
  41. content from feature
  42. >>>>>>> feature
  43. """
  44. expected = b"""<<<<<<<
  45. content from HEAD
  46. =======
  47. content from feature
  48. >>>>>>>
  49. """
  50. result = _normalize_conflict_markers(content)
  51. self.assertEqual(expected, result)
  52. class ExtractConflictRegionsTests(unittest.TestCase):
  53. """Tests for _extract_conflict_regions function."""
  54. def test_extract_single_conflict(self) -> None:
  55. """Test extracting a single conflict region."""
  56. content = b"""line 1
  57. <<<<<<< ours
  58. our change
  59. =======
  60. their change
  61. >>>>>>> theirs
  62. line 2
  63. """
  64. regions = _extract_conflict_regions(content)
  65. self.assertEqual(1, len(regions))
  66. ours, sep, theirs = regions[0]
  67. self.assertEqual(b"our change", ours)
  68. self.assertEqual(b"=======", sep)
  69. self.assertEqual(b"their change", theirs)
  70. def test_extract_multiple_conflicts(self) -> None:
  71. """Test extracting multiple conflict regions."""
  72. content = b"""<<<<<<< ours
  73. change 1
  74. =======
  75. change 2
  76. >>>>>>> theirs
  77. middle line
  78. <<<<<<< ours
  79. change 3
  80. =======
  81. change 4
  82. >>>>>>> theirs
  83. """
  84. regions = _extract_conflict_regions(content)
  85. self.assertEqual(2, len(regions))
  86. class HasConflictMarkersTests(unittest.TestCase):
  87. """Tests for _has_conflict_markers function."""
  88. def test_has_conflict_markers(self) -> None:
  89. """Test detecting conflict markers."""
  90. content = b"""<<<<<<< ours
  91. our change
  92. =======
  93. their change
  94. >>>>>>> theirs
  95. """
  96. self.assertTrue(_has_conflict_markers(content))
  97. def test_no_conflict_markers(self) -> None:
  98. """Test content without conflict markers."""
  99. content = b"""line 1
  100. line 2
  101. line 3
  102. """
  103. self.assertFalse(_has_conflict_markers(content))
  104. def test_partial_conflict_markers(self) -> None:
  105. """Test content with only some conflict markers."""
  106. content = b"""<<<<<<< ours
  107. our change
  108. line 3
  109. """
  110. self.assertFalse(_has_conflict_markers(content))
  111. class RemoveConflictMarkersTests(unittest.TestCase):
  112. """Tests for _remove_conflict_markers function."""
  113. def test_remove_conflict_markers(self) -> None:
  114. """Test removing conflict markers from resolved content."""
  115. content = b"""line 1
  116. <<<<<<< ours
  117. our change
  118. =======
  119. their change
  120. >>>>>>> theirs
  121. line 2
  122. """
  123. # This is a simplified test - in reality the resolved content
  124. # would have the user's chosen resolution
  125. result = _remove_conflict_markers(content)
  126. # The function keeps only lines outside conflict blocks
  127. self.assertNotIn(b"<<<<<<<", result)
  128. self.assertNotIn(b"=======", result)
  129. self.assertNotIn(b">>>>>>>", result)
  130. class RerereCacheTests(unittest.TestCase):
  131. """Tests for RerereCache class."""
  132. def setUp(self) -> None:
  133. """Set up test fixtures."""
  134. self.tempdir = tempfile.mkdtemp()
  135. self.cache = RerereCache(self.tempdir)
  136. def tearDown(self) -> None:
  137. """Clean up test fixtures."""
  138. import shutil
  139. shutil.rmtree(self.tempdir, ignore_errors=True)
  140. def test_record_conflict(self) -> None:
  141. """Test recording a conflict."""
  142. content = b"""line 1
  143. <<<<<<< ours
  144. our change
  145. =======
  146. their change
  147. >>>>>>> theirs
  148. line 2
  149. """
  150. conflict_id = self.cache.record_conflict(b"test.txt", content)
  151. self.assertIsNotNone(conflict_id)
  152. self.assertEqual(40, len(conflict_id)) # SHA-1 hash length
  153. def test_record_conflict_no_markers(self) -> None:
  154. """Test recording content without conflict markers."""
  155. content = b"line 1\nline 2\n"
  156. conflict_id = self.cache.record_conflict(b"test.txt", content)
  157. self.assertIsNone(conflict_id)
  158. def test_status_empty(self) -> None:
  159. """Test status with no conflicts."""
  160. status = self.cache.status()
  161. self.assertEqual([], status)
  162. def test_status_with_conflict(self) -> None:
  163. """Test status with a recorded conflict."""
  164. content = b"""<<<<<<< ours
  165. our change
  166. =======
  167. their change
  168. >>>>>>> theirs
  169. """
  170. conflict_id = self.cache.record_conflict(b"test.txt", content)
  171. status = self.cache.status()
  172. self.assertEqual(1, len(status))
  173. cid, has_resolution = status[0]
  174. self.assertEqual(conflict_id, cid)
  175. self.assertFalse(has_resolution)
  176. def test_has_resolution(self) -> None:
  177. """Test checking for resolution."""
  178. content = b"""<<<<<<< ours
  179. our change
  180. =======
  181. their change
  182. >>>>>>> theirs
  183. """
  184. conflict_id = self.cache.record_conflict(b"test.txt", content)
  185. self.assertIsNotNone(conflict_id)
  186. self.assertFalse(self.cache.has_resolution(conflict_id))
  187. def test_diff(self) -> None:
  188. """Test getting diff for a conflict."""
  189. content = b"""<<<<<<< ours
  190. our change
  191. =======
  192. their change
  193. >>>>>>> theirs
  194. """
  195. conflict_id = self.cache.record_conflict(b"test.txt", content)
  196. self.assertIsNotNone(conflict_id)
  197. preimage, postimage = self.cache.diff(conflict_id)
  198. self.assertIsNotNone(preimage)
  199. self.assertIsNone(postimage) # No resolution recorded yet
  200. def test_clear(self) -> None:
  201. """Test clearing all conflicts."""
  202. content = b"""<<<<<<< ours
  203. our change
  204. =======
  205. their change
  206. >>>>>>> theirs
  207. """
  208. self.cache.record_conflict(b"test.txt", content)
  209. status = self.cache.status()
  210. self.assertEqual(1, len(status))
  211. self.cache.clear()
  212. status = self.cache.status()
  213. self.assertEqual([], status)
  214. def test_forget(self) -> None:
  215. """Test forgetting a specific conflict."""
  216. content = b"""<<<<<<< ours
  217. our change
  218. =======
  219. their change
  220. >>>>>>> theirs
  221. """
  222. conflict_id = self.cache.record_conflict(b"test.txt", content)
  223. self.assertIsNotNone(conflict_id)
  224. self.cache.forget(conflict_id)
  225. status = self.cache.status()
  226. self.assertEqual([], status)
  227. class ConfigTests(unittest.TestCase):
  228. """Tests for rerere configuration functions."""
  229. def test_is_rerere_enabled_false_by_default(self) -> None:
  230. """Test that rerere is disabled by default."""
  231. from dulwich.config import ConfigDict
  232. config = ConfigDict()
  233. self.assertFalse(is_rerere_enabled(config))
  234. def test_is_rerere_enabled_true(self) -> None:
  235. """Test rerere enabled config."""
  236. from dulwich.config import ConfigDict
  237. config = ConfigDict()
  238. config.set((b"rerere",), b"enabled", b"true")
  239. self.assertTrue(is_rerere_enabled(config))
  240. def test_is_rerere_autoupdate_false_by_default(self) -> None:
  241. """Test that rerere.autoupdate is disabled by default."""
  242. from dulwich.config import ConfigDict
  243. config = ConfigDict()
  244. self.assertFalse(is_rerere_autoupdate(config))
  245. def test_is_rerere_autoupdate_true(self) -> None:
  246. """Test rerere.autoupdate enabled config."""
  247. from dulwich.config import ConfigDict
  248. config = ConfigDict()
  249. config.set((b"rerere",), b"autoupdate", b"true")
  250. self.assertTrue(is_rerere_autoupdate(config))
  251. class RerereAutoTests(unittest.TestCase):
  252. """Tests for rerere_auto functionality."""
  253. def setUp(self) -> None:
  254. """Set up test fixtures."""
  255. from dulwich.repo import Repo
  256. self.tempdir = tempfile.mkdtemp()
  257. self.repo = Repo.init(self.tempdir)
  258. # Enable rerere
  259. config = self.repo.get_config()
  260. config.set((b"rerere",), b"enabled", b"true")
  261. config.write_to_path()
  262. def tearDown(self) -> None:
  263. """Clean up test fixtures."""
  264. import shutil
  265. shutil.rmtree(self.tempdir, ignore_errors=True)
  266. def test_rerere_auto_disabled(self) -> None:
  267. """Test that rerere_auto does nothing when disabled."""
  268. from dulwich.rerere import rerere_auto
  269. # Disable rerere
  270. config = self.repo.get_config()
  271. config.set((b"rerere",), b"enabled", b"false")
  272. config.write_to_path()
  273. # Create a fake conflicted file
  274. conflict_file = os.path.join(self.tempdir, "test.txt")
  275. with open(conflict_file, "wb") as f:
  276. f.write(
  277. b"""<<<<<<< ours
  278. our change
  279. =======
  280. their change
  281. >>>>>>> theirs
  282. """
  283. )
  284. result = rerere_auto(self.repo, self.tempdir, [b"test.txt"])
  285. self.assertEqual([], result)
  286. def test_rerere_auto_records_conflicts(self) -> None:
  287. """Test that rerere_auto records conflicts from working tree."""
  288. from dulwich.rerere import rerere_auto
  289. # Create a conflicted file in the working tree
  290. conflict_file = os.path.join(self.tempdir, "test.txt")
  291. with open(conflict_file, "wb") as f:
  292. f.write(
  293. b"""line 1
  294. <<<<<<< ours
  295. our change
  296. =======
  297. their change
  298. >>>>>>> theirs
  299. line 2
  300. """
  301. )
  302. result = rerere_auto(self.repo, self.tempdir, [b"test.txt"])
  303. self.assertEqual(1, len(result))
  304. path, conflict_id = result[0]
  305. self.assertEqual(b"test.txt", path)
  306. self.assertEqual(40, len(conflict_id)) # SHA-1 hash length
  307. def test_rerere_auto_skips_non_conflicted_files(self) -> None:
  308. """Test that rerere_auto skips files without conflict markers."""
  309. from dulwich.rerere import rerere_auto
  310. # Create a non-conflicted file
  311. file_path = os.path.join(self.tempdir, "test.txt")
  312. with open(file_path, "wb") as f:
  313. f.write(b"line 1\nline 2\n")
  314. result = rerere_auto(self.repo, self.tempdir, [b"test.txt"])
  315. self.assertEqual([], result)
  316. def test_rerere_auto_handles_missing_files(self) -> None:
  317. """Test that rerere_auto handles deleted files gracefully."""
  318. from dulwich.rerere import rerere_auto
  319. # Don't create the file
  320. result = rerere_auto(self.repo, self.tempdir, [b"missing.txt"])
  321. self.assertEqual([], result)