Skip to content

Commit a5cc81b

Browse files
committed
Add git-json generator and CLI tool with initial setup
- Implement GitJsonGenerator class to generate comprehensive git information. - Create CLI interface for generating git json with customizable output path. - Add .gitignore for IDE files and temporary directories. - Update README with installation and usage instructions. - Set up CI/CD workflows for testing and release management.
1 parent 02b230a commit a5cc81b

File tree

15 files changed

+813
-1
lines changed

15 files changed

+813
-1
lines changed

.github/codecov.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
codecov:
2+
require_ci_to_pass: true
3+
max_report_age: false
4+
coverage:
5+
status:
6+
project:
7+
default:
8+
target: 80%
9+
patch:
10+
default:
11+
target: 80%
12+
component_management:
13+
default_rules:
14+
statuses:
15+
- type: project
16+
target: auto
17+
individual_components:
18+
- component_id: git_json
19+
name: git_json
20+
paths:
21+
- "git_json/**"

.github/workflows/build.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: build
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Python ${{ matrix.python-version }}
20+
uses: actions/setup-python@v4
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
24+
- name: Install Poetry
25+
uses: snok/[email protected]
26+
27+
- name: Install dependencies
28+
run: poetry install --with dev
29+
30+
- name: Run linting
31+
run: poetry run ruff check .
32+
33+
- name: Run tests
34+
run: poetry run pytest --cov=git_json --cov-report=xml
35+
36+
- name: Upload coverage to Codecov
37+
uses: codecov/codecov-action@v5
38+
with:
39+
files: ./coverage.xml
40+
fail_ci_if_error: false
41+
token: ${{ secrets.CODECOV_TOKEN }}
42+
43+
- name: Test CLI
44+
run: poetry run git-json --package-path test-output

.github/workflows/release.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Release
2+
3+
on:
4+
release:
5+
types: [ created ]
6+
7+
permissions:
8+
contents: write
9+
packages: write
10+
11+
jobs:
12+
release:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v4
20+
with:
21+
python-version: '3.12'
22+
23+
- name: Install Poetry
24+
uses: snok/[email protected]
25+
26+
- name: Install dependencies
27+
run: poetry install --with dev
28+
29+
- name: Build package
30+
run: poetry build
31+
32+
- name: Find wheel file
33+
id: wheel
34+
run: echo "wheel_path=$(ls dist/*.whl)" >> $GITHUB_OUTPUT
35+
36+
- name: Upload package to release
37+
run: gh release upload ${{ github.event.release.tag_name }} ${{ steps.wheel.outputs.wheel_path }}
38+
env:
39+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40+
41+
- name: Publish to PyPI
42+
env:
43+
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }}
44+
run: poetry publish

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.idea
2+
.vscode
3+
14
# Byte-compiled / optimized / DLL files
25
__pycache__/
36
*.py[codz]

