Просмотр исходного кода

Add remaining missing docstrings

Complete the docstring documentation effort by adding all remaining
missing docstrings throughout the codebase. This commit brings the
docstring error count from 110 to 0, achieving 100% coverage.
Jelmer Vernooij 5 месяцев назад
Родитель
Сommit
9f88c4a625

+ 3 - 2
dulwich/__init__.py

@@ -49,14 +49,15 @@ except ImportError:
         remove_in: Optional[Union[str, tuple[int, ...]]] = None,
     ) -> Callable[[F], F]:
         """Decorator to mark functions as deprecated.
-        
+
         Args:
             since: Version when the function was deprecated
             remove_in: Version when the function will be removed
-        
+
         Returns:
             Decorator function
         """
+
         def decorator(func: F) -> F:
             import functools
             import warnings

+ 4 - 6
dulwich/archive.py

@@ -52,7 +52,7 @@ class ChunkedBytesIO:
 
     def __init__(self, contents: list[bytes]) -> None:
         """Initialize ChunkedBytesIO.
-        
+
         Args:
             contents: List of byte chunks
         """
@@ -61,10 +61,10 @@ class ChunkedBytesIO:
 
     def read(self, maxbytes: Optional[int] = None) -> bytes:
         """Read bytes from the chunked stream.
-        
+
         Args:
             maxbytes: Maximum number of bytes to read (None for all)
-        
+
         Returns:
             Bytes read
         """
