pygitgo 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pygitgo/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,61 @@
1
+ from pygitgo.utils.executor import run_command
2
+ from pygitgo.utils.colors import info, success, warning, error, BLUE, RESET
3
+ import subprocess
4
+
5
+
6
+ def get_user():
7
+ try:
8
+ name = run_command(["git", "config", "--global", "user.name"], allow_fail=True)
9
+ email = run_command(["git", "config", "--global", "user.email"], allow_fail=True)
10
+
11
+ if not name or isinstance(name, subprocess.CalledProcessError):
12
+ name = None
13
+ if not email or isinstance(email, subprocess.CalledProcessError):
14
+ email = None
15
+ return name, email
16
+ except:
17
+ return None, None
18
+
19
+ def set_user(name, email):
20
+ run_command(["git", "config", "--global", "user.name", name])
21
+ run_command(["git", "config", "--global", "user.email", email])
22
+ success(f"\nGit user configured Successfully")
23
+ print(f"{BLUE}Username{RESET} : {name}")
24
+ print(f"{BLUE}Email {RESET}: {email}")
25
+
26
+ def ensure_user_configure(default_email=None, default_username=None):
27
+
28
+ name, email = get_user()
29
+
30
+ if name and email:
31
+ return True
32
+
33
+ if default_username and default_email:
34
+ info(f"\nConfiguring Git identity from GitHub...")
35
+ set_user(default_username, default_email)
36
+ return True
37
+
38
+ warning("\nGit user identity is not configured!")
39
+ info("This is required for your commits to be attributed correctly.")
40
+
41
+ if default_username:
42
+ new_username = default_username
43
+ info(f"Using GitHub username: {new_username}")
44
+ else:
45
+ new_username = input("Enter your Username (for commits): ").strip()
46
+
47
+ prompt_email = "Enter your Email (for commits)"
48
+ if default_email:
49
+ prompt_email += f" [{default_email}]"
50
+
51
+ new_email = input(f"{prompt_email}: ").strip()
52
+
53
+ if not new_email and default_email:
54
+ new_email = default_email
55
+
56
+ if new_username and new_email:
57
+ set_user(new_username, new_email)
58
+ return True
59
+
60
+ error("Invalid configuration. Name and Email are required.")
61
+ return False
@@ -0,0 +1,78 @@
1
+ from pygitgo.utils.executor import run_command
2
+ from pygitgo.utils.colors import info, success, warning, error
3
+ from . import ssh_utils
4
+ import os
5
+
6
+ def login():
7
+ if ssh_utils.check_connection():
8
+ success("You are already logged in!")
9
+ return True
10
+
11
+ info("initiating login sequence...")
12
+
13
+ while True:
14
+ email = input("Enter your email for GitHub: ").strip()
15
+ if "@" in email and "." in email:
16
+ break
17
+ else:
18
+ error("Please enter a valid email address.")
19
+
20
+
21
+ key_path = ssh_utils.generate_ssh_key(email=email)
22
+ pub_key_path = str(key_path) + ".pub"
23
+
24
+ with open(pub_key_path, 'r') as f:
25
+ pub_key = f.read()
26
+
27
+ if pub_key:
28
+ success("SSH Key generated successfully!")
29
+
30
+ print("\n" + "=" * len(pub_key))
31
+ print(pub_key, end='')
32
+ print("=" * len(pub_key) + "\n")
33
+
34
+ info("Copy the key above (between the lines).")
35
+
36
+ input(
37
+ "\nPress Enter to open your GitHub SSH key settings page in the browser...\n"
38
+ "Make sure you are logged in so you can add your new key. "
39
+ "After adding, return here to continue."
40
+ )
41
+ ssh_utils.open_github_settings()
42
+
43
+ else:
44
+ error("Failed to read the generated public key.")
45
+ return False
46
+
47
+ input("\nPress Enter after adding the key to GitHub...")
48
+
49
+ if ssh_utils.check_connection():
50
+ from .account import ensure_user_configure
51
+
52
+ github_username = ssh_utils.get_github_username()
53
+
54
+ ensure_user_configure(default_email=email, default_username=github_username)
55
+
56
+ success("\nLogin Successful! You are connected.\n")
57
+ return True
58
+
59
+ error("Login Failed. Please try again.")
60
+ return False
61
+
62
+ def logout():
63
+ key_path = ssh_utils.get_ssh_key_path()
64
+ if not key_path.exists():
65
+ warning("You are already logged out (no keys found).")
66
+ return False
67
+
68
+ try:
69
+ run_command(["git", "config", "--global", "--unset-all", "user.name"], allow_fail=True)
70
+ run_command(["git", "config", "--global", "--unset-all", "user.email"], allow_fail=True)
71
+
72
+ os.remove(key_path)
73
+ os.remove(str(key_path) + '.pub')
74
+ success("User successfully logout")
75
+ return True
76
+ except Exception as e:
77
+ error(f"Failed to remove SSH keys\nCAUSE OF ERROR: {e}")
78
+ return False
@@ -0,0 +1,114 @@
1
+ from pygitgo.utils.executor import run_command
2
+ from pygitgo.utils.colors import info, success, warning, error
3
+ from pygitgo.utils import platform_utils
4
+ from pathlib import Path
5
+ import sys
6
+ import os
7
+
8
+
9
+
10
+ def ensure_github_known_host():
11
+ known_hosts = Path.home() / ".ssh" / "known_hosts"
12
+ known_hosts.parent.mkdir(parents=True, exist_ok=True)
13
+
14
+ try:
15
+ if known_hosts.exists():
16
+ with open(known_hosts, "r") as f:
17
+ if "github.com" in f.read():
18
+ return
19
+ except Exception:
20
+ pass
21
+
22
+ info("Adding GitHub to known_hosts...")
23
+ result = run_command(["ssh-keyscan", "-H", "github.com"], allow_fail=True, return_complete=True)
24
+
25
+ if not isinstance(result, Exception) and result.stdout and "github.com" in result.stdout:
26
+ with open(known_hosts, "a") as f:
27
+ f.write(result.stdout)
28
+ if not result.stdout.endswith("\n"):
29
+ f.write("\n")
30
+ success("GitHub added to known_hosts.")
31
+ else:
32
+ warning("Could not automatically add GitHub to known_hosts. You might be prompted.")
33
+
34
+ def check_connection():
35
+ ensure_github_known_host()
36
+ result = run_command(["ssh", "-T", "git@github.com"], allow_fail=True, return_complete=True)
37
+ return "successfully authenticated" in result.stderr
38
+
39
+ def get_github_username():
40
+
41
+ result = run_command(["ssh", "-T", "git@github.com"], allow_fail=True, return_complete=True)
42
+ output = result.stderr
43
+
44
+ if "Hi " in output and "!" in output:
45
+ try:
46
+ username = output.split("Hi ")[1].split("!")[0]
47
+ return username
48
+ except:
49
+ return None
50
+ return None
51
+
52
+ def get_ssh_key_path():
53
+ return Path.home() / ".ssh" / "id_ed25519"
54
+
55
+ def generate_ssh_key(email):
56
+ if not email or "@" not in email or "." not in email:
57
+ error("Invalid email address provided for SSH key generation.")
58
+ return
59
+
60
+ key_path = get_ssh_key_path()
61
+ if not key_path.parent.exists():
62
+ key_path.parent.mkdir(parents=True)
63
+
64
+ if key_path.exists():
65
+ os.remove(key_path)
66
+ if (key_path.parent / f"{key_path.name}.pub").exists():
67
+ os.remove(key_path.parent / f"{key_path.name}.pub")
68
+
69
+ command = [
70
+ "ssh-keygen",
71
+ "-t", "ed25519",
72
+ "-C", email,
73
+ "-f", str(key_path),
74
+ "-N", ""
75
+ ]
76
+ run_command(command=command)
77
+
78
+ try:
79
+ run_command(["ssh-add", str(key_path)], allow_fail=True)
80
+ except:
81
+ pass
82
+
83
+ return key_path
84
+
85
+ def open_github_settings():
86
+ url = "https://github.com/settings/ssh/new"
87
+ if platform_utils.is_windows():
88
+ os.system(f"start {url}")
89
+ elif platform_utils.is_termux():
90
+ os.system(f"termux-open {url}")
91
+ elif platform_utils.is_linux() or platform_utils.is_macos():
92
+ os.system(f"xdg-open {url}")
93
+ else:
94
+ import webbrowser
95
+ webbrowser.open(url)
96
+
97
+
98
+ def convert_https_to_ssh(url):
99
+
100
+ import re
101
+
102
+ pattern = r'^https?://github\.com/([^/]+)/([^/]+?)(?:\.git)?/?$'
103
+ match = re.match(pattern, url.strip())
104
+
105
+ if match:
106
+ owner = match.group(1)
107
+ repo = match.group(2)
108
+ return f"git@github.com:{owner}/{repo}.git"
109
+
110
+ return None
111
+
112
+
113
+ def is_ssh_url(url):
114
+ return url.strip().startswith("git@")
File without changes
@@ -0,0 +1,148 @@
1
+ from pygitgo.auth.ssh_utils import convert_https_to_ssh, is_ssh_url, check_connection
2
+ from pygitgo.utils.executor import run_command
3
+ from pygitgo.utils.colors import info, success, warning, error
4
+ import subprocess
5
+ import sys
6
+ import os
7
+
8
+ DEFAULT_MAIN_BRANCH = "main"
9
+
10
+
11
+ def get_current_branch():
12
+ branch = run_command(["git", "branch", "--show-current"])
13
+ if branch.strip() == "":
14
+ branch = git_new_branch(DEFAULT_MAIN_BRANCH)
15
+
16
+ return branch
17
+
18
+ def is_branch_exist(branch):
19
+ return bool(run_command(["git", "branch", "-r", "--list", f"*/{branch}"])) or bool(run_command(["git", "branch", "--list", branch]))
20
+
21
+
22
+ def git_new_branch(branch):
23
+ run_command(["git", "checkout", "-b", branch], loading_msg=f"Creating branch '{branch}'...")
24
+ success(f"\nBranch '{branch}' created.\n")
25
+
26
+ return branch
27
+
28
+
29
+ def git_commit(commit_message):
30
+ status_result = run_command(["git", "status", "--porcelain"], allow_fail=True)
31
+ if isinstance(status_result, subprocess.CalledProcessError) or not status_result.strip():
32
+ return False
33
+
34
+ run_command(["git", "add", "."], loading_msg="Staging files...")
35
+ clean_message = commit_message.strip('"\'')
36
+
37
+ run_command(["git", "commit", "-m", clean_message], loading_msg="Committing changes...")
38
+
39
+ return True
40
+
41
+
42
+ def git_init():
43
+ if os.path.isdir(".git"):
44
+ warning("Already a git repository! Skipping init...")
45
+ return True
46
+
47
+ result = run_command(["git", "init", "-b", DEFAULT_MAIN_BRANCH], allow_fail=True, loading_msg="Initializing git repository...")
48
+ if isinstance(result, subprocess.CalledProcessError):
49
+ run_command(["git", "init"], loading_msg="Initializing git repository...")
50
+ run_command(["git", "checkout", "-b", DEFAULT_MAIN_BRANCH], allow_fail=True)
51
+
52
+ success("Git repository initialized.")
53
+ return True
54
+
55
+
56
+ def add_remote_origin(repo_url):
57
+ clean_url = repo_url.strip('"\'')
58
+
59
+ existing_remote = run_command(["git", "remote", "get-url", "origin"], allow_fail=True)
60
+ if not isinstance(existing_remote, subprocess.CalledProcessError):
61
+ warning(f"Remote origin already exists: {existing_remote}")
62
+ run_command(["git", "remote", "set-url", "origin", clean_url], loading_msg="Updating remote URL...")
63
+ else:
64
+ run_command(["git", "remote", "add", "origin", clean_url], loading_msg="Adding remote origin...")
65
+
66
+ success(f"Remote origin set to: {clean_url}")
67
+
68
+
69
+ def confirm_remote_link():
70
+ test_result = run_command(["git", "ls-remote", "origin"], allow_fail=True, loading_msg="Testing connection to remote...")
71
+
72
+ if isinstance(test_result, subprocess.CalledProcessError):
73
+ error("Failed to connect to remote repository!")
74
+ warning("Please check your repository URL and network connection.")
75
+ return False
76
+
77
+ success("Successfully connected to remote repository.")
78
+ return True
79
+
80
+
81
+ def create_main_branch():
82
+ current_branch = run_command(["git", "branch", "--show-current"], allow_fail=True)
83
+
84
+ if isinstance(current_branch, subprocess.CalledProcessError) or not current_branch.strip():
85
+ run_command(["git", "checkout", "-b", "main"], loading_msg="Setting default branch to 'main'...")
86
+ elif current_branch.strip() != "main":
87
+ run_command(["git", "branch", "-m", "main"], loading_msg=f"Renaming branch '{current_branch.strip()}' to 'main'...")
88
+ else:
89
+ success("Already on 'main' branch.")
90
+
91
+
92
+ def check_and_sync_branch(branch):
93
+ try:
94
+ run_command(["git", "fetch", "origin"], loading_msg="Checking if branch is up to date...")
95
+
96
+ try:
97
+ local_commit = run_command(["git", "rev-parse", branch])
98
+ remote_commit = run_command(["git", "rev-parse", f"origin/{branch}"])
99
+
100
+ if local_commit != remote_commit:
101
+ behind_check = run_command(
102
+ ["git", "rev-list", "--count", f"{branch}..origin/{branch}"]
103
+ )
104
+ if behind_check and int(behind_check) > 0:
105
+ warning(f"Local branch is behind remote by {behind_check} commit(s). Pulling changes...")
106
+ output = run_command(["git", "pull", "--rebase", "origin", branch], loading_msg="Pulling changes from remote...")
107
+ if output:
108
+ print(output)
109
+ success("Successfully synced with remote!")
110
+ else:
111
+ success("Branch is up to date or ahead of remote.")
112
+ else:
113
+ success("Branch is already up to date.")
114
+ except:
115
+ warning("Remote branch doesn't exist yet. First push will create it.")
116
+ except:
117
+ warning("Could not fetch from remote. Proceeding with push...")
118
+
119
+
120
+ def git_push(branch):
121
+ remote_url = run_command(["git", "remote", "get-url", "origin"], allow_fail=True)
122
+
123
+ if not isinstance(remote_url, subprocess.CalledProcessError) and remote_url:
124
+ remote_url = remote_url.strip()
125
+
126
+ if not is_ssh_url(remote_url) and check_connection():
127
+ ssh_url = convert_https_to_ssh(remote_url)
128
+ if ssh_url:
129
+ run_command(["git", "remote", "set-url", "origin", ssh_url], loading_msg="Converting remote from HTTPS to SSH for secure push...")
130
+ success(f"Remote updated to: {ssh_url}")
131
+
132
+ run_command(["git", "push", "-u", "origin", branch], loading_msg=f"Pushing to remote branch '{branch}'...")
133
+
134
+
135
+ def handle_rebase():
136
+ status = run_command(["git", "status"], allow_fail=True)
137
+ if isinstance(status, subprocess.CalledProcessError):
138
+ return False
139
+
140
+ if "rebase in progress" in status or "rebase" in status.lower():
141
+ warning("\nConflict detected!")
142
+ warning("Please resolve conflicts manually, then run:")
143
+ info(" git add <files>")
144
+ info(" git rebase --continue")
145
+ warning("When finished, run 'gitgo push <branch> <message>' again.\n")
146
+ sys.exit(1)
147
+
148
+ return True
@@ -0,0 +1,193 @@
1
+ from pygitgo.utils.executor import run_command
2
+ from pygitgo.utils.colors import info, highlight, error, warning, success
3
+ import sys
4
+
5
+
6
+ def all_save_state():
7
+ output = run_command(["git", "stash", "list", "--pretty=%gd: %s"])
8
+ if not output:
9
+ info("\nNo saved states found.\n")
10
+ sys.exit(0)
11
+
12
+ save_states = [result[6:] for result in output.splitlines()]
13
+
14
+ return save_states
15
+
16
+
17
+ def display_save_states():
18
+ save_states = all_save_state()
19
+
20
+ print("\nSaved States:")
21
+ print("-" * 32)
22
+ for state in save_states:
23
+ state_index = state.split(': ', 1)[0].replace("stash@", "").replace("{", "").replace("}", "")
24
+ highlight(f"{int(state_index) + 1} | {state.split(': ', 1)[1]}")
25
+
26
+ print("-" * 32 + '\n')
27
+
28
+
29
+ def validate_state_id(state_id, save_states):
30
+ if not state_id.isdigit():
31
+ error("\nInvalid input. Please enter a valid state ID.\n")
32
+ return False
33
+ elif (int(state_id) - 1) < 0:
34
+ error("\nState ID cannot be negative. Please enter a valid state ID.\n")
35
+ return False
36
+ elif (int(state_id) - 1) >= len(save_states):
37
+ error("\nState ID out of range. Please enter a valid state ID.\n")
38
+ return False
39
+ return True
40
+
41
+
42
+ def ask_state_id(save_states):
43
+ proceed = False
44
+ state_id = None
45
+
46
+ while not proceed:
47
+ state_id = input(">> ").strip().lower()
48
+ if state_id == 'q':
49
+ warning("\nLoad operation cancelled by user.\n")
50
+ sys.exit(0)
51
+ proceed = validate_state_id(state_id, save_states)
52
+
53
+ return state_id
54
+
55
+
56
+ def state_list(arguments):
57
+ if len(arguments) > 1:
58
+ if arguments[1] in ("-h", "--help", "help"):
59
+ print("\nDisplay all saved states.\n")
60
+ warning("\nUsage:\n")
61
+ print(" gitgo state list # Show all saved states")
62
+ print(" gitgo state -l # Alias\n")
63
+ sys.exit(0)
64
+ error("\nToo many arguments for list operation!\n")
65
+ sys.exit(1)
66
+
67
+ display_save_states()
68
+
69
+
70
+ def load_state(arguments):
71
+ if len(arguments) > 2:
72
+ error("\nToo many arguments for load operation!\n")
73
+ sys.exit(1)
74
+
75
+ save_states = all_save_state()
76
+ proceed = False
77
+ state_id = None
78
+
79
+ if len(arguments) == 2:
80
+ if arguments[1] in ("-h", "--help", "help"):
81
+ print("\nLoad a previously saved working state.\n")
82
+ warning("\nUsage:\n")
83
+ print(" gitgo state load <id> # Load a specific state by ID")
84
+ print(" gitgo state load # Show all saved states and prompt for selection")
85
+ print(" gitgo state -o <id> # Alias\n")
86
+ sys.exit(0)
87
+ elif arguments[1].isdigit():
88
+ state_id = arguments[1]
89
+ proceed = validate_state_id(state_id, save_states)
90
+ if not proceed:
91
+ sys.exit(1)
92
+ else:
93
+ error(f"\nInvalid argument '{arguments[1]}' for load operation. Expected a state ID.\n")
94
+ sys.exit(1)
95
+
96
+ if not proceed:
97
+ display_save_states()
98
+ info("\nEnter the ID of the state you want to load (or 'q' to cancel): ")
99
+ state_id = ask_state_id(save_states)
100
+
101
+ run_command(["git", "stash", "apply", str(int(state_id) - 1)])
102
+
103
+ success(f"\nState '{save_states[int(state_id) - 1]}' loaded successfully.\n")
104
+
105
+
106
+ def save_state(arguments):
107
+ if len(arguments) > 2:
108
+ error("\nToo many arguments for save operation!\n")
109
+ sys.exit(1)
110
+ elif len(arguments) < 2:
111
+ state_name = "Auto-Save"
112
+
113
+ if len(arguments) == 2:
114
+ if arguments[1] in ("-h", "--help", "help"):
115
+ print("\nSave the current working state with an optional name.\n")
116
+ warning("\nUsage:\n")
117
+ print(" gitgo state save <name> # Save with a specific name")
118
+ print(" gitgo state save # Save with default name 'Auto-Save'")
119
+ print(" gitgo state -s <name> # Alias\n")
120
+ sys.exit(0)
121
+
122
+ state_name = arguments[1]
123
+
124
+ run_command(["git", "stash", "push", "-m", state_name])
125
+
126
+ success(f"\nState '{state_name}' saved successfully.\n")
127
+
128
+
129
+ def delete_state(arguments):
130
+ if len(arguments) > 2:
131
+ error("\nToo many arguments for delete operation!\n")
132
+ sys.exit(1)
133
+ elif len(arguments) < 2:
134
+ state_id = ask_state_id(all_save_state())
135
+ else:
136
+ if arguments[1] in ("-h", "--help", "help"):
137
+ print("\nDelete one or all saved working states.\n")
138
+ warning("\nUsage:\n")
139
+ print(" gitgo state delete <id> # Delete a specific state by ID")
140
+ print(" gitgo state delete -a # Delete all saved states")
141
+ print(" gitgo state -d <id> # Alias\n")
142
+ print(" gitgo state -d <id> # Alias for delete all\n")
143
+ sys.exit(0)
144
+
145
+ elif arguments[1] == '-a':
146
+ confirm = input("\nAre you sure you want to delete all saved states? (y/n): ").strip().lower()
147
+ if confirm.lower() == 'y':
148
+ run_command(["git", "stash", "clear"])
149
+ success("\nAll saved states deleted successfully.\n")
150
+ sys.exit(0)
151
+ else:
152
+ warning("\nDelete operation cancelled by user.\n")
153
+ sys.exit(0)
154
+
155
+ elif not arguments[1].isdigit():
156
+ error("\nInvalid input. Please enter a valid state ID.\n")
157
+ sys.exit(1)
158
+
159
+ state_id = arguments[1]
160
+ if not validate_state_id(state_id, all_save_state()):
161
+ sys.exit(1)
162
+
163
+ run_command(["git", "stash", "drop", str(int(state_id) - 1)])
164
+ success(f"\nState with ID '{state_id}' deleted successfully.\n")
165
+
166
+
167
+ def state_operations_help():
168
+ warning("\nState Operations Help:\n")
169
+ print(" list, -l Display all saved states.")
170
+ print(" load, -o Load a previously saved working state.")
171
+ print(" save, -s Save the current working state with a given name.")
172
+ print(" delete, -d Delete a previously saved working state.\n")
173
+ info("Use 'gitgo state <operation> --help' for more information on a specific operation.\n")
174
+
175
+
176
+ def state_operations(arguments):
177
+ if len(arguments) == 0 or arguments[0] in ("-h", "--help", "help"):
178
+ state_operations_help()
179
+ sys.exit(0)
180
+
181
+ type_of_operation = arguments[0].lower()
182
+ if type_of_operation in ["list", "-l"]:
183
+ state_list(arguments)
184
+ elif type_of_operation in ["load", "-o"]:
185
+ load_state(arguments)
186
+ elif type_of_operation in ["save", "-s"]:
187
+ save_state(arguments)
188
+ elif type_of_operation in ["delete", "-d"]:
189
+ delete_state(arguments)
190
+ else:
191
+ error(f"\nUnknown state operation: {type_of_operation}\n")
192
+ state_operations_help()
193
+ sys.exit(1)