module documentation

Classes for dealing with git am-style patches.

These patches are basically unified diffs with some extra metadata tacked on.

Class FilePatch Represents a patch for a single file.
Class MailinfoResult Result of mailinfo parsing.
Class PatchHunk Represents a single hunk in a unified diff.
Exception DiffAlgorithmNotAvailable Raised when a requested diff algorithm is not available.
Exception PatchApplicationFailure Raised when a patch does not apply cleanly.
Function apply_patch_hunks Apply patch hunks to file content.
Function apply_patches Apply a list of file patches to a repository.
Function commit_patch_id Compute patch ID for a commit.
Function gen_diff_header Write a blob diff header.
Function get_summary Determine the summary line for use in a filename.
Function git_am_patch_split Parse a git-am-style patch and split it up into bits.
Function git_base85_decode Decode Git's base85-encoded binary data.
Function is_binary See if the first few bytes contain any null characters.
Function mailinfo Extract patch information from an email message.
Function parse_patch_message Extract a Commit object and patch from an e-mail message.
Function parse_unified_diff Parse a unified diff into FilePatch objects.
Function patch_filename Generate patch filename.
Function patch_id Compute patch ID for a diff.
Function shortid Get short object ID.
Function unified_diff difflib.unified_diff that can detect "No newline at end of file" as original "git diff" does.
Function unified_diff_with_algorithm Generate unified diff with specified algorithm.
Function write_blob_diff Write blob diff.
Function write_commit_patch Write a individual file patch.
Function write_object_diff Write the diff for an object.
Function write_tree_diff Write tree diff.
Constant DEFAULT_DIFF_ALGORITHM Undocumented
Constant FIRST_FEW_BYTES Undocumented
Function _apply_rename_or_copy Apply a rename or copy operation.
Function _find_scissors_line Find the scissors line in message body.
Function _format_range_unified Convert range to the "ed" format.
Function _get_sequence_matcher Get appropriate sequence matcher for the given algorithm.
Function _munge_subject Munge email subject line for commit message.
def apply_patch_hunks(patch: FilePatch, original_lines: list[bytes]) -> list[bytes] | None:

Apply patch hunks to file content.

Parameters
patch:FilePatchFilePatch object to apply
original_lines:list[bytes]Original file content as list of lines
Returns
list[bytes] | NonePatched file content as list of lines, or None if patch cannot be applied
def apply_patches(r: Repo, patches: list[FilePatch], cached: bool = False, reverse: bool = False, check: bool = False, strip: int = 1, three_way: bool = False):

Apply a list of file patches to a repository.

Parameters
r:RepoRepository object
patches:list[FilePatch]List of FilePatch objects to apply
cached:boolApply patch to index only, not working tree
reverse:boolApply patch in reverse
check:boolOnly check if patch can be applied, don't apply
strip:intNumber of leading path components to strip (default: 1)
three_way:boolFall back to 3-way merge if patch does not apply cleanly
Raises
ValueErrorIf patch cannot be applied
def commit_patch_id(store: BaseObjectStore, commit_id: ObjectID | RawObjectID) -> bytes:

Compute patch ID for a commit.

Parameters
store:BaseObjectStoreObject store to read objects from
commit_id:ObjectID | RawObjectIDCommit ID (40-byte hex string)
Returns
bytesPatch ID (40-byte hex string)
def gen_diff_header(paths: tuple[bytes | None, bytes | None], modes: tuple[int | None, int | None], shas: tuple[bytes | None, bytes | None]) -> Generator[bytes, None, None]:

Write a blob diff header.

Parameters
paths:tuple[bytes | None, bytes | None]Tuple with old and new path
modes:tuple[int | None, int | None]Tuple with old and new modes
shas:tuple[bytes | None, bytes | None]Tuple with old and new shas
Returns
Generator[bytes, None, None]Undocumented
def get_summary(commit: Commit) -> str:

Determine the summary line for use in a filename.

Returns: Summary string

Parameters
commit:CommitCommit
Returns
strUndocumented
def git_am_patch_split(f: TextIO | BinaryIO, encoding: str | None = None) -> tuple[Commit, bytes, bytes | None]:

Parse a git-am-style patch and split it up into bits.

Returns: Tuple with commit object, diff contents and git version

Parameters
f:TextIO | BinaryIOFile-like object to parse
encoding:str | NoneEncoding to use when creating Git objects
Returns
tuple[Commit, bytes, bytes | None]Undocumented
def git_base85_decode(data: bytes) -> bytes:

Decode Git's base85-encoded binary data.

Git uses a custom base85 encoding with its own alphabet and line format. Each line starts with a length byte followed by base85-encoded data.