@@ -164,9 +164,7 @@ def tar_stream(
 def _walk_tree(
     store: "BaseObjectStore", tree: "Tree", root: bytes = b""
 ) -> Generator[tuple[bytes, "TreeEntry"], None, None]:
-    """Recursively walk a dulwich Tree, yielding tuples of (absolute path, TreeEntry) along the way.
-
-    """
+    """Recursively walk a dulwich Tree, yielding tuples of (absolute path, TreeEntry) along the way."""
     for entry in tree.iteritems():
         entry_abspath = posixpath.join(root, entry.path)
         if stat.S_ISDIR(entry.mode):

+ 1 - 1
dulwich/attrs.py

@@ -165,7 +165,7 @@ class Pattern:
 
     def __init__(self, pattern: bytes):
         """Initialize GitAttributesPattern.
-        
+
         Args:
             pattern: Attribute pattern as bytes
         """

+ 1 - 1
dulwich/bisect.py

@@ -33,7 +33,7 @@ class BisectState:
 
     def __init__(self, repo: Repo) -> None:
         """Initialize BisectState.
-        
+
         Args:
             repo: Repository to perform bisect on
         """

+ 2 - 2
dulwich/bundle.py

@@ -33,7 +33,7 @@ if TYPE_CHECKING:
 
 class Bundle:
     """Git bundle object representation."""
-    
+
     version: Optional[int]
 
     capabilities: dict[str, Optional[str]]
@@ -159,7 +159,7 @@ def read_bundle(f: BinaryIO) -> Bundle:
 
 def write_bundle(f: BinaryIO, bundle: Bundle) -> None:
     """Write a bundle to a file.
-    
+
     Args:
         f: File-like object to write to
         bundle: Bundle object to write

+ 6 - 6
dulwich/cli.py

@@ -57,7 +57,7 @@ class CommitMessageError(Exception):
 
 def signal_int(signal, frame) -> None:
     """Handle interrupt signal by exiting.
-    
+
     Args:
         signal: Signal number
         frame: Current stack frame
@@ -67,7 +67,7 @@ def signal_int(signal, frame) -> None:
 
 def signal_quit(signal, frame) -> None:
     """Handle quit signal by entering debugger.
-    
+
     Args:
         signal: Signal number
         frame: Current stack frame
@@ -178,7 +178,7 @@ class PagerBuffer:
 
     def __init__(self, pager):
         """Initialize PagerBuffer.
-        
+
         Args:
             pager: Pager instance to wrap
         """
@@ -227,7 +227,7 @@ class Pager:
 
     def __init__(self, pager_cmd="cat"):
         """Initialize Pager.
-        
+
         Args:
             pager_cmd: Command to use for paging (default: "cat")
         """
@@ -470,7 +470,7 @@ class cmd_archive(Command):
 
     def run(self, args) -> None:
         """Execute the archive command.
-        
+
         Args:
             args: Command line arguments
         """
@@ -503,7 +503,7 @@ class cmd_add(Command):
 
     def run(self, argv) -> None:
         """Execute the add command.
-        
+
         Args:
             argv: Command line arguments
         """

+ 31 - 0
dulwich/client.py

@@ -2151,6 +2151,7 @@ class BundleClient(GitClient):
           thin_packs: Whether or not thin packs should be retrieved
           report_activity: Optional callback for reporting transport
             activity.
+          config: Optional configuration object
         """
         self._report_activity = report_activity
 
@@ -2608,6 +2609,7 @@ class PLinkSSHVendor(SSHVendor):
 
 
 def ParamikoSSHVendor(**kwargs):
+    """Create a ParamikoSSHVendor (deprecated)."""
     import warnings
 
     warnings.warn(
@@ -2638,6 +2640,19 @@ class SSHGitClient(TraditionalGitClient):
         ssh_command=None,
         **kwargs,
     ) -> None:
+        """Initialize SSHGitClient.
+
+        Args:
+            host: SSH hostname
+            port: Optional SSH port
+            username: Optional username
+            vendor: Optional SSH vendor
+            config: Optional configuration
+            password: Optional password
+            key_filename: Optional SSH key file
+            ssh_command: Optional custom SSH command
+            **kwargs: Additional keyword arguments
+        """
         self.host = host
         self.port = port
         self.username = username
@@ -2670,6 +2685,7 @@ class SSHGitClient(TraditionalGitClient):
             self.ssh_vendor = get_ssh_vendor()
 
     def get_url(self, path):
+        """Get the SSH URL for a path."""
         netloc = self.host
         if self.port is not None:
             netloc += f":{self.port}"
@@ -2681,6 +2697,7 @@ class SSHGitClient(TraditionalGitClient):
 
     @classmethod
     def from_parsedurl(cls, parsedurl, **kwargs):
+        """Create an SSHGitClient from a parsed URL."""
         return cls(
             host=parsedurl.hostname,
             port=parsedurl.port,
@@ -2740,6 +2757,7 @@ class SSHGitClient(TraditionalGitClient):
 
 
 def default_user_agent_string():
+    """Return the default user agent string for Dulwich."""
     # Start user agent with "git/", because GitHub requires this. :-( See
     # https://github.com/jelmer/dulwich/issues/562 for details.
     return "git/dulwich/{}".format(".".join([str(x) for x in dulwich.__version__]))
@@ -2759,6 +2777,9 @@ def default_urllib3_manager(
 
     Args:
       config: `dulwich.config.ConfigDict` instance with Git configuration.
+      pool_manager_cls: Pool manager class to use
+      proxy_manager_cls: Proxy manager class to use
+      base_url: Base URL for proxy bypass checks
       timeout: Timeout for HTTP requests in seconds
       override_kwargs: Additional arguments for `urllib3.ProxyManager`
 
@@ -2860,6 +2881,7 @@ def default_urllib3_manager(
 
 
 def check_for_proxy_bypass(base_url) -> bool:
+    """Check if proxy should be bypassed for the given URL."""
     # Check if a proxy bypass is defined with the no_proxy environment variable
     if base_url:  # only check if base_url is provided
         no_proxy_str = os.environ.get("no_proxy")
@@ -2923,6 +2945,7 @@ class AbstractHttpGitClient(GitClient):
     """
 
     def __init__(self, base_url, dumb=False, **kwargs) -> None:
+        """Initialize AbstractHttpGitClient."""
         self._base_url = base_url.rstrip("/") + "/"
         self.dumb = dumb
         GitClient.__init__(self, **kwargs)
@@ -2934,6 +2957,7 @@ class AbstractHttpGitClient(GitClient):
           url: Request URL.
           headers: Optional custom headers to override defaults.
           data: Request data.
+          raise_for_status: Whether to raise an exception for HTTP errors.
 
         Returns:
           Tuple (response, read), where response is an urllib3
@@ -3334,6 +3358,7 @@ class AbstractHttpGitClient(GitClient):
         return LsRemoteResult(refs, symrefs)
 
     def get_url(self, path):
+        """Get the HTTP URL for a path."""
         return self._get_url(path).rstrip("/")
 
     def _get_url(self, path):
@@ -3341,6 +3366,7 @@ class AbstractHttpGitClient(GitClient):
 
     @classmethod
     def from_parsedurl(cls, parsedurl, **kwargs):
+        """Create an AbstractHttpGitClient from a parsed URL."""
         password = parsedurl.password
         if password is not None:
             kwargs["password"] = urlunquote(password)
@@ -3350,6 +3376,7 @@ class AbstractHttpGitClient(GitClient):
         return cls(urlunparse(parsedurl), **kwargs)
 
     def __repr__(self) -> str:
+        """Return string representation of this client."""
         return f"{type(self).__name__}({self._base_url!r}, dumb={self.dumb!r})"
 
 
@@ -3379,6 +3406,7 @@ class Urllib3HttpGitClient(AbstractHttpGitClient):
         timeout=None,
         **kwargs,
     ) -> None:
+        """Initialize Urllib3HttpGitClient."""
         self._username = username
         self._password = password
         self._timeout = timeout
@@ -3495,6 +3523,7 @@ def get_transport_and_path_from_url(
       url: URL to open (a unicode string)
       config: Optional config object
       operation: Kind of operation that'll be performed; "pull" or "push"
+      **kwargs: Additional keyword arguments
 
     Keyword Args:
       thin_packs: Whether or not thin packs should be retrieved
@@ -3566,6 +3595,7 @@ def get_transport_and_path(
       location: URL or path (a string)
       config: Optional config object
       operation: Kind of operation that'll be performed; "pull" or "push"
+      **kwargs: Additional keyword arguments
 
     Keyword Args:
       thin_packs: Whether or not thin packs should be retrieved
@@ -3614,6 +3644,7 @@ DEFAULT_GIT_CREDENTIALS_PATHS = [
 def get_credentials_from_store(
     scheme, hostname, username=None, fnames=DEFAULT_GIT_CREDENTIALS_PATHS
 ):
+    """Read credentials from a Git credential store."""
     for fname in fnames:
         try:
             with open(fname, "rb") as f:

+ 9 - 0
dulwich/cloud/gcs.py

@@ -32,12 +32,21 @@ from ..pack import PACK_SPOOL_FILE_MAX_SIZE, Pack, PackData, load_pack_index_fil
 
 
 class GcsObjectStore(BucketBasedObjectStore):
+    """Object store implementation for Google Cloud Storage."""
+
     def __init__(self, bucket, subpath="") -> None:
+        """Initialize GcsObjectStore.
+
+        Args:
+          bucket: GCS bucket instance
+          subpath: Subpath within the bucket
+        """
         super().__init__()
         self.bucket = bucket
         self.subpath = subpath
 
     def __repr__(self) -> str:
+        """Return string representation of GcsObjectStore."""
         return f"{type(self).__name__}({self.bucket!r}, subpath={self.subpath!r})"
 
     def _remove_pack(self, name) -> None:

+ 22 - 0
dulwich/commit_graph.py

@@ -68,6 +68,15 @@ class CommitGraphEntry:
         generation: int,
         commit_time: int,
     ) -> None:
+        """Initialize CommitGraphEntry.
+
+        Args:
+          commit_id: The commit object ID
+          tree_id: The tree object ID
+          parents: List of parent commit IDs
+          generation: Generation number
+          commit_time: Commit timestamp
+        """
         self.commit_id = commit_id
         self.tree_id = tree_id
         self.parents = parents
@@ -75,6 +84,7 @@ class CommitGraphEntry:
         self.commit_time = commit_time
 
     def __repr__(self) -> str:
+        """Return string representation of CommitGraphEntry."""
         return (
             f"CommitGraphEntry(commit_id={self.commit_id!r}, "
             f"tree_id={self.tree_id!r}, parents={self.parents!r}, "
@@ -86,10 +96,17 @@ class CommitGraphChunk:
     """Represents a chunk in the commit graph file."""
 
     def __init__(self, chunk_id: bytes, data: bytes) -> None:
+        """Initialize CommitGraphChunk.
+
+        Args:
+          chunk_id: Chunk identifier
+          data: Chunk data
+        """
         self.chunk_id = chunk_id
         self.data = data
 
     def __repr__(self) -> str:
+        """Return string representation of CommitGraphChunk."""
         return f"CommitGraphChunk(chunk_id={self.chunk_id!r}, size={len(self.data)})"
 
 
@@ -97,6 +114,11 @@ class CommitGraph:
     """Git commit graph file reader/writer."""
 
     def __init__(self, hash_version: int = HASH_VERSION_SHA1) -> None:
+        """Initialize CommitGraph.
+
+        Args:
+          hash_version: Hash version to use (SHA1 or SHA256)
+        """
         self.hash_version = hash_version
         self.chunks: dict[bytes, CommitGraphChunk] = {}
         self.entries: list[CommitGraphEntry] = []

+ 6 - 0
dulwich/config.py

@@ -664,6 +664,7 @@ class ConfigDict(Config):
         self._values.setdefault(section)[name] = value
 
     def items(self, section: SectionLike) -> Iterator[tuple[Name, Value]]:
+        """Get items in a section."""
         section_bytes, _ = self._check_section_and_name(section, b"")
         section_dict = self._values.get(section_bytes)
         if section_dict is not None:
@@ -671,6 +672,7 @@ class ConfigDict(Config):
         return iter([])
 
     def sections(self) -> Iterator[Section]:
+        """Get all sections."""
         return iter(self._values.keys())
 
 
@@ -1434,6 +1436,7 @@ class StackedConfig(Config):
         return backends
 
     def get(self, section: SectionLike, name: NameLike) -> Value:
+        """Get value from configuration."""
         if not isinstance(section, tuple):
             section = (section,)
         for backend in self.backends:
@@ -1444,6 +1447,7 @@ class StackedConfig(Config):
         raise KeyError(name)
 
     def get_multivar(self, section: SectionLike, name: NameLike) -> Iterator[Value]:
+        """Get multiple values from configuration."""
         if not isinstance(section, tuple):
             section = (section,)
         for backend in self.backends:
@@ -1455,11 +1459,13 @@ class StackedConfig(Config):
     def set(
         self, section: SectionLike, name: NameLike, value: Union[ValueLike, bool]
     ) -> None:
+        """Set value in configuration."""
         if self.writable is None:
             raise NotImplementedError(self.set)
         return self.writable.set(section, name, value)
 
     def sections(self) -> Iterator[Section]:
+        """Get all sections."""
         seen = set()
         for backend in self.backends:
             for section in backend.sections():

+ 5 - 0
dulwich/contrib/diffstat.py

@@ -199,6 +199,11 @@ def diffstat(lines: list[bytes], max_width: int = 80) -> bytes:
 
 
 def main() -> int:
+    """Main entry point for diffstat command line tool.
+
+    Returns:
+      Exit code (0 for success)
+    """
     argv = sys.argv
     # allow diffstat.py to also be used from the command line
     if len(sys.argv) > 1:

+ 20 - 0
dulwich/contrib/requests_vendor.py

@@ -49,6 +49,8 @@ from ..errors import GitProtocolError, NotGitRepository
 
 
 class RequestsHttpGitClient(AbstractHttpGitClient):
+    """HTTP Git client using the requests library."""
+
     def __init__(
         self,
         base_url: str,
@@ -58,6 +60,16 @@ class RequestsHttpGitClient(AbstractHttpGitClient):
         password: Optional[str] = None,
         **kwargs: object,
     ) -> None:
+        """Initialize RequestsHttpGitClient.
+
+        Args:
+          base_url: Base URL of the Git repository
+          dumb: Whether to use dumb HTTP transport
+          config: Git configuration file
+          username: Username for authentication
+          password: Password for authentication
+          **kwargs: Additional keyword arguments
+        """
         self._username = username
         self._password = password
 
@@ -112,6 +124,14 @@ class RequestsHttpGitClient(AbstractHttpGitClient):
 
 
 def get_session(config: Optional["ConfigFile"]) -> Session:
+    """Create a requests session with Git configuration.
+
+    Args:
+      config: Git configuration file
+
+    Returns:
+      Configured requests Session
+    """
     session = Session()
     session.headers.update({"Pragma": "no-cache"})
 

+ 18 - 0
dulwich/contrib/swift.py

@@ -696,6 +696,12 @@ class SwiftPack(Pack):
     """
 
     def __init__(self, *args: object, **kwargs: object) -> None:
+        """Initialize SwiftPack.
+
+        Args:
+          *args: Arguments to pass to parent class
+          **kwargs: Keyword arguments, must include 'scon' (SwiftConnector)
+        """
         self.scon = kwargs["scon"]
         del kwargs["scon"]
         super().__init__(*args, **kwargs)  # type: ignore
@@ -944,6 +950,12 @@ class SwiftInfoRefsContainer(InfoRefsContainer):
     """Manage references in info/refs object."""
 
     def __init__(self, scon: SwiftConnector, store: object) -> None:
+        """Initialize SwiftInfoRefsContainer.
+
+        Args:
+          scon: Swift connector instance
+          store: Object store instance
+        """
         self.scon = scon
         self.filename = "info/refs"
         self.store = store
@@ -1103,6 +1115,12 @@ class SwiftSystemBackend(Backend):
     """Backend for serving Git repositories from Swift."""
 
     def __init__(self, logger: "logging.Logger", conf: ConfigParser) -> None:
+        """Initialize SwiftSystemBackend.
+
+        Args:
+          logger: Logger instance
+          conf: Configuration parser instance
+        """
         self.conf = conf
         self.logger = logger
 

+ 9 - 0
dulwich/credentials.py

@@ -35,6 +35,15 @@ from .config import ConfigDict, SectionLike
 
 
 def match_urls(url: ParseResult, url_prefix: ParseResult) -> bool:
+    """Check if a URL matches a URL prefix.
+
+    Args:
+      url: Parsed URL to check
+      url_prefix: Parsed URL prefix to match against
+
+    Returns:
+      True if url matches the prefix
+    """
     base_match = (
         url.scheme == url_prefix.scheme
         and url.hostname == url_prefix.hostname

+ 16 - 0
dulwich/diff_tree.py

@@ -54,10 +54,26 @@ class TreeChange(namedtuple("TreeChange", ["type", "old", "new"])):
 
     @classmethod
     def add(cls, new: TreeEntry) -> "TreeChange":
+        """Create a TreeChange for an added entry.
+
+        Args:
+          new: New tree entry
+
+        Returns:
+          TreeChange instance
+        """
         return cls(CHANGE_ADD, _NULL_ENTRY, new)
 
     @classmethod
     def delete(cls, old: TreeEntry) -> "TreeChange":
+        """Create a TreeChange for a deleted entry.
+
+        Args:
+          old: Old tree entry
+
+        Returns:
+          TreeChange instance
+        """
         return cls(CHANGE_DELETE, old, _NULL_ENTRY)
 
 

+ 7 - 0
dulwich/dumb.py

@@ -410,6 +410,11 @@ class DumbRemoteHTTPRepo:
         return dict(self._refs)
 
     def get_head(self) -> Ref:
+        """Get the current HEAD reference.
+
+        Returns:
+          HEAD reference name or commit ID
+        """
         head_resp_bytes = self._fetch_url("HEAD")
         head_split = head_resp_bytes.replace(b"\n", b"").split(b" ")
         head_target = head_split[1] if len(head_split) > 1 else head_split[0]
@@ -445,6 +450,8 @@ class DumbRemoteHTTPRepo:
           graph_walker: GraphWalker instance (not used for dumb HTTP)
           determine_wants: Function that returns list of wanted SHAs
           progress: Optional progress callback
+          get_tagged: Whether to get tagged objects
+          depth: Shallow clone depth
           depth: Depth for shallow clones (not supported for dumb HTTP)
 
         Returns:

+ 34 - 0
dulwich/fastexport.py

@@ -177,6 +177,16 @@ class GitFastExporter:
     def emit_commit(
         self, commit: Commit, ref: Ref, base_tree: Optional[ObjectID] = None
     ) -> bytes:
+        """Emit a commit in fast-export format.
+
+        Args:
+          commit: Commit object to export
+          ref: Reference name for the commit
+          base_tree: Base tree for incremental export
+
+        Returns:
+          Marker for the commit
+        """
         cmd, marker = self._export_commit(commit, ref, base_tree)
         self.print_cmd(cmd)
         return marker
@@ -194,6 +204,14 @@ class GitImportProcessor(processor.ImportProcessor):
         verbose: bool = False,
         outf: Optional[BinaryIO] = None,
     ) -> None:
+        """Initialize GitImportProcessor.
+
+        Args:
+          repo: Repository to import into
+          params: Import parameters
+          verbose: Whether to enable verbose output
+          outf: Output file for verbose messages
+        """
         processor.ImportProcessor.__init__(self, params, verbose)
         self.repo = repo
         self.last_commit = ZERO_SHA
@@ -201,11 +219,27 @@ class GitImportProcessor(processor.ImportProcessor):
         self._contents: dict[bytes, tuple[int, bytes]] = {}
 
     def lookup_object(self, objectish: bytes) -> ObjectID:
+        """Look up an object by reference or marker.
+
+        Args:
+          objectish: Object reference or marker
+
+        Returns:
+          Object ID
+        """
         if objectish.startswith(b":"):
             return self.markers[objectish[1:]]
         return objectish
 
     def import_stream(self, stream: BinaryIO) -> dict[bytes, bytes]:
+        """Import from a fast-import stream.
+
+        Args:
+          stream: Stream to import from
+
+        Returns:
+          Dictionary of markers to object IDs
+        """
         p = parser.ImportParser(stream)
         self.process(p.iter_commands)
         return self.markers

+ 6 - 0
dulwich/file.py

@@ -125,6 +125,12 @@ class FileLocked(Exception):
     def __init__(
         self, filename: Union[str, bytes, os.PathLike], lockfilename: Union[str, bytes]
     ) -> None:
+        """Initialize FileLocked.
+
+        Args:
+          filename: Name of the file that is locked
+          lockfilename: Name of the lock file
+        """
         self.filename = filename
         self.lockfilename = lockfilename
         super().__init__(filename, lockfilename)

+ 22 - 0
dulwich/filters.py

@@ -59,6 +59,14 @@ class ProcessFilterDriver:
         required: bool = False,
         cwd: Optional[str] = None,
     ) -> None:
+        """Initialize ProcessFilterDriver.
+
+        Args:
+          clean_cmd: Command to run for clean filter
+          smudge_cmd: Command to run for smudge filter
+          required: Whether the filter is required
+          cwd: Working directory for filter execution
+        """
         self.clean_cmd = clean_cmd
         self.smudge_cmd = smudge_cmd
         self.required = required
@@ -122,6 +130,12 @@ class FilterRegistry:
     def __init__(
         self, config: Optional["StackedConfig"] = None, repo: Optional["Repo"] = None
     ) -> None:
+        """Initialize FilterRegistry.
+
+        Args:
+          config: Git configuration stack
+          repo: Repository instance
+        """
         self.config = config
         self.repo = repo
         self._drivers: dict[str, FilterDriver] = {}
@@ -377,6 +391,14 @@ class FilterBlobNormalizer:
         filter_registry: Optional[FilterRegistry] = None,
         repo: Optional["Repo"] = None,
     ) -> None:
+        """Initialize FilterBlobNormalizer.
+
+        Args:
+          config_stack: Git configuration stack
+          gitattributes: GitAttributes instance
+          filter_registry: Optional filter registry to use
+          repo: Optional repository instance
+        """
         self.config_stack = config_stack
         self.gitattributes = gitattributes
         self.filter_registry = filter_registry or FilterRegistry(config_stack, repo)

+ 12 - 0
dulwich/greenthreads.py

@@ -89,6 +89,18 @@ class GreenThreadsMissingObjectFinder(MissingObjectFinder):
         concurrency: int = 1,
         get_parents: Optional[Callable[[ObjectID], list[ObjectID]]] = None,
     ) -> None:
+        """Initialize GreenThreadsMissingObjectFinder.
+
+        Args:
+          object_store: Object store to search
+          haves: Objects we have
+          wants: Objects we want
+          progress: Optional progress callback
+          get_tagged: Optional function to get tagged objects
+          concurrency: Number of concurrent green threads
+          get_parents: Optional function to get commit parents
+        """
+
         def collect_tree_sha(sha: ObjectID) -> None:
             self.sha_done.add(sha)
             obj = object_store[sha]

+ 11 - 0
dulwich/index.py

@@ -1332,6 +1332,7 @@ def index_entry_from_stat(
     Args:
       stat_val: POSIX stat_result instance
       hex_sha: Hex sha of the object
+      mode: Optional file mode, will be derived from stat if not provided
     """
     if mode is None:
         mode = cleanup_mode(stat_val.st_mode)
@@ -1366,6 +1367,7 @@ if sys.platform == "win32":
         """
 
         def __init__(self, errno: int, msg: str, filename: Optional[str]) -> None:
+            """Initialize WindowsSymlinkPermissionError."""
             super(PermissionError, self).__init__(
                 errno,
                 f"Unable to create symlink; do you have developer mode enabled? {msg}",
@@ -1419,6 +1421,7 @@ def build_file_from_blob(
       target_path: Path to write to
       honor_filemode: An optional flag to honor core.filemode setting in
         config file, default is core.filemode=True, change executable bit
+      tree_encoding: Encoding to use for tree contents
       symlink_fn: Function to use for creating symlinks
     Returns: stat object for the file
     """
@@ -1608,6 +1611,7 @@ def build_index_from_tree(
         config file, default is core.filemode=True, change executable bit
       validate_path_element: Function to validate path elements to check
         out; default just refuses .git and .. directories.
+      symlink_fn: Function to use for creating symlinks
       blob_normalizer: An optional BlobNormalizer to use for converting line
         endings when writing blobs to the working directory.
       tree_encoding: Encoding used for tree paths (default: utf-8)
@@ -1681,6 +1685,7 @@ def blob_from_path_and_mode(
     Args:
       fs_path: Full file system path to file
       mode: File mode
+      tree_encoding: Encoding to use for tree contents
     Returns: A `Blob` object
     """
     assert isinstance(fs_path, bytes)
@@ -1705,6 +1710,7 @@ def blob_from_path_and_stat(
     Args:
       fs_path: Full file system path to file
       st: A stat object
+      tree_encoding: Encoding to use for tree contents
     Returns: A `Blob` object
     """
     return blob_from_path_and_mode(fs_path, st.st_mode, tree_encoding)
@@ -2440,6 +2446,7 @@ def get_unstaged_changes(
     Args:
       index: index to check
       root_path: path in which to find files
+      filter_blob_callback: Optional callback to filter blobs
     Returns: iterator over paths with unstaged changes
     """
     # For each entry in the index check the sha1 & ensure not staged
@@ -2618,6 +2625,7 @@ def iter_fresh_objects(
     """Iterate over versions of objects on disk referenced by index.
 
     Args:
+      paths: Paths to check
       root_path: Root path to access from
       include_deleted: Include deleted entries with sha and
         mode set to None
@@ -2655,9 +2663,11 @@ class locked_index:
     _file: "_GitFile"
 
     def __init__(self, path: Union[bytes, str]) -> None:
+        """Initialize locked_index."""
         self._path = path
 
     def __enter__(self) -> Index:
+        """Enter context manager and lock index."""
         f = GitFile(self._path, "wb")
         assert isinstance(f, _GitFile)  # GitFile in write mode always returns _GitFile
         self._file = f
@@ -2670,6 +2680,7 @@ class locked_index:
         exc_value: Optional[BaseException],
         traceback: Optional[types.TracebackType],
     ) -> None:
+        """Exit context manager and unlock index."""
         if exc_type is not None:
             self._file.abort()
             return

+ 6 - 0
dulwich/lfs.py

@@ -91,10 +91,12 @@ class LFSStore:
     """Stores objects on disk, indexed by SHA256."""
 
     def __init__(self, path: str) -> None:
+        """Initialize LFSStore."""
         self.path = path
 
     @classmethod
     def create(cls, lfs_dir: str) -> "LFSStore":
+        """Create a new LFS store."""
         if not os.path.isdir(lfs_dir):
             os.mkdir(lfs_dir)
         tmp_dir = os.path.join(lfs_dir, "tmp")
@@ -107,6 +109,7 @@ class LFSStore:
 
     @classmethod
     def from_repo(cls, repo: "Repo", create: bool = False) -> "LFSStore":
+        """Create LFS store from repository."""
         lfs_dir = os.path.join(repo.controldir(), "lfs")
         if create:
             return cls.create(lfs_dir)
@@ -114,6 +117,7 @@ class LFSStore:
 
     @classmethod
     def from_controldir(cls, controldir: str, create: bool = False) -> "LFSStore":
+        """Create LFS store from control directory."""
         lfs_dir = os.path.join(controldir, "lfs")
         if create:
             return cls.create(lfs_dir)
@@ -158,6 +162,7 @@ class LFSPointer:
     """Represents an LFS pointer file."""
 
     def __init__(self, oid: str, size: int) -> None:
+        """Initialize LFSPointer."""
         self.oid = oid
         self.size = size
 
@@ -226,6 +231,7 @@ class LFSFilterDriver:
     def __init__(
         self, lfs_store: "LFSStore", config: Optional["Config"] = None
     ) -> None:
+        """Initialize LFSFilterDriver."""
         self.lfs_store = lfs_store
         self.config = config
 

+ 7 - 0
dulwich/lfs_server.py

@@ -246,6 +246,13 @@ class LFSServer(HTTPServer):
         lfs_store: LFSStore,
         log_requests: bool = False,
     ) -> None:
+        """Initialize LFSServer.
+
+        Args:
+          server_address: Tuple of (host, port) to bind to
+          lfs_store: LFS store instance to use
+          log_requests: Whether to log incoming requests
+        """
         super().__init__(server_address, LFSRequestHandler)
         self.lfs_store = lfs_store
         self.log_requests = log_requests

+ 7 - 3
dulwich/line_ending.py

@@ -163,6 +163,7 @@ class LineEndingFilter(FilterDriver):
         smudge_conversion: Optional[Callable[[bytes], bytes]] = None,
         binary_detection: bool = True,
     ):
+        """Initialize LineEndingFilter."""
         self.clean_conversion = clean_conversion
         self.smudge_conversion = smudge_conversion
         self.binary_detection = binary_detection
@@ -335,6 +336,7 @@ class BlobNormalizer(FilterBlobNormalizer):
         core_eol: str = "native",
         autocrlf: bytes = b"false",
     ) -> None:
+        """Initialize FilteringBlobNormalizer."""
         # Set up a filter registry with line ending filters
         filter_registry = FilterRegistry(config_stack)
 
@@ -431,9 +433,7 @@ class BlobNormalizer(FilterBlobNormalizer):
 def normalize_blob(
     blob: Blob, conversion: Callable[[bytes], bytes], binary_detection: bool
 ) -> Blob:
-    """Takes a blob as input returns either the original blob if binary_detection is True and the blob content looks like binary, else return a new blob with converted data.
-
-    """
+    """Normalize blob by applying line ending conversion."""
     # Read the original blob
     data = blob.data
 
@@ -454,6 +454,8 @@ def normalize_blob(
 
 
 class TreeBlobNormalizer(BlobNormalizer):
+    """Blob normalizer that tracks existing files in a tree."""
+
     def __init__(
         self,
         config_stack: "StackedConfig",
@@ -463,6 +465,7 @@ class TreeBlobNormalizer(BlobNormalizer):
         core_eol: str = "native",
         autocrlf: bytes = b"false",
     ) -> None:
+        """Initialize TreeBlobNormalizer."""
         super().__init__(config_stack, git_attributes, core_eol, autocrlf)
         if tree:
             self.existing_paths = {
@@ -472,6 +475,7 @@ class TreeBlobNormalizer(BlobNormalizer):
             self.existing_paths = set()
 
     def checkin_normalize(self, blob: Blob, tree_path: bytes) -> Blob:
+        """Normalize blob for checkin, considering existing tree state."""
         # Existing files should only be normalized on checkin if it was
         # previously normalized on checkout
         if (

+ 18 - 0
dulwich/lru_cache.py

@@ -78,6 +78,12 @@ class LRUCache(Generic[K, V]):
     def __init__(
         self, max_cache: int = 100, after_cleanup_count: Optional[int] = None
     ) -> None:
+        """Initialize LRUCache.
+
+        Args:
+          max_cache: Maximum number of entries to cache
+          after_cleanup_count: Number of entries to keep after cleanup
+        """
         self._cache: dict[K, _LRUNode[K, V]] = {}
         # The "HEAD" of the lru linked list
         self._most_recently_used = None
@@ -86,9 +92,11 @@ class LRUCache(Generic[K, V]):
         self._update_max_cache(max_cache, after_cleanup_count)
 
     def __contains__(self, key: K) -> bool:
+        """Check if key is in cache."""
         return key in self._cache
 
     def __getitem__(self, key: K) -> V:
+        """Get item from cache and mark as recently used."""
         cache = self._cache
         node = cache[key]
         # Inlined from _record_access to decrease the overhead of __getitem__
@@ -122,6 +130,7 @@ class LRUCache(Generic[K, V]):
         return node.value
 
     def __len__(self) -> int:
+        """Return number of items in cache."""
         return len(self._cache)
 
     def _walk_lru(self) -> Iterator[_LRUNode[K, V]]:
@@ -196,6 +205,15 @@ class LRUCache(Generic[K, V]):
         return self._max_cache
 
     def get(self, key: K, default: Optional[V] = None) -> Optional[V]:
+        """Get value from cache with default if not found.
+
+        Args:
+          key: Key to look up
+          default: Default value if key not found
+
+        Returns:
+          Value from cache or default
+        """
         node = self._cache.get(key, None)
         if node is None:
             return default

+ 21 - 0
dulwich/mailmap.py

@@ -26,6 +26,14 @@ from typing import IO, Optional, Union
 
 
 def parse_identity(text: bytes) -> tuple[Optional[bytes], Optional[bytes]]:
+    """Parse an identity string into name and email.
+
+    Args:
+      text: Identity string in format "Name <email>"
+
+    Returns:
+      Tuple of (name, email) where either can be None
+    """
     # TODO(jelmer): Integrate this with dulwich.fastexport.split_email and
     # dulwich.repo.check_user_identity
     (name_str, email_str) = text.rsplit(b"<", 1)
@@ -81,6 +89,11 @@ class Mailmap:
             ]
         ] = None,
     ) -> None:
+        """Initialize Mailmap.
+
+        Args:
+          map: Optional iterator of (canonical_identity, from_identity) tuples
+        """
         self._table: dict[
             tuple[Optional[bytes], Optional[bytes]],
             tuple[Optional[bytes], Optional[bytes]],
@@ -143,5 +156,13 @@ class Mailmap:
 
     @classmethod
     def from_path(cls, path: str) -> "Mailmap":
+        """Create Mailmap from file path.
+
+        Args:
+          path: Path to mailmap file
+
+        Returns:
+          Mailmap instance
+        """
         with open(path, "rb") as f:
             return cls(read_mailmap(f))

+ 6 - 0
dulwich/merge.py

@@ -18,6 +18,12 @@ class MergeConflict(Exception):
     """Raised when a merge conflict occurs."""
 
     def __init__(self, path: bytes, message: str) -> None:
+        """Initialize MergeConflict.
+
+        Args:
+          path: Path to the conflicted file
+          message: Conflict description
+        """
         self.path = path
         super().__init__(f"Merge conflict in {path!r}: {message}")
 

+ 15 - 0
dulwich/object_store.py

@@ -956,6 +956,7 @@ class PackBasedObjectStore(BaseObjectStore, PackedObjectContainer):
 
         Args:
           sha1: sha for the object.
+          include_comp: Whether to include compression metadata.
         """
         if sha1 == ZERO_SHA:
             raise KeyError(sha1)
@@ -998,6 +999,7 @@ class PackBasedObjectStore(BaseObjectStore, PackedObjectContainer):
         Args:
           objects: Iterable over (object, path) tuples, should support
             __len__.
+          progress: Optional progress reporting function.
         Returns: Pack object of the objects written.
         """
         count = len(objects)
@@ -1357,7 +1359,9 @@ class DiskObjectStore(PackBasedObjectStore):
         Args:
           f: Open file object for the pack.
           path: Path to the pack file.
+          num_objects: Number of objects in the pack.
           indexer: A PackIndexer for indexing the pack.
+          progress: Optional progress reporting function.
         """
         entries = []
         for i, entry in enumerate(indexer):
@@ -1432,6 +1436,7 @@ class DiskObjectStore(PackBasedObjectStore):
             requested bytes are read.
           read_some: Read function that returns at least one byte, but may
             not return the number of bytes requested.
+          progress: Optional progress reporting function.
         Returns: A Pack object pointing at the now-completed thin pack in the
             objects/pack directory.
         """
@@ -1771,6 +1776,7 @@ class MemoryObjectStore(BaseObjectStore):
 
         Args:
           objects: Iterable over a list of (object, path) tuples
+          progress: Optional progress reporting function.
         """
         for obj, path in objects:
             self.add_object(obj)
@@ -1812,6 +1818,8 @@ class MemoryObjectStore(BaseObjectStore):
 
         Args:
           count: Number of items to add
+          unpacked_objects: Iterator of UnpackedObject instances
+          progress: Optional progress reporting function.
         """
         if count == 0:
             return
@@ -1845,6 +1853,7 @@ class MemoryObjectStore(BaseObjectStore):
             requested bytes are read.
           read_some: Read function that returns at least one byte, but may
             not return the number of bytes requested.
+          progress: Optional progress reporting function.
         """
         f, commit, abort = self.add_pack()
         try:
@@ -2142,6 +2151,8 @@ class ObjectStoreGraphWalker:
         Args:
           local_heads: Heads to start search with
           get_parents: Function for finding the parents of a SHA1.
+          shallow: Set of shallow commits.
+          update_shallow: Function to update shallow commits.
         """
         self.heads = set(local_heads)
         self.get_parents = get_parents
@@ -2539,9 +2550,11 @@ def _collect_ancestors(
     """Collect all ancestors of heads up to (excluding) those in common.
 
     Args:
+      store: Object store to get commits from
       heads: commits to start from
       common: commits to end at, or empty set to walk repository
         completely
+      shallow: Set of shallow commits
       get_parents: Optional function for getting the parents of a
         commit.
     Returns: a tuple (A, B) where A - all commits reachable
@@ -2587,6 +2600,7 @@ def iter_tree_contents(
     Iteration is depth-first pre-order, as in e.g. os.walk.
 
     Args:
+      store: Object store to get trees from
       tree_id: SHA1 of the tree.
       include_trees: If True, include tree objects in the iteration.
     Returns: Iterator over TreeEntry namedtuples for all the objects in a
@@ -2614,6 +2628,7 @@ def peel_sha(store: ObjectContainer, sha: bytes) -> tuple[ShaFile, ShaFile]:
     """Peel all tags from a SHA.
 
     Args:
+      store: Object store to get objects from
       sha: The object SHA to peel.
     Returns: The fully-peeled SHA1 of a tag object, after peeling all
         intermediate tags; if the original ref does not point to a tag,

+ 8 - 0
dulwich/objects.py

@@ -650,6 +650,7 @@ class ShaFile:
         return self.sha().hexdigest().encode("ascii")
 
     def __repr__(self) -> str:
+        """Return string representation of this object."""
         return f"<{self.__class__.__name__} {self.id!r}>"
 
     def __ne__(self, other: object) -> bool:
@@ -1251,6 +1252,7 @@ class Tree(ShaFile):
     __slots__ = "_entries"
 
     def __init__(self) -> None:
+        """Initialize an empty Tree."""
         super().__init__()
         self._entries: dict[bytes, tuple[int, bytes]] = {}
 
@@ -1273,9 +1275,11 @@ class Tree(ShaFile):
         return tree
 
     def __contains__(self, name: bytes) -> bool:
+        """Check if name exists in tree."""
         return name in self._entries
 
     def __getitem__(self, name: bytes) -> tuple[int, ObjectID]:
+        """Get tree entry by name."""
         return self._entries[name]
 
     def __setitem__(self, name: bytes, value: tuple[int, ObjectID]) -> None:
@@ -1292,13 +1296,16 @@ class Tree(ShaFile):
         self._needs_serialization = True
 
     def __delitem__(self, name: bytes) -> None:
+        """Delete tree entry by name."""
         del self._entries[name]
         self._needs_serialization = True
 
     def __len__(self) -> int:
+        """Return number of entries in tree."""
         return len(self._entries)
 
     def __iter__(self) -> Iterator[bytes]:
+        """Iterate over tree entry names."""
         return iter(self._entries)
 
     def add(self, name: bytes, mode: int, hexsha: bytes) -> None:
@@ -1611,6 +1618,7 @@ class Commit(ShaFile):
     )
 
     def __init__(self) -> None:
+        """Initialize an empty Commit."""
         super().__init__()
         self._parents: list[bytes] = []
         self._encoding: Optional[bytes] = None

+ 15 - 0
dulwich/objectspec.py

@@ -32,6 +32,14 @@ if TYPE_CHECKING:
 
 
 def to_bytes(text: Union[str, bytes]) -> bytes:
+    """Convert text to bytes.
+
+    Args:
+      text: Text to convert (str or bytes)
+
+    Returns:
+      Bytes representation of text
+    """
     if getattr(text, "encode", None) is not None:
         text = text.encode("ascii")  # type: ignore
     return text  # type: ignore
@@ -233,6 +241,7 @@ def parse_reftuple(
       lh_container: A RefsContainer object
       rh_container: A RefsContainer object
       refspec: A string
+      force: Whether to force the operation
     Returns: A tuple with left and right ref
     Raises:
       KeyError: If one of the refs can not be found
@@ -348,6 +357,12 @@ class AmbiguousShortId(Exception):
     """The short id is ambiguous."""
 
     def __init__(self, prefix: bytes, options: list[ShaFile]) -> None:
+        """Initialize AmbiguousShortId.
+
+        Args:
+          prefix: The ambiguous prefix
+          options: List of matching objects
+        """
         self.prefix = prefix
         self.options = options
 

+ 65 - 3
dulwich/pack.py

@@ -2436,6 +2436,7 @@ def write_pack_object(write, type, object, sha=None, compression_level=-1):
       write: Write function to use
       type: Numeric type of the object
       object: Object to write
+      sha: Optional SHA-1 hasher to update
       compression_level: the zlib compression level
     Returns: Tuple with offset at which the object was written, and crc32
     """
@@ -2460,6 +2461,7 @@ def write_pack(
 
     Args:
       filename: Path to the new pack file (without .pack extension)
+      objects: Objects to write to the pack
       delta_window_size: Delta window size
       deltify: Whether to deltify pack objects
       compression_level: the zlib compression level
@@ -2505,6 +2507,17 @@ def find_reusable_deltas(
     other_haves: Optional[set[bytes]] = None,
     progress=None,
 ) -> Iterator[UnpackedObject]:
+    """Find deltas in a pack that can be reused.
+
+    Args:
+      container: Pack container to search for deltas
+      object_ids: Set of object IDs to find deltas for
+      other_haves: Set of other object IDs we have
+      progress: Optional progress reporting callback
+
+    Returns:
+      Iterator of UnpackedObject entries that can be reused
+    """
     if other_haves is None:
         other_haves = set()
     reused = 0
@@ -2535,6 +2548,7 @@ def deltify_pack_objects(
     Args:
       objects: An iterable of (object, path) tuples to deltify.
       window_size: Window size; None for default
+      progress: Optional progress reporting callback
     Returns: Iterator over type_num, object id, delta_base, content
         delta_base is None for full text entries
     """
@@ -2556,6 +2570,14 @@ def deltify_pack_objects(
 def sort_objects_for_delta(
     objects: Union[Iterator[ShaFile], Iterator[tuple[ShaFile, Optional[PackHint]]]],
 ) -> Iterator[ShaFile]:
+    """Sort objects for optimal delta compression.
+
+    Args:
+      objects: Iterator of objects or (object, hint) tuples
+
+    Returns:
+      Iterator of sorted ShaFile objects
+    """
     magic = []
     for entry in objects:
         if isinstance(entry, tuple):
@@ -2577,6 +2599,16 @@ def sort_objects_for_delta(
 def deltas_from_sorted_objects(
     objects, window_size: Optional[int] = None, progress=None
 ):
+    """Create deltas from sorted objects.
+
+    Args:
+      objects: Iterator of sorted objects to deltify
+      window_size: Delta window size; None for default
+      progress: Optional progress reporting callback
+
+    Returns:
+      Iterator of UnpackedObject entries
+    """
     # TODO(jelmer): Use threads
     if window_size is None:
         window_size = DEFAULT_PACK_DELTA_WINDOW_SIZE
@@ -2627,6 +2659,10 @@ def pack_objects_to_data(
 
     Args:
       objects: Pack objects
+      deltify: Whether to deltify pack objects
+      delta_window_size: Delta window size
+      ofs_delta: Whether to use offset deltas
+      progress: Optional progress reporting callback
     Returns: Tuples with (type_num, hexdigest, delta base, object chunks)
     """
     # TODO(jelmer): support deltaifying
@@ -2728,10 +2764,13 @@ def write_pack_from_container(
     Args:
       write: write function to use
       container: PackedObjectContainer
+      object_ids: Sequence of (object_id, hint) tuples to write
       delta_window_size: Sliding window size for searching for deltas;
                          Set to None for default window size.
       deltify: Whether to deltify objects
+      reuse_deltas: Whether to reuse existing deltas
       compression_level: the zlib compression level to use
+      other_haves: Set of additional object IDs the receiver has
     Returns: Dict mapping id -> (offset, crc32 checksum), pack checksum
     """
     pack_contents_count = len(object_ids)
@@ -2835,6 +2874,7 @@ class PackChunkGenerator:
           num_records: Number of records (defaults to len(records) if not specified)
           progress: Function to report progress to
           compression_level: the zlib compression level
+          reuse_compressed: Whether to reuse compressed chunks
         Returns: Dict mapping id -> (offset, crc32 checksum), pack checksum
         """
         # Write the pack
@@ -3260,6 +3300,18 @@ class Pack:
         threads=None,
         big_file_threshold=None,
     ) -> None:
+        """Initialize a Pack object.
+
+        Args:
+          basename: Base path for pack files (without .pack/.idx extension)
+          resolve_ext_ref: Optional function to resolve external references
+          delta_window_size: Size of the delta compression window
+          window_memory: Memory limit for delta compression window
+          delta_cache_size: Size of the delta cache
+          depth: Maximum depth for delta chains
+          threads: Number of threads to use for operations
+          big_file_threshold: Size threshold for big file handling
+        """
         self._basename = basename
         self._data = None
         self._idx = None
@@ -3285,9 +3337,7 @@ class Pack:
 
     @classmethod
     def from_lazy_objects(cls, data_fn, idx_fn):
-        """Create a new pack object from callables to load pack data and index objects.
-
-        """
+        """Create a new pack object from callables to load pack data and index objects."""
         ret = cls("")
         ret._data_load = data_fn
         ret._idx_load = idx_fn
@@ -3329,18 +3379,22 @@ class Pack:
         return self._idx
 
     def close(self) -> None:
+        """Close the pack file and index."""
         if self._data is not None:
             self._data.close()
         if self._idx is not None:
             self._idx.close()
 
     def __enter__(self):
+        """Enter context manager."""
         return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
+        """Exit context manager."""
         self.close()
 
     def __eq__(self, other):
+        """Check equality with another pack."""
         return isinstance(self, type(other)) and self.index == other.index
 
     def __len__(self) -> int:
@@ -3348,6 +3402,7 @@ class Pack:
         return len(self.index)
 
     def __repr__(self) -> str:
+        """Return string representation of this pack."""
         return f"{self.__class__.__name__}({self._basename!r})"
 
     def __iter__(self):
@@ -3383,9 +3438,11 @@ class Pack:
         # TODO: object connectivity checks
 
     def get_stored_checksum(self) -> bytes:
+        """Return the stored checksum of the pack data."""
         return self.data.get_stored_checksum()
 
     def pack_tuples(self):
+        """Return pack tuples for all objects in pack."""
         return [(o, None) for o in self.iterobjects()]
 
     def __contains__(self, sha1: bytes) -> bool:
@@ -3397,6 +3454,7 @@ class Pack:
             return False
 
     def get_raw(self, sha1: bytes) -> tuple[int, bytes]:
+        """Get raw object data by SHA1."""
         offset = self.index.object_offset(sha1)
         obj_type, obj = self.data.get_object_at(offset)
         type_num, chunks = self.resolve_object(offset, obj_type, obj)
@@ -3416,6 +3474,7 @@ class Pack:
     def iterobjects_subset(
         self, shas: Iterable[ObjectID], *, allow_missing: bool = False
     ) -> Iterator[ShaFile]:
+        """Iterate over a subset of objects in this pack."""
         return (
             uo
             for uo in PackInflater.for_pack_subset(
@@ -3435,6 +3494,7 @@ class Pack:
         allow_missing: bool = False,
         convert_ofs_delta: bool = False,
     ) -> Iterator[UnpackedObject]:
+        """Iterate over unpacked objects in subset."""
         ofs_pending: dict[int, list[UnpackedObject]] = defaultdict(list)
         ofs: dict[bytes, int] = {}
         todo = set(shas)
@@ -3464,6 +3524,7 @@ class Pack:
             raise UnresolvedDeltas(list(todo))
 
     def iter_unpacked(self, include_comp=False):
+        """Iterate over all unpacked objects in this pack."""
         ofs_to_entries = {
             ofs: (sha, crc32) for (sha, ofs, crc32) in self.index.iterentries()
         }
@@ -3580,6 +3641,7 @@ class Pack:
         Args:
           sha: SHA of object to fetch
           include_comp: Whether to include compression data in UnpackedObject
+          convert_ofs_delta: Whether to convert offset deltas to ref deltas
         """
         offset = self.index.object_offset(sha)
         unpacked = self.data.get_unpacked_object_at(offset, include_comp=include_comp)

+ 5 - 0
dulwich/patch.py

@@ -58,8 +58,12 @@ def write_commit_patch(
     """Write a individual file patch.
 
     Args:
+      f: File-like object to write to
       commit: Commit object
+      contents: Contents of the patch
       progress: tuple with current patch number and total.
+      version: Version string to include in patch header
+      encoding: Encoding to use for the patch
 
     Returns:
       tuple with filename and contents
@@ -409,6 +413,7 @@ def write_tree_diff(
 
     Args:
       f: File-like object to write to.
+      store: Object store to read from
       old_tree: Old tree id
       new_tree: New tree id
       diff_binary: Whether to diff files even if they

+ 10 - 1
dulwich/porcelain.py

@@ -1182,6 +1182,7 @@ def print_commit(commit, decode, outstream=sys.stdout) -> None:
 
     Args:
       commit: A `Commit` object
+      decode: Function to decode commit data
       outstream: A stream file to write to
     """
     outstream.write("-" * 50 + "\n")
@@ -1653,6 +1654,7 @@ def submodule_update(
       paths: Optional list of specific submodule paths to update. If None, updates all.
       init: If True, initialize submodules first
       force: Force update even if local changes exist
+      errstream: Error stream for error messages
     """
     from .submodule import iter_cached_submodules
 
@@ -1795,6 +1797,7 @@ def tag_create(
       sign: GPG Sign the tag (bool, defaults to False,
         pass True to use default GPG key,
         pass a str containing Key ID to use a specific GPG key)
+      encoding: Encoding to use for tag messages
     """
     with open_repo_closing(repo) as r:
         object = parse_object(r, objectish)
@@ -2187,6 +2190,7 @@ def push(
       outstream: A stream file to write output
       errstream: A stream file to write errors
       force: Force overwriting refs
+      **kwargs: Additional keyword arguments for the client
     """
     # Open the repo
     with open_repo_closing(repo) as r:
@@ -2315,6 +2319,7 @@ def pull(
         feature, and ignored otherwise.
       protocol_version: desired Git protocol version. By default the highest
         mutually supported protocol version will be used
+      **kwargs: Additional keyword arguments for the client
     """
     # Open the repo
     with open_repo_closing(repo) as r:
@@ -3033,6 +3038,8 @@ def fetch(
       depth: Depth to fetch at
       prune: Prune remote removed refs
       prune_tags: Prune reomte removed tags
+      force: Force fetching even if it would overwrite local changes
+      **kwargs: Additional keyword arguments for the client
     Returns:
       Dictionary with refs on the remote
     """
@@ -3122,6 +3129,7 @@ def ls_remote(remote, config: Optional[Config] = None, **kwargs):
     Args:
       remote: Remote repository location
       config: Configuration to use
+      **kwargs: Additional keyword arguments for the client
     Returns:
       LsRemoteResult object with refs and symrefs
     """
@@ -3630,6 +3638,7 @@ def reset_file(
       repo: dulwich Repo object
       file_path: file to reset, relative to the repository path
       target: branch or commit or b'HEAD' to reset
+      symlink_fn: Function to use for creating symlinks
     """
     tree = parse_tree(repo, treeish=target)
     tree_path = _fs_to_tree_path(file_path)
@@ -4276,7 +4285,7 @@ def cherry_pick(
       repo: Repository to cherry-pick into
       committish: Commit to cherry-pick (can be None only when ``continue_`` or abort is True)
       no_commit: If True, do not create a commit after applying changes
-      ``continue_``: Continue an in-progress cherry-pick after resolving conflicts
+      continue_: Continue an in-progress cherry-pick after resolving conflicts
       abort: Abort an in-progress cherry-pick
 
     Returns:

+ 24 - 0
dulwich/protocol.py

@@ -289,6 +289,14 @@ class Protocol:
         close: Optional[Callable[[], None]] = None,
         report_activity: Optional[Callable[[int, str], None]] = None,
     ) -> None:
+        """Initialize Protocol.
+
+        Args:
+          read: Function to read bytes from the transport
+          write: Function to write bytes to the transport
+          close: Optional function to close the transport
+          report_activity: Optional function to report activity
+        """
         self.read = read
         self.write = write
         self._close = close
@@ -301,6 +309,7 @@ class Protocol:
             self._close()
 
     def __enter__(self) -> "Protocol":
+        """Enter context manager."""
         return self
 
     def __exit__(
@@ -309,6 +318,7 @@ class Protocol:
         exc_val: Optional[BaseException],
         exc_tb: Optional[types.TracebackType],
     ) -> None:
+        """Exit context manager and close transport."""
         self.close()
 
     def read_pkt_line(self) -> Optional[bytes]:
@@ -466,6 +476,15 @@ class ReceivableProtocol(Protocol):
         report_activity: Optional[Callable[[int, str], None]] = None,
         rbufsize: int = _RBUFSIZE,
     ) -> None:
+        """Initialize ReceivableProtocol.
+
+        Args:
+          recv: Function to receive bytes from the transport
+          write: Function to write bytes to the transport
+          close: Optional function to close the transport
+          report_activity: Optional function to report activity
+          rbufsize: Read buffer size
+        """
         super().__init__(self.read, write, close=close, report_activity=report_activity)
         self._recv = recv
         self._rbuf = BytesIO()
@@ -662,6 +681,11 @@ class PktLineParser:
     """Packet line parser that hands completed packets off to a callback."""
 
     def __init__(self, handle_pkt: Callable[[Optional[bytes]], None]) -> None:
+        """Initialize PktLineParser.
+
+        Args:
+          handle_pkt: Callback function to handle completed packets
+        """
         self.handle_pkt = handle_pkt
         self._readahead = BytesIO()
 

+ 10 - 0
dulwich/rebase.py

@@ -38,6 +38,11 @@ class RebaseConflict(RebaseError):
     """Raised when a rebase conflict occurs."""
 
     def __init__(self, conflicted_files: list[bytes]):
+        """Initialize RebaseConflict.
+
+        Args:
+          conflicted_files: List of conflicted file paths
+        """
         self.conflicted_files = conflicted_files
         super().__init__(
             f"Conflicts in: {', '.join(f.decode('utf-8', 'replace') for f in conflicted_files)}"
@@ -197,6 +202,11 @@ class MemoryRebaseStateManager:
     """Manages rebase state in memory for MemoryRepo."""
 
     def __init__(self, repo: Repo) -> None:
+        """Initialize MemoryRebaseStateManager.
+
+        Args:
+          repo: Repository instance
+        """
         self.repo = repo
         self._state: Optional[dict] = None
 

+ 14 - 0
dulwich/refs.py

@@ -530,6 +530,7 @@ class DictRefsContainer(RefsContainer):
     """
 
     def __init__(self, refs, logger=None) -> None:
+        """Initialize DictRefsContainer."""
         super().__init__(logger=logger)
         self._refs = refs
         self._peeled: dict[bytes, ObjectID] = {}
@@ -818,6 +819,7 @@ class DiskRefsContainer(RefsContainer):
         worktree_path: Optional[Union[str, bytes, os.PathLike]] = None,
         logger=None,
     ) -> None:
+        """Initialize DiskRefsContainer."""
         super().__init__(logger=logger)
         # Convert path-like objects to strings, then to bytes for Git compatibility
         self.path = os.fsencode(os.fspath(path))
@@ -1046,6 +1048,9 @@ class DiskRefsContainer(RefsContainer):
         Args:
           name: Name of the ref to set
           other: Name of the ref to point at
+          committer: Optional committer name
+          timestamp: Optional timestamp
+          timezone: Optional timezone
           message: Optional message to describe the change
         """
         self._check_refname(name)
@@ -1090,6 +1095,9 @@ class DiskRefsContainer(RefsContainer):
           old_ref: The old sha the refname must refer to, or None to set
             unconditionally.
           new_ref: The new sha the refname will refer to.
+          committer: Optional committer name
+          timestamp: Optional timestamp
+          timezone: Optional timezone
           message: Set message for reflog
         Returns: True if the set was successful, False otherwise.
         """
@@ -1168,6 +1176,9 @@ class DiskRefsContainer(RefsContainer):
         Args:
           name: The refname to set.
           ref: The new sha the refname will refer to.
+          committer: Optional committer name
+          timestamp: Optional timestamp
+          timezone: Optional timezone
           message: Optional message for reflog
         Returns: True if the add was successful, False otherwise.
         """
@@ -1220,6 +1231,9 @@ class DiskRefsContainer(RefsContainer):
           name: The refname to delete.
           old_ref: The old sha the refname must refer to, or None to
             delete unconditionally.
+          committer: Optional committer name
+          timestamp: Optional timestamp
+          timezone: Optional timezone
           message: Optional message
         Returns: True if the delete was successful, False otherwise.
         """

+ 28 - 0
dulwich/reftable.py

@@ -225,6 +225,13 @@ class RefUpdate:
     """A reference update operation."""
 
     def __init__(self, name: bytes, value_type: int, value: bytes):
+        """Initialize RefUpdate.
+
+        Args:
+          name: Reference name
+          value_type: Type of reference value
+          value: Reference value
+        """
         self.name = name
         self.value_type = value_type
         self.value = value
@@ -236,6 +243,14 @@ class RefRecord:
     def __init__(
         self, refname: bytes, value_type: int, value: bytes, update_index: int = 0
     ):
+        """Initialize RefRecord.
+
+        Args:
+          refname: Reference name
+          value_type: Type of reference value
+          value: Reference value
+          update_index: Update index for the reference
+        """
         self.refname = refname
         self.value_type = value_type
         self.value = value
@@ -323,6 +338,7 @@ class RefBlock:
     """A block containing reference records."""
 
     def __init__(self):
+        """Initialize RefBlock."""
         self.refs = []
 
     def add_ref(
@@ -478,6 +494,13 @@ class ReftableWriter:
         auto_create_head: bool = True,
         is_batch_operation: bool = False,
     ):
+        """Initialize ReftableWriter.
+
+        Args:
+          f: Binary file object to write to
+          auto_create_head: Whether to automatically create HEAD reference
+          is_batch_operation: Whether this is a batch operation
+        """
         self.f = f
         self.refs: dict[bytes, tuple[int, bytes]] = {}
         self.refs_order: list[bytes] = []  # Track insertion order for update indices
@@ -662,6 +685,11 @@ class ReftableReader:
     """Reader for reftable files."""
 
     def __init__(self, f: BinaryIO):
+        """Initialize ReftableReader.
+
+        Args:
+          f: Binary file object to read from
+        """
         self.f = f
         self._read_header()
         self.refs: dict[bytes, tuple[int, bytes]] = {}

+ 12 - 1
dulwich/repo.py

@@ -1512,7 +1512,7 @@ class Repo(BaseRepo):
 
     @replace_me(remove_in="0.26.0")
     def unstage(self, fs_paths: list[str]) -> None:
-        """Unstage specific file in the index
+        """Unstage specific file in the index.
 
         Args:
           fs_paths: a list of files to unstage,
@@ -1770,6 +1770,7 @@ class Repo(BaseRepo):
             return None
 
     def __repr__(self) -> str:
+        """Return string representation of this repository."""
         return f"<Repo at {self.path!r}>"
 
     def set_description(self, description) -> None:
@@ -1832,6 +1833,9 @@ class Repo(BaseRepo):
         Args:
           path: Path in which to create the repository
           mkdir: Whether to create the directory
+          config: Configuration object
+          default_branch: Default branch name
+          symlinks: Whether to support symlinks
           format: Repository format version (defaults to 0)
         Returns: `Repo` instance
         """
@@ -1919,6 +1923,10 @@ class Repo(BaseRepo):
 
         Args:
           path: Path to create bare repository in
+          mkdir: Whether to create the directory
+          object_store: Object store to use
+          config: Configuration object
+          default_branch: Default branch name
           format: Repository format version (defaults to 0)
         Returns: a `Repo` instance
         """
@@ -1944,9 +1952,11 @@ class Repo(BaseRepo):
         self.object_store.close()
 
     def __enter__(self):
+        """Enter context manager."""
         return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
+        """Exit context manager and close repository."""
         self.close()
 
     def _read_gitattributes(self) -> dict[bytes, dict[bytes, bytes]]:
@@ -2189,6 +2199,7 @@ class MemoryRepo(BaseRepo):
 
         Args:
           path: The path to the file, relative to the control dir.
+          basedir: Optional base directory for the path
         Returns: An open file object, or None if the file does not exist.
         """
         contents = self._named_files.get(path, None)

+ 17 - 0
dulwich/server.py

@@ -172,6 +172,8 @@ class BackendRepo(TypingProtocol):
         """Yield the objects required for a list of commits.
 
         Args:
+          determine_wants: Function to determine which objects the client wants
+          graph_walker: Object used to walk the commit graph
           progress: is a callback to send progress messages to the client
           get_tagged: Function that returns a dict of pointed-to sha ->
             tag sha for including tags.
@@ -191,6 +193,17 @@ class DictBackend(Backend):
         self.repos = repos
 
     def open_repository(self, path: str) -> BackendRepo:
+        """Open repository at given path.
+
+        Args:
+          path: Path to the repository
+
+        Returns:
+          Repository object
+
+        Raises:
+          NotGitRepository: If no repository found at path
+        """
         logger.debug("Opening repository at %s", path)
         try:
             return self.repos[path]
@@ -672,6 +685,10 @@ class _ProtocolGraphWalker:
 
         If the client has the 'shallow' capability, this method also reads and
         responds to the 'shallow' and 'deepen' lines from the client. These are
+
+        Args:
+          heads: Dictionary of heads to advertise
+          depth: Maximum depth for shallow clones
         not part of the wants per se, but they set up necessary state for
         walking the graph. Additionally, later code depends on this method
         consuming everything up to the first 'have' line.

+ 13 - 0
dulwich/stash.py

@@ -68,6 +68,12 @@ class Stash:
     """
 
     def __init__(self, repo: "Repo", ref: Ref = DEFAULT_STASH_REF) -> None:
+        """Initialize Stash.
+
+        Args:
+          repo: Repository object
+          ref: Stash reference name
+        """
         self._ref = ref
         self._repo = repo
 
@@ -76,6 +82,11 @@ class Stash:
         return os.path.join(self._repo.commondir(), "logs", os.fsdecode(self._ref))
 
     def stashes(self) -> list["Entry"]:
+        """Get list of stash entries.
+
+        Returns:
+          List of stash entries in chronological order
+        """
         try:
             with GitFile(self._reflog_path, "rb") as f:
                 return list(reversed(list(read_reflog(f))))
@@ -330,7 +341,9 @@ class Stash:
         return cid
 
     def __getitem__(self, index: int) -> "Entry":
+        """Get stash entry by index."""
         return list(self.stashes())[index]
 
     def __len__(self) -> int:
+        """Return number of stash entries."""
         return len(list(self.stashes()))

+ 41 - 3
dulwich/tests/test_object_store.py

@@ -50,6 +50,8 @@ testobject = make_object(Blob, data=b"yummy data")
 
 
 class ObjectStoreTests:
+    """Base class for testing object store implementations."""
+
     store: "BaseObjectStore"
 
     assertEqual: Callable[[object, object], None]
@@ -62,17 +64,20 @@ class ObjectStoreTests:
     assertFalse: Callable[[bool], None]
 
     def test_determine_wants_all(self) -> None:
+        """Test determine_wants_all with valid ref."""
         self.assertEqual(
             [b"1" * 40],
             self.store.determine_wants_all({b"refs/heads/foo": b"1" * 40}),
         )
 
     def test_determine_wants_all_zero(self) -> None:
+        """Test determine_wants_all with zero ref."""
         self.assertEqual(
             [], self.store.determine_wants_all({b"refs/heads/foo": b"0" * 40})
         )
 
     def test_determine_wants_all_depth(self) -> None:
+        """Test determine_wants_all with depth parameter."""
         self.store.add_object(testobject)
         refs = {b"refs/heads/foo": testobject.id}
         with patch.object(self.store, "_get_depth", return_value=1) as m:
@@ -90,6 +95,7 @@ class ObjectStoreTests:
             )
 
     def test_get_depth(self) -> None:
+        """Test getting object depth."""
         self.assertEqual(0, self.store._get_depth(testobject.id))
 
         self.store.add_object(testobject)
@@ -108,26 +114,29 @@ class ObjectStoreTests:
         )
 
     def test_iter(self) -> None:
+        """Test iterating over empty store."""
         self.assertEqual([], list(self.store))
 
     def test_get_nonexistant(self) -> None:
+        """Test getting non-existent object raises KeyError."""
         self.assertRaises(KeyError, lambda: self.store[b"a" * 40])
 
     def test_contains_nonexistant(self) -> None:
+        """Test checking for non-existent object."""
         self.assertNotIn(b"a" * 40, self.store)
 
     def test_add_objects_empty(self) -> None:
+        """Test adding empty list of objects."""
         self.store.add_objects([])
 
     def test_add_commit(self) -> None:
+        """Test adding commit objects."""
         # TODO: Argh, no way to construct Git commit objects without
         # access to a serialized form.
         self.store.add_objects([])
 
     def test_store_resilience(self) -> None:
-        """Test if updating an existing stored object doesn't erase the object from the store.
-
-        """
+        """Test if updating an existing stored object doesn't erase the object from the store."""
         test_object = make_object(Blob, data=b"data")
 
         self.store.add_object(test_object)
@@ -139,6 +148,7 @@ class ObjectStoreTests:
         self.assertEqual(stored_test_object.id, test_object_id)
 
     def test_add_object(self) -> None:
+        """Test adding a single object to store."""
         self.store.add_object(testobject)
         self.assertEqual({testobject.id}, set(self.store))
         self.assertIn(testobject.id, self.store)
@@ -146,6 +156,7 @@ class ObjectStoreTests:
         self.assertEqual(r, testobject)
 
     def test_add_objects(self) -> None:
+        """Test adding multiple objects to store."""
         data = [(testobject, "mypath")]
         self.store.add_objects(data)
         self.assertEqual({testobject.id}, set(self.store))
@@ -154,6 +165,7 @@ class ObjectStoreTests:
         self.assertEqual(r, testobject)
 
     def test_tree_changes(self) -> None:
+        """Test detecting changes between trees."""
         blob_a1 = make_object(Blob, data=b"a1")
         blob_a2 = make_object(Blob, data=b"a2")
         blob_b = make_object(Blob, data=b"b")
@@ -179,6 +191,7 @@ class ObjectStoreTests:
         )
 
     def test_iter_tree_contents(self) -> None:
+        """Test iterating over tree contents."""
         blob_a = make_object(Blob, data=b"a")
         blob_b = make_object(Blob, data=b"b")
         blob_c = make_object(Blob, data=b"c")
@@ -200,6 +213,7 @@ class ObjectStoreTests:
         self.assertEqual([], list(iter_tree_contents(self.store, None)))
 
     def test_iter_tree_contents_include_trees(self) -> None:
+        """Test iterating tree contents including tree objects."""
         blob_a = make_object(Blob, data=b"a")
         blob_b = make_object(Blob, data=b"b")
         blob_c = make_object(Blob, data=b"c")
@@ -230,11 +244,13 @@ class ObjectStoreTests:
         self.assertEqual(expected, list(actual))
 
     def make_tag(self, name, obj):
+        """Helper to create and add a tag object."""
         tag = make_tag(obj, name=name)
         self.store.add_object(tag)
         return tag
 
     def test_peel_sha(self) -> None:
+        """Test peeling SHA to get underlying object."""
         self.store.add_object(testobject)
         tag1 = self.make_tag(b"1", testobject)
         tag2 = self.make_tag(b"2", testobject)
@@ -243,17 +259,20 @@ class ObjectStoreTests:
             self.assertEqual((obj, testobject), peel_sha(self.store, obj.id))
 
     def test_get_raw(self) -> None:
+        """Test getting raw object data."""
         self.store.add_object(testobject)
         self.assertEqual(
             (Blob.type_num, b"yummy data"), self.store.get_raw(testobject.id)
         )
 
     def test_close(self) -> None:
+        """Test closing the object store."""
         # For now, just check that close doesn't barf.
         self.store.add_object(testobject)
         self.store.close()
 
     def test_iter_prefix(self) -> None:
+        """Test iterating objects by prefix."""
         self.store.add_object(testobject)
         self.assertEqual([testobject.id], list(self.store.iter_prefix(testobject.id)))
         self.assertEqual(
@@ -296,20 +315,26 @@ class ObjectStoreTests:
         self.assertEqual(blob1.id, objects[0].id)
 
     def test_iter_prefix_not_found(self) -> None:
+        """Test iterating with prefix that doesn't match any objects."""
         self.assertEqual([], list(self.store.iter_prefix(b"1" * 40)))
 
 
 class PackBasedObjectStoreTests(ObjectStoreTests):
+    """Tests for pack-based object stores."""
+
     store: PackBasedObjectStore
 
     def tearDown(self) -> None:
+        """Clean up by closing all packs."""
         for pack in self.store.packs:
             pack.close()
 
     def test_empty_packs(self) -> None:
+        """Test that new store has no packs."""
         self.assertEqual([], list(self.store.packs))
 
     def test_pack_loose_objects(self) -> None:
+        """Test packing loose objects into packs."""
         b1 = make_object(Blob, data=b"yummy data")
         self.store.add_object(b1)
         b2 = make_object(Blob, data=b"more yummy data")
@@ -324,6 +349,7 @@ class PackBasedObjectStoreTests(ObjectStoreTests):
         self.assertEqual(0, self.store.pack_loose_objects())
 
     def test_repack(self) -> None:
+        """Test repacking multiple packs into one."""
         b1 = make_object(Blob, data=b"yummy data")
         self.store.add_object(b1)
         b2 = make_object(Blob, data=b"more yummy data")
@@ -341,6 +367,7 @@ class PackBasedObjectStoreTests(ObjectStoreTests):
         self.assertEqual(0, self.store.pack_loose_objects())
 
     def test_repack_existing(self) -> None:
+        """Test repacking with existing objects."""
         b1 = make_object(Blob, data=b"yummy data")
         self.store.add_object(b1)
         b2 = make_object(Blob, data=b"more yummy data")
@@ -401,16 +428,21 @@ class PackBasedObjectStoreTests(ObjectStoreTests):
 
 
 class FindShallowTests(TestCase):
+    """Tests for finding shallow commits."""
+
     def setUp(self):
+        """Set up test fixture."""
         super().setUp()
         self._store = MemoryObjectStore()
 
     def make_commit(self, **attrs):
+        """Helper to create and store a commit."""
         commit = make_commit(**attrs)
         self._store.add_object(commit)
         return commit
 
     def make_linear_commits(self, n, message=b""):
+        """Create a linear chain of commits."""
         commits = []
         parents = []
         for _ in range(n):
@@ -419,9 +451,11 @@ class FindShallowTests(TestCase):
         return commits
 
     def assertSameElements(self, expected, actual):
+        """Assert that two sequences contain the same elements."""
         self.assertEqual(set(expected), set(actual))
 
     def test_linear(self):
+        """Test finding shallow commits in a linear history."""
         c1, c2, c3 = self.make_linear_commits(3)
 
         self.assertEqual((set([c3.id]), set([])), find_shallow(self._store, [c3.id], 1))
@@ -439,6 +473,7 @@ class FindShallowTests(TestCase):
         )
 
     def test_multiple_independent(self):
+        """Test finding shallow commits with multiple independent branches."""
         a = self.make_linear_commits(2, message=b"a")
         b = self.make_linear_commits(2, message=b"b")
         c = self.make_linear_commits(2, message=b"c")
@@ -450,6 +485,7 @@ class FindShallowTests(TestCase):
         )
 
     def test_multiple_overlapping(self):
+        """Test finding shallow commits with overlapping branches."""
         # Create the following commit tree:
         # 1--2
         #  \
@@ -465,6 +501,7 @@ class FindShallowTests(TestCase):
         )
 
     def test_merge(self):
+        """Test finding shallow commits with merge commits."""
         c1 = self.make_commit()
         c2 = self.make_commit()
         c3 = self.make_commit(parents=[c1.id, c2.id])
@@ -475,6 +512,7 @@ class FindShallowTests(TestCase):
         )
 
     def test_tag(self):
+        """Test finding shallow commits with tags."""
         c1, c2 = self.make_linear_commits(2)
         tag = make_tag(c2, name=b"tag")
         self._store.add_object(tag)

+ 1 - 0
dulwich/tests/utils.py

@@ -87,6 +87,7 @@ def make_object(cls, **attrs):
     __slots__.
 
     Args:
+      cls: The class to create an instance of
       attrs: dict of attributes to set on the new object.
     Returns: A newly initialized object of type cls.
     """

+ 8 - 0
dulwich/walk.py

@@ -53,6 +53,12 @@ class WalkEntry:
     """Object encapsulating a single result from a walk."""
 
     def __init__(self, walker: "Walker", commit: Commit) -> None:
+        """Initialize WalkEntry.
+
+        Args:
+          walker: Walker instance that created this entry
+          commit: Commit object for this entry
+        """
         self.commit = commit
         self._store = walker.store
         self._get_parents = walker.get_parents
@@ -141,6 +147,7 @@ class WalkEntry:
         return self._changes[path_prefix]
 
     def __repr__(self) -> str:
+        """Return string representation of WalkEntry."""
         return f"<WalkEntry commit={self.commit.id.decode('ascii')}, changes={self.changes()!r}>"
 
 
@@ -435,6 +442,7 @@ class Walker:
         return results
 
     def __iter__(self) -> Iterator[WalkEntry]:
+        """Iterate over walk entries."""
         return iter(self._reorder(iter(self._next, None)))
 
 

+ 8 - 9
dulwich/web.py

@@ -574,6 +574,7 @@ class HTTPGitApplication:
             self.handlers.update(handlers)
 
     def __call__(self, environ, start_response):
+        """Handle WSGI request."""
         path = environ["PATH_INFO"]
         method = environ["REQUEST_METHOD"]
         req = HTTPGitRequest(
@@ -599,14 +600,14 @@ class HTTPGitApplication:
 
 
 class GunzipFilter:
-    """WSGI middleware that unzips gzip-encoded requests before passing on to the underlying application.
-
-    """
+    """WSGI middleware that unzips gzip-encoded requests before passing on to the underlying application."""
 
     def __init__(self, application) -> None:
+        """Initialize GunzipFilter."""
         self.app = application
 
     def __call__(self, environ, start_response):
+        """Handle WSGI request."""
         import gzip
 
         if environ.get("HTTP_CONTENT_ENCODING", "") == "gzip":
@@ -621,14 +622,14 @@ class GunzipFilter:
 
 
 class LimitedInputFilter:
-    """WSGI middleware that limits the input length of a request to that specified in Content-Length.
-
-    """
+    """WSGI middleware that limits the input length of a request to that specified in Content-Length."""
 
     def __init__(self, application) -> None:
+        """Initialize LimitedInputFilter."""
         self.app = application
 
     def __call__(self, environ, start_response):
+        """Handle WSGI request."""
         # This is not necessary if this app is run from a conforming WSGI
         # server. Unfortunately, there's no way to tell that at this point.
         # TODO: git may used HTTP/1.1 chunked encoding instead of specifying
@@ -642,9 +643,7 @@ class LimitedInputFilter:
 
 
 def make_wsgi_chain(*args, **kwargs):
-    """Factory function to create an instance of HTTPGitApplication, correctly wrapped with needed middleware.
-
-    """
+    """Factory function to create an instance of HTTPGitApplication, correctly wrapped with needed middleware."""
     app = HTTPGitApplication(*args, **kwargs)
     wrapped_app = LimitedInputFilter(GunzipFilter(app))
     return wrapped_app

+ 15 - 1
dulwich/worktree.py

@@ -72,6 +72,18 @@ class WorkTreeInfo:
         prunable: bool = False,
         lock_reason: str | None = None,
     ):
+        """Initialize WorkTreeInfo.
+
+        Args:
+          path: Path to the worktree
+          head: Current HEAD commit SHA
+          branch: Current branch (if not detached)
+          bare: Whether this is a bare repository
+          detached: Whether HEAD is detached
+          locked: Whether the worktree is locked
+          prunable: Whether the worktree can be pruned
+          lock_reason: Reason for locking (if locked)
+        """
         self.path = path
         self.head = head
         self.branch = branch
@@ -82,9 +94,11 @@ class WorkTreeInfo:
         self.lock_reason = lock_reason
 
     def __repr__(self) -> str:
+        """Return string representation of WorkTreeInfo."""
         return f"WorkTreeInfo(path={self.path!r}, branch={self.branch!r}, detached={self.detached})"
 
     def __eq__(self, other: object) -> bool:
+        """Check equality with another WorkTreeInfo."""
         if not isinstance(other, WorkTreeInfo):
             return NotImplemented
         return (
@@ -311,7 +325,7 @@ class WorkTree:
         index.write()
 
     def unstage(self, fs_paths: list[str]) -> None:
-        """Unstage specific file in the index
+        """Unstage specific file in the index.
 
         Args:
           fs_paths: a list of files to unstage,