pygitgo 1.7.0__tar.gz → 1.7.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.
Files changed (63) hide show
  1. {pygitgo-1.7.0/src/pygitgo.egg-info → pygitgo-1.7.1}/PKG-INFO +6 -2
  2. {pygitgo-1.7.0 → pygitgo-1.7.1}/README.md +5 -1
  3. {pygitgo-1.7.0 → pygitgo-1.7.1}/pyproject.toml +1 -1
  4. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/git_core.py +3 -3
  5. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/link.py +5 -11
  6. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/pull.py +1 -1
  7. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/push.py +5 -3
  8. pygitgo-1.7.1/src/pygitgo/commands/undo.py +114 -0
  9. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/main.py +10 -5
  10. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/config.py +44 -29
  11. {pygitgo-1.7.0 → pygitgo-1.7.1/src/pygitgo.egg-info}/PKG-INFO +6 -2
  12. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_config_utils.py +48 -1
  13. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_git_core.py +2 -2
  14. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_link.py +4 -4
  15. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_push.py +1 -1
  16. pygitgo-1.7.1/tests/test_undo.py +172 -0
  17. pygitgo-1.7.0/src/pygitgo/commands/undo.py +0 -61
  18. pygitgo-1.7.0/tests/test_undo.py +0 -85
  19. {pygitgo-1.7.0 → pygitgo-1.7.1}/LICENSE +0 -0
  20. {pygitgo-1.7.0 → pygitgo-1.7.1}/setup.cfg +0 -0
  21. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/__init__.py +0 -0
  22. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/auth/__init__.py +0 -0
  23. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/auth/account.py +0 -0
  24. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/auth/manager.py +0 -0
  25. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/auth/ssh_utils.py +0 -0
  26. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/__init__.py +0 -0
  27. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/config.py +0 -0
  28. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/git_branch.py +0 -0
  29. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/git_remote.py +0 -0
  30. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/jump.py +0 -0
  31. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/staging.py +0 -0
  32. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/stash.py +0 -0
  33. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/state.py +0 -0
  34. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/user.py +0 -0
  35. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/exceptions.py +0 -0
  36. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/__init__.py +0 -0
  37. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/bootstrap.py +0 -0
  38. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/colors.py +0 -0
  39. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/executor.py +0 -0
  40. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/platform.py +0 -0
  41. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/update_checker.py +0 -0
  42. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/validators.py +0 -0
  43. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/SOURCES.txt +0 -0
  44. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/dependency_links.txt +0 -0
  45. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/entry_points.txt +0 -0
  46. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/requires.txt +0 -0
  47. {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/top_level.txt +0 -0
  48. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_account.py +0 -0
  49. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_bootstrap.py +0 -0
  50. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_config_command.py +0 -0
  51. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_git_branch.py +0 -0
  52. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_git_remote.py +0 -0
  53. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_jump.py +0 -0
  54. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_manager.py +0 -0
  55. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_platform.py +0 -0
  56. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_pull.py +0 -0
  57. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_ssh_utils.py +0 -0
  58. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_staging.py +0 -0
  59. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_stash.py +0 -0
  60. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_state.py +0 -0
  61. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_update_checker.py +0 -0
  62. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_user.py +0 -0
  63. {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygitgo
3
- Version: 1.7.0
3
+ Version: 1.7.1
4
4
  Summary: GitGo CLI - Your Fast Git Companion. Simplifies git push, link, stash, and user management.
5
5
  Author: Huerte
6
6
  License: GPL-3.0-or-later
@@ -88,7 +88,7 @@ gitgo link https://github.com/username/repo.git "init"
88
88
  ## Features
89
89
 
90
90
  - **Single commands for linking, pushing, and stashing.** No more chaining five commands together.
91
- - **Undo:** Roll back commits, unstage files, or discard local changes. The subcommands say what they do: `undo commit`, `undo add`, `undo changes`.
91
+ - **Undo:** Roll back commits, unstage files, discard local changes, or revert pushes. The subcommands say what they do: `undo commit`, `undo add`, `undo changes`, `undo link`, `undo push`.
92
92
  - **Branch switching with `jump`:** Stashes your uncommitted work, moves to the target branch, syncs with main, and pops the stash. If a merge conflict occurs, the Try-and-Revert engine offers to roll the whole operation back.
93
93
  - **State management:** Named, indexed stash. Run `state list` to see what you saved. No more `stash@{2}` archaeology.
94
94
  - **Custom defaults:** Store your preferred branch name and default commit message. GitGo picks them up on every run.
@@ -195,6 +195,8 @@ Undo recent mistakes with commands named for what they undo.
195
195
  gitgo undo commit # Undo the last commit (files stay staged)
196
196
  gitgo undo add # Unstage files
197
197
  gitgo undo changes # DANGER: permanently discard all uncommitted edits
198
+ gitgo undo link # Remove remote and undo initial commit
199
+ gitgo undo push # DANGER: Revert last push with a force-push
198
200
  ```
199
201
 
200
202
  ### 6. Save Your Work-in-Progress
@@ -270,6 +272,8 @@ Undo recent actions with subcommands named for what they undo.
270
272
  gitgo undo commit # Undo the last commit without losing files
271
273
  gitgo undo add # Unstage files
272
274
  gitgo undo changes # Permanently discard all new files and uncommitted edits
275
+ gitgo undo link # Remove the remote and undo the initial commit
276
+ gitgo undo push # DANGER: Revert the last push with a force-push
273
277
  ```
274
278
 
275
279
  ### `gitgo state`
@@ -63,7 +63,7 @@ gitgo link https://github.com/username/repo.git "init"
63
63
  ## Features
64
64
 
65
65
  - **Single commands for linking, pushing, and stashing.** No more chaining five commands together.
66
- - **Undo:** Roll back commits, unstage files, or discard local changes. The subcommands say what they do: `undo commit`, `undo add`, `undo changes`.
66
+ - **Undo:** Roll back commits, unstage files, discard local changes, or revert pushes. The subcommands say what they do: `undo commit`, `undo add`, `undo changes`, `undo link`, `undo push`.
67
67
  - **Branch switching with `jump`:** Stashes your uncommitted work, moves to the target branch, syncs with main, and pops the stash. If a merge conflict occurs, the Try-and-Revert engine offers to roll the whole operation back.
68
68
  - **State management:** Named, indexed stash. Run `state list` to see what you saved. No more `stash@{2}` archaeology.
69
69
  - **Custom defaults:** Store your preferred branch name and default commit message. GitGo picks them up on every run.
@@ -170,6 +170,8 @@ Undo recent mistakes with commands named for what they undo.
170
170
  gitgo undo commit # Undo the last commit (files stay staged)
171
171
  gitgo undo add # Unstage files
172
172
  gitgo undo changes # DANGER: permanently discard all uncommitted edits
173
+ gitgo undo link # Remove remote and undo initial commit
174
+ gitgo undo push # DANGER: Revert last push with a force-push
173
175
  ```
174
176
 
175
177
  ### 6. Save Your Work-in-Progress
@@ -245,6 +247,8 @@ Undo recent actions with subcommands named for what they undo.
245
247
  gitgo undo commit # Undo the last commit without losing files
246
248
  gitgo undo add # Unstage files
247
249
  gitgo undo changes # Permanently discard all new files and uncommitted edits
250
+ gitgo undo link # Remove the remote and undo the initial commit
251
+ gitgo undo push # DANGER: Revert the last push with a force-push
248
252
  ```
249
253
 
250
254
  ### `gitgo state`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pygitgo"
7
- version = "1.7.0"
7
+ version = "1.7.1"
8
8
  description = "GitGo CLI - Your Fast Git Companion. Simplifies git push, link, stash, and user management."
9
9
  readme = "README.md"
10
10
  license = {text = "GPL-3.0-or-later"}
@@ -1,9 +1,9 @@
1
1
  from pygitgo.auth.ssh_utils import convert_https_to_ssh, get_ssh_key_path, is_ssh_url, check_connection
2
2
  from pygitgo.utils.colors import info, success, warning, error
3
3
  from pygitgo.exceptions import GitGoError, GitCommandError
4
- from pygitgo.utils.executor import run_command
5
- from pygitgo.utils.config import get_config
6
4
  from pygitgo.commands.git_remote import handle_rebase
5
+ from pygitgo.utils.config import get_default_branch
6
+ from pygitgo.utils.executor import run_command
7
7
  import os
8
8
 
9
9
 
@@ -43,7 +43,7 @@ def git_init():
43
43
  warning("Already a git repository! Skipping init...")
44
44
  return False
45
45
 
46
- default_main_branch = get_config("default-branch", "main")
46
+ default_main_branch = get_default_branch()
47
47
 
48
48
  try:
49
49
  run_command(["git", "init", "-b", default_main_branch], loading_msg="Initializing git repository...")
@@ -1,11 +1,11 @@
1
- from pygitgo.utils.colors import success, warning, error, highlight, info, print_banner
2
1
  from pygitgo.commands.git_remote import add_remote_origin, confirm_remote_link
2
+ from pygitgo.utils.colors import success, warning, error, info, print_banner
3
3
  from pygitgo.commands.git_core import git_init, git_commit, git_push
4
4
  from pygitgo.commands.git_branch import get_current_branch
5
5
  from pygitgo.exceptions import GitCommandError, GitGoError
6
- from pygitgo.utils.executor import run_command
7
- from pygitgo.utils.config import get_config
8
6
  from pygitgo.utils.validators import validate_repo_url
7
+ from pygitgo.utils.config import get_default_branch
8
+ from pygitgo.utils.executor import run_command
9
9
  import sys
10
10
 
11
11
 
@@ -42,10 +42,6 @@ def link_operation(args):
42
42
 
43
43
  commit_message = args.message
44
44
 
45
- highlight("INITIATING LINK OPERATION...")
46
- highlight(f"Target: {repo_url}")
47
- print()
48
-
49
45
  initialized = False
50
46
  committed = False
51
47
  remote_added = False
@@ -76,7 +72,7 @@ def link_operation(args):
76
72
  return
77
73
 
78
74
  current_branch = get_current_branch()
79
- main_branch = get_config("default-branch", "main")
75
+ main_branch = get_default_branch()
80
76
 
81
77
  if current_branch != main_branch:
82
78
  run_command(["git", "branch", "-m", main_branch], loading_msg=f"Renaming branch '{current_branch}' to '{main_branch}'...")
@@ -100,14 +96,12 @@ def link_operation(args):
100
96
  warning(f"Then: gitgo push {main_branch} 'your message'\n")
101
97
  return
102
98
 
103
- print_banner("REPOSITORY INITIALIZED AND LINKED.")
104
-
105
99
  git_push(current_branch)
106
100
 
107
101
  print_banner("REPOSITORY INITIALIZED AND DEPLOYED.")
108
102
 
109
103
  print()
110
- info("Run 'gitgo undo commit' to revert this push if the deploy was unintended.")
104
+ info("Run 'gitgo undo link' to remove the remote and undo the initial commit.")
111
105
 
112
106
  except KeyboardInterrupt:
113
107
  print()
@@ -27,7 +27,7 @@ def _pull_interrupt_cleanup():
27
27
  stash_list = run_command(["git", "stash", "list"])
28
28
  if stash_list and "autostash" in stash_list.lower():
29
29
  info("An autostash entry was found. Your local changes are safe.")
30
- info("Run 'gitgo state list' to view it, or 'gitgo state load 1' to restore it.")
30
+ info("Run 'gitgo state list' to find it, then 'gitgo state load <id>' to restore it.")
31
31
  except GitCommandError:
32
32
  pass
33
33
 
@@ -26,7 +26,7 @@ def _push_interrupt_cleanup(original_branch, original_head, created_branch):
26
26
  if commit_was_made:
27
27
  info(f"Commit was saved locally on '{current_branch}' but was not pushed.")
28
28
  info("Run 'gitgo push' to retry the push.")
29
- info("Run 'gitgo undo commit' to undo the commit instead.")
29
+ info("Run 'gitgo undo commit' to undo the local commit instead.")
30
30
  return
31
31
 
32
32
  try:
@@ -141,10 +141,12 @@ def push_operation(args):
141
141
 
142
142
  print_banner("MISSION COMPLETE. ALL TARGETS COMMITTED AND PUSHED.")
143
143
 
144
+ print()
144
145
  if auto_switched_from:
145
- print()
146
146
  info(f"Switched from '{auto_switched_from}' to '{branch}' automatically.")
147
- info(f"Run 'gitgo undo commit' then 'gitgo jump {auto_switched_from}' to revert this push and return.")
147
+ info(f"Run 'gitgo undo push' to revert the push, then 'gitgo jump {auto_switched_from}' to return.")
148
+ else:
149
+ info("Run 'gitgo undo push' to revert this push if it was unintended.")
148
150
 
149
151
  except KeyboardInterrupt:
150
152
  print()
@@ -0,0 +1,114 @@
1
+ from pygitgo.commands.git_branch import get_current_branch
2
+ from pygitgo.utils.colors import success, warning, info, error
3
+ from pygitgo.exceptions import GitCommandError, GitGoError
4
+ from pygitgo.utils.executor import run_command
5
+ import sys
6
+
7
+
8
+ def undo_commit():
9
+ try:
10
+ run_command(["git", "reset", "--soft", "HEAD~"])
11
+ success("Last commit undone. Files are untouched.")
12
+ except GitCommandError as e:
13
+ raise GitGoError(
14
+ f"Undo failed — no previous commit to revert. Details: {e}"
15
+ )
16
+
17
+
18
+ def undo_add():
19
+ run_command(["git", "reset", "HEAD"])
20
+ success("Staging cleared. Files are back to unstaged.")
21
+
22
+
23
+ def undo_changes():
24
+ error("DANGER: This will permanently delete all your new edits and new files!")
25
+ warning("This action cannot be undone.")
26
+ confirm = input("Are you sure you want to throw away all changes? (y/n): ")
27
+ if confirm.lower() != "y":
28
+ info("Canceled. Your files are safe.")
29
+ return
30
+
31
+ reset_done = False
32
+ try:
33
+ run_command(["git", "reset", "--hard", "HEAD"], loading_msg="Throwing away edits...")
34
+ reset_done = True
35
+ run_command(["git", "clean", "-fd"], loading_msg="Removing new files...")
36
+ success("Working tree reset. All changes discarded.")
37
+
38
+ except KeyboardInterrupt:
39
+ print()
40
+ if reset_done:
41
+ warning("Interrupted during file removal. Finishing cleanup...")
42
+ try:
43
+ run_command(["git", "clean", "-fd"])
44
+ success("Working tree reset. All changes discarded.")
45
+ except GitCommandError:
46
+ warning("Could not finish cleanup. Run 'git clean -fd' manually.")
47
+ else:
48
+ success("Reset canceled before any changes were made. Your files are safe.")
49
+ sys.exit(130)
50
+
51
+
52
+ def undo_link():
53
+ try:
54
+ run_command(["git", "remote", "remove", "origin"])
55
+ success("Remote 'origin' removed.")
56
+ except GitCommandError as e:
57
+ raise GitGoError(
58
+ f"Could not remove remote 'origin'. Is one set? Details: {e}"
59
+ )
60
+
61
+ try:
62
+ run_command(["git", "reset", "--soft", "HEAD~"])
63
+ success("Initial commit undone. Files are back to staged, ready to re-link.")
64
+ except GitCommandError:
65
+ info("Remote removed. Could not undo the commit (none or multiple found).")
66
+ info("Run 'gitgo undo commit' separately if needed.")
67
+
68
+
69
+ def undo_push():
70
+ try:
71
+ branch = get_current_branch()
72
+ except GitCommandError as e:
73
+ raise GitGoError(f"Could not determine the current branch: {e}")
74
+
75
+ error("DANGER: This force-pushes to the remote. Other collaborators will be affected.")
76
+ warning("Only use this if no one else has pulled the commit you are reverting.")
77
+ confirm = input("Are you sure you want to undo the last push? (y/n): ")
78
+ if confirm.lower() != "y":
79
+ info("Canceled. Remote is unchanged.")
80
+ return
81
+
82
+ try:
83
+ run_command(["git", "reset", "--soft", "HEAD~"], loading_msg="Reverting last commit locally...")
84
+ except GitCommandError as e:
85
+ raise GitGoError(f"Undo failed — no previous commit to revert. Details: {e}")
86
+
87
+ try:
88
+ run_command(
89
+ ["git", "push", "--force", "origin", branch],
90
+ loading_msg=f"Force-pushing reverted state to '{branch}'..."
91
+ )
92
+ success(f"Last push reverted. Remote '{branch}' is back to the previous commit.")
93
+ info("Your files are still staged locally. Edit and push again when ready.")
94
+ except GitCommandError as e:
95
+ warning("Local commit was undone, but the force-push failed.")
96
+ warning(f"Run manually: git push --force origin {branch}")
97
+ raise GitGoError(f"Force-push failed: {e}")
98
+
99
+
100
+ def undo_operation(args):
101
+ action = args.action
102
+
103
+ if action == "commit":
104
+ undo_commit()
105
+ elif action == "add":
106
+ undo_add()
107
+ elif action == "changes":
108
+ undo_changes()
109
+ elif action == "link":
110
+ undo_link()
111
+ elif action == "push":
112
+ undo_push()
113
+ else:
114
+ raise GitGoError(f"Unknown undo operation: {action}")
@@ -1,4 +1,4 @@
1
- from pygitgo.utils.update_checker import check_for_updates_background
1
+ from pygitgo.utils.update_checker import check_for_updates_background, check_for_updates
2
2
  from pygitgo.utils.bootstrap import ensure_first_run_setup
3
3
  from pygitgo.utils.colors import info, warning, error
4
4
  from pygitgo.commands.config import config_operation
@@ -123,14 +123,16 @@ def main():
123
123
  "Examples:\n"
124
124
  " gitgo undo commit Undo your last commit (your files are safe)\n"
125
125
  " gitgo undo add Undo 'git add' (files are no longer ready to commit)\n"
126
- " gitgo undo changes DANGER: Throw away all new changes and start fresh"
126
+ " gitgo undo changes DANGER: Throw away all new changes and start fresh\n"
127
+ " gitgo undo link Remove the remote and undo the initial commit\n"
128
+ " gitgo undo push DANGER: Revert the last push with a force-push"
127
129
  ),
128
130
  formatter_class=argparse.RawDescriptionHelpFormatter
129
131
  )
