This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 37b5cb9863 [python] Support rename branch api (#7561)
37b5cb9863 is described below
commit 37b5cb98636bc4291d6eca00cc173c44faa80ef1
Author: xuzifu666 <[email protected]>
AuthorDate: Tue Mar 31 16:27:42 2026 +0800
[python] Support rename branch api (#7561)
---
docs/content/pypaimon/python-api.md | 13 ++++++
paimon-python/pypaimon/branch/branch_manager.py | 13 ++++++
.../pypaimon/branch/catalog_branch_manager.py | 16 +++++++
.../pypaimon/branch/filesystem_branch_manager.py | 54 ++++++++++++++++++++++
paimon-python/pypaimon/table/file_store_table.py | 15 ++++++
.../pypaimon/tests/table/file_store_table_test.py | 46 ++++++++++++++++++
6 files changed, 157 insertions(+)
diff --git a/docs/content/pypaimon/python-api.md
b/docs/content/pypaimon/python-api.md
index 7cdd86d731..197a018ef1 100644
--- a/docs/content/pypaimon/python-api.md
+++ b/docs/content/pypaimon/python-api.md
@@ -915,6 +915,19 @@ Delete an existing branch:
table.branch_manager().drop_branch('feature_branch')
```
+### Rename Branch
+
+Rename an existing branch to a new name:
+
+```python
+# Rename a branch
+table.branch_manager().rename_branch('old_branch_name', 'new_branch_name')
+```
+
+{{< hint warning >}}
+The source branch must exist and cannot be the main branch. The target branch
name must be valid and not already exist.
+{{< /hint >}}
+
### Fast Forward
Fast forward the main branch to a specific branch:
diff --git a/paimon-python/pypaimon/branch/branch_manager.py
b/paimon-python/pypaimon/branch/branch_manager.py
index cb2b1e87ad..fbd5fede38 100644
--- a/paimon-python/pypaimon/branch/branch_manager.py
+++ b/paimon-python/pypaimon/branch/branch_manager.py
@@ -65,6 +65,19 @@ class BranchManager:
"""
raise NotImplementedError("Subclasses must implement drop_branch")
+ def rename_branch(self, from_branch: str, to_branch: str) -> None:
+ """
+ Rename a branch.
+
+ Args:
+ from_branch: Current name of the branch
+ to_branch: New name for the branch
+
+ Raises:
+ NotImplementedError: Subclasses must implement this method
+ """
+ raise NotImplementedError("Subclasses must implement rename_branch")
+
def fast_forward(self, branch_name: str) -> None:
"""
Fast forward the current branch to the specified branch.
diff --git a/paimon-python/pypaimon/branch/catalog_branch_manager.py
b/paimon-python/pypaimon/branch/catalog_branch_manager.py
index a70c6ace99..1cffc60f4e 100644
--- a/paimon-python/pypaimon/branch/catalog_branch_manager.py
+++ b/paimon-python/pypaimon/branch/catalog_branch_manager.py
@@ -121,6 +121,22 @@ class CatalogBranchManager(BranchManager):
self._execute_post(_drop)
+ def rename_branch(self, from_branch: str, to_branch: str) -> None:
+ """
+ Rename a branch.
+
+ Args:
+ from_branch: Current name of the branch
+ to_branch: New name for the branch
+
+ Raises:
+ ValueError: If from_branch or to_branch is invalid
+ """
+ def _rename(catalog: Catalog):
+ catalog.rename_branch(self.identifier, from_branch, to_branch)
+
+ self._execute_post(_rename)
+
def fast_forward(self, branch_name: str) -> None:
"""
Fast forward the current branch to the specified branch.
diff --git a/paimon-python/pypaimon/branch/filesystem_branch_manager.py
b/paimon-python/pypaimon/branch/filesystem_branch_manager.py
index e5c2982c39..ca8111b62f 100644
--- a/paimon-python/pypaimon/branch/filesystem_branch_manager.py
+++ b/paimon-python/pypaimon/branch/filesystem_branch_manager.py
@@ -199,6 +199,60 @@ class FileSystemBranchManager(BranchManager):
)
raise RuntimeError(f"Failed to delete branch '{branch_name}'")
from e
+ def rename_branch(self, from_branch: str, to_branch: str) -> None:
+ """
+ Rename a branch.
+
+ Args:
+ from_branch: Current name of the branch
+ to_branch: New name for the branch
+
+ Raises:
+ ValueError: If from_branch or to_branch is blank, from_branch
doesn't exist,
+ to_branch already exists, or trying to rename the main
branch
+ RuntimeError: If the rename operation fails
+ """
+ # Check if from_branch is main branch
+ if self.is_main_branch(from_branch):
+ raise ValueError(f"Cannot rename the main branch '{from_branch}'.")
+
+ # Check if from_branch is blank
+ if not from_branch or from_branch.isspace():
+ raise ValueError("Source branch name shouldn't be blank.")
+
+ # Check if to_branch is blank
+ if not to_branch or to_branch.isspace():
+ raise ValueError("Target branch name shouldn't be blank.")
+
+ # Validate the new branch name
+ try:
+ self.validate_branch(to_branch)
+ except ValueError as e:
+ raise ValueError(f"Invalid target branch name: {e}")
+
+ # Check if from_branch exists
+ if not self.branch_exists(from_branch):
+ raise ValueError(f"Source branch '{from_branch}' doesn't exist.")
+
+ # Check if to_branch already exists
+ if self.branch_exists(to_branch):
+ raise ValueError(f"Target branch '{to_branch}' already exists.")
+
+ try:
+ # Rename the branch directory
+ from_path = self.branch_path(from_branch)
+ to_path = self.branch_path(to_branch)
+ self.file_io.rename(from_path, to_path)
+ logger.info(f"Successfully renamed branch from '{from_branch}' to
'{to_branch}'")
+ except Exception as e:
+ logger.warning(
+ f"Renaming branch from '{from_branch}' to '{to_branch}' failed
due to an exception. "
+ f"Please try again."
+ )
+ raise RuntimeError(
+ f"Failed to rename branch from '{from_branch}' to
'{to_branch}'"
+ ) from e
+
def fast_forward(self, branch_name: str) -> None:
"""
Fast forward the current branch to the specified branch.
diff --git a/paimon-python/pypaimon/table/file_store_table.py
b/paimon-python/pypaimon/table/file_store_table.py
index d725d48021..9115c8e885 100644
--- a/paimon-python/pypaimon/table/file_store_table.py
+++ b/paimon-python/pypaimon/table/file_store_table.py
@@ -132,6 +132,21 @@ class FileStoreTable(Table):
from pypaimon.changelog.changelog_manager import ChangelogManager
return ChangelogManager(self.file_io, self.table_path,
self.current_branch())
+ def rename_branch(self, from_branch: str, to_branch: str) -> None:
+ """
+ Rename a branch.
+
+ Args:
+ from_branch: Current name of the branch
+ to_branch: New name for the branch
+
+ Raises:
+ ValueError: If from_branch or to_branch is blank, from_branch
doesn't exist,
+ or to_branch already exists
+ """
+ branch_mgr = self.branch_manager()
+ branch_mgr.rename_branch(from_branch, to_branch)
+
def create_tag(
self,
tag_name: str,
diff --git a/paimon-python/pypaimon/tests/table/file_store_table_test.py
b/paimon-python/pypaimon/tests/table/file_store_table_test.py
index 72c1ee7e2c..22068d3a01 100644
--- a/paimon-python/pypaimon/tests/table/file_store_table_test.py
+++ b/paimon-python/pypaimon/tests/table/file_store_table_test.py
@@ -339,6 +339,52 @@ class FileStoreTableTest(unittest.TestCase):
self.assertEqual(copied_table.identifier, self.table.identifier)
self.assertEqual(copied_table.table_path, self.table.table_path)
+ def test_rename_branch_basic(self):
+ """Test rename_branch method."""
+ # Get branch_manager
+ branch_manager = self.table.branch_manager()
+
+ # Create a branch first
+ branch_manager.create_branch("old-branch")
+ self.assertTrue(branch_manager.branch_exists("old-branch"))
+
+ # Rename the branch using table's rename_branch method
+ self.table.rename_branch("old-branch", "new-branch")
+
+ # Verify old branch doesn't exist
+ self.assertFalse(branch_manager.branch_exists("old-branch"))
+
+ # Verify new branch exists
+ self.assertTrue(branch_manager.branch_exists("new-branch"))
+
+ def test_rename_branch_from_nonexistent(self):
+ """Test renaming from non-existent branch raises error."""
+ with self.assertRaises(ValueError) as context:
+ self.table.rename_branch("nonexistent-branch", "new-branch")
+
+ self.assertIn("doesn't exist", str(context.exception))
+
+ def test_rename_branch_to_existing(self):
+ """Test renaming to existing branch raises error."""
+ # Get branch_manager
+ branch_manager = self.table.branch_manager()
+
+ # Create two branches
+ branch_manager.create_branch("branch1")
+ branch_manager.create_branch("branch2")
+
+ with self.assertRaises(ValueError) as context:
+ self.table.rename_branch("branch1", "branch2")
+
+ self.assertIn("already exists", str(context.exception))
+
+ def test_rename_main_branch_fails(self):
+ """Test renaming main branch raises error."""
+ with self.assertRaises(ValueError) as context:
+ self.table.rename_branch("main", "new-branch")
+
+ self.assertIn("main branch", str(context.exception))
+
def test_comment_none(self):
"""Test that comment() returns None when table has no comment."""
# Default table created without comment should return None