gitacc-switcher 1.0.4__tar.gz → 1.1.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.
Files changed (30) hide show
  1. {gitacc_switcher-1.0.4/gitacc_switcher.egg-info → gitacc_switcher-1.1.0}/PKG-INFO +9 -2
  2. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/README.md +8 -1
  3. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher/__init__.py +1 -1
  4. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher/account_manager.py +18 -3
  5. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher/cli.py +10 -1
  6. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher/config_manager.py +30 -0
  7. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0/gitacc_switcher.egg-info}/PKG-INFO +9 -2
  8. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/pyproject.toml +1 -1
  9. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/setup.py +1 -1
  10. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/tests/test_account_manager.py +39 -0
  11. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/tests/test_cli.py +19 -2
  12. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/tests/test_config_manager.py +24 -0
  13. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/LICENSE +0 -0
  14. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/MANIFEST.in +0 -0
  15. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher/completion.py +0 -0
  16. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher/hook_manager.py +0 -0
  17. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher/ssh_manager.py +0 -0
  18. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher/utils.py +0 -0
  19. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher.egg-info/SOURCES.txt +0 -0
  20. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher.egg-info/dependency_links.txt +0 -0
  21. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher.egg-info/entry_points.txt +0 -0
  22. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher.egg-info/requires.txt +0 -0
  23. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/gitacc_switcher.egg-info/top_level.txt +0 -0
  24. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/requirements.txt +0 -0
  25. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/setup.cfg +0 -0
  26. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/tests/__init__.py +0 -0
  27. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/tests/test_completion.py +0 -0
  28. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/tests/test_hook_manager.py +0 -0
  29. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.0}/tests/test_ssh_manager.py +0 -0
  30. {gitacc_switcher-1.0.4 → gitacc_switcher-1.1.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.0.4
3
+ Version: 1.1.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
@@ -75,6 +75,7 @@ gitacc add --type ed25519 # specify key type
75
75
  # 2. Switch between them
76
76
  gitacc switch work # full form
77
77
  gitacc work # shorthand — same thing
78
+ gitacc switch work --local # only this repo, leave the global identity alone
78
79
 
79
80
  # 3. See what's registered (active account is marked with *)
80
81
  gitacc list
@@ -101,7 +102,7 @@ gitacc add
101
102
  gitacc add --type ed25519
102
103
  ```
103
104
 
104
- ### `gitacc switch <account>` / `gitacc <account>`
105
+ ### `gitacc switch <account> [--local]` / `gitacc <account>`
105
106
 
106
107
  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`.
107
108
 
@@ -110,6 +111,12 @@ gitacc switch mywork
110
111
  gitacc mywork # shorthand
111
112
  ```
112
113
 
114
+ Pass `--local`/`-l` to set the identity for the current repository only (`git config` local scope), leaving the global `user.name`/`user.email` untouched. Must be run from inside a Git repository.
115
+
116
+ ```bash
117
+ gitacc switch mywork --local
118
+ ```
119
+
113
120
  > The SSH agent must already be running (`eval $(ssh-agent)`). If it isn't, gitacc will tell you.
114
121
 
115
122
  ### `gitacc list`
@@ -42,6 +42,7 @@ gitacc add --type ed25519 # specify key type
42
42
  # 2. Switch between them
43
43
  gitacc switch work # full form
44
44
  gitacc work # shorthand — same thing
45
+ gitacc switch work --local # only this repo, leave the global identity alone
45
46
 
46
47
  # 3. See what's registered (active account is marked with *)
47
48
  gitacc list
@@ -68,7 +69,7 @@ gitacc add
68
69
  gitacc add --type ed25519
69
70
  ```
70
71
 
71
- ### `gitacc switch <account>` / `gitacc <account>`
72
+ ### `gitacc switch <account> [--local]` / `gitacc <account>`
72
73
 
73
74
  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`.
74
75
 
@@ -77,6 +78,12 @@ gitacc switch mywork
77
78
  gitacc mywork # shorthand
78
79
  ```
79
80
 
81
+ Pass `--local`/`-l` to set the identity for the current repository only (`git config` local scope), leaving the global `user.name`/`user.email` untouched. Must be run from inside a Git repository.
82
+
83
+ ```bash
84
+ gitacc switch mywork --local
85
+ ```
86
+
80
87
  > The SSH agent must already be running (`eval $(ssh-agent)`). If it isn't, gitacc will tell you.
81
88
 
82
89
  ### `gitacc list`
@@ -1,4 +1,4 @@
1
1
  """Git Account Switcher - Manage multiple Git SSH accounts easily."""
2
2
 
3
- __version__ = "1.0.4"
3
+ __version__ = "1.1.0"
4
4
  __author__ = "ktechhub"
@@ -156,11 +156,13 @@ class AccountManager:
156
156
  echo_color("g", f'Account "{account_name}" removed successfully!')
157
157
  return True
158
158
 
159
- def switch_account(self, account_name: str) -> bool:
159
+ def switch_account(self, account_name: str, local: bool = False) -> bool:
160
160
  """Switch to a Git account.
161
161
 
162
162
  Args:
163
163
  account_name: Account name to switch to
164
+ local: If True, set the identity for the current repository only
165
+ (git config local scope) instead of globally
164
166
 
165
167
  Returns:
166
168
  True if successful, False otherwise
@@ -171,6 +173,11 @@ class AccountManager:
171
173
  echo_color("r", "Wrong: account name!!")
172
174
  return False
173
175
 
176
+ if local and not self.config_manager.is_git_repo():
177
+ echo_color("r", "Not a Git repository!")
178
+ echo_color("y", "--local requires running inside a Git repository.")
179
+ return False
180
+
174
181
  # Check if SSH agent is already running
175
182
  agent_running = self.ssh_manager.is_ssh_agent_running()
176
183
 
@@ -218,11 +225,19 @@ class AccountManager:
218
225
  # Set Git config
219
226
  name = account_info.get("name")
220
227
  email = account_info.get("email")
221
- if not self.config_manager.set_git_config(name, email):
228
+ if local:
229
+ config_success = self.config_manager.set_git_config_local(name, email)
230
+ else:
231
+ config_success = self.config_manager.set_git_config(name, email)
232
+ if not config_success:
222
233
  echo_color("r", "Failed to set Git config!")
223
234
  return False
224
235
 
225
- echo_color("g", f'Switched to account "{account_name}" successfully!')
236
+ scope = "this repository only" if local else "globally"
237
+ echo_color(
238
+ "g",
239
+ f'Switched to account "{account_name}" successfully! ({scope})',
240
+ )
226
241
  return True
227
242
 
228
243
  def logout(self) -> bool:
@@ -68,6 +68,7 @@ Examples:
68
68
  gitacc add --type ed25519 Add account with specific SSH key type
69
69
  Available types: {key_types}
70
70
  gitacc switch myaccount Switch to an account
71
+ gitacc switch myaccount -l Switch to an account for this repo only
71
72
  gitacc myaccount Switch to an account (short form)
72
73
  gitacc remove myaccount Remove an account
73
74
  gitacc list List all registered accounts
@@ -121,6 +122,12 @@ Examples:
121
122
  "account_name",
122
123
  help="Account name to switch to",
123
124
  )
125
+ parser.add_argument(
126
+ "-l",
127
+ "--local",
128
+ action="store_true",
129
+ help="Set the identity for this repository only, instead of globally",
130
+ )
124
131
  if argcomplete:
125
132
  account_arg.completer = lambda **kwargs: get_account_names()
126
133
  parser.set_defaults(func=self._handle_switch)
@@ -321,7 +328,9 @@ Examples:
321
328
 
322
329
  def _handle_switch(self, args: argparse.Namespace) -> int:
323
330
  """Handle the 'switch' command."""
324
- success = self.account_manager.switch_account(args.account_name)
331
+ success = self.account_manager.switch_account(
332
+ args.account_name, local=getattr(args, "local", False)
333
+ )
325
334
  return 0 if success else 1
326
335
 
327
336
  def _handle_list(self, args: argparse.Namespace) -> int:
@@ -44,6 +44,36 @@ class ConfigManager:
44
44
  except subprocess.CalledProcessError:
45
45
  return False
46
46
 
47
+ def set_git_config_local(
48
+ self, name: str, email: str, repo_path: Optional[Path] = None
49
+ ) -> bool:
50
+ """Set Git local (repository-scoped) user.name and user.email.
51
+
52
+ Args:
53
+ name: Git user name
54
+ email: Git user email
55
+ repo_path: Path to repository (default: current directory)
56
+
57
+ Returns:
58
+ True if successful, False otherwise
59
+ """
60
+ try:
61
+ subprocess.run(
62
+ ["git", "config", "user.name", name],
63
+ cwd=repo_path,
64
+ check=True,
65
+ capture_output=True,
66
+ )
67
+ subprocess.run(
68
+ ["git", "config", "user.email", email],
69
+ cwd=repo_path,
70
+ check=True,
71
+ capture_output=True,
72
+ )
73
+ return True
74
+ except subprocess.CalledProcessError:
75
+ return False
76
+
47
77
  def unset_git_config(self) -> bool:
48
78
  """Unset Git global user.name and user.email.
49
79
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitacc-switcher
3
- Version: 1.0.4
3
+ Version: 1.1.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
@@ -75,6 +75,7 @@ gitacc add --type ed25519 # specify key type
75
75
  # 2. Switch between them
76
76
  gitacc switch work # full form
77
77
  gitacc work # shorthand — same thing
78
+ gitacc switch work --local # only this repo, leave the global identity alone
78
79
 
79
80
  # 3. See what's registered (active account is marked with *)
80
81
  gitacc list
@@ -101,7 +102,7 @@ gitacc add
101
102
  gitacc add --type ed25519
102
103
  ```
103
104
 
104
- ### `gitacc switch <account>` / `gitacc <account>`
105
+ ### `gitacc switch <account> [--local]` / `gitacc <account>`
105
106
 
106
107
  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`.
107
108
 
@@ -110,6 +111,12 @@ gitacc switch mywork
110
111
  gitacc mywork # shorthand
111
112
  ```
112
113
 
114
+ Pass `--local`/`-l` to set the identity for the current repository only (`git config` local scope), leaving the global `user.name`/`user.email` untouched. Must be run from inside a Git repository.
115
+
116
+ ```bash
117
+ gitacc switch mywork --local
118
+ ```
119
+
113
120
  > The SSH agent must already be running (`eval $(ssh-agent)`). If it isn't, gitacc will tell you.
114
121
 
115
122
  ### `gitacc list`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gitacc-switcher"
7
- version = "1.0.4"
7
+ version = "1.1.0"
8
8
  description = "Git Account Switcher - Manage multiple Git SSH accounts easily"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"
@@ -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.0.4",
12
+ version="1.1.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",
@@ -103,6 +103,45 @@ class TestSwitchAccount:
103
103
  manager.switch_account("work")
104
104
  manager.ssh_manager.clear_all_keys.assert_called_once()
105
105
 
106
+ def test_local_fails_when_not_a_git_repo(self, manager):
107
+ manager.config_manager.get_account.return_value = WORK_ACCOUNT
108
+ manager.config_manager.is_git_repo.return_value = False
109
+ assert manager.switch_account("work", local=True) is False
110
+ manager.config_manager.set_git_config_local.assert_not_called()
111
+
112
+ def test_local_sets_local_git_config(self, manager, tmp_path):
113
+ key = tmp_path / "id_rsa_work"
114
+ key.write_text("key")
115
+ account = {**WORK_ACCOUNT, "private_key": str(key)}
116
+ manager.config_manager.get_account.return_value = account
117
+ manager.config_manager.is_git_repo.return_value = True
118
+ manager.ssh_manager.is_ssh_agent_running.return_value = True
119
+ manager.ssh_manager.clear_all_keys.return_value = True
120
+ manager.ssh_manager.add_key_to_agent.return_value = (True, None)
121
+ manager.config_manager.set_git_config_local.return_value = True
122
+ with patch("subprocess.run") as mock_run:
123
+ mock_run.return_value = MagicMock(returncode=0, stdout="1 key\n")
124
+ result = manager.switch_account("work", local=True)
125
+ assert result is True
126
+ manager.config_manager.set_git_config_local.assert_called_once_with(
127
+ "Work User", "work@company.com"
128
+ )
129
+ manager.config_manager.set_git_config.assert_not_called()
130
+
131
+ def test_global_switch_does_not_check_git_repo(self, manager, tmp_path):
132
+ key = tmp_path / "id_rsa_work"
133
+ key.write_text("key")
134
+ account = {**WORK_ACCOUNT, "private_key": str(key)}
135
+ manager.config_manager.get_account.return_value = account
136
+ manager.ssh_manager.is_ssh_agent_running.return_value = True
137
+ manager.ssh_manager.clear_all_keys.return_value = True
138
+ manager.ssh_manager.add_key_to_agent.return_value = (True, None)
139
+ manager.config_manager.set_git_config.return_value = True
140
+ with patch("subprocess.run") as mock_run:
141
+ mock_run.return_value = MagicMock(returncode=0, stdout="1 key\n")
142
+ manager.switch_account("work")
143
+ manager.config_manager.is_git_repo.assert_not_called()
144
+
106
145
 
107
146
  class TestLogout:
108
147
  def test_kills_agent_when_running(self, manager):
@@ -37,6 +37,15 @@ class TestParserCommands:
37
37
  args = cli.parser.parse_args(["switch", "mywork"])
38
38
  assert args.command == "switch"
39
39
  assert args.account_name == "mywork"
40
+ assert args.local is False
41
+
42
+ def test_switch_local_flag(self, cli):
43
+ args = cli.parser.parse_args(["switch", "mywork", "--local"])
44
+ assert args.local is True
45
+
46
+ def test_switch_local_short_flag(self, cli):
47
+ args = cli.parser.parse_args(["switch", "mywork", "-l"])
48
+ assert args.local is True
40
49
 
41
50
  def test_remove_with_account(self, cli):
42
51
  args = cli.parser.parse_args(["remove", "mywork"])
@@ -103,11 +112,19 @@ class TestHandlers:
103
112
 
104
113
  def test_handle_switch_success(self, cli):
105
114
  cli.account_manager.switch_account.return_value = True
106
- assert cli._handle_switch(argparse.Namespace(account_name="work")) == 0
115
+ args = argparse.Namespace(account_name="work", local=False)
116
+ assert cli._handle_switch(args) == 0
107
117
 
108
118
  def test_handle_switch_failure(self, cli):
109
119
  cli.account_manager.switch_account.return_value = False
110
- assert cli._handle_switch(argparse.Namespace(account_name="work")) == 1
120
+ args = argparse.Namespace(account_name="work", local=False)
121
+ assert cli._handle_switch(args) == 1
122
+
123
+ def test_handle_switch_passes_local_flag(self, cli):
124
+ cli.account_manager.switch_account.return_value = True
125
+ args = argparse.Namespace(account_name="work", local=True)
126
+ cli._handle_switch(args)
127
+ cli.account_manager.switch_account.assert_called_once_with("work", local=True)
111
128
 
112
129
  def test_handle_remove_success(self, cli):
113
130
  cli.account_manager.remove_account.return_value = True
@@ -181,6 +181,30 @@ class TestSetGitConfig:
181
181
  assert config.set_git_config("Test", "test@test.com") is False
182
182
 
183
183
 
184
+ class TestSetGitConfigLocal:
185
+ def test_calls_git_config_without_global_flag(self, config):
186
+ with patch("subprocess.run") as mock_run:
187
+ mock_run.return_value = MagicMock(returncode=0)
188
+ result = config.set_git_config_local("Test User", "test@test.com")
189
+ assert result is True
190
+ assert mock_run.call_count == 2
191
+ for call in mock_run.call_args_list:
192
+ assert "--global" not in call.args[0]
193
+
194
+ def test_passes_repo_path_as_cwd(self, config, tmp_path):
195
+ with patch("subprocess.run") as mock_run:
196
+ mock_run.return_value = MagicMock(returncode=0)
197
+ config.set_git_config_local("Test User", "test@test.com", tmp_path)
198
+ for call in mock_run.call_args_list:
199
+ assert call.kwargs["cwd"] == tmp_path
200
+
201
+ def test_returns_false_on_error(self, config):
202
+ with patch(
203
+ "subprocess.run", side_effect=subprocess.CalledProcessError(1, "git")
204
+ ):
205
+ assert config.set_git_config_local("Test", "test@test.com") is False
206
+
207
+
184
208
  class TestGetCurrentGitConfig:
185
209
  def test_returns_name_and_email_keys(self, config):
186
210
  with patch("subprocess.run") as mock_run:
File without changes