Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: Extend Git class for deleting tags, reset and pushing refspecs #979

Merged
merged 1 commit into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pontos/git/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Git,
GitError,
MergeStrategy,
ResetMode,
TagSort,
)
from ._status import Status, StatusEntry
Expand All @@ -19,6 +20,7 @@
"Git",
"GitError",
"MergeStrategy",
"ResetMode",
"Status",
"StatusEntry",
"TagSort",
Expand Down
94 changes: 76 additions & 18 deletions pontos/git/_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@
#

import subprocess
from enum import Enum
from os import PathLike, fspath
from pathlib import Path
from typing import (
Collection,
Iterable,
Iterator,
List,
Optional,
Sequence,
Union,
)

from pontos.enum import StrEnum
from pontos.errors import PontosError

from ._status import StatusEntry, parse_git_status
Expand Down Expand Up @@ -85,7 +84,7 @@ def exec_git(
raise GitError(e.returncode, e.cmd, e.output, e.stderr) from None


class MergeStrategy(Enum):
class MergeStrategy(StrEnum):
"""
Possible strategies for a merge

Expand All @@ -107,7 +106,7 @@ class MergeStrategy(Enum):
SUBTREE = "subtree"


class ConfigScope(Enum):
class ConfigScope(StrEnum):
"""
Possible scopes for git settings

Expand All @@ -126,7 +125,7 @@ class ConfigScope(Enum):
WORKTREE = "worktree"


class TagSort(Enum):
class TagSort(StrEnum):
"""
Sorting for git tags

Expand All @@ -137,6 +136,14 @@ class TagSort(Enum):
VERSION = "version:refname"


class ResetMode(StrEnum):
SOFT = "soft"
MIXED = "mixed"
HARD = "hard"
MERGE = "merge"
KEEP = "keep"


class Git:
"""
Run git commands as subprocesses
Expand Down Expand Up @@ -231,7 +238,7 @@ def rebase(
if strategy == MergeStrategy.ORT_OURS:
args.extend(["--strategy", "ort", "-X", "ours"])
else:
args.extend(["--strategy", strategy.value])
args.extend(["--strategy", str(strategy)])

if onto:
args.extend(["--onto", onto])
Expand Down Expand Up @@ -274,32 +281,43 @@ def clone(

def push(
self,
refspec: Optional[Union[str, Iterable[str]]] = None,
*,
remote: Optional[str] = None,
branch: Optional[str] = None,
follow_tags: bool = False,
force: Optional[bool] = None,
delete: Optional[bool] = None,
) -> None:
"""
Push changes to remote repository

Args:
refspec: Refs to push
remote: Push changes to the named remote
branch: Branch to push. Will only be considered in combination with
a remote.
branch: Branch to push. Will only be considered in
combination with a remote. Deprecated, use refs instead.
follow_tags: Push all tags pointing to a commit included in the to
be pushed branch.
be pushed branch.
force: Force push changes.
delete: Delete remote refspec
"""
args = ["push"]
if follow_tags:
args.append("--follow-tags")
if force:
args.append("--force")
if delete:
args.append("--delete")
if remote:
args.append(remote)
if branch:
args.append(branch)
if refspec:
if isinstance(refspec, str):
args.append(refspec)
else:
args.extend(refspec)

self.exec(*args)

Expand All @@ -308,7 +326,7 @@ def config(
key: str,
value: Optional[str] = None,
*,
scope: Optional[ConfigScope] = None,
scope: Optional[Union[ConfigScope, str]] = None,
) -> str:
"""
Get and set a git config
Expand All @@ -320,7 +338,7 @@ def config(
"""
args = ["config"]
if scope:
args.append(f"--{scope.value}")
args.append(f"--{scope}")

args.append(key)

Expand All @@ -329,7 +347,7 @@ def config(

return self.exec(*args)

def cherry_pick(self, commits: Union[str, List[str]]) -> None:
def cherry_pick(self, commits: Union[str, list[str]]) -> None:
"""
Apply changes of a commit(s) to the current branch

Expand All @@ -348,10 +366,10 @@ def cherry_pick(self, commits: Union[str, List[str]]) -> None:
def list_tags(
self,
*,
sort: Optional[TagSort] = None,
sort: Optional[Union[TagSort, str]] = None,
tag_name: Optional[str] = None,
sort_suffix: Optional[List[str]] = None,
) -> List[str]:
sort_suffix: Optional[list[str]] = None,
) -> list[str]:
"""
List all available tags

Expand All @@ -370,7 +388,7 @@ def list_tags(
args.extend(["-c", f"versionsort.suffix={suffix}"])

args.extend(["tag", "-l"])
args.append(f"--sort={sort.value}")
args.append(f"--sort={sort}")
else:
args = ["tag", "-l"]

Expand Down Expand Up @@ -463,6 +481,19 @@ def tag(

self.exec(*args)

def delete_tag(
self,
tag: str,
) -> None:
"""
Delete a Tag

Args:
tag: Tag name to delete
"""
args = ["tag", "-d", tag]
self.exec(*args)

def fetch(
self,
remote: Optional[str] = None,
Expand Down Expand Up @@ -538,7 +569,7 @@ def log(
*log_args: str,
oneline: Optional[bool] = None,
format: Optional[str] = None,
) -> List[str]:
) -> list[str]:
"""
Get log of a git repository

Expand Down Expand Up @@ -614,7 +645,7 @@ def rev_list(
*commit: str,
max_parents: Optional[int] = None,
abbrev_commit: Optional[bool] = False,
) -> List[str]:
) -> list[str]:
"""
Lists commit objects in reverse chronological order

Expand Down Expand Up @@ -693,3 +724,30 @@ def status(

output = self.exec(*args)
return parse_git_status(output)

def reset(
self,
commit,
*,
mode: Union[ResetMode, str],
) -> None:
"""
Reset the git history

Args:
commit: Git reference to reset the checked out tree to
mode: The reset mode to use

Examples:
This will "list all the commits which are reachable from foo or
bar, but not from baz".

.. code-block:: python

from pontos.git import Git, ResetMode

git = Git()
git.reset("HEAD^", mode=ResetMode.HARD)
"""
args = ["reset", f"--{mode}", commit]
self.exec(*args)
51 changes: 51 additions & 0 deletions tests/git/test_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
Git,
GitError,
MergeStrategy,
ResetMode,
Status,
TagSort,
)
Expand Down Expand Up @@ -193,6 +194,40 @@ def test_push_with_force_false(self, exec_git_mock):

exec_git_mock.assert_called_once_with("push", cwd=None)

@patch("pontos.git._git.exec_git")
def test_push_branch_with_delete(self, exec_git_mock):
git = Git()
git.push(delete=True, branch="v1.2.3", remote="origin")

exec_git_mock.assert_called_once_with(
"push", "--delete", "origin", "v1.2.3", cwd=None
)

@patch("pontos.git._git.exec_git")
def test_push_refspec_with_delete(self, exec_git_mock):
git = Git()
git.push("v1.2.3", delete=True)

exec_git_mock.assert_called_once_with(
"push", "--delete", "v1.2.3", cwd=None
)

@patch("pontos.git._git.exec_git")
def test_push_refspec(self, exec_git_mock):
git = Git()
git.push("v1.2.3")

exec_git_mock.assert_called_once_with("push", "v1.2.3", cwd=None)

@patch("pontos.git._git.exec_git")
def test_push_refspecs(self, exec_git_mock):
git = Git()
git.push(["v1.2.3", "main"])

exec_git_mock.assert_called_once_with(
"push", "v1.2.3", "main", cwd=None
)

@patch("pontos.git._git.exec_git")
def test_config_get(self, exec_git_mock):
git = Git()
Expand Down Expand Up @@ -757,6 +792,22 @@ def test_show_with_no_patch(self, exec_git_mock: MagicMock):

self.assertEqual(show, content.strip())

@patch("pontos.git._git.exec_git")
def test_delete_tag(self, exec_git_mock):
git = Git()
git.delete_tag("v1.2.3")

exec_git_mock.assert_called_once_with("tag", "-d", "v1.2.3", cwd=None)

@patch("pontos.git._git.exec_git")
def test_reset_mixed(self, exec_git_mock):
git = Git()
git.reset("c1234", mode=ResetMode.MIXED)

exec_git_mock.assert_called_once_with(
"reset", "--mixed", "c1234", cwd=None
)


class GitExtendedTestCase(unittest.TestCase):
def test_semantic_list_tags(self):
Expand Down
Loading