| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -511,6 +511,24 @@ def is_line_continuation(value: str) -> bool: | |||
| 511 | 511 | return False | |
| 512 | 512 | return escaped | |
| 513 | 513 | ||
| 514 | + def strip_inline_comment(value: str) -> Tuple[str, bool]: | ||
| 515 | + """Cut an unquoted ``#`` or ``;`` comment and report whether a quote is open. | ||
| 516 | + | ||
| 517 | + Quoting and backslash escapes are honoured, so a ``#`` inside a quoted | ||
| 518 | + value is literal and an unterminated quote swallows the rest of the line. | ||
| 519 | + """ | ||
| 520 | + quoted = escaped = False | ||
| 521 | + for index, char in enumerate(value): | ||
| 522 | + if escaped: | ||
| 523 | + escaped = False | ||
| 524 | + elif char == "\\": | ||
| 525 | + escaped = True | ||
| 526 | + elif char == '"': | ||
| 527 | + quoted = not quoted | ||
| 528 | + elif char in "#;" and not quoted: | ||
| 529 | + return value[:index], False | ||
| 530 | + return value, quoted | ||
| 531 | + | ||
| 514 | 532 | def parse_value(value: str) -> str: | |
| 515 | 533 | parsed: List[str] = [] | |
| 516 | 534 | whitespace: List[str] = [] | |
@@ -575,10 +593,7 @@ def parse_value(value: str) -> str: | |||
| 575 | 593 | optname, vi, optval = mo.group("option", "vi", "value") | |
| 576 | 594 | optname = self.optionxform(optname.rstrip()) | |
| 577 | 595 | ||
| 578 | - if vi in ("=", ":") and ";" in optval and not optval.strip().startswith('"'): | ||
| 579 | - pos = optval.find(";") | ||
| 580 | - if pos != -1 and optval[pos - 1].isspace(): | ||
| 581 | - optval = optval[:pos] | ||
| 596 | + optval, quote_open = strip_inline_comment(optval) | ||
| 582 | 597 | optval = optval.strip() | |
| 583 | 598 | ||
| 584 | 599 | if len(optval) < 2 or optval[0] != '"': | |
@@ -606,12 +621,12 @@ def parse_value(value: str) -> str: | |||
| 606 | 621 | continued = True | |
| 607 | 622 | if continued: | |
| 608 | 623 | optval = parse_value(optval) | |
| 609 | - elif optval[-1] != '"': | ||
| 624 | + elif quote_open: | ||
| 610 | 625 | # Opens quoting and does not close: appears to start multi-line quoting. | |
| 611 | 626 | is_multi_line = True | |
| 612 | 627 | optval = string_decode(optval[1:]) | |
| 613 | 628 | elif re.search(r'(?:^|[^\\])(?:\\\\)*"', optval[1:-1]): | |
| 614 | - # Preserve malformed values containing unescaped quotes. | ||
| 629 | + # Preserve values containing additional unescaped quotes. | ||
| 615 | 630 | pass | |
| 616 | 631 | else: | |
| 617 | 632 | # Opens and closes quoting. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -239,6 +239,51 @@ def test_multi_line_config(self): | |||
| 239 | 239 | ) | |
| 240 | 240 | self.assertEqual(len(config.sections()), 23) | |
| 241 | 241 | ||
| 242 | + def test_inline_comments_are_stripped_like_git(self): | ||
| 243 | + """A `#` or `;` outside quotes starts a comment, with or without a space | ||
| 244 | + before it, and whether or not the value is quoted. Expectations are what | ||
| 245 | + `git config -f <file> --get a.k` prints on git 2.50.1.""" | ||
| 246 | + cases = [ | ||
| 247 | + (b"[a]\n\tk = value # comment\n", "value"), | ||
| 248 | + (b"[a]\n\tk = value ; comment\n", "value"), | ||
| 249 | + (b"[a]\n\tk = value#nospace\n", "value"), | ||
| 250 | + (b"[a]\n\tk = value;nospace\n", "value"), | ||
| 251 | + (b"[a]\n\tk = a # b ; c\n", "a"), | ||
| 252 | + (b'[a]\n\tk = "quoted" # after\n', "quoted"), | ||
| 253 | + # A comment character inside quotes is literal. | ||
| 254 | + (b'[a]\n\tk = "has # inside"\n', "has # inside"), | ||
| 255 | + (b'[a]\n\tk = "has ; inside"\n', "has ; inside"), | ||
| 256 | + ] | ||
| 257 | + for content, expected in cases: | ||
| 258 | + config_file = io.BytesIO(content) | ||
| 259 | + config_file.name = "inline_comment.config" | ||
| 260 | + config = GitConfigParser(config_file) | ||
| 261 | + config.read() | ||
| 262 | + with self.subTest(content=content): | ||
| 263 | + self.assertEqual(config.get_value("a", "k"), expected) | ||
| 264 | + | ||
| 265 | + @with_rw_directory | ||
| 266 | + def test_inline_comments_preserve_balanced_quotes_and_following_settings(self, rw_dir): | ||
| 267 | + config_path = osp.join(rw_dir, "config") | ||
| 268 | + values = (b'"foo"bar', b'"foo\\"bar"baz', b'"foo#;bar"baz') | ||
| 269 | + for value in values: | ||
| 270 | + for comment in (b' # "note"', b' ; "note"'): | ||
| 271 | + with self.subTest(value=value, comment=comment): | ||
| 272 | + with open(config_path, "wb") as config_file: | ||
| 273 | + config_file.write(b"[a]\n\tk = " + value + comment + b"\n\tx = keep\n[b]\n\ty = stay\n") | ||
| 274 | + | ||
| 275 | + with GitConfigParser(config_path, read_only=False) as config: | ||
| 276 | + self.assertEqual(config.get_value("a", "k"), value.decode(defenc)) | ||
| 277 | + self.assertEqual(config.get_value("a", "x"), "keep") | ||
| 278 | + self.assertEqual(config.get_value("b", "y"), "stay") | ||
| 279 | + config.set_value("other", "value", "updated") | ||
| 280 | + | ||
| 281 | + with GitConfigParser(config_path) as config: | ||
| 282 | + self.assertEqual(config.get_value("a", "k"), value.decode(defenc)) | ||
| 283 | + self.assertEqual(config.get_value("a", "x"), "keep") | ||
| 284 | + self.assertEqual(config.get_value("b", "y"), "stay") | ||
| 285 | + self.assertEqual(config.get_value("other", "value"), "updated") | ||
| 286 | + | ||
| 242 | 287 | def test_backslash_line_continuation(self): | |
| 243 | 288 | """An unquoted value ending in a backslash continues on the next line, | |
| 244 | 289 | exactly as git config parses it: the final backslash and the newline | |
| Back | FazBrowse Home | New Git URL |
0 commit comments