Parameters
data:bytesBase85-encoded data as bytes (may contain multiple lines)
Returns
bytesDecoded binary data
Raises
ValueErrorIf the data is invalid
def is_binary(content: bytes) -> bool:

See if the first few bytes contain any null characters.

Parameters
content:bytesBytestring to check for binary content
Returns
boolUndocumented
def mailinfo(msg: email.message.Message | BinaryIO | TextIO, keep_subject: bool = False, keep_non_patch: bool = False, encoding: str | None = None, scissors: bool = False, message_id: bool = False) -> MailinfoResult:

Extract patch information from an email message.

This function parses an email message and extracts commit metadata (author, email, subject) and separates the commit message from the patch content, similar to git mailinfo.

Parameters
msg:email.message.Message | BinaryIO | TextIOEmail message (email.message.Message object) or file handle to read from
keep_subject:boolIf True, keep subject intact without munging (-k)
keep_non_patch:boolIf True, only strip [PATCH] from brackets (-b)
encoding:str | NoneCharacter encoding to use (default: detect from message)
scissors:boolIf True, remove everything before scissors line
message_id:boolIf True, include Message-ID in commit message (-m)
Returns
MailinfoResultMailinfoResult with parsed information
Raises
ValueErrorIf message is malformed or missing required fields
def parse_patch_message(msg: email.message.Message, encoding: str | None = None) -> tuple[Commit, bytes, bytes | None]:

Extract a Commit object and patch from an e-mail message.

Returns: Tuple with commit object, diff contents and git version

Parameters
msg:email.message.MessageAn email message (email.message.Message)
encoding:str | NoneEncoding to use to encode Git commits
Returns
tuple[Commit, bytes, bytes | None]Undocumented
def parse_unified_diff(diff_text: bytes) -> list[FilePatch]:

Parse a unified diff into FilePatch objects.

Parameters
diff_text:bytesUnified diff content as bytes
Returns
list[FilePatch]List of FilePatch objects
def patch_filename(p: bytes | None, root: bytes) -> bytes:

Generate patch filename.

Parameters
p:bytes | NonePath or None
root:bytesRoot directory
Returns
bytesFull patch filename
def patch_id(diff_data: bytes) -> bytes:

Compute patch ID for a diff.

The patch ID is computed by normalizing the diff and computing a SHA1 hash. This follows git's patch-id algorithm which: 1. Removes whitespace from lines starting with + or - 2. Replaces line numbers in @@ headers with a canonical form 3. Computes SHA1 of the result

TODO: This implementation uses a simple line-by-line approach. For better compatibility with git's patch-id, consider using proper patch parsing that: - Handles edge cases in diff format (binary diffs, mode changes, etc.) - Properly parses unified diff format according to the spec - Matches git's exact normalization algorithm byte-for-byte See git's patch-id.c for reference implementation.

Parameters
diff_data:bytesRaw diff data as bytes
Returns
bytesSHA1 hash of normalized diff (40-byte hex string)
def shortid(hexsha: bytes | None) -> bytes:

Get short object ID.

Parameters
hexsha:bytes | NoneFull hex SHA or None
Returns
bytes7-character short ID
def unified_diff(a: Sequence[bytes], b: Sequence[bytes], fromfile: bytes = b'', tofile: bytes = b'', fromfiledate: str = '', tofiledate: str = '', n: int = 3, lineterm: str = '\n', tree_encoding: str = 'utf-8', output_encoding: str = 'utf-8') -> Generator[bytes, None, None]:

difflib.unified_diff that can detect "No newline at end of file" as original "git diff" does.

Based on the same function in Python2.7 difflib.py

def unified_diff_with_algorithm(a: Sequence[bytes], b: Sequence[bytes], fromfile: bytes = b'', tofile: bytes = b'', fromfiledate: str = '', tofiledate: str = '', n: int = 3, lineterm: str = '\n', tree_encoding: str = 'utf-8', output_encoding: str = 'utf-8', algorithm: str | None = None) -> Generator[bytes, None, None]:

Generate unified diff with specified algorithm.

Parameters
a:Sequence[bytes]First sequence of lines
b:Sequence[bytes]Second sequence of lines
fromfile:bytesName of first file
tofile:bytesName of second file
fromfiledate:strDate of first file
tofiledate:strDate of second file
n:intNumber of context lines
lineterm:strLine terminator
tree_encoding:strEncoding for tree paths
output_encoding:strEncoding for output
algorithm:str | NoneDiff algorithm to use ("myers" or "patience")
Returns
Generator[bytes, None, None]Generator yielding diff lines
Raises
DiffAlgorithmNotAvailableIf patience algorithm requested but patiencediff not available
def write_blob_diff(f: IO[bytes], old_file: tuple[bytes | None, int | None, Blob | None], new_file: tuple[bytes | None, int | None, Blob | None], diff_algorithm: str | None = None):

