Jelmer Vernooij 1 неделя назад
Родитель
Сommit
93b2be96d6

+ 1 - 1
docs/tutorial/remote.txt

@@ -6,7 +6,7 @@ Most of the tests in this file require a Dulwich server, so let's start one:
     >>> from dulwich.server import DictBackend, TCPGitServer
     >>> import threading
     >>> repo = Repo.init("remote", mkdir=True)
-    >>> cid = repo.do_commit(b"message", committer=b"Jelmer <jelmer@samba.org>")
+    >>> cid = repo.get_worktree().commit(b"message", committer=b"Jelmer <jelmer@samba.org>")
     >>> backend = DictBackend({b'/': repo})
     >>> dul_server = TCPGitServer(backend, b'localhost', 0)
     >>> server_thread = threading.Thread(target=dul_server.serve)

+ 4 - 4
docs/tutorial/repo.txt

@@ -71,7 +71,7 @@ aren't tracked explicitly by git. Let's create a simple text file and stage it::
     >>> _ = f.write(b"monty")
     >>> f.close()
 
-    >>> repo.stage([b"foo"])
+    >>> repo.get_worktree().stage([b"foo"])
 
 It will now show up in the index::
 
@@ -83,7 +83,7 @@ Creating new commits
 --------------------
 
 Now that we have staged a change, we can commit it. The easiest way to
-do this is by using ``Repo.do_commit``. It is also possible to manipulate
+do this is by using ``WorkTree.commit``. It is also possible to manipulate
 the lower-level objects involved in this, but we'll leave that for a
 separate chapter of the tutorial.
 
@@ -91,10 +91,10 @@ To create a simple commit on the current branch, it is only necessary
 to specify the message. The committer and author will be retrieved from the
 repository configuration or global configuration if they are not specified::
 
-    >>> commit_id = repo.do_commit(
+    >>> commit_id = repo.get_worktree().commit(
     ...     b"The first commit", committer=b"Jelmer Vernooij <jelmer@samba.org>")
 
-``do_commit`` returns the SHA1 of the commit. Since the commit was to the 
+``commit`` returns the SHA1 of the commit. Since the commit was to the 
 default branch, the repository's head will now be set to that commit::
 
     >>> repo.head() == commit_id

+ 1 - 1
dulwich/client.py

@@ -901,7 +901,7 @@ class GitClient:
                     head = None
 
             if checkout and head is not None:
-                target.reset_index()
+                target.get_worktree().reset_index()
         except BaseException:
             if target is not None:
                 target.close()

+ 70 - 83
dulwich/porcelain.py

@@ -133,7 +133,6 @@ from .objects import (
     Tag,
     Tree,
     format_timezone,
-    hex_to_filename,
     parse_timezone,
     pretty_format_tree_entry,
 )
@@ -180,6 +179,9 @@ GitStatus = namedtuple("GitStatus", "staged unstaged untracked")
 # TypeVar for preserving BaseRepo subclass types
 T = TypeVar("T", bound="BaseRepo")
 
+# Type alias for common repository parameter pattern
+RepoPath = Union[str, os.PathLike, Repo]
+
 
 @dataclass
 class CountObjectsResult:
@@ -454,7 +456,7 @@ def archive(
             outstream.write(chunk)
 
 
-def update_server_info(repo: Union[str, os.PathLike, BaseRepo] = ".") -> None:
+def update_server_info(repo: RepoPath = ".") -> None:
     """Update server info files for a repository.
 
     Args:
@@ -464,9 +466,7 @@ def update_server_info(repo: Union[str, os.PathLike, BaseRepo] = ".") -> None:
         server_update_server_info(r)
 
 
-def write_commit_graph(
-    repo: Union[str, os.PathLike, BaseRepo] = ".", reachable=True
-) -> None:
+def write_commit_graph(repo: RepoPath = ".", reachable=True) -> None:
     """Write a commit graph file for a repository.
 
     Args:
@@ -481,9 +481,7 @@ def write_commit_graph(
             r.object_store.write_commit_graph(refs, reachable=reachable)
 
 
-def symbolic_ref(
-    repo: Union[str, os.PathLike, BaseRepo], ref_name, force=False
-) -> None:
+def symbolic_ref(repo: RepoPath, ref_name, force=False) -> None:
     """Set git symbolic ref into HEAD.
 
     Args:
@@ -498,7 +496,7 @@ def symbolic_ref(
         repo_obj.refs.set_symbolic_ref(b"HEAD", ref_path)
 
 
-def pack_refs(repo: Union[str, os.PathLike, BaseRepo], all=False) -> None:
+def pack_refs(repo: RepoPath, all=False) -> None:
     with open_repo_closing(repo) as repo_obj:
         repo_obj.refs.pack_refs(all=all)
 
@@ -609,7 +607,7 @@ def commit(
 
 
 def commit_tree(
-    repo: Union[str, os.PathLike, BaseRepo],
+    repo: RepoPath,
     tree,
     message=None,
     author=None,
@@ -927,9 +925,7 @@ def clean(repo: Union[str, os.PathLike, Repo] = ".", target_dir=None) -> None:
                     os.remove(ap)
 
 
-def remove(
-    repo: Union[str, os.PathLike, Repo] = ".", paths=None, cached=False
-) -> None:
+def remove(repo: Union[str, os.PathLike, Repo] = ".", paths=None, cached=False) -> None:
     """Remove files from the staging area.
 
     Args:
@@ -1160,9 +1156,7 @@ def print_tag(tag, decode, outstream=sys.stdout) -> None:
     outstream.write("\n")
 
 
-def show_blob(
-    repo: Union[str, os.PathLike, BaseRepo], blob, decode, outstream=sys.stdout
-) -> None:
+def show_blob(repo: RepoPath, blob, decode, outstream=sys.stdout) -> None:
     """Write a blob to a stream.
 
     Args:
@@ -1174,9 +1168,7 @@ def show_blob(
     outstream.write(decode(blob.data))
 
 
-def show_commit(
-    repo: Union[str, os.PathLike, BaseRepo], commit, decode, outstream=sys.stdout
-) -> None:
+def show_commit(repo: RepoPath, commit, decode, outstream=sys.stdout) -> None:
     """Show a commit to a stream.
 
     Args:
@@ -1198,9 +1190,7 @@ def show_commit(
     outstream.write(commit_decode(commit, diffstream.getvalue()))
 
 
-def show_tree(
-    repo: Union[str, os.PathLike, BaseRepo], tree, decode, outstream=sys.stdout
-) -> None:
+def show_tree(repo: RepoPath, tree, decode, outstream=sys.stdout) -> None:
     """Print a tree to a stream.
 
     Args:
@@ -1213,9 +1203,7 @@ def show_tree(
         outstream.write(decode(n) + "\n")
 
 
-def show_tag(
-    repo: Union[str, os.PathLike, BaseRepo], tag, decode, outstream=sys.stdout
-) -> None:
+def show_tag(repo: RepoPath, tag, decode, outstream=sys.stdout) -> None:
     """Print a tag to a stream.
 
     Args:
@@ -1229,7 +1217,7 @@ def show_tag(
         show_object(repo, r[tag.object[1]], decode, outstream)
 
 
-def show_object(repo: Union[str, os.PathLike, BaseRepo], obj, decode, outstream):
+def show_object(repo: RepoPath, obj, decode, outstream):
     return {
         b"tree": show_tree,
         b"blob": show_blob,
@@ -1342,7 +1330,7 @@ def show(
 
 
 def diff_tree(
-    repo: Union[str, os.PathLike, BaseRepo],
+    repo: RepoPath,
     old_tree,
     new_tree,
     outstream=default_bytes_out_stream,
@@ -1455,9 +1443,7 @@ def diff(
             diff_module.diff_working_tree_to_index(r, outstream, paths)
 
 
-def rev_list(
-    repo: Union[str, os.PathLike, BaseRepo], commits, outstream=sys.stdout
-) -> None:
+def rev_list(repo: RepoPath, commits, outstream=sys.stdout) -> None:
     """Lists commit objects in reverse chronological order.
 
     Args:
@@ -1521,7 +1507,7 @@ def submodule_init(repo: Union[str, os.PathLike, Repo]) -> None:
         config.write_to_path()
 
 
-def submodule_list(repo: Union[str, os.PathLike, BaseRepo]):
+def submodule_list(repo: RepoPath):
     """List submodules.
 
     Args:
@@ -1754,7 +1740,7 @@ def tag_create(
         r.refs[_make_tag_ref(tag)] = tag_id
 
 
-def tag_list(repo: Union[str, os.PathLike, BaseRepo], outstream=sys.stdout):
+def tag_list(repo: RepoPath, outstream=sys.stdout):
     """List all tags.
 
     Args:
@@ -1766,7 +1752,7 @@ def tag_list(repo: Union[str, os.PathLike, BaseRepo], outstream=sys.stdout):
         return tags
 
 
-def tag_delete(repo: Union[str, os.PathLike, BaseRepo], name) -> None:
+def tag_delete(repo: RepoPath, name) -> None:
     """Remove a tag.
 
     Args:
@@ -1869,7 +1855,7 @@ def notes_remove(
         )
 
 
-def notes_show(repo: Union[str, os.PathLike, BaseRepo], object_sha, ref=b"commits"):
+def notes_show(repo: Union[str, os.PathLike, Repo], object_sha, ref=b"commits"):
     """Show the note for an object.
 
     Args:
@@ -1894,7 +1880,7 @@ def notes_show(repo: Union[str, os.PathLike, BaseRepo], object_sha, ref=b"commit
         return r.notes.get_note(object_sha, notes_ref, config=config)
 
 
-def notes_list(repo: Union[str, os.PathLike, BaseRepo], ref=b"commits"):
+def notes_list(repo: RepoPath, ref=b"commits"):
     """List all notes in a notes ref.
 
     Args:
@@ -2507,7 +2493,7 @@ def get_untracked_paths(
     yield from ignored_dirs
 
 
-def get_tree_changes(repo: Union[str, os.PathLike, BaseRepo]):
+def get_tree_changes(repo: RepoPath):
     """Return add/delete/modify changes to tree by comparing index to HEAD.
 
     Args:
@@ -2650,7 +2636,7 @@ def _make_tag_ref(name: Union[str, bytes]) -> Ref:
     return LOCAL_TAG_PREFIX + name
 
 
-def branch_delete(repo: Union[str, os.PathLike, BaseRepo], name) -> None:
+def branch_delete(repo: RepoPath, name) -> None:
     """Delete a branch.
 
     Args:
@@ -2667,7 +2653,7 @@ def branch_delete(repo: Union[str, os.PathLike, BaseRepo], name) -> None:
 
 
 def branch_create(
-    repo: Union[str, os.PathLike, BaseRepo], name, objectish=None, force=False
+    repo: Union[str, os.PathLike, Repo], name, objectish=None, force=False
 ) -> None:
     """Create a branch.
 
@@ -2754,12 +2740,16 @@ def branch_create(
                     branch_name_bytes = (
                         name.encode(DEFAULT_ENCODING) if isinstance(name, str) else name
                     )
-                    repo_config.set((b"branch", branch_name_bytes), b"remote", remote_name)
-                    repo_config.set((b"branch", branch_name_bytes), b"merge", remote_branch)
+                    repo_config.set(
+                        (b"branch", branch_name_bytes), b"remote", remote_name
+                    )
+                    repo_config.set(
+                        (b"branch", branch_name_bytes), b"merge", remote_branch
+                    )
                     repo_config.write_to_path()
 
 
-def branch_list(repo: Union[str, os.PathLike, BaseRepo]):
+def branch_list(repo: RepoPath):
     """List all branches.
 
     Args:
@@ -2815,7 +2805,7 @@ def branch_list(repo: Union[str, os.PathLike, BaseRepo]):
         return branches
 
 
-def active_branch(repo: Union[str, os.PathLike, BaseRepo]):
+def active_branch(repo: RepoPath):
     """Return the active branch in the repository, if any.
 
     Args:
@@ -2853,7 +2843,7 @@ def get_branch_remote(repo: Union[str, os.PathLike, Repo]):
     return remote_name
 
 
-def get_branch_merge(repo: Union[str, os.PathLike, BaseRepo], branch_name=None):
+def get_branch_merge(repo: RepoPath, branch_name=None):
     """Return the branch's merge reference (upstream branch), if any.
 
     Args:
@@ -3012,7 +3002,7 @@ def ls_remote(remote, config: Optional[Config] = None, **kwargs):
     return client.get_refs(host_path)
 
 
-def repack(repo: Union[str, os.PathLike, BaseRepo]) -> None:
+def repack(repo: RepoPath) -> None:
     """Repack loose files in a repository.
 
     Currently this only packs loose objects.
@@ -3095,7 +3085,7 @@ def ls_tree(
 
 
 def remote_add(
-    repo: Union[str, os.PathLike, BaseRepo],
+    repo: RepoPath,
     name: Union[bytes, str],
     url: Union[bytes, str],
 ) -> None:
@@ -3172,9 +3162,7 @@ def _quote_path(path: str) -> str:
     return quoted
 
 
-def check_ignore(
-    repo: Union[str, os.PathLike, BaseRepo], paths, no_index=False, quote_path=True
-):
+def check_ignore(repo: RepoPath, paths, no_index=False, quote_path=True):
     r"""Debug gitignore files.
 
     Args:
@@ -3226,9 +3214,7 @@ def check_ignore(
                 yield _quote_path(output_path) if quote_path else output_path
 
 
-def update_head(
-    repo: Union[str, os.PathLike, BaseRepo], target, detached=False, new_branch=None
-) -> None:
+def update_head(repo: RepoPath, target, detached=False, new_branch=None) -> None:
     """Update HEAD to point at a new branch/commit.
 
     Note that this does not actually update the working tree.
@@ -3581,16 +3567,16 @@ def sparse_checkout(
     with open_repo_closing(repo) as repo_obj:
         # --- 0) Possibly infer 'cone' from config ---
         if cone is None:
-            cone = repo_obj.infer_cone_mode()
+            cone = repo_obj.get_worktree().infer_cone_mode()
 
         # --- 1) Read or write patterns ---
         if patterns is None:
-            lines = repo_obj.get_sparse_checkout_patterns()
+            lines = repo_obj.get_worktree().get_sparse_checkout_patterns()
             if lines is None:
                 raise Error("No sparse checkout patterns found.")
         else:
             lines = patterns
-            repo_obj.set_sparse_checkout_patterns(patterns)
+            repo_obj.get_worktree().set_sparse_checkout_patterns(patterns)
 
         # --- 2) Determine the set of included paths ---
         index = repo_obj.open_index()
@@ -3621,7 +3607,7 @@ def cone_mode_init(repo: Union[str, os.PathLike, Repo]):
       None
     """
     with open_repo_closing(repo) as repo_obj:
-        repo_obj.configure_for_cone_mode()
+        repo_obj.get_worktree().configure_for_cone_mode()
         patterns = ["/*", "!/*/"]  # root-level files only
         sparse_checkout(repo_obj, patterns, force=True, cone=True)
 
@@ -3642,9 +3628,9 @@ def cone_mode_set(repo: Union[str, os.PathLike, Repo], dirs, force=False):
       None
     """
     with open_repo_closing(repo) as repo_obj:
-        repo_obj.configure_for_cone_mode()
-        repo_obj.set_cone_mode_patterns(dirs=dirs)
-        new_patterns = repo_obj.get_sparse_checkout_patterns()
+        repo_obj.get_worktree().configure_for_cone_mode()
+        repo_obj.get_worktree().set_cone_mode_patterns(dirs=dirs)
+        new_patterns = repo_obj.get_worktree().get_sparse_checkout_patterns()
         # Finally, apply the patterns and update the working tree
         sparse_checkout(repo_obj, new_patterns, force=force, cone=True)
 
@@ -3665,21 +3651,21 @@ def cone_mode_add(repo: Union[str, os.PathLike, Repo], dirs, force=False):
       None
     """
     with open_repo_closing(repo) as repo_obj:
-        repo_obj.configure_for_cone_mode()
+        repo_obj.get_worktree().configure_for_cone_mode()
         # Do not pass base patterns as dirs
         base_patterns = ["/*", "!/*/"]
         existing_dirs = [
             pat.strip("/")
-            for pat in repo_obj.get_sparse_checkout_patterns()
+            for pat in repo_obj.get_worktree().get_sparse_checkout_patterns()
             if pat not in base_patterns
         ]
         added_dirs = existing_dirs + (dirs or [])
-        repo_obj.set_cone_mode_patterns(dirs=added_dirs)
-        new_patterns = repo_obj.get_sparse_checkout_patterns()
+        repo_obj.get_worktree().set_cone_mode_patterns(dirs=added_dirs)
+        new_patterns = repo_obj.get_worktree().get_sparse_checkout_patterns()
         sparse_checkout(repo_obj, patterns=new_patterns, force=force, cone=True)
 
 
-def check_mailmap(repo: Union[str, os.PathLike, BaseRepo], contact):
+def check_mailmap(repo: RepoPath, contact):
     """Check canonical name and email of contact.
 
     Args:
@@ -3697,7 +3683,7 @@ def check_mailmap(repo: Union[str, os.PathLike, BaseRepo], contact):
         return mailmap.lookup(contact)
 
 
-def fsck(repo: Union[str, os.PathLike, BaseRepo]):
+def fsck(repo: RepoPath):
     """Check a repository.
 
     Args:
@@ -3752,7 +3738,7 @@ def stash_drop(repo: Union[str, os.PathLike, Repo], index) -> None:
         stash.drop(index)
 
 
-def ls_files(repo: Union[str, os.PathLike, BaseRepo]):
+def ls_files(repo: RepoPath):
     """List all files in an index."""
     with open_repo_closing(repo) as r:
         return sorted(r.open_index())
@@ -3825,10 +3811,12 @@ def describe(repo: Union[str, os.PathLike, Repo], abbrev=None):
             else:
                 # Lightweight tag case - obj is already the commit
                 commit = obj
-            
+
             if not isinstance(commit, Commit):
-                raise AssertionError(f"Expected Commit object, got {type(commit).__name__}")
-            
+                raise AssertionError(
+                    f"Expected Commit object, got {type(commit).__name__}"
+                )
+
             tags[tag] = [
                 datetime.datetime(*time.gmtime(commit.commit_time)[:6]),
                 commit.id.decode("ascii"),
@@ -3899,7 +3887,7 @@ def get_object_by_path(
         return r[sha]
 
 
-def write_tree(repo: Union[str, os.PathLike, BaseRepo]):
+def write_tree(repo: RepoPath):
     """Write a tree object from the index.
 
     Args:
@@ -4187,7 +4175,7 @@ def cherry_pick(
             except FileNotFoundError:
                 pass
             # Reset index to HEAD
-            r.reset_index(r[b"HEAD"].tree)
+            r.get_worktree().reset_index(r[b"HEAD"].tree)
             return None
 
         # Handle continue
@@ -4270,7 +4258,7 @@ def cherry_pick(
 
         # Update working tree and index
         # Reset index to match merged tree
-        r.reset_index(merged_tree.id)
+        r.get_worktree().reset_index(merged_tree.id)
 
         # Update working tree from the new index
         # Allow overwriting because we're applying the merge result
@@ -4520,9 +4508,7 @@ def prune(
             r.object_store.prune(grace_period=grace_period)
 
 
-def count_objects(
-    repo: Union[str, os.PathLike, BaseRepo] = ".", verbose=False
-) -> CountObjectsResult:
+def count_objects(repo: RepoPath = ".", verbose=False) -> CountObjectsResult:
     """Count unpacked objects and their disk usage.
 
     Args:
@@ -4541,6 +4527,7 @@ def count_objects(
         for sha in object_store._iter_loose_objects():
             loose_count += 1
             from .object_store import DiskObjectStore
+
             assert isinstance(object_store, DiskObjectStore)
             path = object_store._get_shafile_path(sha)
             try:
@@ -4669,7 +4656,7 @@ def rebase(
 
 
 def annotate(
-    repo: Union[str, os.PathLike, BaseRepo],
+    repo: RepoPath,
     path,
     committish: Optional[Union[str, bytes, Commit, Tag]] = None,
 ):
@@ -5162,7 +5149,7 @@ def bisect_replay(repo: Union[str, os.PathLike, Repo], log_file):
         state.replay(log_content)
 
 
-def reflog(repo: Union[str, os.PathLike, BaseRepo] = ".", ref=b"HEAD", all=False):
+def reflog(repo: RepoPath = ".", ref=b"HEAD", all=False):
     """Show reflog entries for a reference or all references.
 
     Args:
@@ -5473,8 +5460,8 @@ def lfs_migrate(
                 files_to_migrate.append(path_str)
 
         # Migrate files
-        for path in files_to_migrate:
-            full_path = os.path.join(r.path, path)
+        for path_str in files_to_migrate:
+            full_path = os.path.join(r.path, path_str)
             if not os.path.exists(full_path):
                 continue
 
@@ -5496,7 +5483,7 @@ def lfs_migrate(
 
             st = os.stat(full_path)
             index_entry = index_entry_from_stat(st, blob.id, 0)
-            path_bytes = path.encode() if isinstance(path, str) else path
+            path_bytes = path_str.encode() if isinstance(path_str, str) else path_str
             index[path_bytes] = index_entry
 
             migrated += 1
@@ -5547,9 +5534,7 @@ def lfs_pointer_check(repo: Union[str, os.PathLike, Repo] = ".", paths=None):
         return results
 
 
-def lfs_fetch(
-    repo: Union[str, os.PathLike, Repo] = ".", remote="origin", refs=None
-):
+def lfs_fetch(repo: Union[str, os.PathLike, Repo] = ".", remote="origin", refs=None):
     """Fetch LFS objects from remote.
 
     Args:
@@ -5806,7 +5791,9 @@ def lfs_status(repo: Union[str, os.PathLike, Repo] = "."):
                         pass
                     else:
                         if not isinstance(staged_obj, Blob):
-                            raise AssertionError(f"Expected Blob object, got {type(staged_obj).__name__}")
+                            raise AssertionError(
+                                f"Expected Blob object, got {type(staged_obj).__name__}"
+                            )
                         staged_pointer = LFSPointer.from_bytes(staged_obj.data)
                         if staged_pointer and staged_pointer.oid != pointer.oid:
                             status["not_staged"].append(path_str)

+ 2 - 2
dulwich/repo.py

@@ -1548,7 +1548,7 @@ class Repo(BaseRepo):
                         head = None
 
                 if checkout and head is not None:
-                    target.reset_index()
+                    target.get_worktree().reset_index()
             except BaseException:
                 target.close()
                 raise
@@ -1822,7 +1822,7 @@ class Repo(BaseRepo):
         with open(os.path.join(worktree_controldir, "HEAD"), "wb") as f:
             f.write(main_repo.head() + b"\n")
         r = cls(os.path.normpath(path))
-        r.reset_index()
+        r.get_worktree().reset_index()
         return r
 
     @classmethod

+ 2 - 2
dulwich/stash.py

@@ -268,7 +268,7 @@ class Stash:
         # Create a dangling commit for the index state
         # Note: We pass ref=None which is handled specially in do_commit
         # to create a commit without updating any reference
-        index_commit_id = self._repo.do_commit(
+        index_commit_id = self._repo.get_worktree().commit(
             tree=index_tree_id,
             message=b"Index stash",
             merge_heads=[self._repo.head()],
@@ -299,7 +299,7 @@ class Stash:
         # TODO(jelmer): Just pass parents into do_commit()?
         self._repo.refs[self._ref] = self._repo.head()
 
-        cid = self._repo.do_commit(
+        cid = self._repo.get_worktree().commit(
             ref=self._ref,
             tree=stash_tree_id,
             message=message,

+ 1 - 1
dulwich/worktree.py

@@ -887,7 +887,7 @@ def add_worktree(
         wt_repo.refs.set_symbolic_ref(b"HEAD", branch)
 
     # Reset index to match HEAD
-    wt_repo.reset_index()
+    wt_repo.get_worktree().reset_index()
 
     return wt_repo
 

+ 1 - 1
examples/memoryrepo.py

@@ -27,7 +27,7 @@ local_repo.object_store.add_object(new_blob)
 last_tree.add(b"test", stat.S_IFREG, new_blob.id)
 local_repo.object_store.add_object(last_tree)
 
-local_repo.do_commit(
+local_repo.get_worktree().commit(
     message=b"Add a file called 'test'", ref=b"refs/heads/master", tree=last_tree.id
 )
 

+ 8 - 6
examples/merge_driver.py

@@ -69,8 +69,10 @@ with open(config_path, "w") as f:
     json.dump(initial_config, f, indent=2)
 
 # Add and commit initial files
-repo.stage([".gitattributes", "config.json"])
-initial_commit = repo.do_commit(b"Initial commit", committer=b"Test <test@example.com>")
+repo.get_worktree().stage([".gitattributes", "config.json"])
+initial_commit = repo.get_worktree().commit(
+    b"Initial commit", committer=b"Test <test@example.com>"
+)
 
 # Register our custom merge driver globally
 registry = get_merge_driver_registry()
@@ -90,8 +92,8 @@ feature_config = {
 with open(config_path, "w") as f:
     json.dump(feature_config, f, indent=2)
 
-repo.stage(["config.json"])
-feature_commit = repo.do_commit(
+repo.get_worktree().stage(["config.json"])
+feature_commit = repo.get_worktree().commit(
     b"Add author and features", committer=b"Alice <alice@example.com>"
 )
 
@@ -108,8 +110,8 @@ master_config = {
 with open(config_path, "w") as f:
     json.dump(master_config, f, indent=2)
 
-repo.stage(["config.json"])
-master_commit = repo.do_commit(
+repo.get_worktree().stage(["config.json"])
+master_commit = repo.get_worktree().commit(
     b"Add description and license", committer=b"Bob <bob@example.com>"
 )
 

+ 2 - 2
fuzzing/fuzz-targets/fuzz_repo.py

@@ -38,8 +38,8 @@ def TestOneInput(data) -> Optional[int]:
             return -1
 
         try:
-            repo.stage(file_names)
-            repo.do_commit(
+            repo.get_worktree().stage(file_names)
+            repo.get_worktree().commit(
                 message=fdp.ConsumeRandomBytes(),
                 committer=fdp.ConsumeRandomBytes(),
                 author=fdp.ConsumeRandomBytes(),

+ 1 - 1
tests/compat/test_client.py

@@ -130,7 +130,7 @@ class DulwichClientTestBase:
                 ("zop", "zop contents"),
             ]:
                 tree_id = self._add_file(local, tree_id, filename, contents)
-                commit_id = local.do_commit(
+                commit_id = local.get_worktree().commit(
                     message=b"add " + filename.encode("utf-8"),
                     committer=b"Joe Example <joe@example.com>",
                     tree=tree_id,

+ 2 - 2
tests/compat/test_dumb.py

@@ -191,7 +191,7 @@ class DumbHTTPClientNoPackTests(CompatTestCase):
                     dest_repo.refs[ref] = sha
 
             # Checkout files
-            dest_repo.reset_index()
+            dest_repo.get_worktree().reset_index()
 
             # Verify the clone
             test_file = os.path.join(dest_path, "test0.txt")
@@ -245,7 +245,7 @@ class DumbHTTPClientNoPackTests(CompatTestCase):
                     dest_repo.refs[ref] = sha
 
             # Reset to new commit
-            dest_repo.reset_index()
+            dest_repo.get_worktree().reset_index()
 
             # Verify the new file exists
             test_file2_dest = os.path.join(dest_path, "test2.txt")

+ 8 - 4
tests/compat/test_patch.py

@@ -53,9 +53,11 @@ class CompatPatchTestCase(CompatTestCase):
             with open(file_path, "w"):
                 pass
 
-        self.repo.stage(file_list)
+        self.repo.get_worktree().stage(file_list)
 
-        first_commit = self.repo.do_commit(b"The first commit")
+        first_commit = self.repo.get_worktree().commit(
+            message=b"The first commit",
+        )
 
         # Make a copy of the repository so we can apply the diff later
         copy_path = os.path.join(self.test_dir, "copy")
@@ -70,9 +72,11 @@ class CompatPatchTestCase(CompatTestCase):
         with open(os.path.join(self.repo_path, "to_add"), "w"):
             pass
 
-        self.repo.stage(["to_modify", "to_delete", "to_add"])
+        self.repo.get_worktree().stage(["to_modify", "to_delete", "to_add"])
 
-        second_commit = self.repo.do_commit(b"The second commit")
+        second_commit = self.repo.get_worktree().commit(
+            message=b"The second commit",
+        )
 
         # Get the patch
         first_tree = self.repo[first_commit].tree

+ 14 - 8
tests/contrib/test_swift_smoke.py

@@ -127,7 +127,7 @@ class SwiftRepoSmokeTest(unittest.TestCase):
 
         local_repo = repo.Repo.init(self.temp_d, mkdir=True)
         # Nothing in the staging area
-        local_repo.do_commit("Test commit", "fbo@localhost")
+        local_repo.get_worktree().commit("Test commit", "fbo@localhost")
         sha = local_repo.refs.read_loose_ref("refs/heads/master")
         swift.SwiftRepo.init_bare(self.scon, self.conf)
         tcp_client = client.TCPGitClient(self.server_address, port=self.port)
@@ -144,7 +144,9 @@ class SwiftRepoSmokeTest(unittest.TestCase):
 
         local_repo = repo.Repo.init(self.temp_d, mkdir=True)
         # Nothing in the staging area
-        local_repo.do_commit("Test commit", "fbo@localhost", ref="refs/heads/mybranch")
+        local_repo.get_worktree().commit(
+            "Test commit", "fbo@localhost", ref="refs/heads/mybranch"
+        )
         sha = local_repo.refs.read_loose_ref("refs/heads/mybranch")
         swift.SwiftRepo.init_bare(self.scon, self.conf)
         tcp_client = client.TCPGitClient(self.server_address, port=self.port)
@@ -168,7 +170,7 @@ class SwiftRepoSmokeTest(unittest.TestCase):
         local_shas = {}
         remote_shas = {}
         for branch in ("master", "mybranch", "pullr-108"):
-            local_shas[branch] = local_repo.do_commit(
+            local_shas[branch] = local_repo.get_worktree().commit(
                 f"Test commit {branch}",
                 "fbo@localhost",
                 ref=f"refs/heads/{branch}",
@@ -194,8 +196,10 @@ class SwiftRepoSmokeTest(unittest.TestCase):
         for f in files:
             open(os.path.join(self.temp_d, f), "w").write(f"DATA {i}")
             i += 1
-        local_repo.stage(files)
-        local_repo.do_commit("Test commit", "fbo@localhost", ref="refs/heads/master")
+        local_repo.get_worktree().stage(files)
+        local_repo.get_worktree().commit(
+            "Test commit", "fbo@localhost", ref="refs/heads/master"
+        )
         swift.SwiftRepo.init_bare(self.scon, self.conf)
         tcp_client = client.TCPGitClient(self.server_address, port=self.port)
         tcp_client.send_pack(
@@ -245,8 +249,10 @@ class SwiftRepoSmokeTest(unittest.TestCase):
         for f in files:
             open(os.path.join(self.temp_d, f), "w").write(f"DATA {i}")
             i += 1
-        local_repo.stage(files)
-        local_repo.do_commit("Test commit", "fbo@localhost", ref="refs/heads/master")
+        local_repo.get_worktree().stage(files)
+        local_repo.get_worktree().commit(
+            "Test commit", "fbo@localhost", ref="refs/heads/master"
+        )
         tcp_client.send_pack(
             "/fakerepo", determine_wants, local_repo.generate_pack_data
         )
@@ -277,7 +283,7 @@ class SwiftRepoSmokeTest(unittest.TestCase):
 
         local_repo = repo.Repo.init(self.temp_d, mkdir=True)
         # Nothing in the staging area
-        sha = local_repo.do_commit("Test commit", "fbo@localhost")
+        sha = local_repo.get_worktree().commit("Test commit", "fbo@localhost")
         otype, data = local_repo.object_store.get_raw(sha)
         commit = objects.ShaFile.from_raw_string(otype, data)
         tag = objects.Tag()

+ 3 - 3
tests/test_annotate.py

@@ -322,11 +322,11 @@ class IntegrationTestCase(TestCase):
             f.write(content)
 
         # Stage file
-        self.repo.stage([filename.encode()])
+        self.repo.get_worktree().stage([filename.encode()])
 
         # Create commit
-        commit_id = self.repo.do_commit(
-            message.encode(),
+        commit_id = self.repo.get_worktree().commit(
+            message=message.encode(),
             committer=b"Test Committer <test@example.com>",
             author=b"Test Author <test@example.com>",
             commit_timestamp=1000000000,

+ 10 - 10
tests/test_cli.py

@@ -945,7 +945,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
         # Initial commit
         tree1 = Tree()
         self.repo.object_store.add_object(tree1)
-        self.repo.do_commit(
+        self.repo.get_worktree().commit(
             message=b"Initial commit",
             tree=tree1.id,
         )
@@ -956,7 +956,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
         tree2 = Tree()
         tree2.add(b"hello.txt", 0o100644, blob.id)
         self.repo.object_store.add_object(tree2)
-        self.repo.do_commit(
+        self.repo.get_worktree().commit(
             message=b"Add hello.txt",
             tree=tree2.id,
         )
@@ -990,7 +990,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
         # Initial commit
         tree1 = Tree()
         self.repo.object_store.add_object(tree1)
-        self.repo.do_commit(
+        self.repo.get_worktree().commit(
             message=b"Initial commit",
             tree=tree1.id,
         )
@@ -1001,7 +1001,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
         tree2 = Tree()
         tree2.add(b"file1.txt", 0o100644, blob1.id)
         self.repo.object_store.add_object(tree2)
-        self.repo.do_commit(
+        self.repo.get_worktree().commit(
             message=b"Add file1.txt",
             tree=tree2.id,
         )
@@ -1013,7 +1013,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
         tree3.add(b"file1.txt", 0o100644, blob1.id)
         tree3.add(b"file2.txt", 0o100644, blob2.id)
         self.repo.object_store.add_object(tree3)
-        self.repo.do_commit(
+        self.repo.get_worktree().commit(
             message=b"Add file2.txt",
             tree=tree3.id,
         )
@@ -1049,7 +1049,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
         tree = Tree()
         tree.add(b"test.txt", 0o100644, blob.id)
         self.repo.object_store.add_object(tree)
-        self.repo.do_commit(
+        self.repo.get_worktree().commit(
             message=b"Test commit",
             tree=tree.id,
         )
@@ -1083,7 +1083,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
         tree0 = Tree()
         self.repo.object_store.add_object(tree0)
         trees.append(tree0)
-        c0 = self.repo.do_commit(
+        c0 = self.repo.get_worktree().commit(
             message=b"Initial commit",
             tree=tree0.id,
         )
@@ -1103,7 +1103,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
             self.repo.object_store.add_object(tree)
             trees.append(tree)
 
-            c = self.repo.do_commit(
+            c = self.repo.get_worktree().commit(
                 message=f"Add file{i}.txt".encode(),
                 tree=tree.id,
             )
@@ -1145,7 +1145,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
         self.repo.object_store.add_object(blob1)
         tree1.add(b"file.txt", 0o100644, blob1.id)
         self.repo.object_store.add_object(tree1)
-        self.repo.do_commit(
+        self.repo.get_worktree().commit(
             message=b"Initial commit",
             tree=tree1.id,
         )
@@ -1155,7 +1155,7 @@ class FormatPatchCommandTest(DulwichCliTestCase):
         self.repo.object_store.add_object(blob2)
         tree2.add(b"file.txt", 0o100644, blob2.id)
         self.repo.object_store.add_object(tree2)
-        self.repo.do_commit(
+        self.repo.get_worktree().commit(
             message=b"Modify file.txt",
             tree=tree2.id,
         )

+ 4 - 4
tests/test_cli_cherry_pick.py

@@ -52,7 +52,7 @@ class CherryPickCommandTests(TestCase):
 
                 # Create a branch and switch to it
                 porcelain.branch_create(".", "feature")
-                porcelain.checkout_branch(".", "feature")
+                porcelain.checkout(".", "feature")
 
                 # Add a file on feature branch
                 with open("file2.txt", "w") as f:
@@ -61,7 +61,7 @@ class CherryPickCommandTests(TestCase):
                 feature_commit = porcelain.commit(".", message=b"Add feature file")
 
                 # Go back to master
-                porcelain.checkout_branch(".", "master")
+                porcelain.checkout(".", "master")
 
                 # Cherry-pick via CLI
                 cmd = cmd_cherry_pick()
@@ -91,7 +91,7 @@ class CherryPickCommandTests(TestCase):
 
                 # Create a branch and switch to it
                 porcelain.branch_create(".", "feature")
-                porcelain.checkout_branch(".", "feature")
+                porcelain.checkout(".", "feature")
 
                 # Add a file on feature branch
                 with open("file2.txt", "w") as f:
@@ -100,7 +100,7 @@ class CherryPickCommandTests(TestCase):
                 feature_commit = porcelain.commit(".", message=b"Add feature file")
 
                 # Go back to master
-                porcelain.checkout_branch(".", "master")
+                porcelain.checkout(".", "master")
 
                 # Cherry-pick with --no-commit
                 cmd = cmd_cherry_pick()

+ 10 - 10
tests/test_cli_merge.py

@@ -50,7 +50,7 @@ class CLIMergeTests(TestCase):
 
             # Create a branch
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             # Add a file on feature branch
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -59,7 +59,7 @@ class CLIMergeTests(TestCase):
             porcelain.commit(tmpdir, message=b"Add feature")
 
             # Go back to master
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
 
             # Test merge via CLI
             old_cwd = os.getcwd()
@@ -91,14 +91,14 @@ class CLIMergeTests(TestCase):
 
             # Create a branch and modify file1
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Feature content\n")
             porcelain.add(tmpdir, paths=["file1.txt"])
             porcelain.commit(tmpdir, message=b"Modify file1 in feature")
 
             # Go back to master and modify file1 differently
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Master content\n")
             porcelain.add(tmpdir, paths=["file1.txt"])
@@ -157,7 +157,7 @@ class CLIMergeTests(TestCase):
 
             # Create a branch
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             # Add a file on feature branch
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -166,7 +166,7 @@ class CLIMergeTests(TestCase):
             porcelain.commit(tmpdir, message=b"Add feature")
 
             # Go back to master and add another file
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
             with open(os.path.join(tmpdir, "file3.txt"), "w") as f:
                 f.write("Master content\n")
             porcelain.add(tmpdir, paths=["file3.txt"])
@@ -203,7 +203,7 @@ class CLIMergeTests(TestCase):
 
             # Create a branch
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             # Add a file on feature branch
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -212,7 +212,7 @@ class CLIMergeTests(TestCase):
             porcelain.commit(tmpdir, message=b"Add feature")
 
             # Go back to master
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
 
             # Test merge via CLI with --no-ff
             old_cwd = os.getcwd()
@@ -242,7 +242,7 @@ class CLIMergeTests(TestCase):
 
             # Create a branch
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             # Add a file on feature branch
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -251,7 +251,7 @@ class CLIMergeTests(TestCase):
             porcelain.commit(tmpdir, message=b"Add feature")
 
             # Go back to master and add another file
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
             with open(os.path.join(tmpdir, "file3.txt"), "w") as f:
                 f.write("Master content\n")
             porcelain.add(tmpdir, paths=["file3.txt"])

+ 30 - 6
tests/test_grafts.py

@@ -159,9 +159,15 @@ class GraftsInRepoTests(GraftsInRepositoryBase, TestCase):
             "author_timezone": 0,
         }
 
-        self._shas.append(r.do_commit(b"empty commit", **commit_kwargs))
-        self._shas.append(r.do_commit(b"empty commit", **commit_kwargs))
-        self._shas.append(r.do_commit(b"empty commit", **commit_kwargs))
+        self._shas.append(
+            r.get_worktree().commit(message=b"empty commit", **commit_kwargs)
+        )
+        self._shas.append(
+            r.get_worktree().commit(message=b"empty commit", **commit_kwargs)
+        )
+        self._shas.append(
+            r.get_worktree().commit(message=b"empty commit", **commit_kwargs)
+        )
 
     def test_init_with_empty_info_grafts(self) -> None:
         r = self._repo
@@ -191,6 +197,7 @@ class GraftsInMemoryRepoTests(GraftsInRepositoryBase, TestCase):
         self._shas = []
 
         tree = Tree()
+        r.object_store.add_object(tree)
 
         commit_kwargs = {
             "committer": b"Test Committer <test@nodomain.com>",
@@ -202,6 +209,23 @@ class GraftsInMemoryRepoTests(GraftsInRepositoryBase, TestCase):
             "tree": tree.id,
         }
 
-        self._shas.append(r.do_commit(b"empty commit", **commit_kwargs))
-        self._shas.append(r.do_commit(b"empty commit", **commit_kwargs))
-        self._shas.append(r.do_commit(b"empty commit", **commit_kwargs))
+        # Create commits directly for MemoryRepo since it doesn't support worktree
+        from dulwich.objects import Commit
+
+        for i in range(3):
+            c = Commit()
+            c.message = b"empty commit"
+            c.committer = commit_kwargs["committer"]
+            c.author = commit_kwargs["author"]
+            c.commit_time = commit_kwargs["commit_timestamp"]
+            c.commit_timezone = commit_kwargs["commit_timezone"]
+            c.author_time = commit_kwargs["author_timestamp"]
+            c.author_timezone = commit_kwargs["author_timezone"]
+            c.tree = commit_kwargs["tree"]
+            if self._shas:
+                c.parents = [self._shas[-1]]
+            r.object_store.add_object(c)
+            self._shas.append(c.id)
+
+        # Set HEAD to point to the last commit
+        r.refs[b"HEAD"] = self._shas[-1]

+ 24 - 20
tests/test_index.py

@@ -837,11 +837,11 @@ class GetUnstagedChangesTests(TestCase):
             with open(foo2_fullpath, "wb") as f:
                 f.write(b"origstuff")
 
-            repo.stage(["foo1", "foo2"])
-            repo.do_commit(
-                b"test status",
-                author=b"author <email>",
+            repo.get_worktree().stage(["foo1", "foo2"])
+            repo.get_worktree().commit(
+                message=b"test status",
                 committer=b"committer <email>",
+                author=b"author <email>",
             )
 
             with open(foo1_fullpath, "wb") as f:
@@ -864,11 +864,11 @@ class GetUnstagedChangesTests(TestCase):
             with open(foo1_fullpath, "wb") as f:
                 f.write(b"origstuff")
 
-            repo.stage(["foo1"])
-            repo.do_commit(
-                b"test status",
-                author=b"author <email>",
+            repo.get_worktree().stage(["foo1"])
+            repo.get_worktree().commit(
+                message=b"test status",
                 committer=b"committer <email>",
+                author=b"author <email>",
             )
 
             os.unlink(foo1_fullpath)
@@ -887,11 +887,11 @@ class GetUnstagedChangesTests(TestCase):
             with open(foo1_fullpath, "wb") as f:
                 f.write(b"origstuff")
 
-            repo.stage(["foo1"])
-            repo.do_commit(
-                b"test status",
-                author=b"author <email>",
+            repo.get_worktree().stage(["foo1"])
+            repo.get_worktree().commit(
+                message=b"test status",
                 committer=b"committer <email>",
+                author=b"author <email>",
             )
 
             os.remove(foo1_fullpath)
@@ -912,11 +912,11 @@ class GetUnstagedChangesTests(TestCase):
             with open(foo1_fullpath, "wb") as f:
                 f.write(b"origstuff")
 
-            repo.stage(["foo1"])
-            repo.do_commit(
-                b"test status",
-                author=b"author <email>",
+            repo.get_worktree().stage(["foo1"])
+            repo.get_worktree().commit(
+                message=b"test status",
                 committer=b"committer <email>",
+                author=b"author <email>",
             )
 
             os.remove(foo1_fullpath)
@@ -1184,8 +1184,10 @@ class TestIndexEntryFromPath(TestCase):
         with open(test_file, "wb") as f:
             f.write(b"test content")
 
-        submodule_repo.stage(["testfile"])
-        commit_id = submodule_repo.do_commit(b"Test commit for submodule")
+        submodule_repo.get_worktree().stage(["testfile"])
+        commit_id = submodule_repo.get_worktree().commit(
+            message=b"Test commit for submodule",
+        )
 
         # Test reading the HEAD
         head_sha = read_submodule_head(sub_repo_dir)
@@ -1241,8 +1243,10 @@ class TestIndexEntryFromPath(TestCase):
         with open(test_file, "wb") as f:
             f.write(b"test content")
 
-        submodule_repo.stage(["testfile"])
-        commit_id = submodule_repo.do_commit(b"Test commit for submodule")
+        submodule_repo.get_worktree().stage(["testfile"])
+        commit_id = submodule_repo.get_worktree().commit(
+            message=b"Test commit for submodule",
+        )
 
         # Create an entry with the correct commit SHA
         correct_entry = IndexEntry(

+ 2 - 2
tests/test_lfs.py

@@ -238,7 +238,7 @@ class LFSIntegrationTests(TestCase):
             f.write(large_content)
 
         # Add files to repo
-        self.repo.stage([".gitattributes", "large.bin"])
+        self.repo.get_worktree().stage([".gitattributes", "large.bin"])
 
         # Get the blob for large.bin from the index
         index = self.repo.open_index()
@@ -390,7 +390,7 @@ class LFSIntegrationTests(TestCase):
         porcelain.commit(self.repo, message=b"Add LFS file")
 
         # Reset index to trigger checkout with filter
-        self.repo.reset_index()
+        self.repo.get_worktree().reset_index()
 
         # Check file content
         with open(pointer_file, "rb") as f:

+ 6 - 6
tests/test_porcelain.py

@@ -6260,14 +6260,14 @@ class ReceivePackTests(PorcelainTestCase):
         with open(fullpath, "w") as f:
             f.write("stuff")
         porcelain.add(repo=self.repo.path, paths=fullpath)
-        self.repo.do_commit(
+        self.repo.get_worktree().commit(
             message=b"test status",
-            author=b"author <email>",
             committer=b"committer <email>",
-            author_timestamp=1402354300,
+            author=b"author <email>",
             commit_timestamp=1402354300,
-            author_timezone=0,
             commit_timezone=0,
+            author_timestamp=1402354300,
+            author_timezone=0,
         )
         outf = BytesIO()
         exitcode = porcelain.receive_pack(self.repo.path, BytesIO(b"0000"), outf)
@@ -6837,7 +6837,7 @@ class CheckIgnoreTests(PorcelainTestCase):
         path = os.path.join(self.repo.path, "foo")
         with open(path, "w") as f:
             f.write("BAR")
-        self.repo.stage(["foo"])
+        self.repo.get_worktree().stage(["foo"])
         with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
             f.write("foo\n")
         self.assertEqual([], list(porcelain.check_ignore(self.repo, [path])))
@@ -6849,7 +6849,7 @@ class CheckIgnoreTests(PorcelainTestCase):
     def test_check_added_rel(self) -> None:
         with open(os.path.join(self.repo.path, "foo"), "w") as f:
             f.write("BAR")
-        self.repo.stage(["foo"])
+        self.repo.get_worktree().stage(["foo"])
         with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
             f.write("foo\n")
         cwd = os.getcwd()

+ 9 - 9
tests/test_porcelain_cherry_pick.py

@@ -46,7 +46,7 @@ class PorcelainCherryPickTests(TestCase):
 
             # Create a branch and switch to it
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             # Add a file on feature branch
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -55,7 +55,7 @@ class PorcelainCherryPickTests(TestCase):
             feature_commit = porcelain.commit(tmpdir, message=b"Add feature file")
 
             # Go back to master
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
 
             # Cherry-pick the feature commit
             new_commit = porcelain.cherry_pick(tmpdir, feature_commit)
@@ -81,7 +81,7 @@ class PorcelainCherryPickTests(TestCase):
 
             # Create a branch and switch to it
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             # Add a file on feature branch
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -90,7 +90,7 @@ class PorcelainCherryPickTests(TestCase):
             feature_commit = porcelain.commit(tmpdir, message=b"Add feature file")
 
             # Go back to master
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
 
             # Cherry-pick with no-commit
             result = porcelain.cherry_pick(tmpdir, feature_commit, no_commit=True)
@@ -119,7 +119,7 @@ class PorcelainCherryPickTests(TestCase):
 
             # Create a branch and modify the file
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Feature modification\n")
@@ -127,7 +127,7 @@ class PorcelainCherryPickTests(TestCase):
             feature_commit = porcelain.commit(tmpdir, message=b"Modify file on feature")
 
             # Go back to master and make a different modification
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Master modification\n")
             porcelain.add(tmpdir, paths=["file1.txt"])
@@ -154,7 +154,7 @@ class PorcelainCherryPickTests(TestCase):
 
             # Create another branch with a different initial commit
             porcelain.branch_create(tmpdir, "other")
-            porcelain.checkout_branch(tmpdir, "other")
+            porcelain.checkout(tmpdir, "other")
 
             # Try to cherry-pick root commit - should fail
             with self.assertRaises(porcelain.Error) as cm:
@@ -176,7 +176,7 @@ class PorcelainCherryPickTests(TestCase):
 
             # Create branch and make conflicting changes
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Feature content\n")
@@ -184,7 +184,7 @@ class PorcelainCherryPickTests(TestCase):
             feature_commit = porcelain.commit(tmpdir, message=b"Feature change")
 
             # Go back to master and make different change
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Master content\n")
             porcelain.add(tmpdir, paths=["file1.txt"])

+ 1 - 1
tests/test_porcelain_filters.py

@@ -207,7 +207,7 @@ class PorcelainFilterTests(TestCase):
         target_config.write_to_path()
 
         # Now checkout the files with autocrlf enabled
-        target_repo.reset_index()
+        target_repo.get_worktree().reset_index()
 
         # Check that the working tree file has CRLF endings
         target_file = os.path.join(target_dir, "test.txt")

+ 16 - 16
tests/test_porcelain_merge.py

@@ -48,7 +48,7 @@ class PorcelainMergeTests(TestCase):
 
             # Create a branch
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             # Add a file on feature branch
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -57,7 +57,7 @@ class PorcelainMergeTests(TestCase):
             feature_commit = porcelain.commit(tmpdir, message=b"Add feature")
 
             # Go back to master
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
 
             # Merge feature branch (should fast-forward)
             merge_commit, conflicts = porcelain.merge(tmpdir, "feature")
@@ -100,7 +100,7 @@ class PorcelainMergeTests(TestCase):
 
             # Create a branch
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             # Add a file on feature branch
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -109,7 +109,7 @@ class PorcelainMergeTests(TestCase):
             feature_commit = porcelain.commit(tmpdir, message=b"Add feature")
 
             # Go back to master
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
 
             # Merge feature branch with no-ff
             merge_commit, conflicts = porcelain.merge(tmpdir, "feature", no_ff=True)
@@ -140,14 +140,14 @@ class PorcelainMergeTests(TestCase):
 
             # Create a branch and modify file1
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Feature content\n")
             porcelain.add(tmpdir, paths=["file1.txt"])
             porcelain.commit(tmpdir, message=b"Modify file1 in feature")
 
             # Go back to master and modify file2
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
                 f.write("Master file2\n")
             porcelain.add(tmpdir, paths=["file2.txt"])
@@ -179,14 +179,14 @@ class PorcelainMergeTests(TestCase):
 
             # Create a branch and modify file1
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Feature content\n")
             porcelain.add(tmpdir, paths=["file1.txt"])
             porcelain.commit(tmpdir, message=b"Modify file1 in feature")
 
             # Go back to master and modify file1 differently
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Master content\n")
             porcelain.add(tmpdir, paths=["file1.txt"])
@@ -220,7 +220,7 @@ class PorcelainMergeTests(TestCase):
 
             # Create a branch
             porcelain.branch_create(tmpdir, "feature")
-            porcelain.checkout_branch(tmpdir, "feature")
+            porcelain.checkout(tmpdir, "feature")
 
             # Add a file on feature branch
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -229,7 +229,7 @@ class PorcelainMergeTests(TestCase):
             porcelain.commit(tmpdir, message=b"Add feature")
 
             # Go back to master and add another file
-            porcelain.checkout_branch(tmpdir, "master")
+            porcelain.checkout(tmpdir, "master")
             with open(os.path.join(tmpdir, "file3.txt"), "w") as f:
                 f.write("Master content\n")
             porcelain.add(tmpdir, paths=["file3.txt"])
@@ -293,7 +293,7 @@ class PorcelainMergeTreeTests(TestCase):
 
             # Create our branch
             porcelain.branch_create(tmpdir, "ours")
-            porcelain.checkout_branch(tmpdir, "ours")
+            porcelain.checkout(tmpdir, "ours")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Our content\n")
             with open(os.path.join(tmpdir, "file2.txt"), "w") as f:
@@ -302,9 +302,9 @@ class PorcelainMergeTreeTests(TestCase):
             our_commit = porcelain.commit(tmpdir, message=b"Our commit")
 
             # Create their branch
-            porcelain.checkout_branch(tmpdir, b"master")
+            porcelain.checkout(tmpdir, b"master")
             porcelain.branch_create(tmpdir, "theirs")
-            porcelain.checkout_branch(tmpdir, "theirs")
+            porcelain.checkout(tmpdir, "theirs")
             with open(os.path.join(tmpdir, "file3.txt"), "w") as f:
                 f.write("Their new file\n")
             porcelain.add(tmpdir, paths=["file3.txt"])
@@ -340,16 +340,16 @@ class PorcelainMergeTreeTests(TestCase):
 
             # Create our branch with changes
             porcelain.branch_create(tmpdir, "ours")
-            porcelain.checkout_branch(tmpdir, "ours")
+            porcelain.checkout(tmpdir, "ours")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Our content\n")
             porcelain.add(tmpdir, paths=["file1.txt"])
             our_commit = porcelain.commit(tmpdir, message=b"Our commit")
 
             # Create their branch with conflicting changes
-            porcelain.checkout_branch(tmpdir, b"master")
+            porcelain.checkout(tmpdir, b"master")
             porcelain.branch_create(tmpdir, "theirs")
-            porcelain.checkout_branch(tmpdir, "theirs")
+            porcelain.checkout(tmpdir, "theirs")
             with open(os.path.join(tmpdir, "file1.txt"), "w") as f:
                 f.write("Their content\n")
             porcelain.add(tmpdir, paths=["file1.txt"])

+ 6 - 6
tests/test_rebase.py

@@ -385,9 +385,9 @@ class RebasePorcelainTestCase(TestCase):
         with open(os.path.join(self.test_dir, "README.md"), "wb") as f:
             f.write(b"# Test Repository\n")
 
-        self.repo.stage(["README.md"])
-        self.initial_commit = self.repo.do_commit(
-            b"Initial commit",
+        self.repo.get_worktree().stage(["README.md"])
+        self.initial_commit = self.repo.get_worktree().commit(
+            message=b"Initial commit",
             committer=b"Test User <test@example.com>",
             author=b"Test User <test@example.com>",
         )
@@ -404,7 +404,7 @@ class RebasePorcelainTestCase(TestCase):
 
         # Create and checkout feature branch
         self.repo.refs[b"refs/heads/feature"] = self.initial_commit
-        porcelain.checkout_branch(self.repo, "feature")
+        porcelain.checkout(self.repo, "feature")
 
         # Add commit to feature branch
         with open(os.path.join(self.test_dir, "feature.txt"), "wb") as f:
@@ -419,7 +419,7 @@ class RebasePorcelainTestCase(TestCase):
         )
 
         # Switch to main and add different commit
-        porcelain.checkout_branch(self.repo, "master")
+        porcelain.checkout(self.repo, "master")
 
         with open(os.path.join(self.test_dir, "main.txt"), "wb") as f:
             f.write(b"Main file\n")
@@ -433,7 +433,7 @@ class RebasePorcelainTestCase(TestCase):
         )
 
         # Switch back to feature and rebase
-        porcelain.checkout_branch(self.repo, "feature")
+        porcelain.checkout(self.repo, "feature")
 
         # Perform rebase
         new_shas = porcelain.rebase(self.repo, "master")

+ 108 - 88
tests/test_repository.py

@@ -203,9 +203,9 @@ class MemoryRepoTests(TestCase):
                 f.write("test content\n")
 
             # Stage and commit using dulwich
-            initial_repo.stage(["test.txt"])
-            initial_repo.do_commit(
-                b"Initial commit\n",
+            initial_repo.get_worktree().stage(["test.txt"])
+            initial_repo.get_worktree().commit(
+                message=b"Initial commit\n",
                 committer=b"Test Committer <test@example.com>",
                 author=b"Test Author <test@example.com>",
             )
@@ -606,8 +606,10 @@ class RepositoryRootTests(TestCase):
 
         o = Repo.init(os.path.join(tmp_dir, "s"), mkdir=True)
         os.symlink("foo", os.path.join(tmp_dir, "s", "bar"))
-        o.stage("bar")
-        o.do_commit(b"add symlink")
+        o.get_worktree().stage("bar")
+        o.get_worktree().commit(
+            message=b"add symlink",
+        )
 
         t = o.clone(os.path.join(tmp_dir, "t"), symlinks=True)
         o.close()
@@ -626,8 +628,10 @@ class RepositoryRootTests(TestCase):
         o = Repo.init(os.path.join(tmp_dir, "s"), mkdir=True)
         o.close()
         os.symlink("foo", os.path.join(tmp_dir, "s", "bar"))
-        o.stage("bar")
-        o.do_commit(b"add symlink")
+        o.get_worktree().stage("bar")
+        o.get_worktree().commit(
+            message=b"add symlink",
+        )
 
         t = o.clone(os.path.join(tmp_dir, "t"), symlinks=False)
         with open(os.path.join(tmp_dir, "t", "bar")) as f:
@@ -655,7 +659,7 @@ class RepositoryRootTests(TestCase):
 
         # Try to stage the malicious path - should be rejected
         with self.assertRaises(ValueError):
-            repo.stage([attack_name])
+            repo.get_worktree().stage([attack_name])
 
         # Test with protectHFS disabled
         config.set(b"core", b"core.protectHFS", b"false")
@@ -849,8 +853,8 @@ exit 0
             f.write(pre_commit_success)
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
-        commit_sha = r.do_commit(
-            b"empty commit",
+        commit_sha = r.get_worktree().commit(
+            message=b"empty commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -899,8 +903,8 @@ exit 0
             f.write(commit_msg_success)
         os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
-        commit_sha = r.do_commit(
-            b"empty commit",
+        commit_sha = r.get_worktree().commit(
+            message=b"empty commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -937,7 +941,7 @@ r.stage(['foo'])
         with open(os.path.join(repo_dir, "blah"), "w") as f:
             f.write("blah")
 
-        r.stage(["blah"])
+        r.get_worktree().stage(["blah"])
 
         pre_commit = os.path.join(r.controldir(), "hooks", "pre-commit")
 
@@ -945,8 +949,8 @@ r.stage(['foo'])
             f.write(pre_commit_contents)
         os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
-        commit_sha = r.do_commit(
-            b"new commit",
+        commit_sha = r.get_worktree().commit(
+            message=b"new commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -979,8 +983,8 @@ rm """
 """
         )
 
-        root_sha = r.do_commit(
-            b"empty commit",
+        root_sha = r.get_worktree().commit(
+            message=b"empty commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12345,
@@ -996,8 +1000,8 @@ rm """
             f.write(post_commit_msg.encode(locale.getpreferredencoding()))
         os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
 
-        commit_sha = r.do_commit(
-            b"empty commit",
+        commit_sha = r.get_worktree().commit(
+            message=b"empty commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12345,
@@ -1021,8 +1025,8 @@ exit 1
         warnings_list, restore_warnings = setup_warning_catcher()
         self.addCleanup(restore_warnings)
 
-        commit_sha2 = r.do_commit(
-            b"empty commit",
+        commit_sha2 = r.get_worktree().commit(
+            message=b"empty commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12345,
@@ -1071,8 +1075,8 @@ exit 1
         self.addCleanup(shutil.rmtree, worktree_temp_dir)
         r = Repo.init(temp_dir)
         self.addCleanup(r.close)
-        root_sha = r.do_commit(
-            b"empty commit",
+        root_sha = r.get_worktree().commit(
+            message=b"empty commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12345,
@@ -1083,8 +1087,8 @@ exit 1
         r.refs[b"refs/heads/master"] = root_sha
         w = Repo._init_new_working_directory(worktree_temp_dir, r)
         self.addCleanup(w.close)
-        new_sha = w.do_commit(
-            b"new commit",
+        new_sha = w.get_worktree().commit(
+            message=b"new commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12345,
@@ -1122,9 +1126,9 @@ class BuildRepoRootTests(TestCase):
 
         with open(os.path.join(r.path, "a"), "wb") as f:
             f.write(b"file contents")
-        r.stage(["a"])
-        commit_sha = r.do_commit(
-            b"msg",
+        r.get_worktree().stage(["a"])
+        commit_sha = r.get_worktree().commit(
+            message=b"msg",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12345,
@@ -1180,9 +1184,9 @@ class BuildRepoRootTests(TestCase):
         r = self._repo
         with open(os.path.join(r.path, "a"), "wb") as f:
             f.write(b"new contents")
-        r.stage(["a"])
-        commit_sha = r.do_commit(
-            b"modified a",
+        r.get_worktree().stage(["a"])
+        commit_sha = r.get_worktree().commit(
+            message=b"modified a",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1199,9 +1203,9 @@ class BuildRepoRootTests(TestCase):
     def test_commit_symlink(self) -> None:
         r = self._repo
         os.symlink("a", os.path.join(r.path, "b"))
-        r.stage(["a", "b"])
-        commit_sha = r.do_commit(
-            b"Symlink b",
+        r.get_worktree().stage(["a", "b"])
+        commit_sha = r.get_worktree().commit(
+            message=b"Symlink b",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1220,8 +1224,8 @@ class BuildRepoRootTests(TestCase):
         r = Repo.init(tmp_dir)
         with open(os.path.join(r.path, "a"), "w") as f:
             f.write("initial text")
-        c1 = r.do_commit(
-            b"initial commit",
+        c1 = r.get_worktree().commit(
+            message=b"initial commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1233,9 +1237,9 @@ class BuildRepoRootTests(TestCase):
             f.write("merged text")
         with open(os.path.join(r.path, ".git", "MERGE_HEAD"), "w") as f:
             f.write("c27a2d21dd136312d7fa9e8baabb82561a1727d0\n")
-        r.stage(["a"])
-        commit_sha = r.do_commit(
-            b"deleted a",
+        r.get_worktree().stage(["a"])
+        commit_sha = r.get_worktree().commit(
+            message=b"deleted a",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1251,9 +1255,9 @@ class BuildRepoRootTests(TestCase):
     def test_commit_deleted(self) -> None:
         r = self._repo
         os.remove(os.path.join(r.path, "a"))
-        r.stage(["a"])
-        commit_sha = r.do_commit(
-            b"deleted a",
+        r.get_worktree().stage(["a"])
+        commit_sha = r.get_worktree().commit(
+            message=b"deleted a",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1269,8 +1273,8 @@ class BuildRepoRootTests(TestCase):
     def test_commit_follows(self) -> None:
         r = self._repo
         r.refs.set_symbolic_ref(b"HEAD", b"refs/heads/bla")
-        commit_sha = r.do_commit(
-            b"commit with strange character",
+        commit_sha = r.get_worktree().commit(
+            message=b"commit with strange character",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1283,8 +1287,8 @@ class BuildRepoRootTests(TestCase):
 
     def test_commit_encoding(self) -> None:
         r = self._repo
-        commit_sha = r.do_commit(
-            b"commit with strange character \xee",
+        commit_sha = r.get_worktree().commit(
+            message=b"commit with strange character \xee",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1361,8 +1365,8 @@ class BuildRepoRootTests(TestCase):
         c = r.get_config()
         c.set(("i18n",), "commitEncoding", "iso8859-1")
         c.write_to_path()
-        commit_sha = r.do_commit(
-            b"commit with strange character \xee",
+        commit_sha = r.get_worktree().commit(
+            message=b"commit with strange character \xee",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1379,7 +1383,9 @@ class BuildRepoRootTests(TestCase):
         c.set((b"user",), b"name", b"Jelmer")
         c.set((b"user",), b"email", b"jelmer@apache.org")
         c.write_to_path()
-        commit_sha = r.do_commit(b"message")
+        commit_sha = r.get_worktree().commit(
+            message=b"message",
+        )
         self.assertEqual(b"Jelmer <jelmer@apache.org>", r[commit_sha].author)
         self.assertEqual(b"Jelmer <jelmer@apache.org>", r[commit_sha].committer)
 
@@ -1391,7 +1397,9 @@ class BuildRepoRootTests(TestCase):
         c.set((b"user",), b"name", b"Jelmer")
         c.set((b"user",), b"email", b"<jelmer@apache.org>")
         c.write_to_path()
-        commit_sha = r.do_commit(b"message")
+        commit_sha = r.get_worktree().commit(
+            message=b"message",
+        )
         self.assertEqual(b"Jelmer <jelmer@apache.org>", r[commit_sha].author)
         self.assertEqual(b"Jelmer <jelmer@apache.org>", r[commit_sha].committer)
 
@@ -1402,7 +1410,15 @@ class BuildRepoRootTests(TestCase):
         c.set((b"user",), b"name", b"Jelmer")
         c.set((b"user",), b"email", b"jelmer@apache.org")
 
-        commit_sha = r.do_commit(b"message", tree=objects.Tree().id)
+        # Create a tree object
+        tree = objects.Tree()
+        r.object_store.add_object(tree)
+
+        # Use do_commit for MemoryRepo since it doesn't support worktree
+        commit_sha = r.do_commit(
+            message=b"message",
+            tree=tree.id,
+        )
         self.assertEqual(b"Jelmer <jelmer@apache.org>", r[commit_sha].author)
         self.assertEqual(b"Jelmer <jelmer@apache.org>", r[commit_sha].committer)
 
@@ -1415,7 +1431,9 @@ class BuildRepoRootTests(TestCase):
         c.set((b"user",), b"name", b"Jelmer")
         c.set((b"user",), b"email", b"jelmer@apache.org")
         c.write_to_path()
-        commit_sha = r.do_commit(b"message")
+        commit_sha = r.get_worktree().commit(
+            message=b"message",
+        )
         self.assertEqual(b"Jelmer <jelmer@apache.org>", r[commit_sha].author)
         self.assertEqual(b"joe <joe@example.com>", r[commit_sha].committer)
 
@@ -1463,8 +1481,8 @@ class BuildRepoRootTests(TestCase):
             # Generate a message
             return b"Generated commit for tree " + commit.tree[:8]
 
-        commit_sha = r.do_commit(
-            message_callback,  # Pass the callback as message
+        commit_sha = r.get_worktree().commit(
+            message=message_callback,
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12345,
@@ -1501,17 +1519,17 @@ class BuildRepoRootTests(TestCase):
         r = self._repo
 
         # Create two parent commits first
-        parent1 = r.do_commit(
-            b"Parent 1",
+        parent1 = r.get_worktree().commit(
+            message=b"Parent 1",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
         )
 
-        parent2 = r.do_commit(
-            b"Parent 2",
+        parent2 = r.get_worktree().commit(
+            message=b"Parent 2",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
-            ref=None,  # Dangling commit
+            ref=None,
         )
 
         def message_callback(repo, commit):
@@ -1519,8 +1537,8 @@ class BuildRepoRootTests(TestCase):
             self.assertEqual(2, len(commit.parents))
             return b"Merge commit with %d parents" % len(commit.parents)
 
-        merge_sha = r.do_commit(
-            message_callback,
+        merge_sha = r.get_worktree().commit(
+            message=message_callback,
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             merge_heads=[parent2],
@@ -1533,8 +1551,8 @@ class BuildRepoRootTests(TestCase):
     def test_commit_branch(self) -> None:
         r = self._repo
 
-        commit_sha = r.do_commit(
-            b"commit to branch",
+        commit_sha = r.get_worktree().commit(
+            message=b"commit to branch",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1550,8 +1568,8 @@ class BuildRepoRootTests(TestCase):
 
         new_branch_head = commit_sha
 
-        commit_sha = r.do_commit(
-            b"commit to branch 2",
+        commit_sha = r.get_worktree().commit(
+            message=b"commit to branch 2",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1566,8 +1584,8 @@ class BuildRepoRootTests(TestCase):
 
     def test_commit_merge_heads(self) -> None:
         r = self._repo
-        merge_1 = r.do_commit(
-            b"commit to branch 2",
+        merge_1 = r.get_worktree().commit(
+            message=b"commit to branch 2",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1576,8 +1594,8 @@ class BuildRepoRootTests(TestCase):
             author_timezone=0,
             ref=b"refs/heads/new_branch",
         )
-        commit_sha = r.do_commit(
-            b"commit with merge",
+        commit_sha = r.get_worktree().commit(
+            message=b"commit with merge",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1593,8 +1611,8 @@ class BuildRepoRootTests(TestCase):
 
         old_shas = set(r.object_store)
         old_refs = r.get_refs()
-        commit_sha = r.do_commit(
-            b"commit with no ref",
+        commit_sha = r.get_worktree().commit(
+            message=b"commit with no ref",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1617,8 +1635,8 @@ class BuildRepoRootTests(TestCase):
 
         old_shas = set(r.object_store)
         old_refs = r.get_refs()
-        commit_sha = r.do_commit(
-            b"commit with no ref",
+        commit_sha = r.get_worktree().commit(
+            message=b"commit with no ref",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,
@@ -1645,21 +1663,23 @@ class BuildRepoRootTests(TestCase):
     def test_stage_deleted(self) -> None:
         r = self._repo
         os.remove(os.path.join(r.path, "a"))
-        r.stage(["a"])
-        r.stage(["a"])  # double-stage a deleted path
+        r.get_worktree().stage(["a"])
+        r.get_worktree().stage(["a"])  # double-stage a deleted path
         self.assertEqual([], list(r.open_index()))
 
     def test_stage_directory(self) -> None:
         r = self._repo
         os.mkdir(os.path.join(r.path, "c"))
-        r.stage(["c"])
+        r.get_worktree().stage(["c"])
         self.assertEqual([b"a"], list(r.open_index()))
 
     def test_stage_submodule(self) -> None:
         r = self._repo
         s = Repo.init(os.path.join(r.path, "sub"), mkdir=True)
-        s.do_commit(b"message")
-        r.stage(["sub"])
+        s.get_worktree().commit(
+            message=b"message",
+        )
+        r.get_worktree().stage(["sub"])
         self.assertEqual([b"a", b"sub"], list(r.open_index()))
 
     def test_unstage_midify_file_with_dir(self) -> None:
@@ -1677,7 +1697,7 @@ class BuildRepoRootTests(TestCase):
         )
         with open(full_path, "a") as f:
             f.write("something new")
-        self._repo.unstage(["new_dir/foo"])
+        self._repo.get_worktree().unstage(["new_dir/foo"])
         status = list(porcelain.status(self._repo))
         self.assertEqual(
             [{"add": [], "delete": [], "modify": []}, [b"new_dir/foo"], []], status
@@ -1689,7 +1709,7 @@ class BuildRepoRootTests(TestCase):
         with open(full_path, "w") as f:
             f.write("hello")
         porcelain.add(self._repo, paths=[full_path])
-        self._repo.unstage([file])
+        self._repo.get_worktree().unstage([file])
         status = list(porcelain.status(self._repo))
         self.assertEqual([{"add": [], "delete": [], "modify": []}, [], ["foo"]], status)
 
@@ -1705,7 +1725,7 @@ class BuildRepoRootTests(TestCase):
         with open(full_path, "w") as f:
             f.write("hello")
         porcelain.add(self._repo, paths=[full_path])
-        self._repo.unstage([file])
+        self._repo.get_worktree().unstage([file])
         status = list(porcelain.status(self._repo))
         self.assertEqual([{"add": [], "delete": [], "modify": []}, [], ["foo"]], status)
 
@@ -1724,7 +1744,7 @@ class BuildRepoRootTests(TestCase):
         with open(full_path, "a") as f:
             f.write("broken")
         porcelain.add(self._repo, paths=[full_path])
-        self._repo.unstage([file])
+        self._repo.get_worktree().unstage([file])
         status = list(porcelain.status(self._repo))
 
         self.assertEqual(
@@ -1744,7 +1764,7 @@ class BuildRepoRootTests(TestCase):
             author=b"John <john@example.com>",
         )
         os.remove(full_path)
-        self._repo.unstage([file])
+        self._repo.get_worktree().unstage([file])
         status = list(porcelain.status(self._repo))
         self.assertEqual(
             [{"add": [], "delete": [], "modify": []}, [b"foo"], []], status
@@ -1756,12 +1776,12 @@ class BuildRepoRootTests(TestCase):
             f.write(b"changed")
         with open(os.path.join(r.path, "b"), "wb") as f:
             f.write(b"added")
-        r.stage(["a", "b"])
+        r.get_worktree().stage(["a", "b"])
         status = list(porcelain.status(self._repo))
         self.assertEqual(
             [{"add": [b"b"], "delete": [], "modify": [b"a"]}, [], []], status
         )
-        r.reset_index()
+        r.get_worktree().reset_index()
         status = list(porcelain.status(self._repo))
         self.assertEqual([{"add": [], "delete": [], "modify": []}, [], ["b"]], status)
 
@@ -1782,9 +1802,9 @@ class BuildRepoRootTests(TestCase):
             # ourselves.
             self.addCleanup(os.remove, full_path)
 
-        r.stage(names)
-        commit_sha = r.do_commit(
-            b"Files with different encodings",
+        r.get_worktree().stage(names)
+        commit_sha = r.get_worktree().commit(
+            message=b"Files with different encodings",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12395,

+ 1 - 1
tests/test_server.py

@@ -1174,7 +1174,7 @@ class UpdateServerInfoTests(TestCase):
             self.assertEqual(b"", f.read())
 
     def test_simple(self) -> None:
-        commit_id = self.repo.do_commit(
+        commit_id = self.repo.get_worktree().commit(
             message=b"foo",
             committer=b"Joe Example <joe@example.com>",
             ref=b"refs/heads/foo",

+ 7 - 5
tests/test_sparse_patterns.py

@@ -196,7 +196,7 @@ class ComputeIncludedPathsFullTests(TestCase):
         with open(full, "wb") as f:
             f.write(content)
         # Stage in the index
-        self.repo.stage([relpath])
+        self.repo.get_worktree().stage([relpath])
 
     def test_basic_inclusion_exclusion(self):
         """Given patterns, check correct set of included paths."""
@@ -237,7 +237,7 @@ class ComputeIncludedPathsConeTests(TestCase):
         os.makedirs(os.path.dirname(full), exist_ok=True)
         with open(full, "wb") as f:
             f.write(content)
-        self.repo.stage([relpath])
+        self.repo.get_worktree().stage([relpath])
 
     def test_cone_mode_patterns(self):
         """Simpler pattern handling in cone mode.
@@ -332,7 +332,7 @@ class DetermineIncludedPathsTests(TestCase):
         os.makedirs(os.path.dirname(path), exist_ok=True)
         with open(path, "wb") as f:
             f.write(b"data")
-        self.repo.stage([relpath])
+        self.repo.get_worktree().stage([relpath])
 
     def test_full_mode(self):
         self._add_file_to_index("foo.py")
@@ -370,9 +370,11 @@ class ApplyIncludedPathsTests(TestCase):
         os.makedirs(os.path.dirname(full), exist_ok=True)
         with open(full, "wb") as f:
             f.write(content)
-        self.repo.stage([relpath])
+        self.repo.get_worktree().stage([relpath])
         # Actually commit so the object is in the store
-        self.repo.do_commit(message=b"Commit " + relpath.encode())
+        self.repo.get_worktree().commit(
+            message=b"Commit " + relpath.encode(),
+        )
 
     def test_set_skip_worktree_bits(self):
         """If a path is not in included_paths, skip_worktree bit is set."""

+ 14 - 9
tests/test_stash.py

@@ -50,7 +50,10 @@ class StashTests(TestCase):
         tree.add(b"initial.txt", 0o100644, blob.id)
         tree_id = self.repo.object_store.add_object(tree)
 
-        self.commit_id = self.repo.do_commit(b"Initial commit", tree=tree_id)
+        self.commit_id = self.repo.get_worktree().commit(
+            message=b"Initial commit",
+            tree=tree_id,
+        )
 
     def tearDown(self):
         shutil.rmtree(self.test_dir)
@@ -77,7 +80,7 @@ class StashTests(TestCase):
         file_path = os.path.join(self.repo_dir, "testfile.txt")
         with open(file_path, "wb") as f:
             f.write(b"test data")
-        self.repo.stage(["testfile.txt"])
+        self.repo.get_worktree().stage(["testfile.txt"])
 
         # Push to stash
         commit_id = stash.push(message=b"Test stash message")
@@ -102,13 +105,13 @@ class StashTests(TestCase):
         file1_path = os.path.join(self.repo_dir, "testfile1.txt")
         with open(file1_path, "wb") as f:
             f.write(b"test data 1")
-        self.repo.stage(["testfile1.txt"])
+        self.repo.get_worktree().stage(["testfile1.txt"])
         commit_id1 = stash.push(message=b"Test stash 1")
 
         file2_path = os.path.join(self.repo_dir, "testfile2.txt")
         with open(file2_path, "wb") as f:
             f.write(b"test data 2")
-        self.repo.stage(["testfile2.txt"])
+        self.repo.get_worktree().stage(["testfile2.txt"])
         stash.push(message=b"Test stash 2")
 
         self.assertEqual(2, len(stash))
@@ -138,7 +141,7 @@ class StashTests(TestCase):
         file_path = os.path.join(self.repo_dir, "testfile.txt")
         with open(file_path, "wb") as f:
             f.write(b"test data")
-        self.repo.stage(["testfile.txt"])
+        self.repo.get_worktree().stage(["testfile.txt"])
 
         # Push to stash
         stash.push(message=b"Test stash message")
@@ -173,13 +176,15 @@ class StashTests(TestCase):
         tracked_path = os.path.join(self.repo_dir, "tracked.txt")
         with open(tracked_path, "wb") as f:
             f.write(b"original content")
-        self.repo.stage(["tracked.txt"])
-        self.repo.do_commit(b"Add tracked file")
+        self.repo.get_worktree().stage(["tracked.txt"])
+        self.repo.get_worktree().commit(
+            message=b"Add tracked file",
+        )
 
         # Modify the tracked file and stage it
         with open(tracked_path, "wb") as f:
             f.write(b"staged changes")
-        self.repo.stage(["tracked.txt"])
+        self.repo.get_worktree().stage(["tracked.txt"])
 
         # Modify it again but don't stage
         with open(tracked_path, "wb") as f:
@@ -189,7 +194,7 @@ class StashTests(TestCase):
         new_file_path = os.path.join(self.repo_dir, "new.txt")
         with open(new_file_path, "wb") as f:
             f.write(b"new file content")
-        self.repo.stage(["new.txt"])
+        self.repo.get_worktree().stage(["new.txt"])
 
         # Push to stash
         stash.push(message=b"Test stash with index")

+ 4 - 2
tests/test_submodule.py

@@ -68,8 +68,10 @@ class SubmoduleTests(TestCase):
             f.write(b"test file content")
 
         # Stage and commit the file to create some basic content
-        repo.stage(["file.txt"])
-        repo.do_commit(b"Initial commit")
+        repo.get_worktree().stage(["file.txt"])
+        repo.get_worktree().commit(
+            message=b"Initial commit",
+        )
 
         # Manually create the raw string for a tree with our file and a submodule
         # Format for tree entries: [mode] [name]\0[sha]

+ 16 - 8
tests/test_worktree.py

@@ -57,9 +57,9 @@ class WorkTreeTestCase(TestCase):
         # Create initial commit with a file
         with open(os.path.join(self.test_dir, "a"), "wb") as f:
             f.write(b"contents of file a")
-        self.repo.stage(["a"])
-        self.root_commit = self.repo.do_commit(
-            b"Initial commit",
+        self.repo.get_worktree().stage(["a"])
+        self.root_commit = self.repo.get_worktree().commit(
+            message=b"Initial commit",
             committer=b"Test Committer <test@nodomain.com>",
             author=b"Test Author <test@nodomain.com>",
             commit_timestamp=12345,
@@ -147,7 +147,9 @@ class WorkTreeStagingTests(WorkTreeTestCase):
         """Test staging a submodule."""
         r = self.repo
         s = Repo.init(os.path.join(r.path, "sub"), mkdir=True)
-        s.do_commit(b"message")
+        s.get_worktree().commit(
+            message=b"message",
+        )
         self.worktree.stage(["sub"])
         self.assertEqual([b"a", b"sub"], list(r.open_index()))
 
@@ -392,7 +394,9 @@ class WorkTreeBackwardCompatibilityTests(WorkTreeTestCase):
 
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter("always")
-            self.repo.stage(["new_file"])
+            self.repo.stage(
+                ["new_file"]
+            )  # Call deprecated method on Repo, not WorkTree
             self.assertTrue(len(w) > 0)
             self.assertTrue(issubclass(w[0].category, DeprecationWarning))
 
@@ -403,7 +407,7 @@ class WorkTreeBackwardCompatibilityTests(WorkTreeTestCase):
 
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter("always")
-            self.repo.unstage(["a"])
+            self.repo.unstage(["a"])  # Call deprecated method on Repo, not WorkTree
             self.assertTrue(len(w) > 0)
             self.assertTrue(issubclass(w[0].category, DeprecationWarning))
 
@@ -414,7 +418,9 @@ class WorkTreeBackwardCompatibilityTests(WorkTreeTestCase):
         # Test get_sparse_checkout_patterns
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter("always")
-            patterns = self.repo.get_sparse_checkout_patterns()
+            patterns = (
+                self.repo.get_sparse_checkout_patterns()
+            )  # Call deprecated method on Repo
             self.assertEqual([], patterns)
             self.assertTrue(len(w) > 0)
             self.assertTrue(issubclass(w[0].category, DeprecationWarning))
@@ -422,7 +428,9 @@ class WorkTreeBackwardCompatibilityTests(WorkTreeTestCase):
         # Test set_sparse_checkout_patterns
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter("always")
-            self.repo.set_sparse_checkout_patterns(["*.py"])
+            self.repo.set_sparse_checkout_patterns(
+                ["*.py"]
+            )  # Call deprecated method on Repo
             self.assertTrue(len(w) > 0)
             self.assertTrue(issubclass(w[0].category, DeprecationWarning))