| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 61ed7ec commit d2f7284
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,6 +26,7 @@ | |||
| 26 | 26 | import os | |
| 27 | 27 | from io import BytesIO | |
| 28 | 28 | import logging | |
| 29 | + from collections import defaultdict | ||
| 29 | 30 | ||
| 30 | 31 | ||
| 31 | 32 | # typing ------------------------------------------------------------------ | |
@@ -335,8 +336,70 @@ def stats(self) -> Stats: | |||
| 335 | 336 | return Stats._list_from_string(self.repo, text) | |
| 336 | 337 | ||
| 337 | 338 | @property | |
| 338 | - def trailers(self) -> Dict: | ||
| 339 | - """Get the trailers of the message as dictionary | ||
| 339 | + def trailers(self) -> Dict[str, str]: | ||
| 340 | + """Get the trailers of the message as a dictionary | ||
| 341 | + | ||
| 342 | + Git messages can contain trailer information that are similar to RFC 822 | ||
| 343 | + e-mail headers (see: https://git-scm.com/docs/git-interpret-trailers). | ||
| 344 | + | ||
| 345 | + WARNING: This function only returns the latest instance of each trailer key | ||
| 346 | + and will be deprecated soon. Please see either ``Commit.trailers_list`` or ``Commit.trailers_dict``. | ||
| 347 | + | ||
| 348 | + :return: | ||
| 349 | + Dictionary containing whitespace stripped trailer information. | ||
| 350 | + Only the latest instance of each trailer key. | ||
| 351 | + """ | ||
| 352 | + return { | ||
| 353 | + k: v[0] for k, v in self.trailers_dict.items() | ||
| 354 | + } | ||
| 355 | + | ||
| 356 | + @property | ||
| 357 | + def trailers_list(self) -> List[str]: | ||
| 358 | + """Get the trailers of the message as a list | ||
| 359 | + | ||
| 360 | + Git messages can contain trailer information that are similar to RFC 822 | ||
| 361 | + e-mail headers (see: https://git-scm.com/docs/git-interpret-trailers). | ||
| 362 | + | ||
| 363 | + This functions calls ``git interpret-trailers --parse`` onto the message | ||
| 364 | + to extract the trailer information, returns the raw trailer data as a list. | ||
| 365 | + | ||
| 366 | + Valid message with trailer:: | ||
| 367 | + | ||
| 368 | + Subject line | ||
| 369 | + | ||
| 370 | + some body information | ||
| 371 | + | ||
| 372 | + another information | ||
| 373 | + | ||
| 374 | + key1: value1.1 | ||
| 375 | + key1: value1.2 | ||
| 376 | + key2 : value 2 with inner spaces | ||
| 377 | + | ||
| 378 | + | ||
| 379 | + Returned list will look like this:: | ||
| 380 | + | ||
| 381 | + [ | ||
| 382 | + "key1: value1.1", | ||
| 383 | + "key1: value1.2", | ||
| 384 | + "key2 : value 2 with inner spaces", | ||
| 385 | + ] | ||
| 386 | + | ||
| 387 | + | ||
| 388 | + :return: | ||
| 389 | + List containing whitespace stripped trailer information. | ||
| 390 | + """ | ||
| 391 | + cmd = ["git", "interpret-trailers", "--parse"] | ||
| 392 | + proc: Git.AutoInterrupt = self.repo.git.execute(cmd, as_process=True, istream=PIPE) # type: ignore | ||
| 393 | + trailer: str = proc.communicate(str(self.message).encode())[0].decode() | ||
| 394 | + trailer = trailer.strip() | ||
| 395 | + if trailer: | ||
| 396 | + return [t.strip() for t in trailer.split("\n")] | ||
| 397 | + | ||
| 398 | + return [] | ||
| 399 | + | ||
| 400 | + @property | ||
| 401 | + def trailers_dict(self) -> Dict[str, List[str]]: | ||
| 402 | + """Get the trailers of the message as a dictionary | ||
| 340 | 403 | ||
| 341 | 404 | Git messages can contain trailer information that are similar to RFC 822 | |
| 342 | 405 | e-mail headers (see: https://git-scm.com/docs/git-interpret-trailers). | |
@@ -345,42 +408,36 @@ def trailers(self) -> Dict: | |||
| 345 | 408 | to extract the trailer information. The key value pairs are stripped of | |
| 346 | 409 | leading and trailing whitespaces before they get saved into a dictionary. | |
| 347 | 410 | ||
| 348 | - Valid message with trailer: | ||
| 349 | - | ||
| 350 | - .. code-block:: | ||
| 411 | + Valid message with trailer:: | ||
| 351 | 412 | ||
| 352 | 413 | Subject line | |
| 353 | 414 | ||
| 354 | 415 | some body information | |
| 355 | 416 | ||
| 356 | 417 | another information | |
| 357 | 418 | ||
| 358 | - key1: value1 | ||
| 419 | + key1: value1.1 | ||
| 420 | + key1: value1.2 | ||
| 359 | 421 | key2 : value 2 with inner spaces | |
| 360 | 422 | ||
| 361 | - dictionary will look like this: | ||
| 362 | 423 | ||
| 363 | - .. code-block:: | ||
| 424 | + Returned dictionary will look like this:: | ||
| 364 | 425 | ||
| 365 | 426 | { | |
| 366 | - "key1": "value1", | ||
| 367 | - "key2": "value 2 with inner spaces" | ||
| 427 | + "key1": ["value1.1", "value1.2"], | ||
| 428 | + "key2": ["value 2 with inner spaces"], | ||
| 368 | 429 | } | |
| 369 | 430 | ||
| 370 | - :return: Dictionary containing whitespace stripped trailer information | ||
| 371 | 431 | ||
| 432 | + :return: | ||
| 433 | + Dictionary containing whitespace stripped trailer information. | ||
| 434 | + Mapping trailer keys to a list of their corresponding values. | ||
| 372 | 435 | """ | |
| 373 | - d = {} | ||
| 374 | - cmd = ["git", "interpret-trailers", "--parse"] | ||
| 375 | - proc: Git.AutoInterrupt = self.repo.git.execute(cmd, as_process=True, istream=PIPE) # type: ignore | ||
| 376 | - trailer: str = proc.communicate(str(self.message).encode())[0].decode() | ||
| 377 | - if trailer.endswith("\n"): | ||
| 378 | - trailer = trailer[0:-1] | ||
| 379 | - if trailer != "": | ||
| 380 | - for line in trailer.split("\n"): | ||
| 381 | - key, value = line.split(":", 1) | ||
| 382 | - d[key.strip()] = value.strip() | ||
| 383 | - return d | ||
| 436 | + d = defaultdict(list) | ||
| 437 | + for trailer in self.trailers_list: | ||
| 438 | + key, value = trailer.split(":", 1) | ||
| 439 | + d[key.strip()].append(value.strip()) | ||
| 440 | + return dict(d) | ||
| 384 | 441 | ||
| 385 | 442 | @classmethod | |
| 386 | 443 | def _iter_from_process_or_stream(cls, repo: "Repo", proc_or_stream: Union[Popen, IO]) -> Iterator["Commit"]: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -494,52 +494,63 @@ def test_datetimes(self): | |||
| 494 | 494 | ||
| 495 | 495 | def test_trailers(self): | |
| 496 | 496 | KEY_1 = "Hello" | |
| 497 | - VALUE_1 = "World" | ||
| 497 | + VALUE_1_1 = "World" | ||
| 498 | + VALUE_1_2 = "Another-World" | ||
| 498 | 499 | KEY_2 = "Key" | |
| 499 | 500 | VALUE_2 = "Value with inner spaces" | |
| 500 | 501 | ||
| 501 | - # Check if KEY 1 & 2 with Value 1 & 2 is extracted from multiple msg variations | ||
| 502 | - msgs = [] | ||
| 503 | - msgs.append(f"Subject\n\n{KEY_1}: {VALUE_1}\n{KEY_2}: {VALUE_2}\n") | ||
| 504 | - msgs.append(f"Subject\n \nSome body of a function\n \n{KEY_1}: {VALUE_1}\n{KEY_2}: {VALUE_2}\n") | ||
| 505 | - msgs.append( | ||
| 506 | - f"Subject\n \nSome body of a function\n\nnon-key: non-value\n\n{KEY_1}: {VALUE_1}\n{KEY_2}: {VALUE_2}\n" | ||
| 507 | - ) | ||
| 508 | - msgs.append( | ||
| 509 | - f"Subject\n \nSome multiline\n body of a function\n\nnon-key: non-value\n\n{KEY_1}: {VALUE_1}\n{KEY_2} : {VALUE_2}\n" | ||
| 510 | - ) | ||
| 511 | - | ||
| 502 | + # Check the following trailer example is extracted from multiple msg variations | ||
| 503 | + TRAILER = f"{KEY_1}: {VALUE_1_1}\n{KEY_2}: {VALUE_2}\n{KEY_1}: {VALUE_1_2}" | ||
| 504 | + msgs = [ | ||
| 505 | + f"Subject\n\n{TRAILER}\n", | ||
| 506 | + f"Subject\n \nSome body of a function\n \n{TRAILER}\n", | ||
| 507 | + f"Subject\n \nSome body of a function\n\nnon-key: non-value\n\n{TRAILER}\n", | ||
| 508 | + ( | ||
| 509 | + # check when trailer has inconsistent whitespace | ||
| 510 | + f"Subject\n \nSome multiline\n body of a function\n\nnon-key: non-value\n\n" | ||
| 511 | + f"{KEY_1}:{VALUE_1_1}\n{KEY_2} : {VALUE_2}\n{KEY_1}: {VALUE_1_2}\n" | ||
| 512 | + ), | ||
| 513 | + ] | ||
| 512 | 514 | for msg in msgs: | |
| 513 | - commit = self.rorepo.commit("master") | ||
| 514 | - commit = copy.copy(commit) | ||
| 515 | + commit = copy.copy(self.rorepo.commit("master")) | ||
| 515 | 516 | commit.message = msg | |
| 516 | - assert KEY_1 in commit.trailers.keys() | ||
| 517 | - assert KEY_2 in commit.trailers.keys() | ||
| 518 | - assert commit.trailers[KEY_1] == VALUE_1 | ||
| 519 | - assert commit.trailers[KEY_2] == VALUE_2 | ||
| 520 | - | ||
| 521 | - # Check that trailer stays empty for multiple msg combinations | ||
| 522 | - msgs = [] | ||
| 523 | - msgs.append(f"Subject\n") | ||
| 524 | - msgs.append(f"Subject\n\nBody with some\nText\n") | ||
| 525 | - msgs.append(f"Subject\n\nBody with\nText\n\nContinuation but\n doesn't contain colon\n") | ||
| 526 | - msgs.append(f"Subject\n\nBody with\nText\n\nContinuation but\n only contains one :\n") | ||
| 527 | - msgs.append(f"Subject\n\nBody with\nText\n\nKey: Value\nLine without colon\n") | ||
| 528 | - msgs.append(f"Subject\n\nBody with\nText\n\nLine without colon\nKey: Value\n") | ||
| 517 | + assert commit.trailers_list == [ | ||
| 518 | + f"{KEY_1}: {VALUE_1_1}", | ||
| 519 | + f"{KEY_2}: {VALUE_2}", | ||
| 520 | + f"{KEY_1}: {VALUE_1_2}", | ||
| 521 | + ] | ||
| 522 | + assert commit.trailers_dict == { | ||
| 523 | + KEY_1: [VALUE_1_1, VALUE_1_2], | ||
| 524 | + KEY_2: [VALUE_2], | ||
| 525 | + } | ||
| 526 | + assert commit.trailers == { | ||
| 527 | + KEY_1: VALUE_1_1, | ||
| 528 | + KEY_2: VALUE_2, | ||
| 529 | + } | ||
| 530 | + | ||
| 531 | + # check that trailer stays empty for multiple msg combinations | ||
| 532 | + msgs = [ | ||
| 533 | + f"Subject\n", | ||
| 534 | + f"Subject\n\nBody with some\nText\n", | ||
| 535 | + f"Subject\n\nBody with\nText\n\nContinuation but\n doesn't contain colon\n", | ||
| 536 | + f"Subject\n\nBody with\nText\n\nContinuation but\n only contains one :\n", | ||
| 537 | + f"Subject\n\nBody with\nText\n\nKey: Value\nLine without colon\n", | ||
| 538 | + f"Subject\n\nBody with\nText\n\nLine without colon\nKey: Value\n", | ||
| 539 | + ] | ||
| 529 | 540 | ||
| 530 | 541 | for msg in msgs: | |
| 531 | - commit = self.rorepo.commit("master") | ||
| 532 | - commit = copy.copy(commit) | ||
| 542 | + commit = copy.copy(self.rorepo.commit("master")) | ||
| 533 | 543 | commit.message = msg | |
| 534 | - assert len(commit.trailers.keys()) == 0 | ||
| 544 | + assert commit.trailers_list == [] | ||
| 545 | + assert commit.trailers_dict == {} | ||
| 546 | + assert commit.trailers == {} | ||
| 535 | 547 | ||
| 536 | 548 | # check that only the last key value paragraph is evaluated | |
| 537 | - commit = self.rorepo.commit("master") | ||
| 538 | - commit = copy.copy(commit) | ||
| 539 | - commit.message = f"Subject\n\nMultiline\nBody\n\n{KEY_1}: {VALUE_1}\n\n{KEY_2}: {VALUE_2}\n" | ||
| 540 | - assert KEY_1 not in commit.trailers.keys() | ||
| 541 | - assert KEY_2 in commit.trailers.keys() | ||
| 542 | - assert commit.trailers[KEY_2] == VALUE_2 | ||
| 549 | + commit = copy.copy(self.rorepo.commit("master")) | ||
| 550 | + commit.message = f"Subject\n\nMultiline\nBody\n\n{KEY_1}: {VALUE_1_1}\n\n{KEY_2}: {VALUE_2}\n" | ||
| 551 | + assert commit.trailers_list == [f"{KEY_2}: {VALUE_2}"] | ||
| 552 | + assert commit.trailers_dict == {KEY_2: [VALUE_2]} | ||
| 553 | + assert commit.trailers == {KEY_2: VALUE_2} | ||
| 543 | 554 | ||
| 544 | 555 | def test_commit_co_authors(self): | |
| 545 | 556 | commit = copy.copy(self.rorepo.commit("4251bd5")) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments