git-version-sync 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- git_version_sync/__init__.py +0 -0
- git_version_sync/__main__.py +4 -0
- git_version_sync/cli.py +36 -0
- git_version_sync/core/__init__.py +3 -0
- git_version_sync/core/bump.py +107 -0
- git_version_sync/core/check.py +101 -0
- git_version_sync/core/git.py +66 -0
- git_version_sync/core/sync.py +34 -0
- git_version_sync/models/__init__.py +1 -0
- git_version_sync/models/bump.py +13 -0
- git_version_sync/parser.py +83 -0
- git_version_sync/utils/__init__.py +1 -0
- git_version_sync/utils/config.py +20 -0
- git_version_sync-1.4.0.dist-info/METADATA +136 -0
- git_version_sync-1.4.0.dist-info/RECORD +17 -0
- git_version_sync-1.4.0.dist-info/WHEEL +4 -0
- git_version_sync-1.4.0.dist-info/entry_points.txt +3 -0
|
File without changes
|
git_version_sync/cli.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from git_version_sync.models import BumpRequest
|
|
2
|
+
from .parser import create_parser
|
|
3
|
+
from .core import do_check, do_bump, do_sync
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
parser = create_parser()
|
|
7
|
+
args = parser.parse_args()
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
match args.command:
|
|
11
|
+
case 'bump':
|
|
12
|
+
bump_request = BumpRequest(
|
|
13
|
+
args.part,
|
|
14
|
+
tag_message=args.message,
|
|
15
|
+
force=args.force,
|
|
16
|
+
push=args.push,
|
|
17
|
+
release=args.release,
|
|
18
|
+
draft=args.draft,
|
|
19
|
+
)
|
|
20
|
+
print(do_bump(bump_request))
|
|
21
|
+
|
|
22
|
+
case 'sync':
|
|
23
|
+
print(do_sync(args.to_git, args.to_config))
|
|
24
|
+
|
|
25
|
+
case 'check':
|
|
26
|
+
print(do_check(args.fetch))
|
|
27
|
+
|
|
28
|
+
case _:
|
|
29
|
+
print(f"Invalid command {args.command}")
|
|
30
|
+
|
|
31
|
+
print("\n")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
print(f"{e}")
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import subprocess, re
|
|
2
|
+
from packaging.version import Version
|
|
3
|
+
|
|
4
|
+
from .git import commit_config_change, push_to_remote, create_github_release
|
|
5
|
+
from .check import parse_highest_verion, get_local_tags, get_config_tag
|
|
6
|
+
from ..models import BumpRequest
|
|
7
|
+
from ..utils import get_config_path
|
|
8
|
+
|
|
9
|
+
def bump_git_tag(new_version: Version, message: str|None = None) -> None:
|
|
10
|
+
msg = message if message and message.strip() else f"bump version to v{new_version}"
|
|
11
|
+
command = [
|
|
12
|
+
'git',
|
|
13
|
+
'tag',
|
|
14
|
+
'-a', f'v{new_version}',
|
|
15
|
+
'-m', msg
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
subprocess.run(
|
|
19
|
+
command,
|
|
20
|
+
capture_output=True,
|
|
21
|
+
text=True,
|
|
22
|
+
check=True
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def bump_config_version(new_version: Version) -> None:
|
|
26
|
+
config_path = get_config_path()
|
|
27
|
+
content = config_path.read_text(encoding="utf-8")
|
|
28
|
+
|
|
29
|
+
pattern = r'^(version\s*=\s*["\']).*?(["\'])'
|
|
30
|
+
replacement = rf'\g<1>{new_version}\g<2>'
|
|
31
|
+
|
|
32
|
+
new_content, count = re.subn(pattern, replacement, content, flags=re.MULTILINE)
|
|
33
|
+
|
|
34
|
+
if count == 0:
|
|
35
|
+
raise RuntimeError(f"Version field not found in {config_path.stem}")
|
|
36
|
+
|
|
37
|
+
config_path.write_text(new_content, encoding="utf-8")
|
|
38
|
+
|
|
39
|
+
def bump_version(
|
|
40
|
+
request: BumpRequest,
|
|
41
|
+
new_version: Version,
|
|
42
|
+
) -> None:
|
|
43
|
+
print("Updating pyproject.toml version...")
|
|
44
|
+
bump_config_version(new_version)
|
|
45
|
+
|
|
46
|
+
print("Committing new change...")
|
|
47
|
+
commit_config_change(new_version)
|
|
48
|
+
|
|
49
|
+
print(f"Creating git tag v{new_version}...")
|
|
50
|
+
bump_git_tag(new_version, request.tag_message)
|
|
51
|
+
|
|
52
|
+
if request.push:
|
|
53
|
+
print("Pushing commit and tag to remote...")
|
|
54
|
+
push_to_remote(new_version)
|
|
55
|
+
|
|
56
|
+
if request.release is not None:
|
|
57
|
+
print(f"Creating GitHub Release for v{new_version}...")
|
|
58
|
+
create_github_release(new_version, request.release, request.draft)
|
|
59
|
+
|
|
60
|
+
def get_new_major(version: Version) -> str:
|
|
61
|
+
return f"{version.major + 1}.0.0"
|
|
62
|
+
|
|
63
|
+
def get_new_minor(version: Version) -> str:
|
|
64
|
+
return f"{version.major}.{version.minor + 1}.0"
|
|
65
|
+
|
|
66
|
+
def get_new_patch(version: Version):
|
|
67
|
+
return f"{version.major}.{version.minor}.{version.micro + 1}"
|
|
68
|
+
|
|
69
|
+
def do_bump(request: BumpRequest):
|
|
70
|
+
config_tag = get_config_tag()
|
|
71
|
+
local_tags = get_local_tags()
|
|
72
|
+
highest_local_tag = parse_highest_verion(local_tags)
|
|
73
|
+
|
|
74
|
+
if config_tag != highest_local_tag and not request.force:
|
|
75
|
+
raise RuntimeError(
|
|
76
|
+
f"Version mismatch detected!\n"
|
|
77
|
+
f" Config: v{config_tag}\n"
|
|
78
|
+
f" Git : v{highest_local_tag}\n"
|
|
79
|
+
f"Please run `git-version-sync sync` first or fix the mismatch."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if highest_local_tag:
|
|
83
|
+
old_version = max(config_tag, highest_local_tag)
|
|
84
|
+
else:
|
|
85
|
+
old_version = config_tag
|
|
86
|
+
|
|
87
|
+
config_path = get_config_path()
|
|
88
|
+
|
|
89
|
+
if not config_path.exists():
|
|
90
|
+
raise RuntimeError(f"Config file not found: {config_path}")
|
|
91
|
+
|
|
92
|
+
match request.bump_type:
|
|
93
|
+
case "major":
|
|
94
|
+
new_version = get_new_major(old_version)
|
|
95
|
+
case "minor":
|
|
96
|
+
new_version = get_new_minor(old_version)
|
|
97
|
+
case "patch":
|
|
98
|
+
new_version = get_new_patch(old_version)
|
|
99
|
+
|
|
100
|
+
new_version = Version(new_version)
|
|
101
|
+
|
|
102
|
+
bump_version(
|
|
103
|
+
request,
|
|
104
|
+
new_version
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return f"\nSuccess bump version to v{new_version}"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import subprocess, tomllib
|
|
2
|
+
from packaging.version import Version
|
|
3
|
+
from git_version_sync.utils import get_git_path, get_config_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_local_tags() -> set[str]:
|
|
7
|
+
command = ["git", "tag", "--list"]
|
|
8
|
+
result = subprocess.run(
|
|
9
|
+
command,
|
|
10
|
+
capture_output=True,
|
|
11
|
+
text=True,
|
|
12
|
+
check=True,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
tags = {tag for tag in result.stdout.strip().splitlines()}
|
|
16
|
+
|
|
17
|
+
return tags
|
|
18
|
+
|
|
19
|
+
def parse_highest_verion(tags: set[str]) -> Version|None:
|
|
20
|
+
valid_version = []
|
|
21
|
+
for tag in tags:
|
|
22
|
+
try:
|
|
23
|
+
clean_tag = tag.removeprefix("v")
|
|
24
|
+
valid_version.append(Version(clean_tag))
|
|
25
|
+
except Exception:
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
return max(valid_version) if valid_version else None
|
|
29
|
+
|
|
30
|
+
def get_config_tag() -> Version:
|
|
31
|
+
config_path = get_config_path()
|
|
32
|
+
|
|
33
|
+
with config_path.open('rb') as f:
|
|
34
|
+
config = tomllib.load(f)
|
|
35
|
+
project_config = config.get('project', {})
|
|
36
|
+
config_tag = project_config.get("version", None)
|
|
37
|
+
|
|
38
|
+
if config_tag is None:
|
|
39
|
+
raise ValueError("No version found in project config.")
|
|
40
|
+
|
|
41
|
+
return Version(config_tag)
|
|
42
|
+
|
|
43
|
+
def get_remote_tags() -> set[str]:
|
|
44
|
+
command = ['git', 'ls-remote', '--tags', 'origin']
|
|
45
|
+
result = subprocess.run(
|
|
46
|
+
command,
|
|
47
|
+
capture_output=True,
|
|
48
|
+
text=True,
|
|
49
|
+
check=True
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if result.returncode != 0:
|
|
53
|
+
return set()
|
|
54
|
+
|
|
55
|
+
remote_tags = set()
|
|
56
|
+
for line in result.stdout.strip().splitlines():
|
|
57
|
+
if not line:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
parts = line.split()
|
|
61
|
+
if len(parts) == 2:
|
|
62
|
+
ref = parts[1]
|
|
63
|
+
if ref.endswith("^{}"):
|
|
64
|
+
continue
|
|
65
|
+
tag_name = ref.removeprefix("refs/tags/")
|
|
66
|
+
remote_tags.add(tag_name)
|
|
67
|
+
|
|
68
|
+
return remote_tags
|
|
69
|
+
|
|
70
|
+
def do_check(fetch_true: bool = False) -> str:
|
|
71
|
+
config_tag = get_config_tag()
|
|
72
|
+
local_tags = get_local_tags()
|
|
73
|
+
remote_tags = get_remote_tags()
|
|
74
|
+
|
|
75
|
+
output = []
|
|
76
|
+
|
|
77
|
+
highest_local_version = parse_highest_verion(local_tags)
|
|
78
|
+
if highest_local_version is None:
|
|
79
|
+
return "No local tags found."
|
|
80
|
+
|
|
81
|
+
if highest_local_version == config_tag:
|
|
82
|
+
output.append(f"Version is synchronized with highest local tag (v{config_tag})")
|
|
83
|
+
else:
|
|
84
|
+
output.append(
|
|
85
|
+
f"Version mismatch\n"
|
|
86
|
+
f"Git Local: {highest_local_version}\n"
|
|
87
|
+
f"Config: {config_tag}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if fetch_true:
|
|
91
|
+
missing_in_local = remote_tags - local_tags
|
|
92
|
+
if missing_in_local:
|
|
93
|
+
output.append(f"New tag(s) found from remote: ")
|
|
94
|
+
for tag in sorted(missing_in_local):
|
|
95
|
+
output.append(f" - {tag}")
|
|
96
|
+
output.append("")
|
|
97
|
+
|
|
98
|
+
return "\n".join(output)
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
print(do_check())
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import subprocess, shutil
|
|
2
|
+
|
|
3
|
+
from packaging.version import Version
|
|
4
|
+
from git_version_sync.utils import get_config_path
|
|
5
|
+
|
|
6
|
+
def commit_config_change(new_version: Version) -> None:
|
|
7
|
+
subprocess.run([
|
|
8
|
+
'git', 'add', str(get_config_path())],
|
|
9
|
+
capture_output=True,
|
|
10
|
+
text=True,
|
|
11
|
+
check=True
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
commit_msg = f"chore({get_config_path().name}): bump version to v{new_version}"
|
|
16
|
+
subprocess.run(
|
|
17
|
+
['git', 'commit', '-m', commit_msg],
|
|
18
|
+
capture_output=True,
|
|
19
|
+
text=True,
|
|
20
|
+
check=True
|
|
21
|
+
)
|
|
22
|
+
except subprocess.CalledProcessError as e:
|
|
23
|
+
output = (e.stdout or "") + (e.stderr or "")
|
|
24
|
+
if "nothing to commit" in output:
|
|
25
|
+
return
|
|
26
|
+
raise RuntimeError(f"Git commit failed: \n{e.stderr.strip()}") from e
|
|
27
|
+
|
|
28
|
+
def push_to_remote(new_version: Version) -> None:
|
|
29
|
+
try:
|
|
30
|
+
command = [
|
|
31
|
+
'git', 'push',
|
|
32
|
+
'origin', 'HEAD',
|
|
33
|
+
f'v{new_version}'
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
subprocess.run(
|
|
37
|
+
command,
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
check=True
|
|
41
|
+
)
|
|
42
|
+
except subprocess.CalledProcessError as e:
|
|
43
|
+
raise RuntimeError(f"Failed to push to remote: \n{e.stderr.strip()}") from e
|
|
44
|
+
|
|
45
|
+
def create_github_release(version: Version, message: str|None=None, draft: bool=False):
|
|
46
|
+
tag_name = f"v{version}"
|
|
47
|
+
command = ['gh', 'release', 'create', tag_name, '--generate-notes']
|
|
48
|
+
|
|
49
|
+
if not shutil.which('gh'):
|
|
50
|
+
raise RuntimeError("Github CLI ('gh') not installed yet, please install 'gh' first.")
|
|
51
|
+
|
|
52
|
+
if message and message.strip():
|
|
53
|
+
command.extend(['--notes', message])
|
|
54
|
+
if draft:
|
|
55
|
+
command.extend(['--draft'])
|
|
56
|
+
|
|
57
|
+
push_to_remote(version)
|
|
58
|
+
try:
|
|
59
|
+
subprocess.run(
|
|
60
|
+
command,
|
|
61
|
+
capture_output=True,
|
|
62
|
+
text=True,
|
|
63
|
+
check=True
|
|
64
|
+
)
|
|
65
|
+
except subprocess.CalledProcessError as e:
|
|
66
|
+
raise RuntimeError(f"Failed to create release tag: \n{e.stderr.strip()}") from e
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .bump import bump_config_version, bump_git_tag
|
|
2
|
+
from .check import get_config_tag, get_local_tags, parse_highest_verion
|
|
3
|
+
|
|
4
|
+
def do_sync(to_git: bool=False, to_config: bool=False) -> str:
|
|
5
|
+
config_tag = get_config_tag()
|
|
6
|
+
local_tags = get_local_tags()
|
|
7
|
+
highest_local_tag = parse_highest_verion(local_tags)
|
|
8
|
+
|
|
9
|
+
if config_tag == highest_local_tag:
|
|
10
|
+
return f"Already in sync at (v{config_tag})"
|
|
11
|
+
|
|
12
|
+
if to_git:
|
|
13
|
+
if highest_local_tag:
|
|
14
|
+
bump_config_version(highest_local_tag)
|
|
15
|
+
return f"Synced config version to match Git tag v{highest_local_tag}"
|
|
16
|
+
else:
|
|
17
|
+
raise RuntimeError("No git tag found on local.")
|
|
18
|
+
|
|
19
|
+
elif to_config and highest_local_tag:
|
|
20
|
+
bump_git_tag(config_tag, message=f"Sync git tag to v{config_tag}")
|
|
21
|
+
return f"Synced Git tag to match config version v{config_tag}"
|
|
22
|
+
|
|
23
|
+
else:
|
|
24
|
+
if highest_local_tag:
|
|
25
|
+
target_version = max(config_tag, highest_local_tag)
|
|
26
|
+
else:
|
|
27
|
+
target_version = config_tag
|
|
28
|
+
|
|
29
|
+
if highest_local_tag and highest_local_tag > config_tag:
|
|
30
|
+
bump_config_version(target_version)
|
|
31
|
+
else:
|
|
32
|
+
bump_git_tag(target_version)
|
|
33
|
+
|
|
34
|
+
return f"Synced workspace to highest version v{target_version}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .bump import BumpRequest
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
BumpType = Literal["major", "minor", "patch"]
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class BumpRequest:
|
|
8
|
+
bump_type : BumpType
|
|
9
|
+
tag_message : str|None
|
|
10
|
+
force : bool
|
|
11
|
+
push : bool
|
|
12
|
+
release : str|None
|
|
13
|
+
draft : bool
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
def create_parser():
|
|
4
|
+
parser = argparse.ArgumentParser(
|
|
5
|
+
prog="git-version-sync",
|
|
6
|
+
description="Sync Git tags and pyproject.toml versions.",
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
10
|
+
|
|
11
|
+
# Subcommand: check
|
|
12
|
+
check_parser = subparsers.add_parser(
|
|
13
|
+
"check",
|
|
14
|
+
help="Check and compare current version status between Git tags and pyproject.toml"
|
|
15
|
+
)
|
|
16
|
+
check_parser.add_argument(
|
|
17
|
+
"--fetch",
|
|
18
|
+
action="store_true",
|
|
19
|
+
help="Fetch remote tags before checking"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Subcommand: sync
|
|
23
|
+
sync_parser = subparsers.add_parser(
|
|
24
|
+
"sync",
|
|
25
|
+
help="Sync version discrepancies between pyproject.toml and Git tags"
|
|
26
|
+
)
|
|
27
|
+
sync_group = sync_parser.add_mutually_exclusive_group()
|
|
28
|
+
sync_group.add_argument(
|
|
29
|
+
"--to-git",
|
|
30
|
+
action="store_true",
|
|
31
|
+
help="Force config version (pyproject.toml) to match the highest Git tag"
|
|
32
|
+
)
|
|
33
|
+
sync_group.add_argument(
|
|
34
|
+
"--to-config",
|
|
35
|
+
action="store_true",
|
|
36
|
+
help="Force Git tag to match the version in pyproject.toml"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Subcommand: bump
|
|
40
|
+
bump_parser = subparsers.add_parser(
|
|
41
|
+
"bump",
|
|
42
|
+
help="Increment version in pyproject.toml and create a corresponding Git tag"
|
|
43
|
+
)
|
|
44
|
+
bump_parser.add_argument(
|
|
45
|
+
"part",
|
|
46
|
+
choices=["major", "minor", "patch"],
|
|
47
|
+
help="Version part to increment (major, minor, or patch)"
|
|
48
|
+
)
|
|
49
|
+
bump_parser.add_argument(
|
|
50
|
+
"-f",
|
|
51
|
+
"--force",
|
|
52
|
+
action="store_true",
|
|
53
|
+
help="Force bump even if version mismatch occurs"
|
|
54
|
+
)
|
|
55
|
+
bump_parser.add_argument(
|
|
56
|
+
"-p",
|
|
57
|
+
"--push",
|
|
58
|
+
action="store_true",
|
|
59
|
+
help="Automatically push commit and the new tag to remote"
|
|
60
|
+
)
|
|
61
|
+
bump_parser.add_argument(
|
|
62
|
+
"-m",
|
|
63
|
+
"--message",
|
|
64
|
+
type=str,
|
|
65
|
+
help="Custom annotation message for the created Git tag"
|
|
66
|
+
)
|
|
67
|
+
bump_parser.add_argument(
|
|
68
|
+
"-r",
|
|
69
|
+
"--release",
|
|
70
|
+
nargs="?",
|
|
71
|
+
const="",
|
|
72
|
+
default=None,
|
|
73
|
+
metavar="NOTES",
|
|
74
|
+
help="Create a GitHub release for the bumped version (requires 'gh' CLI)"
|
|
75
|
+
)
|
|
76
|
+
bump_parser.add_argument(
|
|
77
|
+
"-d",
|
|
78
|
+
"--draft",
|
|
79
|
+
action="store_true",
|
|
80
|
+
help="Save the GitHub release as a draft (requires --release)",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return parser
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .config import get_git_path, get_config_path
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
def get_git_path() -> Path:
|
|
5
|
+
command = ["git", "rev-parse", "--show-toplevel"]
|
|
6
|
+
result = subprocess.run(
|
|
7
|
+
command,
|
|
8
|
+
capture_output=True,
|
|
9
|
+
text=True,
|
|
10
|
+
check=True
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
return Path(result.stdout.strip())
|
|
14
|
+
|
|
15
|
+
def get_config_path() -> Path:
|
|
16
|
+
config_path = get_git_path() / "pyproject.toml"
|
|
17
|
+
if config_path.exists():
|
|
18
|
+
return config_path
|
|
19
|
+
else:
|
|
20
|
+
raise RuntimeError(f"Config file not found: {config_path}")
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: git-version-sync
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: Tools to sync and update semantic versions
|
|
5
|
+
Keywords: git,versioning,semver,pyproject,cli
|
|
6
|
+
Author: Finsa-SC
|
|
7
|
+
Author-email: Finsa-SC <finsakusumaputra@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Dist: packaging>=26.3
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Project-URL: Homepage, https://github.com/Finsa-SC/git-version-sync
|
|
12
|
+
Project-URL: Repository, https://github.com/Finsa-SC/git-version-sync
|
|
13
|
+
Project-URL: Issues, https://github.com/Finsa-SC/git-version-sync/issues
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# git-version-sync
|
|
17
|
+
|
|
18
|
+
A command-line tool to synchronize semantic versions between Git tags and `pyproject.toml`.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Check** version status and consistency between Git tags and `pyproject.toml`
|
|
23
|
+
- **Sync** version discrepancies with flexible sync directions
|
|
24
|
+
- **Bump** versions following semantic versioning (major, minor, patch)
|
|
25
|
+
- **Auto Push** option to push commits and tags directly to remote
|
|
26
|
+
- **Custom annotations** for Git tags
|
|
27
|
+
- **Force mode** for bypassing version mismatches
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
### Requirements
|
|
32
|
+
- Python >= 3.11
|
|
33
|
+
- Git
|
|
34
|
+
|
|
35
|
+
### Via PyPI (Recommended)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install git-version-sync
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or with pipx for isolated installation:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pipx install git-version-sync
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### From Source
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone <repository-url>
|
|
51
|
+
cd git-version-sync
|
|
52
|
+
pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
### Check Command
|
|
58
|
+
|
|
59
|
+
Verify version consistency between Git tags and `pyproject.toml`:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git-version-sync check
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Options:**
|
|
66
|
+
- `--fetch` - Fetch remote tags before checking
|
|
67
|
+
|
|
68
|
+
### Sync Command
|
|
69
|
+
|
|
70
|
+
Synchronize versions when discrepancies are detected:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git-version-sync sync
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Options (mutually exclusive):**
|
|
77
|
+
- `--to-git` - Force `pyproject.toml` version to match the highest Git tag
|
|
78
|
+
- `--to-config` - Force Git tag to match `pyproject.toml` version
|
|
79
|
+
|
|
80
|
+
### Bump Command
|
|
81
|
+
|
|
82
|
+
Increment the version in `pyproject.toml` and create a corresponding Git tag:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git-version-sync bump {major|minor|patch}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Options:**
|
|
89
|
+
- `-f, --force` - Force bump even if version mismatch occurs
|
|
90
|
+
- `-p, --push` - Automatically push commit and tag to remote
|
|
91
|
+
- `-m, --message MESSAGE` - Custom annotation message for the Git tag
|
|
92
|
+
|
|
93
|
+
## Examples
|
|
94
|
+
|
|
95
|
+
### Check current version status
|
|
96
|
+
```bash
|
|
97
|
+
$ git-version-sync check
|
|
98
|
+
Version is synchronized with highest local tag (v1.0.0)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Bump patch version
|
|
102
|
+
```bash
|
|
103
|
+
$ git-version-sync bump patch
|
|
104
|
+
Success bump version to v1.0.1
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Bump minor version with custom message and auto-push
|
|
108
|
+
```bash
|
|
109
|
+
$ git-version-sync bump minor -m "Add new features" -p
|
|
110
|
+
Success bump version to v1.1.0
|
|
111
|
+
Pushing commit and tag to remote...
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Bump major version with force flag
|
|
115
|
+
```bash
|
|
116
|
+
$ git-version-sync bump major -f
|
|
117
|
+
Success bump version to v2.0.0
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Sync to Git tags
|
|
121
|
+
```bash
|
|
122
|
+
$ git-version-sync sync --to-git
|
|
123
|
+
Successfully synced version to match highest tag
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Dependencies
|
|
127
|
+
|
|
128
|
+
- `packaging>=26.3` - For version parsing and comparison
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
See LICENSE file for details.
|
|
133
|
+
|
|
134
|
+
## Author
|
|
135
|
+
|
|
136
|
+
Finsa-SC
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
git_version_sync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
git_version_sync/__main__.py,sha256=0g3iknXOS9gZUcpL_trgAcuCJnZZKjdsT_xt61WOVb4,60
|
|
3
|
+
git_version_sync/cli.py,sha256=-PQ_HZ6XahKdIhFQQmnLksS7z3o-G3fmBpJe8y6QC4Y,944
|
|
4
|
+
git_version_sync/core/__init__.py,sha256=5rc6piRQPZeqxBFN5V2lRdjdPGKREJtW1zh77msNuLc,79
|
|
5
|
+
git_version_sync/core/bump.py,sha256=NbbLsV4PcWmgLIzYg-1YIPRrZyYyrbXTLmSTmmtpI2E,3214
|
|
6
|
+
git_version_sync/core/check.py,sha256=8mNbE3g9fI3ZsOQUCW65s7sBa6SlCWCWJMM3ClN0CVM,2717
|
|
7
|
+
git_version_sync/core/git.py,sha256=7CPRgXiAoCp-G6AKdiwh5ChDFqrhnZZY0NXoOUogstI,2012
|
|
8
|
+
git_version_sync/core/sync.py,sha256=Yg_XA730DxI5PvaIpG4n05Zv9qOD9ZjbjVsSRhkQNQY,1250
|
|
9
|
+
git_version_sync/models/__init__.py,sha256=aW3l0SjQ5DuGh9gkrU7KO-kqp3notaCJO4humOKeEYk,29
|
|
10
|
+
git_version_sync/models/bump.py,sha256=ndCF9FL4baNc9h15GNTKoGAfSaBOqwqMllb_1-fCvcA,301
|
|
11
|
+
git_version_sync/parser.py,sha256=XDLVfZJJsuYFly1ucrpOgCds-q0OlQrmOptcg5wHwU0,2349
|
|
12
|
+
git_version_sync/utils/__init__.py,sha256=SlMounMaDolLS1rMQ4nIZlOaupbsu6GFql4IyU8DDqI,49
|
|
13
|
+
git_version_sync/utils/config.py,sha256=LtkXM_SJUtuKm2iZ5tF2-6poez2VIso-39FHiiF6tlI,502
|
|
14
|
+
git_version_sync-1.4.0.dist-info/WHEEL,sha256=7hzKWg-J8I3Buqyw5tBii5z_MmAVsDYXXijd_QAtNZ8,81
|
|
15
|
+
git_version_sync-1.4.0.dist-info/entry_points.txt,sha256=wR2mKbwOuvSbzPstfLGoA6-Qbm2amNdhUsYfYZJCqT0,64
|
|
16
|
+
git_version_sync-1.4.0.dist-info/METADATA,sha256=noL5_ZO-VDIsse_kykLym7y--xTJqPRcb8lSXiPtiIs,2985
|
|
17
|
+
git_version_sync-1.4.0.dist-info/RECORD,,
|