Write blob diff.

Note: The use of write_object_diff is recommended over this function.

Parameters
f:IO[bytes]File-like object to write to
old_file:tuple[bytes | None, int | None, Blob | None](path, mode, hexsha) tuple (None if nonexisting)
new_file:tuple[bytes | None, int | None, Blob | None](path, mode, hexsha) tuple (None if nonexisting)
diff_algorithm:str | NoneAlgorithm to use for diffing ("myers" or "patience")
def write_commit_patch(f: IO[bytes], commit: Commit, contents: str | bytes, progress: tuple[int, int], version: str | None = None, encoding: str | None = None):

Write a individual file patch.

Parameters
f:IO[bytes]File-like object to write to
commit:CommitCommit object
contents:str | bytesContents of the patch
progress:tuple[int, int]tuple with current patch number and total.
version:str | NoneVersion string to include in patch header
encoding:str | NoneEncoding to use for the patch
Returns
tuple with filename and contents
def write_object_diff(f: IO[bytes], store: BaseObjectStore, old_file: tuple[bytes | None, int | None, ObjectID | None], new_file: tuple[bytes | None, int | None, ObjectID | None], diff_binary: bool = False, diff_algorithm: str | None = None):

Write the diff for an object.

Note: the tuple elements should be None for nonexistent files

Parameters
f:IO[bytes]File-like object to write to
store:BaseObjectStoreStore to retrieve objects from, if necessary
old_file:tuple[bytes | None, int | None, ObjectID | None](path, mode, hexsha) tuple
new_file:tuple[bytes | None, int | None, ObjectID | None](path, mode, hexsha) tuple
diff_binary:boolWhether to diff files even if they are considered binary files by is_binary().
diff_algorithm:str | NoneAlgorithm to use for diffing ("myers" or "patience")
def write_tree_diff(f: IO[bytes], store: BaseObjectStore, old_tree: ObjectID | None, new_tree: ObjectID | None, diff_binary: bool = False, diff_algorithm: str | None = None):

Write tree diff.

Parameters
f:IO[bytes]File-like object to write to.
store:BaseObjectStoreObject store to read from
old_tree:ObjectID | NoneOld tree id
new_tree:ObjectID | NoneNew tree id
diff_binary:boolWhether to diff files even if they are considered binary files by is_binary().
diff_algorithm:str | NoneAlgorithm to use for diffing ("myers" or "patience")
DEFAULT_DIFF_ALGORITHM: str =

Undocumented

Value
'myers'
FIRST_FEW_BYTES: int =

Undocumented

Value
8000
def _apply_rename_or_copy(r: Repo, src_path: bytes, dst_path: bytes, strip: int, patch: FilePatch, is_rename: bool, cached: bool, check: bool) -> tuple[list[bytes] | None, bool]:

Apply a rename or copy operation.

Parameters
r:RepoRepository object
src_path:bytesSource path
dst_path:bytesDestination path
strip:intNumber of path components to strip
patch:FilePatchFilePatch object
is_rename:boolTrue for rename, False for copy
cached:boolApply to index only, not working tree
check:boolCheck only, don't apply
Returns
A tuple of (original_lines, should_continue) where
  • original_lines: Content lines if hunks need to be applied, None otherwise
  • should_continue: True to skip to next patch, False to continue processing
def _find_scissors_line(lines: list[bytes]) -> int | None:

Find the scissors line in message body.

Parameters
lines:list[bytes]List of lines in the message body
Returns
int | NoneIndex of scissors line, or None if not found
def _format_range_unified(start: int, stop: int) -> str:

Convert range to the "ed" format.

def _get_sequence_matcher(algorithm: str, a: Sequence[bytes], b: Sequence[bytes]) -> SequenceMatcher[bytes]:

Get appropriate sequence matcher for the given algorithm.

Parameters
algorithm:strDiff algorithm ("myers" or "patience")
a:Sequence[bytes]First sequence
b:Sequence[bytes]Second sequence
Returns
SequenceMatcher[bytes]Configured sequence matcher instance
Raises
DiffAlgorithmNotAvailableIf patience requested but not available
def _munge_subject(subject: str, keep_subject: bool, keep_non_patch: bool) -> str:

Munge email subject line for commit message.

Parameters
subject:strOriginal subject line
keep_subject:boolIf True, keep subject intact (-k option)
keep_non_patch:boolIf True, only strip [PATCH] (-b option)
Returns
strProcessed subject line