test_ignore.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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(check_ignore(self.test_dir, abs_paths, quote_path=True))
  52. # Test with quote_path=False
  53. ignored_unquoted = set(check_ignore(self.test_dir, abs_paths, quote_path=False))
  54. # Verify quoted results
  55. expected_quoted = {
  56. '"\\321\\202\\320\\265\\321\\201\\321\\202.txt"', # тест.txt
  57. '"\\321\\204\\320\\260\\320\\271\\320\\273.\\346\\265\\213\\350\\257\\225"', # файл.测试
  58. }
  59. self.assertEqual(ignored_quoted, expected_quoted)
  60. # Verify unquoted results
  61. expected_unquoted = {"тест.txt", "файл.测试"}
  62. self.assertEqual(ignored_unquoted, expected_unquoted)
  63. def test_quote_path_ascii_filenames(self) -> None:
  64. """Test that ASCII filenames are unaffected by quote_path setting."""
  65. # Create a repository
  66. repo = Repo.init(self.test_dir)
  67. self.addCleanup(repo.close)
  68. # Create .gitignore
  69. gitignore_path = os.path.join(self.test_dir, ".gitignore")
  70. with open(gitignore_path, "w") as f:
  71. f.write("*.tmp\n")
  72. f.write("test*\n")
  73. # Create ASCII files
  74. test_files = ["test.txt", "file.tmp", "normal.txt"]
  75. for filename in test_files:
  76. filepath = os.path.join(self.test_dir, filename)
  77. with open(filepath, "w") as f:
  78. f.write("test content")
  79. # Test both settings
  80. abs_paths = [os.path.join(self.test_dir, f) for f in test_files]
  81. ignored_quoted = set(check_ignore(self.test_dir, abs_paths, quote_path=True))
  82. ignored_unquoted = set(check_ignore(self.test_dir, abs_paths, quote_path=False))
  83. # Both should return the same results for ASCII filenames
  84. expected = {"test.txt", "file.tmp"}
  85. self.assertEqual(ignored_quoted, expected)
  86. self.assertEqual(ignored_unquoted, expected)
  87. class QuotePathTests(TestCase):
  88. """Tests for _quote_path function."""
  89. def test_ascii_paths(self) -> None:
  90. """Test that ASCII paths are not quoted."""
  91. self.assertEqual(_quote_path("file.txt"), "file.txt")
  92. self.assertEqual(_quote_path("dir/file.txt"), "dir/file.txt")
  93. self.assertEqual(_quote_path("path with spaces.txt"), "path with spaces.txt")
  94. def test_unicode_paths(self) -> None:
  95. """Test that unicode paths are quoted with C-style escapes."""
  96. # Russian characters
  97. self.assertEqual(
  98. _quote_path("тест.txt"), '"\\321\\202\\320\\265\\321\\201\\321\\202.txt"'
  99. )
  100. # Chinese characters
  101. self.assertEqual(
  102. _quote_path("файл.测试"),
  103. '"\\321\\204\\320\\260\\320\\271\\320\\273.\\346\\265\\213\\350\\257\\225"',
  104. )
  105. # Mixed ASCII and unicode
  106. self.assertEqual(
  107. _quote_path("test-тест.txt"),
  108. '"test-\\321\\202\\320\\265\\321\\201\\321\\202.txt"',
  109. )
  110. def test_special_characters(self) -> None:
  111. """Test that special characters are properly escaped."""
  112. # Quotes in filename
  113. self.assertEqual(
  114. _quote_path('file"with"quotes.txt'), '"file\\"with\\"quotes.txt"'
  115. )
  116. # Backslashes in filename
  117. self.assertEqual(
  118. _quote_path("file\\with\\backslashes.txt"),
  119. '"file\\\\with\\\\backslashes.txt"',
  120. )
  121. # Mixed special chars and unicode
  122. self.assertEqual(
  123. _quote_path('тест"файл.txt'),
  124. '"\\321\\202\\320\\265\\321\\201\\321\\202\\"\\321\\204\\320\\260\\320\\271\\320\\273.txt"',
  125. )
  126. def test_empty_and_edge_cases(self) -> None:
  127. """Test edge cases."""
  128. self.assertEqual(_quote_path(""), "")
  129. self.assertEqual(_quote_path("a"), "a") # Single ASCII char
  130. self.assertEqual(_quote_path("я"), '"\\321\\217"') # Single unicode char