gitacc-switcher 1.1.0__tar.gz → 1.2.0__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.
- {gitacc_switcher-1.1.0/gitacc_switcher.egg-info → gitacc_switcher-1.2.0}/PKG-INFO +14 -3
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/README.md +13 -2
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher/__init__.py +1 -1
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher/account_manager.py +40 -3
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher/cli.py +31 -2
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher/config_manager.py +120 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0/gitacc_switcher.egg-info}/PKG-INFO +14 -3
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/pyproject.toml +1 -1
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/setup.py +1 -1
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/tests/test_account_manager.py +132 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/tests/test_cli.py +38 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/tests/test_config_manager.py +93 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/LICENSE +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/MANIFEST.in +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher/completion.py +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher/hook_manager.py +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher/ssh_manager.py +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher/utils.py +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher.egg-info/SOURCES.txt +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher.egg-info/dependency_links.txt +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher.egg-info/entry_points.txt +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher.egg-info/requires.txt +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher.egg-info/top_level.txt +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/requirements.txt +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/setup.cfg +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/tests/__init__.py +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/tests/test_completion.py +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/tests/test_hook_manager.py +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/tests/test_ssh_manager.py +0 -0
- {gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitacc-switcher
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Git Account Switcher - Manage multiple Git SSH accounts easily
|
|
5
5
|
Home-page: https://github.com/ktechhub/gitacc_switcher
|
|
6
6
|
Author: ktechhub
|
|
@@ -46,6 +46,7 @@ A Python CLI tool to easily switch between multiple Git SSH accounts. Manage mul
|
|
|
46
46
|
- Switch between accounts easily — one command, no fuss
|
|
47
47
|
- Generate SSH keys automatically (rsa, ed25519, ecdsa, and more)
|
|
48
48
|
- Optional passphrase support for SSH keys
|
|
49
|
+
- Opt-in SSH commit signing per account
|
|
49
50
|
- Colored terminal output with active account indicator
|
|
50
51
|
- Pre-commit hook to prevent commits with the wrong account
|
|
51
52
|
- Shell autocomplete for bash and zsh
|
|
@@ -87,7 +88,7 @@ gitacc verify # manual check
|
|
|
87
88
|
|
|
88
89
|
## Commands
|
|
89
90
|
|
|
90
|
-
### `gitacc add [--type TYPE]`
|
|
91
|
+
### `gitacc add [--type TYPE] [--sign]`
|
|
91
92
|
|
|
92
93
|
Add a new Git account. Prompts for:
|
|
93
94
|
- Account identifier (used in key filename and as a reference)
|
|
@@ -100,8 +101,15 @@ Available key types: `rsa` (default), `ed25519`, `ecdsa`, `ecdsa-sk`, `ed25519-s
|
|
|
100
101
|
```bash
|
|
101
102
|
gitacc add
|
|
102
103
|
gitacc add --type ed25519
|
|
104
|
+
gitacc add --sign # opt in to SSH commit signing for this account
|
|
103
105
|
```
|
|
104
106
|
|
|
107
|
+
Pass `--sign` to opt this account into SSH commit signing. When you later `gitacc switch` to it,
|
|
108
|
+
`user.signingkey` is set to the account's public key and `gpg.format=ssh` / `commit.gpgsign=true`
|
|
109
|
+
are enabled (locally too, with `--local`), so every commit made under this identity is signed.
|
|
110
|
+
Switching to an account without `--sign` clears any signing config left by a previous account.
|
|
111
|
+
Toggle it later with `gitacc update <account> --sign` / `--no-sign`.
|
|
112
|
+
|
|
105
113
|
### `gitacc switch <account> [--local]` / `gitacc <account>`
|
|
106
114
|
|
|
107
115
|
Switch to a registered account. Clears all keys from the SSH agent, loads only this account's key, and sets `git config --global user.name/email`.
|
|
@@ -139,7 +147,7 @@ gitacc remove mywork
|
|
|
139
147
|
gitacc remove # prompts for account name
|
|
140
148
|
```
|
|
141
149
|
|
|
142
|
-
### `gitacc update <account> [--name NAME] [--email EMAIL]`
|
|
150
|
+
### `gitacc update <account> [--name NAME] [--email EMAIL] [--sign | --no-sign]`
|
|
143
151
|
|
|
144
152
|
Update the Git display name and/or email for an existing account. Prompts for any field not provided as a flag.
|
|
145
153
|
|
|
@@ -147,6 +155,8 @@ Update the Git display name and/or email for an existing account. Prompts for an
|
|
|
147
155
|
gitacc update mywork --name "Jane Doe"
|
|
148
156
|
gitacc update mywork --email "jane@newcompany.com"
|
|
149
157
|
gitacc update mywork # prompts for both
|
|
158
|
+
gitacc update mywork --sign # enable SSH commit signing for this account
|
|
159
|
+
gitacc update mywork --no-sign # disable it
|
|
150
160
|
```
|
|
151
161
|
|
|
152
162
|
### `gitacc init <account>`
|
|
@@ -213,6 +223,7 @@ gitacc --version
|
|
|
213
223
|
email = jane@company.com
|
|
214
224
|
private_key = /home/jane/.ssh/id_ed25519_mywork
|
|
215
225
|
public_key = /home/jane/.ssh/id_ed25519_mywork.pub
|
|
226
|
+
sign = false
|
|
216
227
|
```
|
|
217
228
|
|
|
218
229
|
## Requirements
|
|
@@ -13,6 +13,7 @@ A Python CLI tool to easily switch between multiple Git SSH accounts. Manage mul
|
|
|
13
13
|
- Switch between accounts easily — one command, no fuss
|
|
14
14
|
- Generate SSH keys automatically (rsa, ed25519, ecdsa, and more)
|
|
15
15
|
- Optional passphrase support for SSH keys
|
|
16
|
+
- Opt-in SSH commit signing per account
|
|
16
17
|
- Colored terminal output with active account indicator
|
|
17
18
|
- Pre-commit hook to prevent commits with the wrong account
|
|
18
19
|
- Shell autocomplete for bash and zsh
|
|
@@ -54,7 +55,7 @@ gitacc verify # manual check
|
|
|
54
55
|
|
|
55
56
|
## Commands
|
|
56
57
|
|
|
57
|
-
### `gitacc add [--type TYPE]`
|
|
58
|
+
### `gitacc add [--type TYPE] [--sign]`
|
|
58
59
|
|
|
59
60
|
Add a new Git account. Prompts for:
|
|
60
61
|
- Account identifier (used in key filename and as a reference)
|
|
@@ -67,8 +68,15 @@ Available key types: `rsa` (default), `ed25519`, `ecdsa`, `ecdsa-sk`, `ed25519-s
|
|
|
67
68
|
```bash
|
|
68
69
|
gitacc add
|
|
69
70
|
gitacc add --type ed25519
|
|
71
|
+
gitacc add --sign # opt in to SSH commit signing for this account
|
|
70
72
|
```
|
|
71
73
|
|
|
74
|
+
Pass `--sign` to opt this account into SSH commit signing. When you later `gitacc switch` to it,
|
|
75
|
+
`user.signingkey` is set to the account's public key and `gpg.format=ssh` / `commit.gpgsign=true`
|
|
76
|
+
are enabled (locally too, with `--local`), so every commit made under this identity is signed.
|
|
77
|
+
Switching to an account without `--sign` clears any signing config left by a previous account.
|
|
78
|
+
Toggle it later with `gitacc update <account> --sign` / `--no-sign`.
|
|
79
|
+
|
|
72
80
|
### `gitacc switch <account> [--local]` / `gitacc <account>`
|
|
73
81
|
|
|
74
82
|
Switch to a registered account. Clears all keys from the SSH agent, loads only this account's key, and sets `git config --global user.name/email`.
|
|
@@ -106,7 +114,7 @@ gitacc remove mywork
|
|
|
106
114
|
gitacc remove # prompts for account name
|
|
107
115
|
```
|
|
108
116
|
|
|
109
|
-
### `gitacc update <account> [--name NAME] [--email EMAIL]`
|
|
117
|
+
### `gitacc update <account> [--name NAME] [--email EMAIL] [--sign | --no-sign]`
|
|
110
118
|
|
|
111
119
|
Update the Git display name and/or email for an existing account. Prompts for any field not provided as a flag.
|
|
112
120
|
|
|
@@ -114,6 +122,8 @@ Update the Git display name and/or email for an existing account. Prompts for an
|
|
|
114
122
|
gitacc update mywork --name "Jane Doe"
|
|
115
123
|
gitacc update mywork --email "jane@newcompany.com"
|
|
116
124
|
gitacc update mywork # prompts for both
|
|
125
|
+
gitacc update mywork --sign # enable SSH commit signing for this account
|
|
126
|
+
gitacc update mywork --no-sign # disable it
|
|
117
127
|
```
|
|
118
128
|
|
|
119
129
|
### `gitacc init <account>`
|
|
@@ -180,6 +190,7 @@ gitacc --version
|
|
|
180
190
|
email = jane@company.com
|
|
181
191
|
private_key = /home/jane/.ssh/id_ed25519_mywork
|
|
182
192
|
public_key = /home/jane/.ssh/id_ed25519_mywork.pub
|
|
193
|
+
sign = false
|
|
183
194
|
```
|
|
184
195
|
|
|
185
196
|
## Requirements
|
|
@@ -18,11 +18,14 @@ class AccountManager:
|
|
|
18
18
|
self.ssh_manager = SSHManager()
|
|
19
19
|
self.hook_manager = HookManager()
|
|
20
20
|
|
|
21
|
-
def add_account(self, key_type: str = "rsa") -> bool:
|
|
21
|
+
def add_account(self, key_type: str = "rsa", sign: bool = False) -> bool:
|
|
22
22
|
"""Add a new Git account.
|
|
23
23
|
|
|
24
24
|
Args:
|
|
25
25
|
key_type: SSH key type (default: rsa)
|
|
26
|
+
sign: Opt-in to SSH commit signing with this account's key when
|
|
27
|
+
switched to (sets user.signingkey, gpg.format=ssh,
|
|
28
|
+
commit.gpgsign=true)
|
|
26
29
|
|
|
27
30
|
Returns:
|
|
28
31
|
True if successful, False otherwise
|
|
@@ -93,7 +96,7 @@ class AccountManager:
|
|
|
93
96
|
|
|
94
97
|
# Add to .gitacc file
|
|
95
98
|
success = self.config_manager.add_account(
|
|
96
|
-
account_name, git_name, email, private_key, public_key
|
|
99
|
+
account_name, git_name, email, private_key, public_key, sign=sign
|
|
97
100
|
)
|
|
98
101
|
if not success:
|
|
99
102
|
echo_color("r", "Failed to save account info!")
|
|
@@ -233,6 +236,24 @@ class AccountManager:
|
|
|
233
236
|
echo_color("r", "Failed to set Git config!")
|
|
234
237
|
return False
|
|
235
238
|
|
|
239
|
+
# Apply (or clear) this account's SSH commit-signing preference
|
|
240
|
+
sign_enabled = str(account_info.get("sign", "false")).lower() == "true"
|
|
241
|
+
public_key = account_info.get("public_key")
|
|
242
|
+
if local:
|
|
243
|
+
if sign_enabled and public_key:
|
|
244
|
+
signing_success = self.config_manager.set_git_signing_config_local(
|
|
245
|
+
public_key
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
signing_success = self.config_manager.unset_git_signing_config_local()
|
|
249
|
+
else:
|
|
250
|
+
if sign_enabled and public_key:
|
|
251
|
+
signing_success = self.config_manager.set_git_signing_config(public_key)
|
|
252
|
+
else:
|
|
253
|
+
signing_success = self.config_manager.unset_git_signing_config()
|
|
254
|
+
if not signing_success:
|
|
255
|
+
echo_color("y", "Warning: Could not update commit-signing config.")
|
|
256
|
+
|
|
236
257
|
scope = "this repository only" if local else "globally"
|
|
237
258
|
echo_color(
|
|
238
259
|
"g",
|
|
@@ -250,6 +271,7 @@ class AccountManager:
|
|
|
250
271
|
self.ssh_manager.kill_ssh_agent()
|
|
251
272
|
|
|
252
273
|
self.config_manager.unset_git_config()
|
|
274
|
+
self.config_manager.unset_git_signing_config()
|
|
253
275
|
echo_color("g", "Logged out successfully!")
|
|
254
276
|
return True
|
|
255
277
|
|
|
@@ -285,13 +307,16 @@ class AccountManager:
|
|
|
285
307
|
account_name: str,
|
|
286
308
|
new_git_name: Optional[str] = None,
|
|
287
309
|
new_email: Optional[str] = None,
|
|
310
|
+
new_sign: Optional[bool] = None,
|
|
288
311
|
) -> bool:
|
|
289
|
-
"""Update the Git name and/or
|
|
312
|
+
"""Update the Git name, email, and/or commit-signing preference for an
|
|
313
|
+
existing account.
|
|
290
314
|
|
|
291
315
|
Args:
|
|
292
316
|
account_name: Account identifier to update
|
|
293
317
|
new_git_name: New Git name (if None, will prompt)
|
|
294
318
|
new_email: New email (if None, will prompt)
|
|
319
|
+
new_sign: New commit-signing preference (if None, leave unchanged)
|
|
295
320
|
|
|
296
321
|
Returns:
|
|
297
322
|
True if successful, False otherwise
|
|
@@ -340,6 +365,18 @@ class AccountManager:
|
|
|
340
365
|
)
|
|
341
366
|
changed = True
|
|
342
367
|
|
|
368
|
+
if new_sign is not None:
|
|
369
|
+
current_sign = str(account_info.get("sign", "false")).lower() == "true"
|
|
370
|
+
if new_sign != current_sign:
|
|
371
|
+
if not self.config_manager.update_account_field(
|
|
372
|
+
account_name, "sign", "true" if new_sign else "false"
|
|
373
|
+
):
|
|
374
|
+
echo_color("r", "Failed to update commit-signing setting!")
|
|
375
|
+
return False
|
|
376
|
+
state = "enabled" if new_sign else "disabled"
|
|
377
|
+
echo_color("g", f'Commit signing {state} for "{account_name}"')
|
|
378
|
+
changed = True
|
|
379
|
+
|
|
343
380
|
if not changed:
|
|
344
381
|
echo_color("y", "No changes made.")
|
|
345
382
|
|
|
@@ -67,6 +67,7 @@ Examples:
|
|
|
67
67
|
gitacc add Add a new Git account
|
|
68
68
|
gitacc add --type ed25519 Add account with specific SSH key type
|
|
69
69
|
Available types: {key_types}
|
|
70
|
+
gitacc add --sign Add account with SSH commit signing enabled
|
|
70
71
|
gitacc switch myaccount Switch to an account
|
|
71
72
|
gitacc switch myaccount -l Switch to an account for this repo only
|
|
72
73
|
gitacc myaccount Switch to an account (short form)
|
|
@@ -93,6 +94,13 @@ Examples:
|
|
|
93
94
|
choices=get_ssh_key_types(),
|
|
94
95
|
help="SSH key type (default: rsa). Available: dsa, ecdsa, ecdsa-sk, ed25519, ed25519-sk, rsa",
|
|
95
96
|
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"--sign",
|
|
99
|
+
dest="sign",
|
|
100
|
+
action="store_true",
|
|
101
|
+
help="Sign commits with this account's SSH key when switched to "
|
|
102
|
+
"(sets user.signingkey, gpg.format=ssh, commit.gpgsign=true)",
|
|
103
|
+
)
|
|
96
104
|
parser.set_defaults(func=self._handle_add)
|
|
97
105
|
|
|
98
106
|
def _add_remove_command(self, subparsers: argparse._SubParsersAction) -> None:
|
|
@@ -195,6 +203,22 @@ Examples:
|
|
|
195
203
|
dest="new_email",
|
|
196
204
|
help="New email address (will prompt if not provided)",
|
|
197
205
|
)
|
|
206
|
+
sign_group = parser.add_mutually_exclusive_group()
|
|
207
|
+
sign_group.add_argument(
|
|
208
|
+
"--sign",
|
|
209
|
+
dest="sign",
|
|
210
|
+
action="store_const",
|
|
211
|
+
const=True,
|
|
212
|
+
default=None,
|
|
213
|
+
help="Enable SSH commit signing for this account",
|
|
214
|
+
)
|
|
215
|
+
sign_group.add_argument(
|
|
216
|
+
"--no-sign",
|
|
217
|
+
dest="sign",
|
|
218
|
+
action="store_const",
|
|
219
|
+
const=False,
|
|
220
|
+
help="Disable SSH commit signing for this account",
|
|
221
|
+
)
|
|
198
222
|
if argcomplete:
|
|
199
223
|
account_arg.completer = lambda **kwargs: get_account_names()
|
|
200
224
|
parser.set_defaults(func=self._handle_update)
|
|
@@ -318,7 +342,9 @@ Examples:
|
|
|
318
342
|
|
|
319
343
|
def _handle_add(self, args: argparse.Namespace) -> int:
|
|
320
344
|
"""Handle the 'add' command."""
|
|
321
|
-
success = self.account_manager.add_account(
|
|
345
|
+
success = self.account_manager.add_account(
|
|
346
|
+
key_type=args.key_type, sign=getattr(args, "sign", False)
|
|
347
|
+
)
|
|
322
348
|
return 0 if success else 1
|
|
323
349
|
|
|
324
350
|
def _handle_remove(self, args: argparse.Namespace) -> int:
|
|
@@ -383,7 +409,10 @@ Examples:
|
|
|
383
409
|
def _handle_update(self, args: argparse.Namespace) -> int:
|
|
384
410
|
"""Handle the 'update' command."""
|
|
385
411
|
success = self.account_manager.update_account(
|
|
386
|
-
args.account_name,
|
|
412
|
+
args.account_name,
|
|
413
|
+
args.new_git_name,
|
|
414
|
+
args.new_email,
|
|
415
|
+
new_sign=getattr(args, "sign", None),
|
|
387
416
|
)
|
|
388
417
|
return 0 if success else 1
|
|
389
418
|
|
|
@@ -93,6 +93,122 @@ class ConfigManager:
|
|
|
93
93
|
except subprocess.CalledProcessError:
|
|
94
94
|
return False
|
|
95
95
|
|
|
96
|
+
def set_git_signing_config(self, signing_key: str) -> bool:
|
|
97
|
+
"""Set Git global SSH commit signing config using an account's SSH key.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
signing_key: Path to the SSH public key to sign commits with
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
True if successful, False otherwise
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
subprocess.run(
|
|
107
|
+
["git", "config", "--global", "user.signingkey", signing_key],
|
|
108
|
+
check=True,
|
|
109
|
+
capture_output=True,
|
|
110
|
+
)
|
|
111
|
+
subprocess.run(
|
|
112
|
+
["git", "config", "--global", "gpg.format", "ssh"],
|
|
113
|
+
check=True,
|
|
114
|
+
capture_output=True,
|
|
115
|
+
)
|
|
116
|
+
subprocess.run(
|
|
117
|
+
["git", "config", "--global", "commit.gpgsign", "true"],
|
|
118
|
+
check=True,
|
|
119
|
+
capture_output=True,
|
|
120
|
+
)
|
|
121
|
+
return True
|
|
122
|
+
except subprocess.CalledProcessError:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
def unset_git_signing_config(self) -> bool:
|
|
126
|
+
"""Unset Git global SSH commit signing config.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if successful, False otherwise
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
subprocess.run(
|
|
133
|
+
["git", "config", "--global", "--unset", "user.signingkey"],
|
|
134
|
+
capture_output=True,
|
|
135
|
+
)
|
|
136
|
+
subprocess.run(
|
|
137
|
+
["git", "config", "--global", "--unset", "gpg.format"],
|
|
138
|
+
capture_output=True,
|
|
139
|
+
)
|
|
140
|
+
subprocess.run(
|
|
141
|
+
["git", "config", "--global", "--unset", "commit.gpgsign"],
|
|
142
|
+
capture_output=True,
|
|
143
|
+
)
|
|
144
|
+
return True
|
|
145
|
+
except subprocess.CalledProcessError:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
def set_git_signing_config_local(
|
|
149
|
+
self, signing_key: str, repo_path: Optional[Path] = None
|
|
150
|
+
) -> bool:
|
|
151
|
+
"""Set Git local (repository-scoped) SSH commit signing config.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
signing_key: Path to the SSH public key to sign commits with
|
|
155
|
+
repo_path: Path to repository (default: current directory)
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
True if successful, False otherwise
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
subprocess.run(
|
|
162
|
+
["git", "config", "user.signingkey", signing_key],
|
|
163
|
+
cwd=repo_path,
|
|
164
|
+
check=True,
|
|
165
|
+
capture_output=True,
|
|
166
|
+
)
|
|
167
|
+
subprocess.run(
|
|
168
|
+
["git", "config", "gpg.format", "ssh"],
|
|
169
|
+
cwd=repo_path,
|
|
170
|
+
check=True,
|
|
171
|
+
capture_output=True,
|
|
172
|
+
)
|
|
173
|
+
subprocess.run(
|
|
174
|
+
["git", "config", "commit.gpgsign", "true"],
|
|
175
|
+
cwd=repo_path,
|
|
176
|
+
check=True,
|
|
177
|
+
capture_output=True,
|
|
178
|
+
)
|
|
179
|
+
return True
|
|
180
|
+
except subprocess.CalledProcessError:
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
def unset_git_signing_config_local(self, repo_path: Optional[Path] = None) -> bool:
|
|
184
|
+
"""Unset Git local (repository-scoped) SSH commit signing config.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
repo_path: Path to repository (default: current directory)
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
True if successful, False otherwise
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
subprocess.run(
|
|
194
|
+
["git", "config", "--unset", "user.signingkey"],
|
|
195
|
+
cwd=repo_path,
|
|
196
|
+
capture_output=True,
|
|
197
|
+
)
|
|
198
|
+
subprocess.run(
|
|
199
|
+
["git", "config", "--unset", "gpg.format"],
|
|
200
|
+
cwd=repo_path,
|
|
201
|
+
capture_output=True,
|
|
202
|
+
)
|
|
203
|
+
subprocess.run(
|
|
204
|
+
["git", "config", "--unset", "commit.gpgsign"],
|
|
205
|
+
cwd=repo_path,
|
|
206
|
+
capture_output=True,
|
|
207
|
+
)
|
|
208
|
+
return True
|
|
209
|
+
except subprocess.CalledProcessError:
|
|
210
|
+
return False
|
|
211
|
+
|
|
96
212
|
def get_current_git_config(self) -> Dict[str, Optional[str]]:
|
|
97
213
|
"""Get current Git global config.
|
|
98
214
|
|
|
@@ -187,6 +303,7 @@ class ConfigManager:
|
|
|
187
303
|
email: str,
|
|
188
304
|
private_key: str,
|
|
189
305
|
public_key: str,
|
|
306
|
+
sign: bool = False,
|
|
190
307
|
) -> bool:
|
|
191
308
|
"""Add account to .gitacc file.
|
|
192
309
|
|
|
@@ -196,6 +313,8 @@ class ConfigManager:
|
|
|
196
313
|
email: Git user email
|
|
197
314
|
private_key: Path to private SSH key
|
|
198
315
|
public_key: Path to public SSH key
|
|
316
|
+
sign: Whether to sign commits with this account's SSH key when
|
|
317
|
+
switched to (sets user.signingkey/gpg.format/commit.gpgsign)
|
|
199
318
|
|
|
200
319
|
Returns:
|
|
201
320
|
True if successful, False otherwise
|
|
@@ -207,6 +326,7 @@ class ConfigManager:
|
|
|
207
326
|
f.write(f"\temail = {email}\n")
|
|
208
327
|
f.write(f"\tprivate_key = {private_key}\n")
|
|
209
328
|
f.write(f"\tpublic_key = {public_key}\n")
|
|
329
|
+
f.write(f"\tsign = {'true' if sign else 'false'}\n")
|
|
210
330
|
return True
|
|
211
331
|
except Exception:
|
|
212
332
|
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitacc-switcher
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Git Account Switcher - Manage multiple Git SSH accounts easily
|
|
5
5
|
Home-page: https://github.com/ktechhub/gitacc_switcher
|
|
6
6
|
Author: ktechhub
|
|
@@ -46,6 +46,7 @@ A Python CLI tool to easily switch between multiple Git SSH accounts. Manage mul
|
|
|
46
46
|
- Switch between accounts easily — one command, no fuss
|
|
47
47
|
- Generate SSH keys automatically (rsa, ed25519, ecdsa, and more)
|
|
48
48
|
- Optional passphrase support for SSH keys
|
|
49
|
+
- Opt-in SSH commit signing per account
|
|
49
50
|
- Colored terminal output with active account indicator
|
|
50
51
|
- Pre-commit hook to prevent commits with the wrong account
|
|
51
52
|
- Shell autocomplete for bash and zsh
|
|
@@ -87,7 +88,7 @@ gitacc verify # manual check
|
|
|
87
88
|
|
|
88
89
|
## Commands
|
|
89
90
|
|
|
90
|
-
### `gitacc add [--type TYPE]`
|
|
91
|
+
### `gitacc add [--type TYPE] [--sign]`
|
|
91
92
|
|
|
92
93
|
Add a new Git account. Prompts for:
|
|
93
94
|
- Account identifier (used in key filename and as a reference)
|
|
@@ -100,8 +101,15 @@ Available key types: `rsa` (default), `ed25519`, `ecdsa`, `ecdsa-sk`, `ed25519-s
|
|
|
100
101
|
```bash
|
|
101
102
|
gitacc add
|
|
102
103
|
gitacc add --type ed25519
|
|
104
|
+
gitacc add --sign # opt in to SSH commit signing for this account
|
|
103
105
|
```
|
|
104
106
|
|
|
107
|
+
Pass `--sign` to opt this account into SSH commit signing. When you later `gitacc switch` to it,
|
|
108
|
+
`user.signingkey` is set to the account's public key and `gpg.format=ssh` / `commit.gpgsign=true`
|
|
109
|
+
are enabled (locally too, with `--local`), so every commit made under this identity is signed.
|
|
110
|
+
Switching to an account without `--sign` clears any signing config left by a previous account.
|
|
111
|
+
Toggle it later with `gitacc update <account> --sign` / `--no-sign`.
|
|
112
|
+
|
|
105
113
|
### `gitacc switch <account> [--local]` / `gitacc <account>`
|
|
106
114
|
|
|
107
115
|
Switch to a registered account. Clears all keys from the SSH agent, loads only this account's key, and sets `git config --global user.name/email`.
|
|
@@ -139,7 +147,7 @@ gitacc remove mywork
|
|
|
139
147
|
gitacc remove # prompts for account name
|
|
140
148
|
```
|
|
141
149
|
|
|
142
|
-
### `gitacc update <account> [--name NAME] [--email EMAIL]`
|
|
150
|
+
### `gitacc update <account> [--name NAME] [--email EMAIL] [--sign | --no-sign]`
|
|
143
151
|
|
|
144
152
|
Update the Git display name and/or email for an existing account. Prompts for any field not provided as a flag.
|
|
145
153
|
|
|
@@ -147,6 +155,8 @@ Update the Git display name and/or email for an existing account. Prompts for an
|
|
|
147
155
|
gitacc update mywork --name "Jane Doe"
|
|
148
156
|
gitacc update mywork --email "jane@newcompany.com"
|
|
149
157
|
gitacc update mywork # prompts for both
|
|
158
|
+
gitacc update mywork --sign # enable SSH commit signing for this account
|
|
159
|
+
gitacc update mywork --no-sign # disable it
|
|
150
160
|
```
|
|
151
161
|
|
|
152
162
|
### `gitacc init <account>`
|
|
@@ -213,6 +223,7 @@ gitacc --version
|
|
|
213
223
|
email = jane@company.com
|
|
214
224
|
private_key = /home/jane/.ssh/id_ed25519_mywork
|
|
215
225
|
public_key = /home/jane/.ssh/id_ed25519_mywork.pub
|
|
226
|
+
sign = false
|
|
216
227
|
```
|
|
217
228
|
|
|
218
229
|
## Requirements
|
|
@@ -9,7 +9,7 @@ long_description = readme_file.read_text() if readme_file.exists() else ""
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name="gitacc-switcher",
|
|
12
|
-
version="1.
|
|
12
|
+
version="1.2.0",
|
|
13
13
|
description="Git Account Switcher - Manage multiple Git SSH accounts easily",
|
|
14
14
|
long_description=long_description,
|
|
15
15
|
long_description_content_type="text/markdown",
|
|
@@ -90,6 +90,59 @@ class TestSwitchAccount:
|
|
|
90
90
|
"Work User", "work@company.com"
|
|
91
91
|
)
|
|
92
92
|
|
|
93
|
+
def test_unsigned_account_unsets_signing_config(self, manager, tmp_path):
|
|
94
|
+
key = tmp_path / "id_rsa_work"
|
|
95
|
+
key.write_text("key")
|
|
96
|
+
account = {**WORK_ACCOUNT, "private_key": str(key), "sign": "false"}
|
|
97
|
+
manager.config_manager.get_account.return_value = account
|
|
98
|
+
manager.ssh_manager.is_ssh_agent_running.return_value = True
|
|
99
|
+
manager.ssh_manager.clear_all_keys.return_value = True
|
|
100
|
+
manager.ssh_manager.add_key_to_agent.return_value = (True, None)
|
|
101
|
+
manager.config_manager.set_git_config.return_value = True
|
|
102
|
+
with patch("subprocess.run") as mock_run:
|
|
103
|
+
mock_run.return_value = MagicMock(returncode=0, stdout="1 key\n")
|
|
104
|
+
result = manager.switch_account("work")
|
|
105
|
+
assert result is True
|
|
106
|
+
manager.config_manager.unset_git_signing_config.assert_called_once()
|
|
107
|
+
manager.config_manager.set_git_signing_config.assert_not_called()
|
|
108
|
+
|
|
109
|
+
def test_signed_account_sets_signing_config(self, manager, tmp_path):
|
|
110
|
+
key = tmp_path / "id_rsa_work"
|
|
111
|
+
key.write_text("key")
|
|
112
|
+
account = {**WORK_ACCOUNT, "private_key": str(key), "sign": "true"}
|
|
113
|
+
manager.config_manager.get_account.return_value = account
|
|
114
|
+
manager.ssh_manager.is_ssh_agent_running.return_value = True
|
|
115
|
+
manager.ssh_manager.clear_all_keys.return_value = True
|
|
116
|
+
manager.ssh_manager.add_key_to_agent.return_value = (True, None)
|
|
117
|
+
manager.config_manager.set_git_config.return_value = True
|
|
118
|
+
with patch("subprocess.run") as mock_run:
|
|
119
|
+
mock_run.return_value = MagicMock(returncode=0, stdout="1 key\n")
|
|
120
|
+
result = manager.switch_account("work")
|
|
121
|
+
assert result is True
|
|
122
|
+
manager.config_manager.set_git_signing_config.assert_called_once_with(
|
|
123
|
+
"/home/user/.ssh/id_rsa_work.pub"
|
|
124
|
+
)
|
|
125
|
+
manager.config_manager.unset_git_signing_config.assert_not_called()
|
|
126
|
+
|
|
127
|
+
def test_signed_account_local_sets_local_signing_config(self, manager, tmp_path):
|
|
128
|
+
key = tmp_path / "id_rsa_work"
|
|
129
|
+
key.write_text("key")
|
|
130
|
+
account = {**WORK_ACCOUNT, "private_key": str(key), "sign": "true"}
|
|
131
|
+
manager.config_manager.get_account.return_value = account
|
|
132
|
+
manager.config_manager.is_git_repo.return_value = True
|
|
133
|
+
manager.ssh_manager.is_ssh_agent_running.return_value = True
|
|
134
|
+
manager.ssh_manager.clear_all_keys.return_value = True
|
|
135
|
+
manager.ssh_manager.add_key_to_agent.return_value = (True, None)
|
|
136
|
+
manager.config_manager.set_git_config_local.return_value = True
|
|
137
|
+
with patch("subprocess.run") as mock_run:
|
|
138
|
+
mock_run.return_value = MagicMock(returncode=0, stdout="1 key\n")
|
|
139
|
+
result = manager.switch_account("work", local=True)
|
|
140
|
+
assert result is True
|
|
141
|
+
manager.config_manager.set_git_signing_config_local.assert_called_once_with(
|
|
142
|
+
"/home/user/.ssh/id_rsa_work.pub"
|
|
143
|
+
)
|
|
144
|
+
manager.config_manager.set_git_signing_config.assert_not_called()
|
|
145
|
+
|
|
93
146
|
def test_clears_all_keys_before_adding(self, manager, tmp_path):
|
|
94
147
|
key = tmp_path / "id_rsa_work"
|
|
95
148
|
key.write_text("key")
|
|
@@ -159,6 +212,11 @@ class TestLogout:
|
|
|
159
212
|
manager.logout()
|
|
160
213
|
manager.config_manager.unset_git_config.assert_called_once()
|
|
161
214
|
|
|
215
|
+
def test_unsets_signing_config(self, manager):
|
|
216
|
+
manager.ssh_manager.is_ssh_agent_running.return_value = False
|
|
217
|
+
manager.logout()
|
|
218
|
+
manager.config_manager.unset_git_signing_config.assert_called_once()
|
|
219
|
+
|
|
162
220
|
|
|
163
221
|
class TestUpdateAccount:
|
|
164
222
|
def test_update_name(self, manager):
|
|
@@ -216,6 +274,58 @@ class TestUpdateAccount:
|
|
|
216
274
|
manager.config_manager.get_account.return_value = None
|
|
217
275
|
assert manager.update_account("nobody", "name", "email") is False
|
|
218
276
|
|
|
277
|
+
def test_enables_signing(self, manager):
|
|
278
|
+
manager.config_manager.get_account.return_value = {
|
|
279
|
+
"name": "Work User",
|
|
280
|
+
"email": "work@co.com",
|
|
281
|
+
"sign": "false",
|
|
282
|
+
}
|
|
283
|
+
manager.config_manager.update_account_field.return_value = True
|
|
284
|
+
result = manager.update_account(
|
|
285
|
+
"work", new_git_name="Work User", new_email="work@co.com", new_sign=True
|
|
286
|
+
)
|
|
287
|
+
assert result is True
|
|
288
|
+
manager.config_manager.update_account_field.assert_called_once_with(
|
|
289
|
+
"work", "sign", "true"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def test_disables_signing(self, manager):
|
|
293
|
+
manager.config_manager.get_account.return_value = {
|
|
294
|
+
"name": "Work User",
|
|
295
|
+
"email": "work@co.com",
|
|
296
|
+
"sign": "true",
|
|
297
|
+
}
|
|
298
|
+
manager.config_manager.update_account_field.return_value = True
|
|
299
|
+
result = manager.update_account(
|
|
300
|
+
"work", new_git_name="Work User", new_email="work@co.com", new_sign=False
|
|
301
|
+
)
|
|
302
|
+
assert result is True
|
|
303
|
+
manager.config_manager.update_account_field.assert_called_once_with(
|
|
304
|
+
"work", "sign", "false"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def test_no_sign_change_does_not_write(self, manager):
|
|
308
|
+
manager.config_manager.get_account.return_value = {
|
|
309
|
+
"name": "Work User",
|
|
310
|
+
"email": "work@co.com",
|
|
311
|
+
"sign": "true",
|
|
312
|
+
}
|
|
313
|
+
manager.update_account(
|
|
314
|
+
"work", new_git_name="Work User", new_email="work@co.com", new_sign=True
|
|
315
|
+
)
|
|
316
|
+
manager.config_manager.update_account_field.assert_not_called()
|
|
317
|
+
|
|
318
|
+
def test_sign_none_leaves_setting_unchanged(self, manager):
|
|
319
|
+
manager.config_manager.get_account.return_value = {
|
|
320
|
+
"name": "Work User",
|
|
321
|
+
"email": "work@co.com",
|
|
322
|
+
"sign": "true",
|
|
323
|
+
}
|
|
324
|
+
manager.update_account(
|
|
325
|
+
"work", new_git_name="Work User", new_email="work@co.com", new_sign=None
|
|
326
|
+
)
|
|
327
|
+
manager.config_manager.update_account_field.assert_not_called()
|
|
328
|
+
|
|
219
329
|
|
|
220
330
|
class TestRemoveAccount:
|
|
221
331
|
def test_returns_false_for_missing_account(self, manager):
|
|
@@ -294,3 +404,25 @@ class TestAddAccount:
|
|
|
294
404
|
"gitacc_switcher.account_manager.ask_yes_no", return_value=True
|
|
295
405
|
), patch("getpass.getpass", side_effect=["pass1", "pass2"]):
|
|
296
406
|
assert manager.add_account() is False
|
|
407
|
+
|
|
408
|
+
def test_sign_flag_passed_to_config_manager(self, manager, tmp_path):
|
|
409
|
+
self._setup_add(manager, tmp_path)
|
|
410
|
+
inputs = ["mywork", "My Work", "work@co.com"]
|
|
411
|
+
with patch("builtins.input", side_effect=inputs), patch(
|
|
412
|
+
"gitacc_switcher.account_manager.ask_yes_no", return_value=False
|
|
413
|
+
):
|
|
414
|
+
result = manager.add_account(sign=True)
|
|
415
|
+
assert result is True
|
|
416
|
+
_, kwargs = manager.config_manager.add_account.call_args
|
|
417
|
+
assert kwargs["sign"] is True
|
|
418
|
+
|
|
419
|
+
def test_sign_defaults_to_false(self, manager, tmp_path):
|
|
420
|
+
self._setup_add(manager, tmp_path)
|
|
421
|
+
inputs = ["mywork", "My Work", "work@co.com"]
|
|
422
|
+
with patch("builtins.input", side_effect=inputs), patch(
|
|
423
|
+
"gitacc_switcher.account_manager.ask_yes_no", return_value=False
|
|
424
|
+
):
|
|
425
|
+
result = manager.add_account()
|
|
426
|
+
assert result is True
|
|
427
|
+
_, kwargs = manager.config_manager.add_account.call_args
|
|
428
|
+
assert kwargs["sign"] is False
|
|
@@ -33,6 +33,14 @@ class TestParserCommands:
|
|
|
33
33
|
with pytest.raises(SystemExit):
|
|
34
34
|
cli.parser.parse_args(["add", "--type", "invalid"])
|
|
35
35
|
|
|
36
|
+
def test_add_sign_defaults_to_false(self, cli):
|
|
37
|
+
args = cli.parser.parse_args(["add"])
|
|
38
|
+
assert args.sign is False
|
|
39
|
+
|
|
40
|
+
def test_add_sign_flag(self, cli):
|
|
41
|
+
args = cli.parser.parse_args(["add", "--sign"])
|
|
42
|
+
assert args.sign is True
|
|
43
|
+
|
|
36
44
|
def test_switch_parses_account(self, cli):
|
|
37
45
|
args = cli.parser.parse_args(["switch", "mywork"])
|
|
38
46
|
assert args.command == "switch"
|
|
@@ -73,6 +81,19 @@ class TestParserCommands:
|
|
|
73
81
|
assert args.account_name == "mywork"
|
|
74
82
|
assert args.new_git_name is None
|
|
75
83
|
assert args.new_email is None
|
|
84
|
+
assert args.sign is None
|
|
85
|
+
|
|
86
|
+
def test_update_sign_flag(self, cli):
|
|
87
|
+
args = cli.parser.parse_args(["update", "mywork", "--sign"])
|
|
88
|
+
assert args.sign is True
|
|
89
|
+
|
|
90
|
+
def test_update_no_sign_flag(self, cli):
|
|
91
|
+
args = cli.parser.parse_args(["update", "mywork", "--no-sign"])
|
|
92
|
+
assert args.sign is False
|
|
93
|
+
|
|
94
|
+
def test_update_sign_and_no_sign_mutually_exclusive(self, cli):
|
|
95
|
+
with pytest.raises(SystemExit):
|
|
96
|
+
cli.parser.parse_args(["update", "mywork", "--sign", "--no-sign"])
|
|
76
97
|
|
|
77
98
|
def test_update_with_name_flag(self, cli):
|
|
78
99
|
args = cli.parser.parse_args(["update", "mywork", "--name", "New Name"])
|
|
@@ -110,6 +131,13 @@ class TestHandlers:
|
|
|
110
131
|
cli.account_manager.add_account.return_value = False
|
|
111
132
|
assert cli._handle_add(argparse.Namespace(key_type="rsa")) == 1
|
|
112
133
|
|
|
134
|
+
def test_handle_add_passes_sign_flag(self, cli):
|
|
135
|
+
cli.account_manager.add_account.return_value = True
|
|
136
|
+
cli._handle_add(argparse.Namespace(key_type="rsa", sign=True))
|
|
137
|
+
cli.account_manager.add_account.assert_called_once_with(
|
|
138
|
+
key_type="rsa", sign=True
|
|
139
|
+
)
|
|
140
|
+
|
|
113
141
|
def test_handle_switch_success(self, cli):
|
|
114
142
|
cli.account_manager.switch_account.return_value = True
|
|
115
143
|
args = argparse.Namespace(account_name="work", local=False)
|
|
@@ -160,6 +188,16 @@ class TestHandlers:
|
|
|
160
188
|
)
|
|
161
189
|
assert cli._handle_update(args) == 1
|
|
162
190
|
|
|
191
|
+
def test_handle_update_passes_sign_flag(self, cli):
|
|
192
|
+
cli.account_manager.update_account.return_value = True
|
|
193
|
+
args = argparse.Namespace(
|
|
194
|
+
account_name="work", new_git_name=None, new_email=None, sign=True
|
|
195
|
+
)
|
|
196
|
+
cli._handle_update(args)
|
|
197
|
+
cli.account_manager.update_account.assert_called_once_with(
|
|
198
|
+
"work", None, None, new_sign=True
|
|
199
|
+
)
|
|
200
|
+
|
|
163
201
|
|
|
164
202
|
# ---------------------------------------------------------------------------
|
|
165
203
|
# List shows active account marker
|
|
@@ -109,6 +109,14 @@ class TestAddAccount:
|
|
|
109
109
|
config.add_account("personal", "Personal", "p@p.com", "/k2", "/k2.pub")
|
|
110
110
|
assert len(config.read_accounts()) == 2
|
|
111
111
|
|
|
112
|
+
def test_defaults_sign_to_false(self, config):
|
|
113
|
+
config.add_account("test", "Test User", "t@t.com", "/k", "/k.pub")
|
|
114
|
+
assert config.get_account("test")["sign"] == "false"
|
|
115
|
+
|
|
116
|
+
def test_persists_sign_true_when_enabled(self, config):
|
|
117
|
+
config.add_account("test", "Test User", "t@t.com", "/k", "/k.pub", sign=True)
|
|
118
|
+
assert config.get_account("test")["sign"] == "true"
|
|
119
|
+
|
|
112
120
|
|
|
113
121
|
class TestRemoveAccount:
|
|
114
122
|
def test_removes_account(self, config_with_accounts):
|
|
@@ -205,6 +213,91 @@ class TestSetGitConfigLocal:
|
|
|
205
213
|
assert config.set_git_config_local("Test", "test@test.com") is False
|
|
206
214
|
|
|
207
215
|
|
|
216
|
+
class TestSetGitSigningConfig:
|
|
217
|
+
def test_sets_signingkey_format_and_gpgsign(self, config):
|
|
218
|
+
with patch("subprocess.run") as mock_run:
|
|
219
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
220
|
+
result = config.set_git_signing_config("/home/user/.ssh/id_rsa.pub")
|
|
221
|
+
assert result is True
|
|
222
|
+
assert mock_run.call_count == 3
|
|
223
|
+
calls = [call.args[0] for call in mock_run.call_args_list]
|
|
224
|
+
assert [
|
|
225
|
+
"git",
|
|
226
|
+
"config",
|
|
227
|
+
"--global",
|
|
228
|
+
"user.signingkey",
|
|
229
|
+
"/home/user/.ssh/id_rsa.pub",
|
|
230
|
+
] in calls
|
|
231
|
+
assert ["git", "config", "--global", "gpg.format", "ssh"] in calls
|
|
232
|
+
assert ["git", "config", "--global", "commit.gpgsign", "true"] in calls
|
|
233
|
+
|
|
234
|
+
def test_returns_false_on_error(self, config):
|
|
235
|
+
with patch(
|
|
236
|
+
"subprocess.run", side_effect=subprocess.CalledProcessError(1, "git")
|
|
237
|
+
):
|
|
238
|
+
assert config.set_git_signing_config("/k.pub") is False
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class TestUnsetGitSigningConfig:
|
|
242
|
+
def test_unsets_all_signing_keys(self, config):
|
|
243
|
+
with patch("subprocess.run") as mock_run:
|
|
244
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
245
|
+
result = config.unset_git_signing_config()
|
|
246
|
+
assert result is True
|
|
247
|
+
assert mock_run.call_count == 3
|
|
248
|
+
calls = [call.args[0] for call in mock_run.call_args_list]
|
|
249
|
+
assert ["git", "config", "--global", "--unset", "user.signingkey"] in calls
|
|
250
|
+
assert ["git", "config", "--global", "--unset", "gpg.format"] in calls
|
|
251
|
+
assert ["git", "config", "--global", "--unset", "commit.gpgsign"] in calls
|
|
252
|
+
|
|
253
|
+
def test_succeeds_even_when_key_was_unset(self, config):
|
|
254
|
+
# git config --unset on a missing key returns non-zero, but we don't
|
|
255
|
+
# pass check=True for unset, so this should not raise/fail.
|
|
256
|
+
with patch("subprocess.run") as mock_run:
|
|
257
|
+
mock_run.return_value = MagicMock(returncode=5)
|
|
258
|
+
assert config.unset_git_signing_config() is True
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class TestSetGitSigningConfigLocal:
|
|
262
|
+
def test_calls_git_config_without_global_flag(self, config):
|
|
263
|
+
with patch("subprocess.run") as mock_run:
|
|
264
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
265
|
+
result = config.set_git_signing_config_local("/k.pub")
|
|
266
|
+
assert result is True
|
|
267
|
+
for call in mock_run.call_args_list:
|
|
268
|
+
assert "--global" not in call.args[0]
|
|
269
|
+
|
|
270
|
+
def test_passes_repo_path_as_cwd(self, config, tmp_path):
|
|
271
|
+
with patch("subprocess.run") as mock_run:
|
|
272
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
273
|
+
config.set_git_signing_config_local("/k.pub", tmp_path)
|
|
274
|
+
for call in mock_run.call_args_list:
|
|
275
|
+
assert call.kwargs["cwd"] == tmp_path
|
|
276
|
+
|
|
277
|
+
def test_returns_false_on_error(self, config):
|
|
278
|
+
with patch(
|
|
279
|
+
"subprocess.run", side_effect=subprocess.CalledProcessError(1, "git")
|
|
280
|
+
):
|
|
281
|
+
assert config.set_git_signing_config_local("/k.pub") is False
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class TestUnsetGitSigningConfigLocal:
|
|
285
|
+
def test_calls_git_config_without_global_flag(self, config):
|
|
286
|
+
with patch("subprocess.run") as mock_run:
|
|
287
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
288
|
+
result = config.unset_git_signing_config_local()
|
|
289
|
+
assert result is True
|
|
290
|
+
for call in mock_run.call_args_list:
|
|
291
|
+
assert "--global" not in call.args[0]
|
|
292
|
+
|
|
293
|
+
def test_passes_repo_path_as_cwd(self, config, tmp_path):
|
|
294
|
+
with patch("subprocess.run") as mock_run:
|
|
295
|
+
mock_run.return_value = MagicMock(returncode=0)
|
|
296
|
+
config.unset_git_signing_config_local(tmp_path)
|
|
297
|
+
for call in mock_run.call_args_list:
|
|
298
|
+
assert call.kwargs["cwd"] == tmp_path
|
|
299
|
+
|
|
300
|
+
|
|
208
301
|
class TestGetCurrentGitConfig:
|
|
209
302
|
def test_returns_name_and_email_keys(self, config):
|
|
210
303
|
with patch("subprocess.run") as mock_run:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitacc_switcher-1.1.0 → gitacc_switcher-1.2.0}/gitacc_switcher.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|