Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 46d3d05

Browse files
committed
Add more checks for the validity of refnames
This change adds checks based on the rules described in [0] in order to more robustly check a refname's validity. [0]: https://git-scm.com/docs/git-check-ref-format
1 parent a5a6464 commit 46d3d05

File tree

2 files changed

+84
-2
lines changed

2 files changed

+84
-2
lines changed

git/refs/symbolic.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,61 @@ def dereference_recursive(cls, repo: "Repo", ref_path: Union[PathLike, None]) ->
161161
return hexsha
162162
# END recursive dereferencing
163163

164+
@staticmethod
165+
def _check_ref_name_valid(ref_path: PathLike) -> None:
166+
# Based on the rules described in https://git-scm.com/docs/git-check-ref-format/#_description
167+
previous: Union[str, None] = None
168+
one_before_previous: Union[str, None] = None
169+
for c in str(ref_path):
170+
if c in " ~^:?*[\\":
171+
raise ValueError(
172+
f"Invalid reference '{ref_path}': references cannot contain spaces, tildes (~), carets (^),"
173+
f" colons (:), question marks (?), asterisks (*), open brackets ([) or backslashes (\\)"
174+
)
175+
elif c == ".":
176+
if previous is None or previous == "/":
177+
raise ValueError(
178+
f"Invalid reference '{ref_path}': references cannot start with a period (.) or contain '/.'"
179+
)
180+
elif previous == ".":
181+
raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '..'")
182+
elif c == "/":
183+
if previous == "/":
184+
raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '//'")
185+
elif previous is None:
186+
raise ValueError(
187+
f"Invalid reference '{ref_path}': references cannot start with forward slashes '/'"
188+
)
189+
elif c == "{" and previous == "@":
190+
raise ValueError(f"Invalid reference '{ref_path}': references cannot contain '@{{'")
191+
elif ord(c) < 32 or ord(c) == 127:
192+
raise ValueError(f"Invalid reference '{ref_path}': references cannot contain ASCII control characters")
193+
194+
one_before_previous = previous
195+
previous = c
196+
197+
if previous == ".":
198+
raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a period (.)")
199+
elif previous == "/":
200+
raise ValueError(f"Invalid reference '{ref_path}': references cannot end with a forward slash (/)")
201+
elif previous == "@" and one_before_previous is None:
202+
raise ValueError(f"Invalid reference '{ref_path}': references cannot be '@'")
203+
elif any([component.endswith(".lock") for component in str(ref_path).split("/")]):
204+
raise ValueError(
205+
f"Invalid reference '{ref_path}': references cannot have slash-separated components that end with"
206+
f" '.lock'"
207+
)
208+
164209
@classmethod
165210
def _get_ref_info_helper(
166211
cls, repo: "Repo", ref_path: Union[PathLike, None]
167212
) -> Union[Tuple[str, None], Tuple[None, str]]:
168213
"""Return: (str(sha), str(target_ref_path)) if available, the sha the file at
169214
rela_path points to, or None. target_ref_path is the reference we
170215
point to, or None"""
171-
if ".." in str(ref_path):
172-
raise ValueError(f"Invalid reference '{ref_path}'")
216+
if ref_path:
217+
cls._check_ref_name_valid(ref_path)
218+
173219
tokens: Union[None, List[str], Tuple[str, str]] = None
174220
repodir = _git_dir(repo, ref_path)
175221
try:

test/test_refs.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,3 +631,39 @@ def test_refs_outside_repo(self):
631631
ref_file.flush()
632632
ref_file_name = Path(ref_file.name).name
633633
self.assertRaises(BadName, self.rorepo.commit, f"../../{ref_file_name}")
634+
635+
def test_validity_ref_names(self):
636+
check_ref = SymbolicReference._check_ref_name_valid
637+
# Based on the rules specified in https://git-scm.com/docs/git-check-ref-format/#_description
638+
# Rule 1
639+
self.assertRaises(ValueError, check_ref, ".ref/begins/with/dot")
640+
self.assertRaises(ValueError, check_ref, "ref/component/.begins/with/dot")
641+
self.assertRaises(ValueError, check_ref, "ref/ends/with/a.lock")
642+
self.assertRaises(ValueError, check_ref, "ref/component/ends.lock/with/period_lock")
643+
# Rule 2
644+
check_ref("valid_one_level_refname")
645+
# Rule 3
646+
self.assertRaises(ValueError, check_ref, "ref/contains/../double/period")
647+
# Rule 4
648+
for c in " ~^:":
649+
self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{c}/character")
650+
for code in range(0, 32):
651+
self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{chr(code)}/ASCII/control_character")
652+
self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{chr(127)}/ASCII/control_character")
653+
# Rule 5
654+
for c in "*?[":
655+
self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{c}/character")
656+
# Rule 6
657+
self.assertRaises(ValueError, check_ref, "/ref/begins/with/slash")
658+
self.assertRaises(ValueError, check_ref, "ref/ends/with/slash/")
659+
self.assertRaises(ValueError, check_ref, "ref/contains//double/slash/")
660+
# Rule 7
661+
self.assertRaises(ValueError, check_ref, "ref/ends/with/dot.")
662+
# Rule 8
663+
self.assertRaises(ValueError, check_ref, "ref/contains@{/at_brace")
664+
# Rule 9
665+
self.assertRaises(ValueError, check_ref, "@")
666+
# Rule 10
667+
self.assertRaises(ValueError, check_ref, "ref/contain\\s/backslash")
668+
# Valid reference name should not raise
669+
check_ref("valid/ref/name")

0 commit comments

Comments
 (0)