smart-commit-tool 0.1.0__tar.gz → 1.0.1__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Makar Pavlovich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: smart-commit-tool
3
+ Version: 1.0.1
4
+ Summary: Automated pre-push workflow manager with built-in code quality enforcement and smart branch protection.
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: colorama
8
+ Requires-Dist: tomli; python_version < '3.11'
9
+ Description-Content-Type: text/markdown
10
+
11
+ # 🚀 Smart Commit
12
+
13
+ [English](README.md) | [Русский](README.ru.md)
14
+
15
+ **Smart Commit** is a universal CLI tool designed to streamline and secure your Git workflow. It orchestrates linting, testing, committing, and pushing into a single, bulletproof operation.
16
+
17
+ **The Golden Rule:** If your tests fail, your code doesn't ship.
18
+
19
+ ---
20
+
21
+ ## ✨ Key Features
22
+
23
+ * 🌍 **Language Agnostic**: Works seamlessly with Python, JS, Go, Rust, Dart, or any other stack.
24
+ * ⚡ **All-in-One Command**: Replaces the tedious `lint -> test -> add -> commit -> push` routine.
25
+ * 🛡️ **Branch Protection**: Safeguards your `main`, `master`, or `prod` branches from accidental direct pushes.
26
+ * 🔧 **Zero-Setup**: Install via `pip` and configure in under a minute using a single file.
27
+
28
+ ---
29
+
30
+ ## 📦 Installation
31
+
32
+ ```bash
33
+ pip install smart-commit-tool
34
+
35
+ ```
36
+
37
+ > **Note:** Once installed, the tool is available via the simple and concise `smart-commit` command.
38
+
39
+ ---
40
+
41
+ ## 🔧 Configuration (`pyproject.toml`)
42
+
43
+ Smart Commit leverages `pyproject.toml` for its configuration. Simply create this file in your project root (even if you aren't using Python for your main project).
44
+
45
+ ```toml
46
+ [tool.smart_commit]
47
+ repository_url = "https://github.com/youruser/yourrepo.git"
48
+ protected_branches = ["main", "master", "prod"]
49
+
50
+ # List any shell commands you want to run BEFORE the push.
51
+ # If any command returns a non-zero exit code, the process aborts.
52
+ commands = [
53
+ "ruff check .", # Python Linter
54
+ "pytest -v", # Python Tests
55
+ "npm run lint", # Node.js Linter
56
+ "go test ./..." # Go Tests
57
+ ]
58
+
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 🚀 Usage
64
+
65
+ Navigate to your project root (where your `pyproject.toml` is located) and run:
66
+
67
+ ```bash
68
+ smart-commit
69
+
70
+ ```
71
+
72
+ ### CLI Arguments
73
+
74
+ | Flag | Description |
75
+ | --- | --- |
76
+ | `-b, --branch` | Specify target branch (skips interactive prompt) |
77
+ | `-m, --message` | Set commit message (skips interactive prompt) |
78
+
79
+ ---
80
+
81
+ ## 🔄 How It Works (The Algorithm)
82
+
83
+ 1. **Environment Sanity Check**: Verifies Git initialization and remote repository connectivity.
84
+ 2. **Branch Guard**: If you attempt to push to a protected branch, the tool prompts you to create a new feature branch instead.
85
+ 3. **Pre-push Validation**: Sequentially executes your defined `commands`. If any step fails, the entire process stops immediately to keep your remote clean.
86
+ 4. **Optimized Push**: Automatically handles `add` and `commit`. If the remote branch has moved forward, it helps you `rebase` to ensure a linear, clean history.
87
+
88
+ ---
89
+
90
+ ## 📄 License
91
+ Distributed under the MIT License. Feel free to use, modify, and share!
@@ -0,0 +1,81 @@
1
+ # 🚀 Smart Commit
2
+
3
+ [English](README.md) | [Русский](README.ru.md)
4
+
5
+ **Smart Commit** is a universal CLI tool designed to streamline and secure your Git workflow. It orchestrates linting, testing, committing, and pushing into a single, bulletproof operation.
6
+
7
+ **The Golden Rule:** If your tests fail, your code doesn't ship.
8
+
9
+ ---
10
+
11
+ ## ✨ Key Features
12
+
13
+ * 🌍 **Language Agnostic**: Works seamlessly with Python, JS, Go, Rust, Dart, or any other stack.
14
+ * ⚡ **All-in-One Command**: Replaces the tedious `lint -> test -> add -> commit -> push` routine.
15
+ * 🛡️ **Branch Protection**: Safeguards your `main`, `master`, or `prod` branches from accidental direct pushes.
16
+ * 🔧 **Zero-Setup**: Install via `pip` and configure in under a minute using a single file.
17
+
18
+ ---
19
+
20
+ ## 📦 Installation
21
+
22
+ ```bash
23
+ pip install smart-commit-tool
24
+
25
+ ```
26
+
27
+ > **Note:** Once installed, the tool is available via the simple and concise `smart-commit` command.
28
+
29
+ ---
30
+
31
+ ## 🔧 Configuration (`pyproject.toml`)
32
+
33
+ Smart Commit leverages `pyproject.toml` for its configuration. Simply create this file in your project root (even if you aren't using Python for your main project).
34
+
35
+ ```toml
36
+ [tool.smart_commit]
37
+ repository_url = "https://github.com/youruser/yourrepo.git"
38
+ protected_branches = ["main", "master", "prod"]
39
+
40
+ # List any shell commands you want to run BEFORE the push.
41
+ # If any command returns a non-zero exit code, the process aborts.
42
+ commands = [
43
+ "ruff check .", # Python Linter
44
+ "pytest -v", # Python Tests
45
+ "npm run lint", # Node.js Linter
46
+ "go test ./..." # Go Tests
47
+ ]
48
+
49
+ ```
50
+
51
+ ---
52
+
53
+ ## 🚀 Usage
54
+
55
+ Navigate to your project root (where your `pyproject.toml` is located) and run:
56
+
57
+ ```bash
58
+ smart-commit
59
+
60
+ ```
61
+
62
+ ### CLI Arguments
63
+
64
+ | Flag | Description |
65
+ | --- | --- |
66
+ | `-b, --branch` | Specify target branch (skips interactive prompt) |
67
+ | `-m, --message` | Set commit message (skips interactive prompt) |
68
+
69
+ ---
70
+
71
+ ## 🔄 How It Works (The Algorithm)
72
+
73
+ 1. **Environment Sanity Check**: Verifies Git initialization and remote repository connectivity.
74
+ 2. **Branch Guard**: If you attempt to push to a protected branch, the tool prompts you to create a new feature branch instead.
75
+ 3. **Pre-push Validation**: Sequentially executes your defined `commands`. If any step fails, the entire process stops immediately to keep your remote clean.
76
+ 4. **Optimized Push**: Automatically handles `add` and `commit`. If the remote branch has moved forward, it helps you `rebase` to ensure a linear, clean history.
77
+
78
+ ---
79
+
80
+ ## 📄 License
81
+ Distributed under the MIT License. Feel free to use, modify, and share!
@@ -0,0 +1,80 @@
1
+ # 🚀 Smart Commit
2
+ [English](README.md) | [Русский](README.ru.md)
3
+
4
+ **Smart Commit** — это универсальный CLI-инструмент для автоматизации Git-воркфлоу. Он объединяет проверки, тесты, коммит и пуш в одну безопасную операцию.
5
+
6
+ **Главный принцип:** если тесты не прошли — код не улетит в репозиторий.
7
+
8
+ ---
9
+
10
+ ## ✨ Почему это удобно?
11
+
12
+ * 🌍 **Языковой агностик**: Работает с Python, JS, Go, Rust, Dart и любым другим стеком.
13
+ * ⚡ **Всё в одной команде**: Заменяет рутину `lint -> test -> add -> commit -> push`.
14
+ * 🛡️ **Защита веток**: Больше никаких случайных пушей в `main` или `prod`.
15
+ * 🔧 **Zero-Setup**: Устанавливается через `pip`, настраивается за 1 минуту в одном файле.
16
+
17
+ ---
18
+
19
+ ## 📦 Установка
20
+
21
+ ```bash
22
+ pip install smart-commit-tool
23
+
24
+ ```
25
+
26
+ > **Важно:** После установки вам доступна короткая и удобная команда `smart-commit`.
27
+
28
+ ---
29
+
30
+ ## 🔧 Настройка (pyproject.toml)
31
+
32
+ Инструмент использует файл `pyproject.toml` для конфигурации. Создайте его в корне вашего проекта (даже если вы не пишете на Python).
33
+
34
+ ```toml
35
+ [tool.smart_commit]
36
+ repository_url = "https://github.com/youruser/yourrepo.git"
37
+ protected_branches = ["main", "master", "prod"]
38
+
39
+ # Здесь вы просто перечисляете через запятую любые консольные команды,
40
+ # которые должны успешно выполниться ПЕРЕД тем, как код уйдет в Git.
41
+ commands = [
42
+ "ruff check .", # Линтер
43
+ "pytest -v", # Тесты
44
+ "npm run lint", # Можно запускать команды любого языка!
45
+ "go test ./..." # Инструмент универсален
46
+ ]
47
+
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 🚀 Как пользоваться
53
+
54
+ Перейдите в папку проекта, где лежит ваш `pyproject.toml`, и запустите:
55
+
56
+ ```bash
57
+ smart-commit
58
+
59
+ ```
60
+
61
+ ### Параметры запуска
62
+
63
+ | Флаг | Описание |
64
+ | --- | --- |
65
+ | `-b, --branch` | Сразу указать ветку (пропустит интерактивный выбор) |
66
+ | `-m, --message` | Сразу указать сообщение коммита |
67
+
68
+ ---
69
+
70
+ ## 🔄 Как это работает (Алгоритм)
71
+
72
+ 1. **Проверка окружения**: Инструмент проверяет наличие Git и связь с удаленным репозиторием.
73
+ 2. **Branch Guard**: Если вы пытаетесь пушить в защищенную ветку (`main`), скрипт предложит создать новую ветку.
74
+ 3. **Валидация (Pre-push)**: Последовательно запускаются ваши `commands`. Если любая из них вернет ошибку — процесс мгновенно прерывается.
75
+ 4. **Умный Push**: Инструмент сам сделает `add`, `commit` и отправит код. Если на сервере появились новые изменения, он предложит сделать `rebase`, чтобы история оставалась чистой.
76
+
77
+ ---
78
+
79
+ ## 📄 Лицензия
80
+ Распространяется под лицензией **MIT**. Пользуйтесь, меняйте и делитесь с друзьями!
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "smart-commit-tool"
7
- version = "0.1.0"
7
+ version = "1.0.1"
8
8
  description = "Automated pre-push workflow manager with built-in code quality enforcement and smart branch protection."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -17,7 +17,7 @@ dependencies = [
17
17
  smart-commit = "smart_commit.cli:main"
18
18
 
19
19
  [tool.smart_commit]
20
- repository_url = "https://github.com/mokinprokin/smart_commit.git"
20
+ repository_url = "https://github.com/mokinprokin/SmartCommit.git"
21
21
  commands = ["ruff check ."]
22
22
  protected_branches = ["main", "master", "prod", "release"]
23
23
 
@@ -0,0 +1,65 @@
1
+ import argparse
2
+ import sys
3
+ from .exceptions import SmartCommitError
4
+ from .config import ConfigService
5
+ from .services.security import SecurityService
6
+ from .services.git import GitService
7
+ from .services.validator import ValidatorService
8
+ from .logger import Console, logger
9
+
10
+
11
+ def main():
12
+ parser = argparse.ArgumentParser(description="Smart Commit & Push Tool")
13
+ parser.add_argument("-b", "--branch", help="Specify target branch")
14
+ parser.add_argument("-m", "--message", help="Commit message")
15
+ args = parser.parse_args()
16
+
17
+ try:
18
+ logger.info("=== Smart Commit Process Started ===")
19
+
20
+ config = ConfigService.load_or_create()
21
+ protected_branches = config.get("protected_branches", ["main", "master"])
22
+ commands = config.get("commands", [])
23
+
24
+ GitService.ensure_repo()
25
+ SecurityService.ensure_gitignore()
26
+ SecurityService.check_env_leaks()
27
+
28
+ current_branch = GitService.get_current_branch()
29
+ branch = args.branch or current_branch
30
+ GitService.switch_branch(branch)
31
+ if branch in protected_branches:
32
+ Console.warning(
33
+ f"You are pushing directly to a protected branch: '{branch}'."
34
+ )
35
+ ans = input("Are you sure you want to continue? [y/N]: ").strip().lower()
36
+ if ans != "y":
37
+ raise SmartCommitError("Operation cancelled by user.")
38
+
39
+ message = args.message
40
+ if not message:
41
+ message = input("📝 Enter commit message: ").strip()
42
+
43
+ if not message:
44
+ raise SmartCommitError("Commit message cannot be empty.")
45
+
46
+ ValidatorService.check_conventional_commit(message)
47
+ if commands:
48
+ Console.info("--- 🛠 RUNNING PIPELINE (from pyproject.toml) ---")
49
+ ValidatorService.run_commands(commands)
50
+
51
+ Console.info("--- 🚀 FINALIZING COMMIT ---")
52
+ GitService.smart_stage()
53
+ GitService.commit(message)
54
+ GitService.push_with_retry(branch)
55
+
56
+ except SmartCommitError as e:
57
+ Console.error(str(e))
58
+ sys.exit(1)
59
+ except KeyboardInterrupt:
60
+ Console.warning("\nProcess interrupted by user.")
61
+ sys.exit(0)
62
+
63
+
64
+ if __name__ == "__main__":
65
+ main()
@@ -0,0 +1,50 @@
1
+ import sys
2
+ from pathlib import Path
3
+ from .exceptions import SmartCommitError
4
+ from .logger import Console, logger
5
+
6
+ if sys.version_info >= (3, 11):
7
+ import tomllib
8
+ else:
9
+ import tomli as tomllib
10
+
11
+
12
+ class ConfigService:
13
+ DEFAULT_CONFIG = """
14
+ # Auto-generated by Smart Commit
15
+ [tool.smart_commit]
16
+ repository_url = ""
17
+ commands = ["echo 'Running pre-commit pipeline...'"]
18
+ protected_branches = ["main", "master", "prod", "release"]
19
+ """
20
+
21
+ @classmethod
22
+ def load_or_create(cls) -> dict:
23
+ """Reads configuration. If the section is missing, appends default settings."""
24
+ path = Path("pyproject.toml")
25
+
26
+ if not path.exists():
27
+ Console.warning("pyproject.toml not found. Creating a base file...")
28
+ path.touch()
29
+
30
+ try:
31
+ with open(path, "rb") as f:
32
+ data = tomllib.load(f)
33
+ except Exception as e:
34
+ raise SmartCommitError(f"Failed to parse pyproject.toml: {e}")
35
+
36
+ config = data.get("tool", {}).get("smart_commit")
37
+
38
+ if config is None:
39
+ Console.info(
40
+ "Section [tool.smart_commit] not found. Generating default pipeline..."
41
+ )
42
+ logger.info("Appending default tool.smart_commit to pyproject.toml")
43
+ with open(path, "a", encoding="utf-8") as f:
44
+ f.write(cls.DEFAULT_CONFIG)
45
+
46
+ with open(path, "rb") as f:
47
+ data = tomllib.load(f)
48
+ config = data.get("tool", {}).get("smart_commit", {})
49
+
50
+ return config
@@ -0,0 +1,14 @@
1
+ class SmartCommitError(Exception):
2
+ detail = "Unknown error"
3
+
4
+ def __init__(self, *args):
5
+ message = args[0] if args else self.detail
6
+ super().__init__(message)
7
+
8
+
9
+ class GitOperationError(SmartCommitError):
10
+ detail = "Git execution failed"
11
+
12
+
13
+ class ValidationError(SmartCommitError):
14
+ detail = "Validation check failed"
@@ -0,0 +1,32 @@
1
+ import logging
2
+ from colorama import init, Fore, Style
3
+
4
+ init(autoreset=True)
5
+
6
+ logging.basicConfig(
7
+ level=logging.DEBUG,
8
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
9
+ )
10
+ logger = logging.getLogger("SmartCommit")
11
+
12
+
13
+ class Console:
14
+ @staticmethod
15
+ def info(msg: str):
16
+ print(f"{Fore.CYAN}ℹ️ {msg}")
17
+ logger.info(msg)
18
+
19
+ @staticmethod
20
+ def success(msg: str):
21
+ print(f"{Fore.GREEN}{Style.BRIGHT}✅ {msg}")
22
+ logger.info(f"SUCCESS: {msg}")
23
+
24
+ @staticmethod
25
+ def warning(msg: str):
26
+ print(f"{Fore.YELLOW}⚠️ {msg}")
27
+ logger.warning(msg)
28
+
29
+ @staticmethod
30
+ def error(msg: str):
31
+ print(f"{Fore.RED}{Style.BRIGHT}❌ {msg}")
32
+ logger.error(msg)
@@ -0,0 +1,104 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+ from ..exceptions import GitOperationError
4
+ from ..logger import Console, logger
5
+
6
+
7
+ class GitService:
8
+ @classmethod
9
+ def _run(cls, cmd: list[str], silent: bool = True) -> subprocess.CompletedProcess:
10
+ """Internal helper for executing Git commands."""
11
+ logger.debug(f"Git CMD: {' '.join(cmd)}")
12
+ return subprocess.run(cmd, capture_output=silent, text=True)
13
+
14
+ @classmethod
15
+ def ensure_repo(cls):
16
+ """Checks for repository initialization and Detached HEAD state."""
17
+ if not Path(".git").exists():
18
+ Console.warning("Git not initialized. Initializing now...")
19
+ cls._run(["git", "init"])
20
+
21
+ res = cls._run(["git", "branch", "--show-current"])
22
+ if not res.stdout.strip():
23
+ raise GitOperationError(
24
+ "You are in a DETACHED HEAD state. Process stopped for safety."
25
+ )
26
+
27
+ @classmethod
28
+ def get_current_branch(cls) -> str:
29
+ return cls._run(["git", "branch", "--show-current"]).stdout.strip()
30
+
31
+ @classmethod
32
+ def switch_branch(cls, branch: str):
33
+ """Safely switches to the target branch or creates it."""
34
+ current = cls.get_current_branch()
35
+ if current != branch:
36
+ Console.info(f"🌿 Switching branch: {current} ➔ {branch}")
37
+ has_commits = cls._run(["git", "rev-parse", "HEAD"]).returncode == 0
38
+
39
+ if not has_commits:
40
+ res = cls._run(["git", "branch", "-M", branch])
41
+ else:
42
+ res = cls._run(["git", "checkout", "-B", branch])
43
+
44
+ if res.returncode != 0:
45
+ error_msg = res.stderr.strip() or res.stdout.strip()
46
+ raise GitOperationError(f"Failed to switch branch: {error_msg}")
47
+
48
+ @classmethod
49
+ def has_staged_changes(cls) -> bool:
50
+ """Checks if there are files already in the staging area."""
51
+ res = cls._run(["git", "diff", "--cached", "--quiet"])
52
+ return res.returncode != 0
53
+
54
+ @classmethod
55
+ def smart_stage(cls):
56
+ """Intelligent file staging."""
57
+ if cls.has_staged_changes():
58
+ Console.info("Staged files detected. Committing current index only.")
59
+ else:
60
+ Console.warning("No files in the staging area.")
61
+ ans = (
62
+ input("Would you like to stage all changes (git add .)? [y/N]: ")
63
+ .strip()
64
+ .lower()
65
+ )
66
+ if ans == "y":
67
+ cls._run(["git", "add", "."])
68
+ else:
69
+ raise GitOperationError("Nothing to commit. Operation cancelled.")
70
+
71
+ @classmethod
72
+ def commit(cls, message: str):
73
+ """Commits changes and extracts Git output if it fails."""
74
+ res = cls._run(["git", "commit", "-m", message])
75
+ if res.returncode != 0:
76
+ error_msg = res.stderr.strip() or res.stdout.strip()
77
+ raise GitOperationError(f"Commit failed. Git says:\n{error_msg}")
78
+
79
+ @classmethod
80
+ def push_with_retry(cls, branch: str):
81
+ """Pushes to remote, attempting a pull --rebase if rejected."""
82
+ Console.info(f"Pushing to branch: {branch}...")
83
+ res = cls._run(["git", "push", "-u", "origin", branch])
84
+
85
+ if res.returncode == 0:
86
+ Console.success(f"Code successfully pushed to origin/{branch}!")
87
+ return
88
+
89
+ Console.warning("Push rejected. Remote changes detected.")
90
+ Console.info("Synchronizing (pull --rebase)...")
91
+ pull_res = cls._run(["git", "pull", "origin", branch, "--rebase"])
92
+
93
+ if pull_res.returncode != 0:
94
+ error_msg = pull_res.stderr.strip() or pull_res.stdout.strip()
95
+ raise GitOperationError(
96
+ f"🛑 Rebase conflict detected!\nGit says: {error_msg}"
97
+ )
98
+
99
+ Console.success("Sync successful. Retrying push...")
100
+ push_retry = cls._run(["git", "push", "-u", "origin", branch])
101
+
102
+ if push_retry.returncode != 0:
103
+ error_msg = push_retry.stderr.strip() or push_retry.stdout.strip()
104
+ raise GitOperationError(f"Final push failed after rebase:\n{error_msg}")
@@ -0,0 +1,86 @@
1
+ import subprocess
2
+ from pathlib import Path
3
+ from ..logger import Console, logger
4
+
5
+
6
+ class SecurityService:
7
+ BASE_GITIGNORE = """
8
+ # Universal .gitignore generated by Smart Commit
9
+ __pycache__/
10
+ *.py[cod]
11
+ *$py.class
12
+ .venv/
13
+ venv/
14
+ env/
15
+ .env*
16
+ !.env.example
17
+ .vscode/
18
+ .idea/
19
+ dist/
20
+ build/
21
+ *.egg-info/
22
+ node_modules/
23
+ .DS_Store
24
+ .smart_commit.log
25
+ """
26
+
27
+ @classmethod
28
+ def ensure_gitignore(cls):
29
+ """Ensures .gitignore exists or creates a secure default."""
30
+ path = Path(".gitignore")
31
+ if not path.exists():
32
+ Console.warning(
33
+ ".gitignore not found. Creating a secure default configuration..."
34
+ )
35
+ with open(path, "w", encoding="utf-8") as f:
36
+ f.write(cls.BASE_GITIGNORE.lstrip())
37
+ logger.info("Created default .gitignore")
38
+
39
+ @classmethod
40
+ def check_env_leaks(cls):
41
+ """
42
+ Scans for .env* files and uses Git to check if they are ignored.
43
+ Prevents accidental leaks of secrets.
44
+ """
45
+ Console.info("🛡️ Scanning for potential secret leaks (.env files)...")
46
+
47
+ env_files = [
48
+ f
49
+ for f in Path(".").rglob(".env*")
50
+ if not any(
51
+ part in ["venv", ".venv", "env", "node_modules", ".git"]
52
+ for part in f.parts
53
+ )
54
+ ]
55
+
56
+ leaked_files = []
57
+
58
+ for env_file in env_files:
59
+ res = subprocess.run(
60
+ ["git", "check-ignore", "-q", str(env_file)], capture_output=True
61
+ )
62
+ if res.returncode == 1:
63
+ leaked_files.append(str(env_file))
64
+
65
+ if leaked_files:
66
+ Console.error(
67
+ "CRITICAL SECURITY RISK: .env files found that are NOT in .gitignore!"
68
+ )
69
+ for lf in leaked_files:
70
+ print(f" 👉 {lf}")
71
+
72
+ ans = (
73
+ input("🛑 Add these files to .gitignore automatically? [Y/n]: ")
74
+ .strip()
75
+ .lower()
76
+ )
77
+ if ans != "n":
78
+ with open(".gitignore", "a", encoding="utf-8") as f:
79
+ f.write("\n# Auto-added by Smart Commit to prevent leaks\n")
80
+ for lf in leaked_files:
81
+ f.write(f"{lf}\n")
82
+ Console.success("Files successfully added to .gitignore.")
83
+ else:
84
+ Console.warning(
85
+ "Action declined. Please be careful: secrets might be pushed!"
86
+ )
@@ -0,0 +1,28 @@
1
+ import subprocess
2
+ import re
3
+ from ..exceptions import ValidationError
4
+ from ..logger import Console, logger
5
+
6
+
7
+ class ValidatorService:
8
+ @classmethod
9
+ def check_conventional_commit(cls, message: str):
10
+ """Validates message against Conventional Commits standard."""
11
+ pattern = r"^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?:\s.+"
12
+ if not re.match(pattern, message):
13
+ logger.warning(f"Non-conventional commit message: {message}")
14
+ Console.warning(
15
+ "Commit message does not follow Conventional Commits (e.g., 'feat: add auth')."
16
+ )
17
+
18
+ @classmethod
19
+ def run_commands(cls, commands: list[str]):
20
+ """Runs pre-commit pipeline commands (linters, tests)."""
21
+ for cmd in commands:
22
+ Console.info(f"Executing: {cmd}")
23
+ logger.info(f"Running validation command: {cmd}")
24
+ res = subprocess.run(cmd, shell=True)
25
+ if res.returncode != 0:
26
+ raise ValidationError(
27
+ f"Pipeline failed at: '{cmd}'. Please fix the issues!"
28
+ )
@@ -1,78 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: smart-commit-tool
3
- Version: 0.1.0
4
- Summary: Automated pre-push workflow manager with built-in code quality enforcement and smart branch protection.
5
- Requires-Python: >=3.11
6
- Requires-Dist: colorama
7
- Requires-Dist: tomli; python_version < '3.11'
8
- Description-Content-Type: text/markdown
9
-
10
- # 🚀 Smart Commit Tool
11
-
12
- Smart Commit Tool is a robust Git wrapper designed for developers who value code quality. It automates the "Pre-check → Commit → Sync → Push" workflow, ensuring that no broken code ever reaches your remote repository.
13
-
14
- ## ✨ Features
15
-
16
- - ⚡ **Automated Validations**: Runs your test suites (Ruff, Pytest, etc.) before every push.
17
- - 🛡️ **Branch Protection**: Prevents accidental pushes to sensitive branches like `main` or `prod`.
18
- - 🔄 **Optimistic Sync**: Automatically handles `pull --rebase` only when conflicts occur, keeping your history clean.
19
- - 🎨 **Terminal UX**: Full color-coded feedback and interactive prompts for a better developer experience.
20
- - ⚙️ **Configurable**: Managed entirely via your existing `pyproject.toml`.
21
-
22
- ## 📦 Installation
23
-
24
- Since the project uses `hatchling`, you can install it locally in editable mode (perfect for development):
25
-
26
- ```bash
27
- pip install smart-commit
28
- ```
29
-
30
- After installation, the `smart-commit` command will be available in your terminal.
31
-
32
- ## 🚀 Usage
33
-
34
- Simply run the command in your project root:
35
-
36
- ```bash
37
- smart-commit
38
- ```
39
-
40
- ### CLI Arguments
41
-
42
- | Argument | Description |
43
- |---------------|---------------------------------------------------|
44
- | `-b, --branch`| Specify the target branch (skips interactive prompt) |
45
- | `-m, --message`| Set the commit message (skips interactive prompt) |
46
-
47
- ## 🔧 Configuration
48
-
49
- Add the following section to your `pyproject.toml` to customize the behavior:
50
-
51
- ```toml
52
- [tool.smart_commit]
53
- repository_url = "https://github.com/youruser/yourrepo.git"
54
- protected_branches = ["main", "master", "prod", "release"]
55
- commands = [
56
- "ruff check .",
57
- "pytest",
58
- ...
59
- ]
60
- ```
61
-
62
- ### How it works:
63
-
64
- 1. **Environment Check**: Ensures Git is initialized and the remote URL is correct.
65
- 2. **Branch Guard**: If you are on a protected branch, it prompts you to switch to a new one.
66
- 3. **Validation**: Runs every command in your `commands` list. If one fails, the process stops.
67
- 4. **Smart Push**: Attempts a push. If the remote has changed, it performs a rebase and retries automatically.
68
-
69
- ## 🛠 Tech Stack
70
-
71
- - Python 3.11+
72
- - **Colorama**: For cross-platform terminal styling.
73
- - **Tomli/Tomllib**: For seamless configuration parsing.
74
- - **Hatchling**: Modern build backend.
75
-
76
- ## 📄 License
77
-
78
- Distributed under the MIT License. See `LICENSE` for more information.
@@ -1,69 +0,0 @@
1
- # 🚀 Smart Commit Tool
2
-
3
- Smart Commit Tool is a robust Git wrapper designed for developers who value code quality. It automates the "Pre-check → Commit → Sync → Push" workflow, ensuring that no broken code ever reaches your remote repository.
4
-
5
- ## ✨ Features
6
-
7
- - ⚡ **Automated Validations**: Runs your test suites (Ruff, Pytest, etc.) before every push.
8
- - 🛡️ **Branch Protection**: Prevents accidental pushes to sensitive branches like `main` or `prod`.
9
- - 🔄 **Optimistic Sync**: Automatically handles `pull --rebase` only when conflicts occur, keeping your history clean.
10
- - 🎨 **Terminal UX**: Full color-coded feedback and interactive prompts for a better developer experience.
11
- - ⚙️ **Configurable**: Managed entirely via your existing `pyproject.toml`.
12
-
13
- ## 📦 Installation
14
-
15
- Since the project uses `hatchling`, you can install it locally in editable mode (perfect for development):
16
-
17
- ```bash
18
- pip install smart-commit
19
- ```
20
-
21
- After installation, the `smart-commit` command will be available in your terminal.
22
-
23
- ## 🚀 Usage
24
-
25
- Simply run the command in your project root:
26
-
27
- ```bash
28
- smart-commit
29
- ```
30
-
31
- ### CLI Arguments
32
-
33
- | Argument | Description |
34
- |---------------|---------------------------------------------------|
35
- | `-b, --branch`| Specify the target branch (skips interactive prompt) |
36
- | `-m, --message`| Set the commit message (skips interactive prompt) |
37
-
38
- ## 🔧 Configuration
39
-
40
- Add the following section to your `pyproject.toml` to customize the behavior:
41
-
42
- ```toml
43
- [tool.smart_commit]
44
- repository_url = "https://github.com/youruser/yourrepo.git"
45
- protected_branches = ["main", "master", "prod", "release"]
46
- commands = [
47
- "ruff check .",
48
- "pytest",
49
- ...
50
- ]
51
- ```
52
-
53
- ### How it works:
54
-
55
- 1. **Environment Check**: Ensures Git is initialized and the remote URL is correct.
56
- 2. **Branch Guard**: If you are on a protected branch, it prompts you to switch to a new one.
57
- 3. **Validation**: Runs every command in your `commands` list. If one fails, the process stops.
58
- 4. **Smart Push**: Attempts a push. If the remote has changed, it performs a rebase and retries automatically.
59
-
60
- ## 🛠 Tech Stack
61
-
62
- - Python 3.11+
63
- - **Colorama**: For cross-platform terminal styling.
64
- - **Tomli/Tomllib**: For seamless configuration parsing.
65
- - **Hatchling**: Modern build backend.
66
-
67
- ## 📄 License
68
-
69
- Distributed under the MIT License. See `LICENSE` for more information.
File without changes
@@ -1,214 +0,0 @@
1
- import subprocess
2
- import sys
3
- import argparse
4
- from pathlib import Path
5
- from typing import Any
6
- from colorama import init, Fore, Style
7
-
8
- # Initialize colorama for cross-platform colored output
9
- init(autoreset=True)
10
-
11
- if sys.version_info >= (3, 11):
12
- import tomllib
13
- else:
14
- import tomli as tomllib
15
-
16
-
17
- class SmartCommitError(Exception):
18
- """Base class for script-related errors."""
19
-
20
- pass
21
-
22
-
23
- def run_cmd(
24
- cmd: list[str] | str, silent: bool = False, use_shell: bool = False
25
- ) -> subprocess.CompletedProcess[str]:
26
- """
27
- Executes a shell command.
28
- Prefer passing a list of arguments (use_shell=False) for internal git commands.
29
- """
30
- if not silent:
31
- display_cmd = cmd if isinstance(cmd, str) else " ".join(cmd)
32
- print(f"{Fore.CYAN}🛠 Executing: {Style.BRIGHT}{display_cmd}")
33
-
34
- result = subprocess.run(cmd, shell=use_shell, capture_output=silent, text=True)
35
- return result
36
-
37
-
38
- def ensure_gitignore() -> None:
39
- """Creates a standard .gitignore for Python if it doesn't exist."""
40
- if not Path(".gitignore").exists():
41
- print(f"{Fore.YELLOW}📝 .gitignore not found. Creating a standard one...")
42
- content = (
43
- "__pycache__/\n*.py[cod]\n*$py.class\n.venv/\nvenv/\n"
44
- ".env\n.vscode/\ndist/\nbuild/\n*.egg-info/\n"
45
- )
46
- with open(".gitignore", "w", encoding="utf-8") as f:
47
- f.write(content)
48
-
49
-
50
- def get_config() -> dict[str, Any]:
51
- """Reads configuration from pyproject.toml."""
52
- path = Path("pyproject.toml")
53
- if not path.exists():
54
- raise SmartCommitError(
55
- "pyproject.toml not found. Please run the command in the project root."
56
- )
57
-
58
- with open(path, "rb") as f:
59
- data = tomllib.load(f)
60
- return data.get("tool", {}).get("smart_commit", {})
61
-
62
-
63
- def ensure_git_setup(expected_url: str) -> None:
64
- """Checks and configures Git repository and remote."""
65
- if not Path(".git").exists():
66
- print(f"{Fore.YELLOW}📁 Git not initialized. Initializing repository...")
67
- run_cmd(["git", "init"])
68
-
69
- res = run_cmd(["git", "remote", "get-url", "origin"], silent=True)
70
- current_url = res.stdout.strip()
71
-
72
- if res.returncode != 0:
73
- print(f"{Fore.CYAN}🔗 Adding remote origin: {expected_url}")
74
- run_cmd(["git", "remote", "add", "origin", expected_url])
75
- elif expected_url not in current_url:
76
- print(f"{Fore.YELLOW}🔄 Updating repository URL to: {expected_url}")
77
- run_cmd(["git", "remote", "set-url", "origin", expected_url])
78
- else:
79
- print(f"{Fore.GREEN}✅ Git repository is correctly configured.")
80
-
81
-
82
- def switch_branch(branch: str) -> None:
83
- """Safely switches branches, handling unborn (empty) repositories."""
84
- has_commits = run_cmd(["git", "rev-parse", "HEAD"], silent=True).returncode == 0
85
-
86
- if not has_commits:
87
- run_cmd(["git", "branch", "-M", branch], silent=True)
88
- else:
89
- run_cmd(["git", "checkout", "-B", branch], silent=True)
90
-
91
-
92
- def main() -> None:
93
- parser = argparse.ArgumentParser(description="Smart Commit & Push Tool")
94
- parser.add_argument("-b", "--branch", help="Specify branch (skips prompt)")
95
- parser.add_argument("-m", "--message", help="Commit message (skips prompt)")
96
- args = parser.parse_args()
97
-
98
- try:
99
- config = get_config()
100
- repo_url = config.get("repository_url")
101
- commands: list[str] = config.get("commands", [])
102
- protected_branches = config.get(
103
- "protected_branches", ["main", "master", "prod", "production"]
104
- )
105
-
106
- if not repo_url:
107
- raise SmartCommitError(
108
- "In pyproject.toml, [tool.smart_commit].repository_url is missing."
109
- )
110
-
111
- ensure_git_setup(repo_url)
112
- ensure_gitignore()
113
-
114
- print(f"\n{Fore.MAGENTA}{Style.BRIGHT}--- 🚀 SMART COMMIT PRE-CHECK ---")
115
-
116
- branch = args.branch
117
- if not branch:
118
- current_branch = run_cmd(
119
- ["git", "branch", "--show-current"], silent=True
120
- ).stdout.strip()
121
-
122
- if current_branch:
123
- user_input = input(f"{Fore.BLUE}🌿 Branch [{current_branch}]: ").strip()
124
- branch = user_input if user_input else current_branch
125
- else:
126
- branch = input(f"{Fore.BLUE}🌿 Branch name (e.g., main): ").strip()
127
-
128
- message = args.message
129
- if not message:
130
- message = input(f"{Fore.BLUE}📝 Commit message: ").strip()
131
-
132
- if not branch or not message:
133
- raise SmartCommitError("Branch and message cannot be empty.")
134
-
135
- if branch in protected_branches:
136
- print(
137
- f"\n{Fore.RED}{Style.BRIGHT}⚠️ WARNING: You are pushing directly to a protected branch: '{branch}'."
138
- )
139
- choice = (
140
- input(
141
- f"{Fore.YELLOW}Continue? [y (yes) / n (cancel) / b (create new branch)]: "
142
- )
143
- .lower()
144
- .strip()
145
- )
146
-
147
- if choice == "n":
148
- raise SmartCommitError("Operation cancelled by user.")
149
- elif choice == "b":
150
- new_branch = input(f"{Fore.BLUE}Enter new branch name: ").strip()
151
- if not new_branch:
152
- raise SmartCommitError("Branch name cannot be empty.")
153
- branch = new_branch
154
- print(f"{Fore.CYAN}🌿 Switching to new branch '{branch}'...")
155
- elif choice != "y":
156
- raise SmartCommitError("Invalid input. Operation cancelled.")
157
-
158
- switch_branch(branch)
159
-
160
- print(f"\n{Fore.MAGENTA}{Style.BRIGHT}--- 🛠 RUNNING VALIDATIONS ---")
161
- for cmd in commands:
162
- if run_cmd(cmd, use_shell=True).returncode != 0:
163
- raise SmartCommitError(
164
- f"Validation failed for: '{cmd}'. Please fix the issues and try again!"
165
- )
166
-
167
- print(f"\n{Fore.MAGENTA}{Style.BRIGHT}--- ✅ ALL CHECKS PASSED. PUSHING... ---")
168
-
169
- if run_cmd(["git", "add", "."]).returncode != 0:
170
- raise SmartCommitError("Error occurred during 'git add'.")
171
-
172
- if run_cmd(["git", "commit", "-m", message]).returncode != 0:
173
- print(f"{Fore.YELLOW}ℹ️ No changes to commit or commit error occurred.")
174
-
175
- print(f"{Fore.CYAN}📤 Pushing changes to {Style.BRIGHT}{branch}...")
176
- push_res = run_cmd(["git", "push", "-u", "origin", branch], silent=True)
177
-
178
- if push_res.returncode == 0:
179
- print(
180
- f"\n{Fore.GREEN}{Style.BRIGHT}🎉 Success! Code validated and pushed to '{branch}'."
181
- )
182
- return
183
-
184
- print(f"{Fore.YELLOW}⚠️ Push rejected. Remote changes detected.")
185
- print(f"{Fore.CYAN}📥 Synchronizing (pull --rebase)...")
186
-
187
- pull_res = run_cmd(["git", "pull", "origin", branch, "--rebase"], silent=True)
188
-
189
- if pull_res.returncode != 0:
190
- raise SmartCommitError(
191
- "🛑 Conflict detected during pull!\n"
192
- "Please resolve conflicts manually (git rebase --continue) and run the script again."
193
- )
194
-
195
- print(f"{Fore.GREEN}🔄 Sync successful. Retrying push...")
196
- push_res_retry = run_cmd(["git", "push", "-u", "origin", branch])
197
-
198
- if push_res_retry.returncode == 0:
199
- print(
200
- f"\n{Fore.GREEN}{Style.BRIGHT}🎉 Success! Changes synchronized and pushed to '{branch}'."
201
- )
202
- else:
203
- raise SmartCommitError("Failed to push changes after rebase.")
204
-
205
- except SmartCommitError as e:
206
- print(f"\n{Fore.RED}{Style.BRIGHT}❌ Error: {e}")
207
- sys.exit(1)
208
- except KeyboardInterrupt:
209
- print(f"\n{Fore.YELLOW}Process interrupted by user.")
210
- sys.exit(0)
211
-
212
-
213
- if __name__ == "__main__":
214
- main()