130
132
  undo_parser.add_argument(
131
133
  "action",
132
- choices=["commit", "add", "changes"],
133
- help="What to undo: 'commit', 'add', or 'changes'"
134
+ choices=["commit", "add", "changes", "link", "push"],
135
+ help="What to undo: 'commit', 'add', 'changes', 'link', or 'push'"
134
136
  )
135
137
 
136
138
  pull_parser = subparsers.add_parser("pull",
@@ -147,12 +149,15 @@ def main():
147
149
  args = parser.parse_args()
148
150
 
149
151
  if getattr(args, 'version', False):
150
- print(f"GitGo {get_version()}")
152
+ current_v = get_version()
153
+ print(f"GitGo {current_v}")
151
154
  print(f"Support GitGo: https://ko-fi.com/huerte")
155
+ check_for_updates(current_v)
152
156
  return
153
157
 
154
158
  if args.ready:
155
159
  info("ALL UNITS ONLINE. GitGo STANDING BY. AWAITING COMMANDS...")
160
+ check_for_updates(get_version())
156
161
  return
157
162
 
158
163
  if not args.command:
@@ -1,29 +1,44 @@
1
- from pygitgo.utils.executor import run_command
2
- from pygitgo.utils.colors import error, warning, success, info
3
- from pygitgo.exceptions import GitCommandError
4
-
5
-
6
- def get_config(key, fallback_value):
7
-
8
- config_key = f"gitgo.{key}"
9
-
10
- try:
11
- result = run_command(['git', 'config', '--global', config_key])
12
- return result.strip()
13
- except GitCommandError:
14
- return fallback_value
15
-
16
-
17
- def set_config(key, value):
18
-
19
- config_key = f"gitgo.{key}"
20
-
21
- try:
22
- result = run_command(['git', 'config', '--global', config_key, value])
23
- success(f"\nConfiguration saved: {key} = '{value}'")
24
- return True
25
- except GitCommandError:
26
- error(f"\nFailed to save configuration for '{key}'.")
27
- return False
28
-
29
-
1
+ from pygitgo.utils.executor import run_command
2
+ from pygitgo.utils.colors import error, warning, success, info
3
+ from pygitgo.exceptions import GitCommandError
4
+ import subprocess
5
+
6
+
7
+ def get_config(key, fallback_value):
8
+
9
+ config_key = f"gitgo.{key}"
10
+
11
+ try:
12
+ result = run_command(['git', 'config', '--global', config_key])
13
+ return result.strip()
14
+ except GitCommandError:
15
+ return fallback_value
16
+
17
+
18
+ def set_config(key, value):
19
+
20
+ config_key = f"gitgo.{key}"
21
+
22
+ try:
23
+ result = run_command(['git', 'config', '--global', config_key, value])
24
+ success(f"\nConfiguration saved: {key} = '{value}'")
25
+ return True
26
+ except GitCommandError:
27
+ error(f"\nFailed to save configuration for '{key}'.")
28
+ return False
29
+
30
+
31
+ def get_default_branch() -> str:
32
+ try:
33
+ branch = subprocess.check_output(
34
+ ["git", "config", "--get", "init.defaultBranch"],
35
+ text=True,
36
+ stderr=subprocess.DEVNULL,
37
+ ).strip()
38
+ if branch:
39
+ return branch
40
+ except (subprocess.CalledProcessError, FileNotFoundError):
41
+ pass
42
+
43
+ return get_config("default-branch", "main")
44
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pygitgo
3
- Version: 1.7.0
3
+ Version: 1.7.1
4
4
  Summary: GitGo CLI - Your Fast Git Companion. Simplifies git push, link, stash, and user management.
5
5
  Author: Huerte
6
6
  License: GPL-3.0-or-later
@@ -88,7 +88,7 @@ gitgo link https://github.com/username/repo.git "init"
88
88
  ## Features
89
89
 
90
90
  - **Single commands for linking, pushing, and stashing.** No more chaining five commands together.
91
- - **Undo:** Roll back commits, unstage files, or discard local changes. The subcommands say what they do: `undo commit`, `undo add`, `undo changes`.
91
+ - **Undo:** Roll back commits, unstage files, discard local changes, or revert pushes. The subcommands say what they do: `undo commit`, `undo add`, `undo changes`, `undo link`, `undo push`.
92
92
  - **Branch switching with `jump`:** Stashes your uncommitted work, moves to the target branch, syncs with main, and pops the stash. If a merge conflict occurs, the Try-and-Revert engine offers to roll the whole operation back.
93
93
  - **State management:** Named, indexed stash. Run `state list` to see what you saved. No more `stash@{2}` archaeology.
94
94
  - **Custom defaults:** Store your preferred branch name and default commit message. GitGo picks them up on every run.
@@ -195,6 +195,8 @@ Undo recent mistakes with commands named for what they undo.
195
195
  gitgo undo commit # Undo the last commit (files stay staged)
196
196
  gitgo undo add # Unstage files
197
197
  gitgo undo changes # DANGER: permanently discard all uncommitted edits
198
+ gitgo undo link # Remove remote and undo initial commit
199
+ gitgo undo push # DANGER: Revert last push with a force-push
198
200
  ```
199
201
 
200
202
  ### 6. Save Your Work-in-Progress
@@ -270,6 +272,8 @@ Undo recent actions with subcommands named for what they undo.
270
272
  gitgo undo commit # Undo the last commit without losing files
271
273
  gitgo undo add # Unstage files
272
274
  gitgo undo changes # Permanently discard all new files and uncommitted edits
275
+ gitgo undo link # Remove the remote and undo the initial commit
276
+ gitgo undo push # DANGER: Revert the last push with a force-push
273
277
  ```
274
278
 
275
279
  ### `gitgo state`
@@ -1,4 +1,4 @@
1
- from pygitgo.utils.config import get_config, set_config
1
+ from pygitgo.utils.config import get_config, set_config, get_default_branch
2
2
  import subprocess
3
3
 
4
4
 
@@ -55,3 +55,50 @@ def test_get_config_error_handling(mocker):
55
55
  fake_run = mocker.patch('pygitgo.utils.config.run_command', side_effect=GitCommandError(['git']))
56
56
  result = get_config("default-key", "fallback")
57
57
  assert result == "fallback"
58
+
59
+
60
+ # --- get_default_branch tests ---
61
+
62
+ def test_get_default_branch_uses_git_init_default_branch(mocker):
63
+ # Level 1: native git config wins over gitgo config and hard fallback.
64
+ mocker.patch(
65
+ "pygitgo.utils.config.subprocess.check_output",
66
+ return_value="develop\n",
67
+ )
68
+ result = get_default_branch()
69
+ assert result == "develop"
70
+
71
+
72
+ def test_get_default_branch_falls_back_to_gitgo_config(mocker):
73
+ # Level 2: init.defaultBranch unset, gitgo.default-branch is set.
74
+ mocker.patch(
75
+ "pygitgo.utils.config.subprocess.check_output",
76
+ side_effect=subprocess.CalledProcessError(1, "git"),
77
+ )
78
+ mocker.patch("pygitgo.utils.config.run_command", return_value="trunk")
79
+ result = get_default_branch()
80
+ assert result == "trunk"
81
+
82
+
83
+ def test_get_default_branch_hard_fallback_to_main(mocker):
84
+ # Level 3: both sources absent, returns "main".
85
+ mocker.patch(
86
+ "pygitgo.utils.config.subprocess.check_output",
87
+ side_effect=subprocess.CalledProcessError(1, "git"),
88
+ )
89
+ from pygitgo.exceptions import GitCommandError
90
+ mocker.patch("pygitgo.utils.config.run_command", side_effect=GitCommandError(["git"]))
91
+ result = get_default_branch()
92
+ assert result == "main"
93
+
94
+
95
+ def test_get_default_branch_handles_git_not_found(mocker):
96
+ # FileNotFoundError when git is not on PATH should not crash.
97
+ mocker.patch(
98
+ "pygitgo.utils.config.subprocess.check_output",
99
+ side_effect=FileNotFoundError,
100
+ )
101
+ from pygitgo.exceptions import GitCommandError
102
+ mocker.patch("pygitgo.utils.config.run_command", side_effect=GitCommandError(["git"]))
103
+ result = get_default_branch()
104
+ assert result == "main"
@@ -30,7 +30,7 @@ def test_git_init_already_initialized(mocker):
30
30
 
31
31
  def test_git_init_success(mocker):
32
32
  mocker.patch('os.path.isdir', return_value=False)
33
- mocker.patch('pygitgo.commands.git_core.get_config', return_value='main')
33
+ mocker.patch('pygitgo.commands.git_core.get_default_branch', return_value='main')
34
34
  fake_success = mocker.patch('pygitgo.commands.git_core.success')
35
35
  fake_run = mocker.patch('pygitgo.commands.git_core.run_command', return_value='ok')
36
36
 
@@ -46,7 +46,7 @@ def test_git_init_success(mocker):
46
46
  def test_git_init_fallback(mocker):
47
47
  from pygitgo.exceptions import GitCommandError
48
48
  mocker.patch('os.path.isdir', return_value=False)
49
- mocker.patch('pygitgo.commands.git_core.get_config', return_value='main')
49
+ mocker.patch('pygitgo.commands.git_core.get_default_branch', return_value='main')
50
50
  fake_success = mocker.patch('pygitgo.commands.git_core.success')
51
51
 
52
52
  fake_run = mocker.patch(
@@ -35,10 +35,10 @@ def test_link_new_repo_no_remote_refs(mocker):
35
35
  fake_add_remote = mocker.patch("pygitgo.commands.link.add_remote_origin")
36
36
  mocker.patch("pygitgo.commands.link.confirm_remote_link", return_value=True)
37
37
  mocker.patch("pygitgo.commands.link.get_current_branch", return_value="master")
38
- mocker.patch("pygitgo.commands.link.get_config", return_value="main")
38
+ mocker.patch("pygitgo.commands.link.get_default_branch", return_value="main")
39
39
  fake_push = mocker.patch("pygitgo.commands.link.git_push")
40
40
 
41
- fake_run = mocker.patch("pygitgo.commands.link.run_command", return_value="") # remote_refs is empty
41
+ fake_run = mocker.patch("pygitgo.commands.link.run_command", return_value="")
42
42
 
43
43
  args = Namespace(url="git@github.com:user/repo.git", message="Initial commit")
44
44
  link_operation(args)
@@ -57,7 +57,7 @@ def test_link_new_repo_with_remote_refs_pull_success(mocker):
57
57
  mocker.patch("pygitgo.commands.link.add_remote_origin")
58
58
  mocker.patch("pygitgo.commands.link.confirm_remote_link", return_value=True)
59
59
  mocker.patch("pygitgo.commands.link.get_current_branch", return_value="main")
60
- mocker.patch("pygitgo.commands.link.get_config", return_value="main")
60
+ mocker.patch("pygitgo.commands.link.get_default_branch", return_value="main")
61
61
  fake_push = mocker.patch("pygitgo.commands.link.git_push")
62
62
  fake_success = mocker.patch("pygitgo.commands.link.success")
63
63
 
@@ -87,7 +87,7 @@ def test_link_new_repo_with_remote_refs_pull_failure(mocker):
87
87
  mocker.patch("pygitgo.commands.link.add_remote_origin")
88
88
  mocker.patch("pygitgo.commands.link.confirm_remote_link", return_value=True)
89
89
  mocker.patch("pygitgo.commands.link.get_current_branch", return_value="main")
90
- mocker.patch("pygitgo.commands.link.get_config", return_value="main")
90
+ mocker.patch("pygitgo.commands.link.get_default_branch", return_value="main")
91
91
  fake_error = mocker.patch("pygitgo.commands.link.error")
92
92
  fake_warning = mocker.patch("pygitgo.commands.link.warning")
93
93
 
@@ -39,7 +39,7 @@ def test_push_wrong_branch_auto_switch(mocker):
39
39
 
40
40
  fake_info.assert_any_call("Switching to target branch 'feature-branch'...")
41
41
  fake_info.assert_any_call("Switched from 'main' to 'feature-branch' automatically.")
42
- fake_info.assert_any_call("Run 'gitgo undo commit' then 'gitgo jump main' to revert this push and return.")
42
+ fake_info.assert_any_call("Run 'gitgo undo push' to revert the push, then 'gitgo jump main' to return.")
43
43
  jump_args = fake_jump.call_args[0][0]
44
44
  assert jump_args.branch == "feature-branch"
45
45
  fake_commit.assert_called_once_with("Init commit")
@@ -0,0 +1,172 @@
1
+ import pytest
2
+ from unittest.mock import patch, MagicMock, call
3
+ from pygitgo.commands.undo import undo_commit, undo_add, undo_changes, undo_link, undo_push, undo_operation
4
+ from pygitgo.exceptions import GitGoError, GitCommandError
5
+ from argparse import Namespace
6
+
7
+
8
+ @patch("pygitgo.commands.undo.run_command")
9
+ @patch("pygitgo.commands.undo.success")
10
+ def test_undo_commit_success(mock_success, mock_run_command):
11
+ undo_commit()
12
+ mock_run_command.assert_called_once_with(["git", "reset", "--soft", "HEAD~"])
13
+ mock_success.assert_called_once()
14
+
15
+
16
+ @patch("pygitgo.commands.undo.run_command")
17
+ def test_undo_commit_failure(mock_run_command):
18
+ mock_run_command.side_effect = GitCommandError(["git", "reset", "--soft", "HEAD~"])
19
+ with pytest.raises(GitGoError):
20
+ undo_commit()
21
+ mock_run_command.assert_called_once()
22
+
23
+
24
+ @patch("pygitgo.commands.undo.run_command")
25
+ @patch("pygitgo.commands.undo.success")
26
+ def test_undo_add_success(mock_success, mock_run_command):
27
+ undo_add()
28
+ mock_run_command.assert_called_once_with(["git", "reset", "HEAD"])
29
+ mock_success.assert_called_once()
30
+
31
+
32
+ @patch("pygitgo.commands.undo.run_command")
33
+ def test_undo_add_failure(mock_run_command):
34
+ mock_run_command.side_effect = GitCommandError(["git", "reset", "HEAD"])
35
+ with pytest.raises(GitCommandError):
36
+ undo_add()
37
+ mock_run_command.assert_called_once()
38
+
39
+
40
+ @patch("pygitgo.commands.undo.run_command")
41
+ @patch("pygitgo.commands.undo.success")
42
+ @patch("pygitgo.commands.undo.input", return_value="y")
43
+ def test_undo_changes_success(mock_input, mock_success, mock_run_command):
44
+ undo_changes()
45
+ mock_input.assert_called_once()
46
+ assert mock_run_command.call_count == 2
47
+ mock_run_command.assert_any_call(["git", "reset", "--hard", "HEAD"], loading_msg="Throwing away edits...")
48
+ mock_run_command.assert_any_call(["git", "clean", "-fd"], loading_msg="Removing new files...")
49
+ mock_success.assert_called_once()
50
+
51
+
52
+ @patch("pygitgo.commands.undo.run_command")
53
+ @patch("pygitgo.commands.undo.info")
54
+ @patch("pygitgo.commands.undo.input", return_value="n")
55
+ def test_undo_changes_abort(mock_input, mock_info, mock_run_command):
56
+ undo_changes()
57
+ mock_input.assert_called_once()
58
+ mock_info.assert_called_once()
59
+ mock_run_command.assert_not_called()
60
+
61
+
62
+ # --- undo_link tests ---
63
+
64
+ @patch("pygitgo.commands.undo.run_command")
65
+ @patch("pygitgo.commands.undo.success")
66
+ def test_undo_link_success(mock_success, mock_run_command):
67
+ undo_link()
68
+ mock_run_command.assert_any_call(["git", "remote", "remove", "origin"])
69
+ mock_run_command.assert_any_call(["git", "reset", "--soft", "HEAD~"])
70
+ assert mock_success.call_count == 2
71
+
72
+
73
+ @patch("pygitgo.commands.undo.run_command")
74
+ def test_undo_link_no_remote(mock_run_command):
75
+ mock_run_command.side_effect = GitCommandError(["git", "remote", "remove", "origin"])
76
+ with pytest.raises(GitGoError, match="Could not remove remote"):
77
+ undo_link()
78
+
79
+
80
+ @patch("pygitgo.commands.undo.run_command")
81
+ @patch("pygitgo.commands.undo.success")
82
+ @patch("pygitgo.commands.undo.info")
83
+ def test_undo_link_no_commit_to_reset(mock_info, mock_success, mock_run_command):
84
+ # First call (remote remove) succeeds, second (reset) fails.
85
+ mock_run_command.side_effect = [None, GitCommandError(["git", "reset"])]
86
+ undo_link()
87
+ mock_success.assert_called_once_with("Remote 'origin' removed.")
88
+ assert mock_info.call_count == 2
89
+
90
+
91
+ # --- undo_push tests ---
92
+
93
+ @patch("pygitgo.commands.undo.run_command")
94
+ @patch("pygitgo.commands.undo.get_current_branch", return_value="main")
95
+ @patch("pygitgo.commands.undo.success")
96
+ @patch("pygitgo.commands.undo.input", return_value="y")
97
+ def test_undo_push_success(mock_input, mock_success, mock_branch, mock_run_command):
98
+ undo_push()
99
+ mock_run_command.assert_any_call(["git", "reset", "--soft", "HEAD~"], loading_msg="Reverting last commit locally...")
100
+ mock_run_command.assert_any_call(["git", "push", "--force", "origin", "main"], loading_msg="Force-pushing reverted state to 'main'...")
101
+ mock_success.assert_called_once()
102
+
103
+
104
+ @patch("pygitgo.commands.undo.run_command")
105
+ @patch("pygitgo.commands.undo.get_current_branch", return_value="main")
106
+ @patch("pygitgo.commands.undo.info")
107
+ @patch("pygitgo.commands.undo.input", return_value="n")
108
+ def test_undo_push_abort(mock_input, mock_info, mock_branch, mock_run_command):
109
+ undo_push()
110
+ mock_run_command.assert_not_called()
111
+ mock_info.assert_called_once_with("Canceled. Remote is unchanged.")
112
+
113
+
114
+ @patch("pygitgo.commands.undo.run_command")
115
+ @patch("pygitgo.commands.undo.get_current_branch", return_value="main")
116
+ @patch("pygitgo.commands.undo.input", return_value="y")
117
+ def test_undo_push_no_commit(mock_input, mock_branch, mock_run_command):
118
+ mock_run_command.side_effect = GitCommandError(["git", "reset"])
119
+ with pytest.raises(GitGoError, match="Undo failed"):
120
+ undo_push()
121
+
122
+
123
+ @patch("pygitgo.commands.undo.run_command")
124
+ @patch("pygitgo.commands.undo.get_current_branch", return_value="main")
125
+ @patch("pygitgo.commands.undo.input", return_value="y")
126
+ def test_undo_push_force_push_fails(mock_input, mock_branch, mock_run_command):
127
+ mock_run_command.side_effect = [None, GitCommandError(["git", "push", "--force"])]
128
+ with pytest.raises(GitGoError, match="Force-push failed"):
129
+ undo_push()
130
+
131
+
132
+ # --- undo_operation routing tests ---
133
+
134
+ @patch("pygitgo.commands.undo.undo_commit")
135
+ def test_undo_operation_commit(mock_undo_commit):
136
+ args = Namespace(action="commit")
137
+ undo_operation(args)
138
+ mock_undo_commit.assert_called_once()
139
+
140
+
141
+ @patch("pygitgo.commands.undo.undo_add")
142
+ def test_undo_operation_add(mock_undo_add):
143
+ args = Namespace(action="add")
144
+ undo_operation(args)
145
+ mock_undo_add.assert_called_once()
146
+
147
+
148
+ @patch("pygitgo.commands.undo.undo_changes")
149
+ def test_undo_operation_changes(mock_undo_changes):
150
+ args = Namespace(action="changes")
151
+ undo_operation(args)
152
+ mock_undo_changes.assert_called_once()
153
+
154
+
155
+ @patch("pygitgo.commands.undo.undo_link")
156
+ def test_undo_operation_link(mock_undo_link):
157
+ args = Namespace(action="link")
158
+ undo_operation(args)
159
+ mock_undo_link.assert_called_once()
160
+
161
+
162
+ @patch("pygitgo.commands.undo.undo_push")
163
+ def test_undo_operation_push(mock_undo_push):
164
+ args = Namespace(action="push")
165
+ undo_operation(args)
166
+ mock_undo_push.assert_called_once()
167
+
168
+
169
+ def test_undo_operation_invalid():
170
+ args = Namespace(action="invalid")
171
+ with pytest.raises(GitGoError):
172
+ undo_operation(args)
@@ -1,61 +0,0 @@
1
- from pygitgo.utils.colors import success, warning, info, error
2
- from pygitgo.exceptions import GitCommandError, GitGoError
3
- from pygitgo.utils.executor import run_command
4
- import sys
5
-
6
-
7
- def undo_commit():
8
- try:
9
- run_command(["git", "reset", "--soft", "HEAD~"])
10
- success("Last commit undone. Files are untouched.")
11
- except GitCommandError as e:
12
- raise GitGoError(
13
- f"Undo failed — no previous commit to revert. Details: {e}"
14
- )
15
-
16
-
17
- def undo_add():
18
- run_command(["git", "reset", "HEAD"])
19
- success("Staging cleared. Files are back to unstaged.")
20
-
21
-
22
- def undo_changes():
23
- error("DANGER: This will permanently delete all your new edits and new files!")
24
- warning("This action cannot be undone.")
25
- confirm = input("Are you sure you want to throw away all changes? (y/n): ")
26
- if confirm.lower() != "y":
27
- info("Canceled. Your files are safe.")
28
- return
29
-
30
- reset_done = False
31
- try:
32
- run_command(["git", "reset", "--hard", "HEAD"], loading_msg="Throwing away edits...")
33
- reset_done = True
34
- run_command(["git", "clean", "-fd"], loading_msg="Removing new files...")
35
- success("Working tree reset. All changes discarded.")
36
-
37
- except KeyboardInterrupt:
38
- print()
39
- if reset_done:
40
- warning("Interrupted during file removal. Finishing cleanup...")
41
- try:
42
- run_command(["git", "clean", "-fd"])
43
- success("Working tree reset. All changes discarded.")
44
- except GitCommandError:
45
- warning("Could not finish cleanup. Run 'git clean -fd' manually.")
46
- else:
47
- success("Reset canceled before any changes were made. Your files are safe.")
48
- sys.exit(130)
49
-
50
-
51
- def undo_operation(args):
52
- action = args.action
53
-
54
- if action == "commit":
55
- undo_commit()
56
- elif action == "add":
57
- undo_add()
58
- elif action == "changes":
59
- undo_changes()
60
- else:
61
- raise GitGoError(f"Unknown undo operation: {action}")
@@ -1,85 +0,0 @@
1
- import pytest
2
- from unittest.mock import patch, MagicMock
3
- from pygitgo.commands.undo import undo_commit, undo_add, undo_changes, undo_operation
4
- from pygitgo.exceptions import GitGoError, GitCommandError
5
- from argparse import Namespace
6
-
7
- @patch("pygitgo.commands.undo.run_command")
8
- @patch("pygitgo.commands.undo.success")
9
- def test_undo_commit_success(mock_success, mock_run_command):
10
- undo_commit()
11
- mock_run_command.assert_called_once_with(["git", "reset", "--soft", "HEAD~"])
12
- mock_success.assert_called_once()
13
-
14
-
15
- @patch("pygitgo.commands.undo.run_command")
16
- def test_undo_commit_failure(mock_run_command):
17
- mock_run_command.side_effect = GitCommandError(["git", "reset", "--soft", "HEAD~"])
18
- with pytest.raises(GitGoError):
19
- undo_commit()
20
- mock_run_command.assert_called_once()
21
-
22
-
23
- @patch("pygitgo.commands.undo.run_command")
24
- @patch("pygitgo.commands.undo.success")
25
- def test_undo_add_success(mock_success, mock_run_command):
26
- undo_add()
27
- mock_run_command.assert_called_once_with(["git", "reset", "HEAD"])
28
- mock_success.assert_called_once()
29
-
30
-
31
- @patch("pygitgo.commands.undo.run_command")
32
- def test_undo_add_failure(mock_run_command):
33
- mock_run_command.side_effect = GitCommandError(["git", "reset", "HEAD"])
34
- with pytest.raises(GitCommandError):
35
- undo_add()
36
- mock_run_command.assert_called_once()
37
-
38
-
39
- @patch("pygitgo.commands.undo.run_command")
40
- @patch("pygitgo.commands.undo.success")
41
- @patch("pygitgo.commands.undo.input", return_value="y")
42
- def test_undo_changes_success(mock_input, mock_success, mock_run_command):
43
- undo_changes()
44
- mock_input.assert_called_once()
45
- assert mock_run_command.call_count == 2
46
- mock_run_command.assert_any_call(["git", "reset", "--hard", "HEAD"], loading_msg="Throwing away edits...")
47
- mock_run_command.assert_any_call(["git", "clean", "-fd"], loading_msg="Removing new files...")
48
- mock_success.assert_called_once()
49
-
50
-
51
- @patch("pygitgo.commands.undo.run_command")
52
- @patch("pygitgo.commands.undo.info")
53
- @patch("pygitgo.commands.undo.input", return_value="n")
54
- def test_undo_changes_abort(mock_input, mock_info, mock_run_command):
55
- undo_changes()
56
- mock_input.assert_called_once()
57
- mock_info.assert_called_once()
58
- mock_run_command.assert_not_called()
59
-
60
-
61
- @patch("pygitgo.commands.undo.undo_commit")
62
- def test_undo_operation_commit(mock_undo_commit):
63
- args = Namespace(action="commit")
64
- undo_operation(args)
65
- mock_undo_commit.assert_called_once()
66
-
67
-
68
- @patch("pygitgo.commands.undo.undo_add")
69
- def test_undo_operation_add(mock_undo_add):
70
- args = Namespace(action="add")
71
- undo_operation(args)
72
- mock_undo_add.assert_called_once()
73
-
74
-
75
- @patch("pygitgo.commands.undo.undo_changes")
76
- def test_undo_operation_changes(mock_undo_changes):
77
- args = Namespace(action="changes")
78
- undo_operation(args)
79
- mock_undo_changes.assert_called_once()
80
-
81
-
82
- def test_undo_operation_invalid():
83
- args = Namespace(action="invalid")
84
- with pytest.raises(GitGoError):
85
- undo_operation(args)
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
File without changes
File without changes
File without changes