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.
- {pygitgo-1.7.0/src/pygitgo.egg-info → pygitgo-1.7.1}/PKG-INFO +6 -2
- {pygitgo-1.7.0 → pygitgo-1.7.1}/README.md +5 -1
- {pygitgo-1.7.0 → pygitgo-1.7.1}/pyproject.toml +1 -1
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/git_core.py +3 -3
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/link.py +5 -11
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/pull.py +1 -1
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/push.py +5 -3
- pygitgo-1.7.1/src/pygitgo/commands/undo.py +114 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/main.py +10 -5
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/config.py +44 -29
- {pygitgo-1.7.0 → pygitgo-1.7.1/src/pygitgo.egg-info}/PKG-INFO +6 -2
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_config_utils.py +48 -1
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_git_core.py +2 -2
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_link.py +4 -4
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_push.py +1 -1
- pygitgo-1.7.1/tests/test_undo.py +172 -0
- pygitgo-1.7.0/src/pygitgo/commands/undo.py +0 -61
- pygitgo-1.7.0/tests/test_undo.py +0 -85
- {pygitgo-1.7.0 → pygitgo-1.7.1}/LICENSE +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/setup.cfg +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/__init__.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/auth/__init__.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/auth/account.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/auth/manager.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/auth/ssh_utils.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/__init__.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/config.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/git_branch.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/git_remote.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/jump.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/staging.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/stash.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/state.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/commands/user.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/exceptions.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/__init__.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/bootstrap.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/colors.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/executor.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/platform.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/update_checker.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo/utils/validators.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/SOURCES.txt +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/dependency_links.txt +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/entry_points.txt +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/requires.txt +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/src/pygitgo.egg-info/top_level.txt +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_account.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_bootstrap.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_config_command.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_git_branch.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_git_remote.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_jump.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_manager.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_platform.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_pull.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_ssh_utils.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_staging.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_stash.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_state.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_update_checker.py +0 -0
- {pygitgo-1.7.0 → pygitgo-1.7.1}/tests/test_user.py +0 -0
- {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.
|
|
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,
|
|
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,
|
|
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.
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 '
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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="")
|
|
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.
|
|
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.
|
|
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
|
|
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}")
|
pygitgo-1.7.0/tests/test_undo.py
DELETED
|
@@ -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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|