README.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,77 @@
1-
# git-info
1+
# git-json
2+
[![codecov](https://codecov.io/github/unenv/git-json/graph/badge.svg?token=0QQ7JROLWL)](https://codecov.io/github/unenv/git-json)
3+
![PyPI - Version](https://img.shields.io/pypi/v/git-json)
4+
5+
A Python tool to generate comprehensive git information for your projects.
6+
7+
## Installation
8+
9+
```bash
10+
pip install git-json
11+
```
12+
13+
## Usage
14+
15+
### Command Line
16+
17+
```bash
18+
# Generate git info to default 'resources' directory
19+
git-json
20+
21+
# Generate git info to custom directory
22+
git-json --package-path my-resources
23+
```
24+
25+
### Python API
26+
27+
```python
28+
from git_json import GitJsonGenerator
29+
30+
generator = GitJsonGenerator()
31+
generator.generate_git_json("output-directory")
32+
```
33+
34+
### Configuration
35+
36+
You can set a default output path in your `pyproject.toml`:
37+
38+
```toml
39+
[tool.git-json]
40+
path = "my-custom-path"
41+
```
42+
43+
This is useful when using git-json as a poetry script:
44+
45+
```toml
46+
[tool.poetry.scripts]
47+
git-json = "git_json.cli:main"
48+
49+
[tool.git-json]
50+
path = "src/resources"
51+
```
52+
53+
## Output
54+
55+
Generates a `git.json` file with comprehensive git information including:
56+
57+
- Branch information
58+
- Commit details (hash, message, author, time)
59+
- Build information (host, time, user)
60+
- Repository status (dirty, ahead/behind)
61+
- Tags and version information
62+
63+
## Development
64+
65+
```bash
66+
# Install dependencies
67+
poetry install --with dev
68+
69+
# Run tests
70+
poetry run pytest
71+
72+
# Run linting
73+
poetry run ruff check .
74+
75+
# Generate git info
76+
poetry run git-json
77+
```

git_json/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .generator import GitJsonGenerator
2+
3+
__all__ = ["GitJsonGenerator"]

git_json/cli.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
from pathlib import Path
5+
from .generator import GitJsonGenerator
6+
7+
try:
8+
import tomllib
9+
except ImportError:
10+
import tomli as tomllib
11+
12+
def _get_default_path():
13+
"""Get default path from pyproject.toml or fallback."""
14+
try:
15+
pyproject_path = Path("pyproject.toml")
16+
if pyproject_path.exists():
17+
with open(pyproject_path, "rb") as f:
18+
data = tomllib.load(f)
19+
return data.get("tool", {}).get("git-json", {}).get("path", "resources")
20+
except Exception:
21+
pass
22+
return "resources"
23+
24+
def main():
25+
"""Generate git info."""
26+
parser = argparse.ArgumentParser(description="Generate git info")
27+
default_path = _get_default_path()
28+
parser.add_argument("--package-path", default=default_path,
29+
help=f"Package path for git info output (default: {default_path})")
30+
args = parser.parse_args()
31+
32+
generator = GitJsonGenerator()
33+
generator.generate_git_json(args.package_path)
34+
35+
if __name__ == "__main__":
36+
main()

git_json/generator.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import json
2+
import subprocess
3+
from datetime import datetime
4+
from pathlib import Path
5+
6+
7+
class GitJsonGenerator:
8+
def generate_git_json(self, package_path: str = "resources"):
9+
target_dir = Path(package_path)
10+
target_dir.mkdir(parents=True, exist_ok=True)
11+
12+
git_json = {
13+
"git.branch": self.run_git_command("git rev-parse --abbrev-ref HEAD"),
14+
"git.build.host": subprocess.run("hostname", shell=True, capture_output=True, text=True).stdout.strip(),
15+
"git.build.time": datetime.now().strftime("%d.%m.%Y @ %H:%M:%S %Z"),
16+
"git.build.user.email": self.run_git_command("git config user.email"),
17+
"git.build.user.name": self.run_git_command("git config user.name"),
18+
"git.build.version": "1.0.0",
19+
"git.closest.tag.commit.count": self.run_git_command("git describe --tags --abbrev=0 --match='*' --always"),
20+
"git.closest.tag.name": self.run_git_command("git describe --tags --abbrev=0 --match='*' --always"),
21+
"git.commit.author.time": self.run_git_command('git log -1 --format=%ad --date=format:"%d.%m.%Y @ %H:%M:%S "'),
22+
"git.commit.committer.time": self.run_git_command('git log -1 --format=%cd --date=format:"%d.%m.%Y @ %H:%M:%S "'),
23+
"git.commit.id": self.run_git_command("git rev-parse HEAD"),
24+
"git.commit.id.abbrev": self.run_git_command("git rev-parse --short HEAD"),
25+
"git.commit.id.describe": self.run_git_command("git describe --always --dirty"),
26+
"git.commit.id.describe-short": self.run_git_command("git describe --always --dirty --abbrev=10"),
27+
"git.commit.message.full": self.run_git_command("git log -1 --format=%B").replace("\n", "\\n"),
28+
"git.commit.message.short": self.run_git_command("git log -1 --format=%s"),
29+
"git.commit.time": self.run_git_command('git log -1 --format=%cd --date=format:"%d.%m.%Y @ %H:%M:%S "'),
30+
"git.commit.user.email": self.run_git_command("git log -1 --format=%ae"),
31+
"git.commit.user.name": self.run_git_command("git log -1 --format=%an"),
32+
"git.dirty": "true" if self.run_git_command("git diff --quiet; echo $?") == "1" else "false",
33+
"git.local.branch.ahead": self.run_git_command("git rev-list --count HEAD@{upstream}..HEAD 2>/dev/null || echo 0"),
34+
"git.local.branch.behind": self.run_git_command("git rev-list --count HEAD...HEAD@{upstream} 2>/dev/null || echo 0"),
35+
"git.remote.origin.url": self.run_git_command("git config --get remote.origin.url"),
36+
"git.tags": self.run_git_command("git tag --points-at HEAD"),
37+
"git.total.commit.count": self.run_git_command("git rev-list --count HEAD")
38+
}
39+
40+
json_file = target_dir / "git.json"
41+
with open(json_file, "w") as f:
42+
json.dump(git_json, f, indent=4)
43+
44+
print(f"Generated git info: {json_file.absolute()}")
45+
46+
def run_git_command(self, cmd: str) -> str:
47+
try:
48+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True)
49+
return result.stdout.strip()
50+
except subprocess.CalledProcessError:
51+
return ""

0 commit comments

Comments
 (0)