Skip to content

Commit e98f57b

Browse files
authored
Merge pull request #1672 from trail-of-forks/robust-refname-checks
Add more checks for the validity of refnames
2 parents 1774f1e + 46d3d05 commit e98f57b

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy