Procházet zdrojové kódy

Add rebase support to porcelain and CLI

- Add rebase() function to porcelain module
- Add cmd_rebase to CLI with support for:
  - Basic rebase operation
  - --onto option
  - --branch option
  - --abort to abort in-progress rebase
  - --continue to continue after resolving conflicts
  - --skip to skip current commit (placeholder)
Jelmer Vernooij před 1 měsícem
rodič
revize
73ced29d9b
2 změnil soubory, kde provedl 146 přidání a 0 odebrání
  1. 67 0
      dulwich/cli.py
  2. 79 0
      dulwich/porcelain.py

+ 67 - 0
dulwich/cli.py

@@ -1168,6 +1168,72 @@ class cmd_count_objects(Command):
             print(f"{stats.count} objects, {stats.size // 1024} kilobytes")
 
 
+class cmd_rebase(Command):
+    def run(self, args) -> Optional[int]:
+        parser = argparse.ArgumentParser()
+        parser.add_argument(
+            "upstream", nargs="?", help="Upstream branch to rebase onto"
+        )
+        parser.add_argument("--onto", type=str, help="Rebase onto specific commit")
+        parser.add_argument(
+            "--branch", type=str, help="Branch to rebase (default: current)"
+        )
+        parser.add_argument(
+            "--abort", action="store_true", help="Abort an in-progress rebase"
+        )
+        parser.add_argument(
+            "--continue",
+            dest="continue_rebase",
+            action="store_true",
+            help="Continue an in-progress rebase",
+        )
+        parser.add_argument(
+            "--skip", action="store_true", help="Skip current commit and continue"
+        )
+        args = parser.parse_args(args)
+
+        # Handle abort/continue/skip first
+        if args.abort:
+            try:
+                porcelain.rebase(".", None, abort=True)
+                print("Rebase aborted.")
+            except porcelain.Error as e:
+                print(f"Error: {e}")
+                return 1
+            return
+
+        if args.continue_rebase:
+            try:
+                new_shas = porcelain.rebase(".", None, continue_rebase=True)
+                print("Rebase complete.")
+            except porcelain.Error as e:
+                print(f"Error: {e}")
+                return 1
+            return
+
+        # Normal rebase requires upstream
+        if not args.upstream:
+            print("Error: Missing required argument 'upstream'")
+            return 1
+
+        try:
+            new_shas = porcelain.rebase(
+                ".",
+                args.upstream,
+                onto=args.onto,
+                branch=args.branch,
+            )
+
+            if new_shas:
+                print(f"Successfully rebased {len(new_shas)} commits.")
+            else:
+                print("Already up to date.")
+
+        except porcelain.Error as e:
+            print(f"Error: {e}")
+            return 1
+
+
 class cmd_help(Command):
     def run(self, args) -> None:
         parser = argparse.ArgumentParser()
@@ -1228,6 +1294,7 @@ commands = {
     "pack-refs": cmd_pack_refs,
     "pull": cmd_pull,
     "push": cmd_push,
+    "rebase": cmd_rebase,
     "receive-pack": cmd_receive_pack,
     "remote": cmd_remote,
     "repack": cmd_repack,

+ 79 - 0
dulwich/porcelain.py

@@ -3010,3 +3010,82 @@ def count_objects(repo=".", verbose=False) -> CountObjectsResult:
             packs=pack_count,
             size_pack=pack_size,
         )
+
+
+def rebase(
+    repo: Union[Repo, str],
+    upstream: Union[bytes, str],
+    onto: Optional[Union[bytes, str]] = None,
+    branch: Optional[Union[bytes, str]] = None,
+    abort: bool = False,
+    continue_rebase: bool = False,
+    skip: bool = False,
+) -> list[bytes]:
+    """Rebase commits onto another branch.
+
+    Args:
+      repo: Repository to rebase in
+      upstream: Upstream branch/commit to rebase onto
+      onto: Specific commit to rebase onto (defaults to upstream)
+      branch: Branch to rebase (defaults to current branch)
+      abort: Abort an in-progress rebase
+      continue_rebase: Continue an in-progress rebase
+      skip: Skip current commit and continue rebase
+
+    Returns:
+      List of new commit SHAs created by rebase
+
+    Raises:
+      Error: If rebase fails or conflicts occur
+    """
+    from .rebase import RebaseConflict, RebaseError, Rebaser
+
+    with open_repo_closing(repo) as r:
+        rebaser = Rebaser(r)
+
+        if abort:
+            try:
+                rebaser.abort_rebase()
+                return []
+            except RebaseError as e:
+                raise Error(str(e))
+
+        if continue_rebase:
+            try:
+                result = rebaser.continue_rebase()
+                if result is None:
+                    # Rebase complete
+                    return []
+                elif isinstance(result, tuple) and result[1]:
+                    # Still have conflicts
+                    raise Error(
+                        f"Conflicts in: {', '.join(f.decode('utf-8', 'replace') for f in result[1])}"
+                    )
+            except RebaseError as e:
+                raise Error(str(e))
+
+        # Convert string refs to bytes
+        if isinstance(upstream, str):
+            upstream = upstream.encode("utf-8")
+        if isinstance(onto, str):
+            onto = onto.encode("utf-8") if onto else None
+        if isinstance(branch, str):
+            branch = branch.encode("utf-8") if branch else None
+
+        try:
+            # Start rebase
+            rebaser.start_rebase(upstream, onto, branch)
+
+            # Continue rebase automatically
+            result = rebaser.continue_rebase()
+            if result is not None:
+                # Conflicts
+                raise RebaseConflict(result[1])
+
+            # Return the SHAs of the rebased commits
+            return [c.id for c in rebaser._done]
+
+        except RebaseConflict as e:
+            raise Error(str(e))
+        except RebaseError as e:
+            raise Error(str(e))