test_ignore.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # test_ignore.py -- tests for porcelain ignore (check_ignore)
  2. # Copyright (C) 2017 Jelmer Vernooij <jelmer@jelmer.uk>
  3. #
  4. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
  5. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  6. # General Public License as published by the Free Software Foundation; version 2.0
  7. # or (at your option) any later version. You can redistribute it and/or
  8. # modify it under the terms of either of these two licenses.
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. # You should have received a copy of the licenses; if not, see
  17. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  18. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  19. # License, Version 2.0.
  20. #
  21. """Tests for porcelain ignore (check_ignore) functions."""
  22. import os
  23. import shutil
  24. import tempfile
  25. from dulwich.porcelain import _quote_path, check_ignore
  26. from dulwich.repo import Repo
  27. from .. import TestCase
  28. class CheckIgnoreQuotePathTests(TestCase):
  29. """Integration tests for check_ignore with quote_path parameter."""
  30. def setUp(self) -> None:
  31. self.test_dir = tempfile.mkdtemp()
  32. self.addCleanup(shutil.rmtree, self.test_dir)
  33. def test_quote_path_true_unicode_filenames(self) -> None:
  34. """Test that quote_path=True returns quoted unicode filenames."""
  35. # Create a repository
  36. repo = Repo.init(self.test_dir)
  37. self.addCleanup(repo.close)
  38. # Create .gitignore with unicode patterns
  39. gitignore_path = os.path.join(self.test_dir, ".gitignore")
  40. with open(gitignore_path, "w", encoding="utf-8") as f:
  41. f.write("тест*\n")
  42. f.write("*.测试\n")
  43. # Create unicode files
  44. test_files = ["тест.txt", "файл.测试", "normal.txt"]
  45. for filename in test_files:
  46. filepath = os.path.join(self.test_dir, filename)
  47. with open(filepath, "w", encoding="utf-8") as f:
  48. f.write("test content")
  49. # Test with quote_path=True (default)
  50. abs_paths = [os.path.join(self.test_dir, f) for f in test_files]
  51. ignored_quoted = set(
  52. check_ignore(self.test_dir, abs_paths, quote_path=True)
  53. )
  54. # Test with quote_path=False
  55. ignored_unquoted = set(
  56. check_ignore(self.test_dir, abs_paths, quote_path=False)
  57. )
  58. # Verify quoted results
  59. expected_quoted = {
  60. '"\\321\\202\\320\\265\\321\\201\\321\\202.txt"', # тест.txt
  61. '"\\321\\204\\320\\260\\320\\271\\320\\273.\\346\\265\\213\\350\\257\\225"', # файл.测试
  62. }
  63. self.assertEqual(ignored_quoted, expected_quoted)
  64. # Verify unquoted results
  65. expected_unquoted = {"тест.txt", "файл.测试"}
  66. self.assertEqual(ignored_unquoted, expected_unquoted)
  67. def test_quote_path_ascii_filenames(self) -> None:
  68. """Test that ASCII filenames are unaffected by quote_path setting."""
  69. # Create a repository
  70. repo = Repo.init(self.test_dir)
  71. self.addCleanup(repo.close)
  72. # Create .gitignore
  73. gitignore_path = os.path.join(self.test_dir, ".gitignore")
  74. with open(gitignore_path, "w") as f:
  75. f.write("*.tmp\n")
  76. f.write("test*\n")
  77. # Create ASCII files
  78. test_files = ["test.txt", "file.tmp", "normal.txt"]
  79. for filename in test_files:
  80. filepath = os.path.join(self.test_dir, filename)
  81. with open(filepath, "w") as f:
  82. f.write("test content")
  83. # Test both settings
  84. abs_paths = [os.path.join(self.test_dir, f) for f in test_files]
  85. ignored_quoted = set(
  86. check_ignore(self.test_dir, abs_paths, quote_path=True)
  87. )
  88. ignored_unquoted = set(
  89. check_ignore(self.test_dir, abs_paths, quote_path=False)
  90. )
  91. # Both should return the same results for ASCII filenames
  92. expected = {"test.txt", "file.tmp"}
  93. self.assertEqual(ignored_quoted, expected)
  94. self.assertEqual(ignored_unquoted, expected)
  95. class QuotePathTests(TestCase):
  96. """Tests for _quote_path function."""
  97. def test_ascii_paths(self) -> None:
  98. """Test that ASCII paths are not quoted."""
  99. self.assertEqual(_quote_path("file.txt"), "file.txt")
  100. self.assertEqual(_quote_path("dir/file.txt"), "dir/file.txt")
  101. self.assertEqual(_quote_path("path with spaces.txt"), "path with spaces.txt")
  102. def test_unicode_paths(self) -> None:
  103. """Test that unicode paths are quoted with C-style escapes."""
  104. # Russian characters
  105. self.assertEqual(
  106. _quote_path("тест.txt"), '"\\321\\202\\320\\265\\321\\201\\321\\202.txt"'
  107. )
  108. # Chinese characters
  109. self.assertEqual(
  110. _quote_path("файл.测试"),
  111. '"\\321\\204\\320\\260\\320\\271\\320\\273.\\346\\265\\213\\350\\257\\225"',
  112. )
  113. # Mixed ASCII and unicode
  114. self.assertEqual(
  115. _quote_path("test-тест.txt"),
  116. '"test-\\321\\202\\320\\265\\321\\201\\321\\202.txt"',
  117. )
  118. def test_special_characters(self) -> None:
  119. """Test that special characters are properly escaped."""
  120. # Quotes in filename
  121. self.assertEqual(
  122. _quote_path('file"with"quotes.txt'), '"file\\"with\\"quotes.txt"'
  123. )
  124. # Backslashes in filename
  125. self.assertEqual(
  126. _quote_path("file\\with\\backslashes.txt"),
  127. '"file\\\\with\\\\backslashes.txt"',
  128. )
  129. # Mixed special chars and unicode
  130. self.assertEqual(
  131. _quote_path('тест"файл.txt'),
  132. '"\\321\\202\\320\\265\\321\\201\\321\\202\\"\\321\\204\\320\\260\\320\\271\\320\\273.txt"',
  133. )
  134. def test_empty_and_edge_cases(self) -> None:
  135. """Test edge cases."""
  136. self.assertEqual(_quote_path(""), "")
  137. self.assertEqual(_quote_path("a"), "a") # Single ASCII char
  138. self.assertEqual(_quote_path("я"), '"\\321\\217"') # Single unicode char