commitmessagegenerator 2.2.0__py3-none-any.whl → 2.5.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.
@@ -1,7 +1,15 @@
1
1
  import argparse
2
2
  import subprocess
3
3
  from .generator import gerar_mensagem_commit
4
- from .configure import api_key, get_configured_model, get_auto_add_setting, update_setting, get_api_key_status
4
+ from .configure import (
5
+ api_key,
6
+ get_configured_model,
7
+ get_auto_add_setting,
8
+ update_setting,
9
+ get_api_key_status,
10
+ load_config_env,
11
+ resolve_env_path,
12
+ )
5
13
  import sys
6
14
  import getpass
7
15
 
@@ -16,6 +24,11 @@ MODEL_MAP = {
16
24
 
17
25
  def configure_menu():
18
26
  """Interactive configuration menu"""
27
+ configure_menu_with_scope("auto")
28
+
29
+
30
+ def configure_menu_with_scope(config_scope):
31
+ """Interactive configuration menu with target scope."""
19
32
  while True:
20
33
  # Get current settings
21
34
  api_set = get_api_key_status()
@@ -37,15 +50,15 @@ def configure_menu():
37
50
  print("\nExiting configuration.\n")
38
51
  break
39
52
  elif choice == "1":
40
- configure_api_key()
53
+ configure_api_key(config_scope)
41
54
  elif choice == "2":
42
- configure_model()
55
+ configure_model(config_scope)
43
56
  elif choice == "3":
44
- configure_staging()
57
+ configure_staging(config_scope)
45
58
  else:
46
59
  print("\nInvalid option. Please try again.")
47
60
 
48
- def configure_api_key():
61
+ def configure_api_key(config_scope):
49
62
  """Configure the API key"""
50
63
  print("\n" + "-"*40)
51
64
  print("API KEY CONFIGURATION")
@@ -59,10 +72,10 @@ def configure_api_key():
59
72
  print("\nNo changes made.")
60
73
  return
61
74
 
62
- update_setting("GEMINI_API_KEY", key)
75
+ update_setting("GEMINI_API_KEY", key, scope=config_scope)
63
76
  print("\n✓ API Key saved successfully!")
64
77
 
65
- def configure_model():
78
+ def configure_model(config_scope):
66
79
  """Configure the AI model"""
67
80
  current_model = get_configured_model()
68
81
 
@@ -87,12 +100,12 @@ def configure_model():
87
100
 
88
101
  if choice in MODEL_MAP:
89
102
  selected_model = MODEL_MAP[choice]
90
- update_setting("AI_MODEL", selected_model)
103
+ update_setting("AI_MODEL", selected_model, scope=config_scope)
91
104
  print(f"\n✓ Model changed to: {selected_model}")
92
105
  else:
93
106
  print("\nInvalid option. No changes made.")
94
107
 
95
- def configure_staging():
108
+ def configure_staging(config_scope):
96
109
  """Configure file staging behavior"""
97
110
  auto_add = get_auto_add_setting()
98
111
 
@@ -111,10 +124,10 @@ def configure_staging():
111
124
  return
112
125
 
113
126
  if choice == "1":
114
- update_setting("AUTO_ADD_ALL", "true")
127
+ update_setting("AUTO_ADD_ALL", "true", scope=config_scope)
115
128
  print("\n✓ Set to: Auto-add all files")
116
129
  elif choice == "2":
117
- update_setting("AUTO_ADD_ALL", "false")
130
+ update_setting("AUTO_ADD_ALL", "false", scope=config_scope)
118
131
  print("\n✓ Set to: Staged only")
119
132
  else:
120
133
  print("\nInvalid option. No changes made.")
@@ -125,14 +138,18 @@ def main():
125
138
  parser.add_argument("-cp", "--commitpush", action="store_true", help="Commits and pushes with the generated message")
126
139
  parser.add_argument("-cf", "--configure", action="store_true", help="Configures the GEMINI_API_KEY environment variable")
127
140
  parser.add_argument("-s", "--status", action="store_true", help="Shows current configuration status")
141
+ parser.add_argument(
142
+ "--config-scope",
143
+ choices=["auto", "local", "global"],
144
+ default="auto",
145
+ help="Where -cf writes configuration (.env): auto, local, or global",
146
+ )
128
147
  args = parser.parse_args()
129
148
 
130
149
  if args.status:
131
- from .configure import get_configured_model, get_auto_add_setting
132
150
  import os
133
- from dotenv import load_dotenv
134
-
135
- load_dotenv()
151
+
152
+ env_path = load_config_env()
136
153
  key = os.getenv("GEMINI_API_KEY")
137
154
  model = get_configured_model()
138
155
  auto_add = get_auto_add_setting()
@@ -141,6 +158,10 @@ def main():
141
158
  print(f"API Key: {'✓ Set' if key else '✗ Not set'}")
142
159
  print(f"Model: {model}")
143
160
  print(f"Auto-add all files: {'✓ Yes' if auto_add else '✗ No (staged only)'}")
161
+ if env_path is not None:
162
+ print(f"Config file: {env_path}")
163
+ else:
164
+ print("Config file: not found (.env not discovered)")
144
165
  return
145
166
 
146
167
  if not args.configure:
@@ -161,7 +182,10 @@ def main():
161
182
  subprocess.run(["git", "push"])
162
183
 
163
184
  if args.configure:
164
- configure_menu()
185
+ configure_menu_with_scope(args.config_scope)
186
+ configured_path = resolve_env_path()
187
+ if configured_path is not None:
188
+ print(f"\nConfiguration file in use: {configured_path}")
165
189
 
166
190
  if len(sys.argv) == 1:
167
191
  print("\nRemoving staged changes (git reset)...")
@@ -1,9 +1,80 @@
1
1
  import os
2
+ from pathlib import Path
3
+ from dotenv import load_dotenv
2
4
 
3
- def update_setting(setting_name, value):
5
+
6
+ GLOBAL_ENV_PATH = Path.home() / ".commitgen" / ".env"
7
+ CONFIG_SCOPES = {"auto", "local", "global"}
8
+
9
+
10
+ def _iter_local_env_candidates():
11
+ """Yield .env paths from cwd up to filesystem root."""
12
+ current = Path.cwd().resolve()
13
+ for directory in (current, *current.parents):
14
+ yield directory / ".env"
15
+
16
+
17
+ def resolve_local_env_path():
18
+ """Resolve an existing local .env from cwd up to filesystem root."""
19
+ for candidate in _iter_local_env_candidates():
20
+ if candidate.exists():
21
+ return candidate
22
+ return None
23
+
24
+
25
+ def resolve_env_path():
26
+ """Resolve the best available config file path."""
27
+ local_path = resolve_local_env_path()
28
+ if local_path is not None:
29
+ return local_path
30
+ if GLOBAL_ENV_PATH.exists():
31
+ return GLOBAL_ENV_PATH
32
+ return None
33
+
34
+
35
+ def resolve_writable_env_path(scope="auto"):
36
+ """
37
+ Resolve where settings should be written.
38
+
39
+ scope:
40
+ - auto: existing local .env, then existing global .env, else new global .env
41
+ - local: .env in current working directory
42
+ - global: ~/.commitgen/.env
43
+ """
44
+ if scope not in CONFIG_SCOPES:
45
+ raise ValueError(f"Invalid config scope: {scope}")
46
+
47
+ if scope == "local":
48
+ return Path.cwd().resolve() / ".env"
49
+
50
+ if scope == "global":
51
+ return GLOBAL_ENV_PATH
52
+
53
+ existing_path = resolve_env_path()
54
+ if existing_path is not None:
55
+ return existing_path
56
+ return GLOBAL_ENV_PATH
57
+
58
+
59
+ def load_config_env():
60
+ """
61
+ Load configuration from the resolved .env path (if found).
62
+ Returns the loaded path or None.
63
+ """
64
+ env_path = resolve_env_path()
65
+ if env_path is not None:
66
+ load_dotenv(dotenv_path=env_path, override=False)
67
+ return env_path
68
+ load_dotenv(override=False)
69
+ return None
70
+
71
+ def update_setting(setting_name, value, scope="auto"):
4
72
  """Update a single setting in the .env file"""
5
- if os.path.exists(".env"):
6
- with open(".env", "r") as f:
73
+ env_path = resolve_writable_env_path(scope=scope)
74
+ env_path.parent.mkdir(parents=True, exist_ok=True)
75
+
76
+ if env_path.exists():
77
+ with open(env_path, "r", encoding="utf-8") as f:
7
78
  lines = f.readlines()
8
79
 
9
80
  found = False
@@ -19,33 +90,40 @@ def update_setting(setting_name, value):
19
90
  if not found:
20
91
  new_lines.append(f"{setting_name}={value}\n")
21
92
 
22
- with open(".env", "w") as f:
93
+ with open(env_path, "w", encoding="utf-8") as f:
23
94
  f.writelines(new_lines)
24
95
  else:
25
- with open(".env", "w") as f:
96
+ with open(env_path, "w", encoding="utf-8") as f:
26
97
  f.write(f"{setting_name}={value}\n")
27
98
 
28
- _ensure_gitignore()
99
+ _ensure_gitignore(env_path)
100
+
101
+ def _ensure_gitignore(env_path):
102
+ """Ensure .env is in .gitignore for local (non-global) config files."""
103
+ if env_path == GLOBAL_ENV_PATH:
104
+ return
29
105
 
30
- def _ensure_gitignore():
31
- """Ensure .env is in .gitignore"""
32
- if os.path.exists(".gitignore"):
33
- with open(".gitignore", "r+") as outfile:
106
+ gitignore_path = env_path.parent / ".gitignore"
107
+ if gitignore_path.exists():
108
+ with open(gitignore_path, "r+", encoding="utf-8") as outfile:
34
109
  lines = outfile.readlines()
35
110
  env_in_gitignore = next((line for line in lines if line.strip() == ".env"), None)
36
111
  if not env_in_gitignore:
37
112
  outfile.write("\n.env")
38
113
 
39
- def api_key(key, model="gemini-2.0-flash", auto_add_all=True):
114
+ def api_key(key, model="gemini-2.0-flash", auto_add_all=True, scope="auto"):
40
115
  """Set all configuration at once (legacy function)"""
41
116
  config = {
42
117
  "GEMINI_API_KEY": key,
43
118
  "AI_MODEL": model,
44
119
  "AUTO_ADD_ALL": str(auto_add_all).lower()
45
120
  }
46
-
47
- if os.path.exists(".env"):
48
- with open(".env", "r") as f:
121
+
122
+ env_path = resolve_writable_env_path(scope=scope)
123
+ env_path.parent.mkdir(parents=True, exist_ok=True)
124
+
125
+ if env_path.exists():
126
+ with open(env_path, "r", encoding="utf-8") as f:
49
127
  lines = f.readlines()
50
128
 
51
129
  existing_keys = set()
@@ -62,19 +140,24 @@ def api_key(key, model="gemini-2.0-flash", auto_add_all=True):
62
140
  if key_name not in existing_keys:
63
141
  new_lines.append(f"{key_name}={value}\n")
64
142
 
65
- with open(".env", "w") as f:
143
+ with open(env_path, "w", encoding="utf-8") as f:
66
144
  f.writelines(new_lines)
67
145
  else:
68
- with open(".env", "w") as f:
146
+ with open(env_path, "w", encoding="utf-8") as f:
69
147
  for key_name, value in config.items():
70
148
  f.write(f"{key_name}={value}\n")
71
149
 
72
- _ensure_gitignore()
150
+ _ensure_gitignore(env_path)
73
151
 
74
152
  def get_api_key_status():
75
153
  """Check if API key is set"""
76
- if os.path.exists(".env"):
77
- with open(".env", "r") as f:
154
+ env_key = os.getenv("GEMINI_API_KEY")
155
+ if env_key:
156
+ return True
157
+
158
+ env_path = resolve_env_path()
159
+ if env_path is not None:
160
+ with open(env_path, "r", encoding="utf-8") as f:
78
161
  lines = f.readlines()
79
162
  api_line = next((line for line in lines if line.startswith("GEMINI_API_KEY=")), None)
80
163
  if api_line:
@@ -84,8 +167,13 @@ def get_api_key_status():
84
167
 
85
168
  def get_configured_model():
86
169
  """Get the currently configured AI model from .env file"""
87
- if os.path.exists(".env"):
88
- with open(".env", "r") as outfile:
170
+ env_model = os.getenv("AI_MODEL")
171
+ if env_model:
172
+ return env_model
173
+
174
+ env_path = resolve_env_path()
175
+ if env_path is not None:
176
+ with open(env_path, "r", encoding="utf-8") as outfile:
89
177
  lines = outfile.readlines()
90
178
  model_line = next((line for line in lines if line.startswith("AI_MODEL=")), None)
91
179
  if model_line:
@@ -94,8 +182,13 @@ def get_configured_model():
94
182
 
95
183
  def get_auto_add_setting():
96
184
  """Get the auto-add all files setting from .env file"""
97
- if os.path.exists(".env"):
98
- with open(".env", "r") as outfile:
185
+ env_auto_add = os.getenv("AUTO_ADD_ALL")
186
+ if env_auto_add is not None:
187
+ return env_auto_add.strip().lower() == "true"
188
+
189
+ env_path = resolve_env_path()
190
+ if env_path is not None:
191
+ with open(env_path, "r", encoding="utf-8") as outfile:
99
192
  lines = outfile.readlines()
100
193
  auto_add_line = next((line for line in lines if line.startswith("AUTO_ADD_ALL=")), None)
101
194
  if auto_add_line:
@@ -1,11 +1,10 @@
1
1
  import os
2
- from dotenv import load_dotenv
3
2
  from google import genai
4
3
  from git import Repo
5
- from .configure import get_configured_model, get_auto_add_setting
4
+ from .configure import get_configured_model, get_auto_add_setting, load_config_env
6
5
 
7
6
  def gerar_mensagem_commit():
8
- load_dotenv()
7
+ load_config_env()
9
8
  key = os.getenv("GEMINI_API_KEY")
10
9
  if not key:
11
10
  raise RuntimeError("The GEMINI_API_KEY environment variable is not set.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commitmessagegenerator
3
- Version: 2.2.0
3
+ Version: 2.5.0
4
4
  Summary: Generate commit messages with AI (Google Gemini) automatically using `git diff`.
5
5
  Author-email: Gabriel Terceiro <gcarolinoterceiro@gmail.com>
6
6
  License: MIT
@@ -35,6 +35,14 @@ pip install commitmessagegenerator
35
35
  commitgen -cf
36
36
  ```
37
37
 
38
+ You can explicitly choose where configuration is written:
39
+
40
+ ```bash
41
+ commitgen -cf --config-scope auto # default behavior
42
+ commitgen -cf --config-scope local # always write .env in current directory
43
+ commitgen -cf --config-scope global # always write ~/.commitgen/.env
44
+ ```
45
+
38
46
  This opens an interactive configuration menu where you can:
39
47
 
40
48
  1. Set or update your Gemini API key
@@ -57,6 +65,14 @@ AI_MODEL=gemini-2.0-flash
57
65
  AUTO_ADD_ALL=true
58
66
  ```
59
67
 
68
+ Config discovery order:
69
+
70
+ 1. `.env` in current directory
71
+ 2. `.env` in parent directories (useful when running from subfolders)
72
+ 3. Global config in `~/.commitgen/.env` (created automatically by `commitgen -cf --config-scope auto` when no local `.env` exists)
73
+
74
+ Environment variables already set in your shell (e.g. `GEMINI_API_KEY`) are also respected.
75
+
60
76
  ## 🚀 Usage
61
77
 
62
78
  With the terminal, inside any Git repository with pending changes, run:
@@ -77,6 +93,7 @@ The command will:
77
93
  - `commitgen -c` - Generate and commit with the message
78
94
  - `commitgen -cp` - Generate, commit, and push
79
95
  - `commitgen -cf` - Configure API key, model, and file staging behavior
96
+ - `commitgen -cf --config-scope [auto|local|global]` - Choose where `.env` is created/updated
80
97
  - `commitgen -s` - Show current configuration status
81
98
 
82
99
  ### Available Models
@@ -0,0 +1,10 @@
1
+ commitmessagegenerator/__init__.py,sha256=Wsl1vaSI5fWuK3B9MfZnPp1PxnRfTmnvW_20vgswgT0,45
2
+ commitmessagegenerator/cli.py,sha256=bMCpefo7rmfP74NKsWOd_4VZXP5jQrIyminT6QTX24o,6603
3
+ commitmessagegenerator/configure.py,sha256=NQgv770wiA_PrxvcKmXNdyuPaKoMV4HyIOUgw1vBjOs,6655
4
+ commitmessagegenerator/generator.py,sha256=qc3Ot8-ZOl4v3nRJq4qcimRiFwjT9osUvGtmSpOr1mc,1614
5
+ commitmessagegenerator-2.5.0.dist-info/licenses/LICENSE,sha256=q7KJbcPCqUAvkBuI1QNc8Kg9XPfBfnNkLN9WKwudO8U,11
6
+ commitmessagegenerator-2.5.0.dist-info/METADATA,sha256=vuSJQtNX6ofivJXS-zL6SuEJKM_48W2DNqI3r3wOoa0,4114
7
+ commitmessagegenerator-2.5.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
8
+ commitmessagegenerator-2.5.0.dist-info/entry_points.txt,sha256=VmVQY00e0SuHsTFZmOCcyN0VYCQlVnZrdZNJdUGLCVo,62
9
+ commitmessagegenerator-2.5.0.dist-info/top_level.txt,sha256=G8wUZw8MTtvYs1WgehFVTPKqw5Td7gGedZZIQbZH1Co,23
10
+ commitmessagegenerator-2.5.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (82.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,10 +0,0 @@
1
- commitmessagegenerator/__init__.py,sha256=Wsl1vaSI5fWuK3B9MfZnPp1PxnRfTmnvW_20vgswgT0,45
2
- commitmessagegenerator/cli.py,sha256=IoSYuH8-Zc5YNdS6pmSg0nwo-UNufNPZb78Z76llSTk,5775
3
- commitmessagegenerator/configure.py,sha256=5OtQ9Y2udsIGeSztnsiFqSrcKWcm4pGKS6uyZS9-YvU,3749
4
- commitmessagegenerator/generator.py,sha256=c0IUPxP8A10OMokWwKYoes5gDi_-BJ0COl0Vm2ZM6ew,1624
5
- commitmessagegenerator-2.2.0.dist-info/licenses/LICENSE,sha256=q7KJbcPCqUAvkBuI1QNc8Kg9XPfBfnNkLN9WKwudO8U,11
6
- commitmessagegenerator-2.2.0.dist-info/METADATA,sha256=MBfGnghBea_1vjbxqyRD2cPISPZb7pzJ2heW2AYJv9w,3376
7
- commitmessagegenerator-2.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- commitmessagegenerator-2.2.0.dist-info/entry_points.txt,sha256=VmVQY00e0SuHsTFZmOCcyN0VYCQlVnZrdZNJdUGLCVo,62
9
- commitmessagegenerator-2.2.0.dist-info/top_level.txt,sha256=G8wUZw8MTtvYs1WgehFVTPKqw5Td7gGedZZIQbZH1Co,23
10
- commitmessagegenerator-2.2.0.dist-info/RECORD,,