gitpush-tool 0.3.0__tar.gz → 0.3.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitpush-tool
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Supercharged Git push tool with automatic GitHub repo creation and pushing
5
5
  Home-page: https://github.com/inevitablegs/gitpush
6
6
  Author: Ganesh Sonawane
@@ -40,22 +40,22 @@ gh auth login # Authenticate with GitHub
40
40
  ### 1. New Repository Creation
41
41
  ```bash
42
42
  # Create public repo
43
- gitpush_tool "Initial commit" --new-repo my-project
43
+ gitpush "Initial commit" --new-repo my-project
44
44
 
45
45
  # Create private repo
46
- gitpush_tool "Initial commit" --new-repo private-project --private
46
+ gitpush "Initial commit" --new-repo private-project --private
47
47
  ```
48
48
 
49
49
  ### 2. Standard Git Operations
50
50
  ```bash
51
51
  # Regular push
52
- gitpush_tool "Fixed login bug"
52
+ gitpush "Fixed login bug"
53
53
 
54
54
  # Force push
55
- gitpush_tool "Rebased history" --force
55
+ gitpush "Rebased history" --force
56
56
 
57
57
  # Push tags
58
- gitpush_tool --tags
58
+ gitpush --tags
59
59
  ```
60
60
 
61
61
  ## ⚙️ Configuration
@@ -27,36 +27,36 @@ pip install gitpush-tool
27
27
 
28
28
  | Command | Description |
29
29
  |--------|-------------|
30
- | `gitpush_tool "Commit message"` | Standard push with commit |
31
- | `gitpush_tool` | Push without commit (only staged changes) |
32
- | `gitpush_tool --force` | Safe force push |
33
- | `gitpush_tool --tags` | Push all tags |
30
+ | `gitpush "Commit message"` | Standard push with commit |
31
+ | `gitpush` | Push without commit (only staged changes) |
32
+ | `gitpush --force` | Safe force push |
33
+ | `gitpush --tags` | Push all tags |
34
34
 
35
35
  ### New Repository Workflow
36
36
 
37
37
  ```bash
38
38
  # Create new public repo
39
- gitpush_tool "Initial commit" --new-repo project-name
39
+ gitpush "Initial commit" --new-repo project-name
40
40
 
41
41
  # Create private repo with description
42
- gitpush_tool "Initial commit" --new-repo private-project --private --description "My awesome project"
42
+ gitpush "Initial commit" --new-repo private-project --private --description "My awesome project"
43
43
  ```
44
44
 
45
45
  ### Branch Management
46
46
 
47
47
  ```bash
48
48
  # Push to specific branch
49
- gitpush_tool "Commit message" feature-branch
49
+ gitpush "Commit message" feature-branch
50
50
 
51
51
  # Push to specific remote and branch
52
- gitpush_tool "Commit message" main upstream
52
+ gitpush "Commit message" main upstream
53
53
  ```
54
54
 
55
55
  ### Initialization
56
56
 
57
57
  ```bash
58
58
  # Initialize new repo only
59
- gitpush_tool --init
59
+ gitpush --init
60
60
  ```
61
61
 
62
62
  ## Workflow Examples 🔥
@@ -67,17 +67,17 @@ gitpush_tool --init
67
67
  mkdir my-app
68
68
  cd my-app
69
69
  touch README.md main.py
70
- gitpush_tool "Initial commit" --new-repo my-app
70
+ gitpush "Initial commit" --new-repo my-app
71
71
  ```
72
72
 
73
73
  ### Scenario 2: Existing Project Updates
74
74
 
75
75
  ```bash
76
76
  # After making changes
77
- gitpush_tool "Fixed authentication bug"
77
+ gitpush "Fixed authentication bug"
78
78
 
79
79
  # Force push after rebase
80
- gitpush_tool "Rebased commits" --force
80
+ gitpush "Rebased commits" --force
81
81
  ```
82
82
 
83
83
  ### Scenario 3: Create Empty Repository
@@ -85,7 +85,7 @@ gitpush_tool "Rebased commits" --force
85
85
  ```bash
86
86
  mkdir empty-project
87
87
  cd empty-project
88
- gitpush_tool --init
88
+ gitpush --init
89
89
  ```
90
90
 
91
91
  ## Configuration ⚙️
@@ -161,6 +161,6 @@ Contributions welcome! Please follow these steps:
161
161
 
162
162
  ## License 📄
163
163
 
164
- MIT - See LICENSE for details.
164
+ MIT - See LICENSE for details.
165
165
 
166
166
  <center>✨ <strong>Happy Coding!</strong> ✨</center>
@@ -11,9 +11,9 @@ Key Features:
11
11
  • Fresh project setup in one command
12
12
 
13
13
  Basic Usage:
14
- gitpush_tool "Commit message" # Standard push
15
- gitpush_tool --new-repo project-name # Create new repo
16
- gitpush_tool --force # Safe force push
14
+ gitpush "Commit message" # Standard push
15
+ gitpush --new-repo project-name # Create new repo
16
+ gitpush --force # Safe force push
17
17
 
18
18
  Install GitHub CLI first:
19
19
  macOS: brew install gh
@@ -0,0 +1 @@
1
+ __version__ = "0.3.2"
@@ -0,0 +1,545 @@
1
+ import os
2
+ import argparse
3
+ import sys
4
+ import subprocess
5
+ import shutil
6
+ import platform
7
+ import json
8
+ import tempfile
9
+ import urllib.request
10
+ from typing import Optional
11
+
12
+ # --- Installation Orchestrator and Helpers (Your Code, Integrated) ---
13
+
14
+ def check_gh_installed() -> bool:
15
+ """Check if GitHub CLI is installed with proper verification"""
16
+ if shutil.which("gh"):
17
+ try:
18
+ # Verify gh is actually working
19
+ subprocess.run(["gh", "--version"], check=True, capture_output=True)
20
+ return True
21
+ except (subprocess.CalledProcessError, FileNotFoundError):
22
+ # Found but not working - might be a PATH issue or broken install
23
+ return False
24
+ return False
25
+
26
+ def install_gh_cli() -> bool:
27
+ """Main installation function with comprehensive error handling"""
28
+ system = platform.system()
29
+ machine = platform.machine().lower()
30
+
31
+ print("\n🔧 Installing GitHub CLI...")
32
+ print(f"📋 System: {system}, Architecture: {machine}")
33
+
34
+ try:
35
+ if system == "Windows":
36
+ return install_gh_cli_windows()
37
+ elif system == "Darwin":
38
+ return install_gh_cli_mac()
39
+ elif system == "Linux":
40
+ return install_gh_cli_linux()
41
+ else:
42
+ print(f"❌ Unsupported OS: {system}")
43
+ return False
44
+ except Exception as e:
45
+ print(f"❌ Installation failed: {str(e)}")
46
+ return False
47
+
48
+ def install_gh_cli_windows() -> bool:
49
+ """Windows installation with multiple fallback methods and PATH management"""
50
+ methods = [
51
+ try_winget_install,
52
+ try_scoop_install,
53
+ try_choco_install,
54
+ try_direct_msi_install,
55
+ try_direct_zip_install
56
+ ]
57
+
58
+ for method in methods:
59
+ if method():
60
+ if verify_gh_installation():
61
+ return True
62
+ print(" ⚠️ Trying next installation method...")
63
+
64
+ print("❌ All Windows installation methods failed.")
65
+ return False
66
+
67
+ def try_winget_install() -> bool:
68
+ """Attempt installation via winget"""
69
+ if not shutil.which("winget"):
70
+ return False
71
+
72
+ print("\n 🔄 Attempting winget installation...")
73
+ try:
74
+ subprocess.run(
75
+ ["winget", "install", "--id", "GitHub.cli", "--silent", "--accept-package-agreements", "--accept-source-agreements"],
76
+ check=True,
77
+ capture_output=True
78
+ )
79
+ return True
80
+ except subprocess.CalledProcessError as e:
81
+ print(f" ⚠️ winget failed: {e.stderr.decode(errors='ignore').strip() if e.stderr else 'Unknown error'}")
82
+ return False
83
+
84
+ def try_scoop_install() -> bool:
85
+ """Attempt installation via scoop"""
86
+ if not shutil.which("scoop"):
87
+ return False
88
+
89
+ print("\n 🔄 Attempting scoop installation...")
90
+ try:
91
+ subprocess.run(["scoop", "install", "gh"], check=True, capture_output=True)
92
+ return True
93
+ except subprocess.CalledProcessError as e:
94
+ print(f" ⚠️ scoop failed: {e.stderr.decode(errors='ignore').strip() if e.stderr else 'Unknown error'}")
95
+ return False
96
+
97
+ def try_choco_install() -> bool:
98
+ """Attempt installation via chocolatey"""
99
+ if not shutil.which("choco"):
100
+ return False
101
+
102
+ print("\n 🔄 Attempting chocolatey installation...")
103
+ try:
104
+ subprocess.run(["choco", "install", "gh", "-y"], check=True, capture_output=True)
105
+ return True
106
+ except subprocess.CalledProcessError as e:
107
+ print(f" ⚠️ chocolatey failed: {e.stderr.decode(errors='ignore').strip() if e.stderr else 'Unknown error'}")
108
+ return False
109
+
110
+ def try_direct_msi_install() -> bool:
111
+ """Direct MSI installation with proper PATH handling"""
112
+ print("\n 🔄 Attempting direct MSI installation...")
113
+ temp_dir = ""
114
+ try:
115
+ release_info = get_github_release_info()
116
+ if not release_info: return False
117
+
118
+ msi_asset = next((a for a in release_info.get('assets', []) if a['name'].endswith('_windows_amd64.msi')), None)
119
+ if not msi_asset:
120
+ print(" ❌ Could not find Windows MSI installer.")
121
+ return False
122
+
123
+ temp_dir = tempfile.mkdtemp()
124
+ msi_path = os.path.join(temp_dir, msi_asset['name'])
125
+ print(f" ⬇️ Downloading {msi_asset['name']}...")
126
+ if not download_file(msi_asset['browser_download_url'], msi_path): return False
127
+
128
+ print(" 🛠 Installing (this may require administrator privileges)...")
129
+ subprocess.run(["msiexec", "/i", msi_path, "/quiet", "/norestart"], check=True)
130
+
131
+ shutil.rmtree(temp_dir, ignore_errors=True)
132
+
133
+ program_files = os.environ.get("ProgramFiles", "C:\\Program Files")
134
+ gh_path = os.path.join(program_files, "GitHub CLI", "gh.exe")
135
+ if os.path.exists(gh_path): add_to_path(os.path.dirname(gh_path))
136
+
137
+ return True
138
+ except Exception as e:
139
+ print(f" ❌ MSI installation failed: {str(e)}")
140
+ if temp_dir: shutil.rmtree(temp_dir, ignore_errors=True)
141
+ return False
142
+
143
+ def try_direct_zip_install() -> bool:
144
+ """Fallback ZIP installation for Windows"""
145
+ print("\n 🔄 Attempting direct ZIP installation...")
146
+ temp_dir = ""
147
+ try:
148
+ release_info = get_github_release_info()
149
+ if not release_info: return False
150
+
151
+ zip_asset = next((a for a in release_info.get('assets', []) if a['name'].endswith('windows_amd64.zip')), None)
152
+ if not zip_asset:
153
+ print(" ❌ Could not find Windows ZIP package.")
154
+ return False
155
+
156
+ temp_dir = tempfile.mkdtemp()
157
+ zip_path = os.path.join(temp_dir, zip_asset['name'])
158
+ print(f" ⬇️ Downloading {zip_asset['name']}...")
159
+ if not download_file(zip_asset['browser_download_url'], zip_path): return False
160
+
161
+ print(" 📦 Extracting...")
162
+ shutil.unpack_archive(zip_path, temp_dir)
163
+
164
+ bin_dir = next((root for root, _, files in os.walk(temp_dir) if "gh.exe" in files), None)
165
+ if not bin_dir:
166
+ print(" ❌ Could not find gh.exe in extracted files.")
167
+ shutil.rmtree(temp_dir, ignore_errors=True)
168
+ return False
169
+
170
+ install_dir = os.path.join(os.environ.get("LOCALAPPDATA", ""), "GitHubCLI")
171
+ os.makedirs(install_dir, exist_ok=True)
172
+
173
+ shutil.copytree(bin_dir, install_dir, dirs_exist_ok=True)
174
+ add_to_path(install_dir)
175
+
176
+ shutil.rmtree(temp_dir, ignore_errors=True)
177
+ return True
178
+ except Exception as e:
179
+ print(f" ❌ ZIP installation failed: {str(e)}")
180
+ if temp_dir: shutil.rmtree(temp_dir, ignore_errors=True)
181
+ return False
182
+
183
+ def install_gh_cli_mac() -> bool:
184
+ """macOS installation with multiple methods"""
185
+ if shutil.which("brew"):
186
+ print("\n 🔄 Attempting Homebrew installation...")
187
+ try:
188
+ subprocess.run(["brew", "install", "gh"], check=True, capture_output=True)
189
+ if verify_gh_installation(): return True
190
+ except subprocess.CalledProcessError as e:
191
+ print(f" ⚠️ Homebrew failed: {e.stderr.decode(errors='ignore').strip() if e.stderr else 'Unknown error'}")
192
+
193
+ print("❌ All macOS installation methods failed.")
194
+ return False
195
+
196
+ def install_gh_cli_linux() -> bool:
197
+ """Linux installation with distro detection and multiple methods"""
198
+ package_managers = [
199
+ ("apt-get", "sudo apt-get update && sudo apt-get install -y gh"),
200
+ ("apt", "sudo apt update && sudo apt install -y gh"),
201
+ ("dnf", "sudo dnf install -y gh"),
202
+ ("yum", "sudo yum install -y gh"),
203
+ ("pacman", "sudo pacman -S --noconfirm github-cli"),
204
+ ("zypper", "sudo zypper install -y gh"),
205
+ ]
206
+ for pm, command in package_managers:
207
+ if shutil.which(pm):
208
+ print(f"\n 🔄 Attempting installation via {pm}...")
209
+ try:
210
+ subprocess.run(command, shell=True, check=True, capture_output=True)
211
+ if verify_gh_installation(): return True
212
+ except subprocess.CalledProcessError as e:
213
+ print(f" ⚠️ {pm} failed: {e.stderr.decode(errors='ignore').strip() if e.stderr else 'Unknown error'}")
214
+
215
+ print("❌ All Linux package manager installations failed.")
216
+ return False
217
+
218
+ def get_github_release_info() -> Optional[dict]:
219
+ """Get latest release info from GitHub API"""
220
+ try:
221
+ with urllib.request.urlopen("https://api.github.com/repos/cli/cli/releases/latest") as response:
222
+ return json.loads(response.read().decode())
223
+ except Exception as e:
224
+ print(f" ❌ Failed to get release info from GitHub API: {str(e)}")
225
+ return None
226
+
227
+ def download_file(url: str, path: str) -> bool:
228
+ """Download a file with progress reporting"""
229
+ try:
230
+ def reporthook(count, block_size, total_size):
231
+ if total_size > 0:
232
+ percent = int(count * block_size * 100 / total_size)
233
+ sys.stdout.write(f"\r Downloading... {percent}%")
234
+ sys.stdout.flush()
235
+
236
+ urllib.request.urlretrieve(url, path, reporthook=reporthook)
237
+ sys.stdout.write("\r Downloading... 100%\n")
238
+ sys.stdout.flush()
239
+ return True
240
+ except Exception as e:
241
+ print(f"\n ❌ Download failed: {str(e)}")
242
+ return False
243
+
244
+ def add_to_path(directory: str):
245
+ """Add directory to PATH for the current session and try to make it permanent."""
246
+ print(f" ✅ Adding {directory} to PATH...")
247
+ os.environ["PATH"] = f"{directory}{os.pathsep}{os.environ['PATH']}"
248
+
249
+ if platform.system() == "Windows":
250
+ try:
251
+ # This makes the PATH change permanent for the current user
252
+ subprocess.run(
253
+ f'setx PATH "%PATH%;{directory}"',
254
+ shell=True, check=True, capture_output=True
255
+ )
256
+ except Exception as e:
257
+ print(f" ⚠️ Could not make PATH change permanent: {e}")
258
+ print(" You may need to add it manually.")
259
+ else: # macOS and Linux
260
+ # Suggest adding to shell profile
261
+ profile_file = ""
262
+ shell = os.environ.get("SHELL", "")
263
+ if "bash" in shell: profile_file = "~/.bashrc"
264
+ elif "zsh" in shell: profile_file = "~/.zshrc"
265
+ else: profile_file = "~/.profile"
266
+ print(f" To make this change permanent, add the following to your {profile_file}:")
267
+ print(f' export PATH="{directory}:$PATH"')
268
+
269
+ def verify_gh_installation() -> bool:
270
+ """Verify gh is properly installed and in PATH"""
271
+ if not shutil.which("gh"):
272
+ return False
273
+ try:
274
+ result = subprocess.run(["gh", "--version"], check=True, capture_output=True, text=True)
275
+ print(f"✅ GitHub CLI successfully installed: {result.stdout.splitlines()[0]}")
276
+ return True
277
+ except (subprocess.CalledProcessError, FileNotFoundError):
278
+ return False
279
+
280
+ def check_and_install_gh() -> bool:
281
+ """Main function to check and install GitHub CLI, WITH USER PROMPT."""
282
+ if check_gh_installed():
283
+ return True
284
+
285
+ # --- ADDED USER PROMPT ---
286
+ print("\n❓ GitHub CLI (gh) is required for this feature but is not installed.", file=sys.stderr)
287
+ try:
288
+ answer = input(" Would you like this tool to attempt an automatic installation? (y/n): ").lower().strip()
289
+ if answer != 'y':
290
+ print("\n❌ Installation cancelled by user. Please install gh manually from https://cli.github.com/")
291
+ return False
292
+ except (EOFError, KeyboardInterrupt):
293
+ print("\n❌ Installation cancelled by user.")
294
+ return False
295
+
296
+ if not install_gh_cli():
297
+ print("\n❌ Failed to install GitHub CLI automatically. Please try manual installation:")
298
+ print(" Visit https://github.com/cli/cli#installation for instructions.")
299
+ return False
300
+
301
+ # After installation, a PATH refresh might be needed
302
+ if not check_gh_installed():
303
+ print("\n‼️ IMPORTANT: Installation completed, but GitHub CLI is not yet available in this terminal session.")
304
+ print(" Please open a NEW terminal and run your command again.")
305
+ return False
306
+
307
+ return True
308
+
309
+
310
+ # --- Core Tool Functions ---
311
+
312
+ def gh_authenticated():
313
+ """Check if user is authenticated with GitHub CLI"""
314
+ try:
315
+ result = subprocess.run(["gh", "auth", "status"], capture_output=True, text=True, check=True)
316
+ return "Logged in to github.com" in result.stderr
317
+ except (subprocess.CalledProcessError, FileNotFoundError):
318
+ return False
319
+
320
+
321
+ def is_local_ahead() -> bool:
322
+ try:
323
+ result = subprocess.run(
324
+ ["git", "rev-list", "--left-right", "--count", "origin/main...HEAD"],
325
+ capture_output=True, text=True, check=True
326
+ )
327
+ behind_ahead = result.stdout.strip().split()
328
+ if len(behind_ahead) == 2:
329
+ behind, ahead = map(int, behind_ahead)
330
+ return ahead > 0
331
+ return False
332
+ except subprocess.CalledProcessError:
333
+ return False
334
+
335
+
336
+ def authenticate_with_gh():
337
+ """Authenticate user with GitHub CLI"""
338
+ print("\n🔑 GitHub authentication required.")
339
+ print("The tool will use the GitHub CLI (gh) to open a browser for secure login.")
340
+
341
+ try:
342
+ subprocess.run(["gh", "auth", "login", "--web", "-h", "github.com"], check=True)
343
+ return True
344
+ except subprocess.CalledProcessError:
345
+ print("❌ Authentication failed. Please try running 'gh auth login' manually.", file=sys.stderr)
346
+ return False
347
+
348
+ def initialize_git_repository():
349
+ """Initialize git repository if not already initialized"""
350
+ if os.path.exists(".git"):
351
+ return False
352
+
353
+ print("🛠 Initializing git repository")
354
+ try:
355
+ subprocess.run(["git", "init"], check=True, capture_output=True)
356
+ subprocess.run(["git", "branch", "-M", "main"], check=True, capture_output=True)
357
+
358
+ if not os.path.exists(".gitignore"):
359
+ with open(".gitignore", "w") as f:
360
+ f.write("""# Python
361
+ __pycache__/
362
+ *.py[cod]
363
+ *.so
364
+ .Python
365
+ env/
366
+ venv/
367
+ .env
368
+
369
+ # IDE
370
+ .vscode/
371
+ .idea/
372
+ *.swp
373
+ *.swo
374
+
375
+ # System
376
+ .DS_Store
377
+ Thumbs.db
378
+
379
+ # Project specific
380
+ *.log
381
+ *.tmp
382
+ *.bak
383
+ """)
384
+ print("📁 Created .gitignore file")
385
+ return True
386
+ except subprocess.CalledProcessError as e:
387
+ print(f"❌ Failed to initialize Git repository: {e.stderr.decode(errors='ignore').strip()}", file=sys.stderr)
388
+ return False
389
+
390
+ def create_initial_commit(commit_message="Initial commit"):
391
+ """Create initial commit if no commits exist"""
392
+ try:
393
+ result = subprocess.run(["git", "rev-list", "--count", "HEAD"], capture_output=True, text=True)
394
+ commit_count = int(result.stdout.strip()) if result.stdout.strip().isdigit() else 0
395
+
396
+ if commit_count == 0:
397
+ print("📦 Creating initial commit")
398
+ subprocess.run(["git", "add", "."], check=True)
399
+ subprocess.run(["git", "commit", "-m", commit_message], check=True)
400
+ return True
401
+ return False
402
+ except subprocess.CalledProcessError as e:
403
+ error_output = e.stderr.decode(errors='ignore').strip()
404
+ if "nothing to commit" in error_output:
405
+ print(f"❌ Failed to create initial commit: No files found to commit.", file=sys.stderr)
406
+ print("➡️ Add some files to your project directory before creating a repository.", file=sys.stderr)
407
+ else:
408
+ print(f"❌ Failed to create initial commit: {error_output}", file=sys.stderr)
409
+ return False
410
+
411
+ def create_with_gh_cli(repo_name, private=False, description="", commit_message="Initial commit"):
412
+ """Create and push to new repository using GitHub CLI"""
413
+ try:
414
+ if not os.path.exists(".git"):
415
+ if not initialize_git_repository():
416
+ return False
417
+
418
+ if not create_initial_commit(commit_message):
419
+ if subprocess.run(["git", "status"], capture_output=True).returncode != 0:
420
+ return False
421
+ print("ℹ️ Using existing commits")
422
+
423
+ private_flag = "--private" if private else "--public"
424
+ cmd = ["gh", "repo", "create", repo_name, private_flag, "--source=.", "--remote=origin", "--push"]
425
+ if description: cmd.extend(["--description", description])
426
+
427
+ print("🚀 Creating repository and pushing code...")
428
+ process = subprocess.run(cmd, check=True, capture_output=True, text=True)
429
+
430
+ repo_url = process.stderr.strip()
431
+ print(f"✅ Successfully created repository: {repo_url}")
432
+ return True
433
+ except subprocess.CalledProcessError as e:
434
+ error_message = e.stderr.strip()
435
+ if "already exists" in error_message:
436
+ print(f"❌ Failed to create repository: {error_message}", file=sys.stderr)
437
+ print("➡️ Please choose a different repository name.", file=sys.stderr)
438
+ else:
439
+ print(f"❌ Failed to create repository: {error_message}", file=sys.stderr)
440
+ return False
441
+ except Exception as e:
442
+ print(f"❌ An unexpected error occurred: {str(e)}", file=sys.stderr)
443
+ return False
444
+
445
+ def standard_git_push(commit_message, branch, remote, force=False, tags=False):
446
+ """Handle standard git push operations"""
447
+ try:
448
+ subprocess.run(["git", "add", "."], check=True)
449
+
450
+ if commit_message:
451
+ print(f"📦 Committing with message: '{commit_message}'")
452
+ subprocess.run(["git", "commit", "-m", commit_message, "--allow-empty-message"], check=True)
453
+ else:
454
+ print("ℹ️ No commit message provided. Pushing only staged changes.")
455
+
456
+ if is_local_ahead():
457
+ push_cmd = ["git", "push"]
458
+ else:
459
+ push_cmd = ["git", "push", "origin", "main"]
460
+
461
+ if force:
462
+ push_cmd.append("--force-with-lease")
463
+ print("⚠️ Using safe force push (--force-with-lease).")
464
+ if tags: push_cmd.append("--tags")
465
+ if remote and branch: push_cmd.extend([remote, branch])
466
+
467
+ print(f"🚀 Executing: {' '.join(push_cmd)}")
468
+ subprocess.run(push_cmd, check=True)
469
+ print("✅ Successfully pushed changes.")
470
+ return True
471
+ except subprocess.CalledProcessError as e:
472
+ error_output = e.stderr.decode(errors='ignore').strip() if e.stderr else str(e)
473
+ if "nothing to commit" in error_output:
474
+ print("ℹ️ No changes to commit. Nothing to do.")
475
+ return True
476
+ print(f"❌ Push failed: {error_output}", file=sys.stderr)
477
+ return False
478
+
479
+ # --- Main Entry Point ---
480
+
481
+ def run():
482
+ parser = argparse.ArgumentParser(
483
+ description="🚀 Supercharged Git push tool with GitHub repo creation",
484
+ formatter_class=argparse.RawDescriptionHelpFormatter,
485
+ epilog="""Examples:
486
+ Standard push: gitpush "My new feature"
487
+ Create new repo: gitpush "Initial commit" --new-repo my-awesome-project
488
+ Private repository: gitpush "Initial commit" --new-repo my-secret-project --private
489
+ Force push (safe): gitpush "Rebased feature" --force
490
+ Initialize only: gitpush --init
491
+ """
492
+ )
493
+ parser.add_argument("commit", nargs="?", help="Commit message (optional if just pushing staged changes).")
494
+ parser.add_argument("branch", nargs="?", default=None, help="Branch name (defaults to current branch).")
495
+ parser.add_argument("remote", nargs="?", default="origin", help="Remote name (default: origin).")
496
+ parser.add_argument("--force", action="store_true", help="Force push with --force-with-lease.")
497
+ parser.add_argument("--tags", action="store_true", help="Push all tags.")
498
+ parser.add_argument("--init", action="store_true", help="Initialize a new Git repository and exit.")
499
+ parser.add_argument("--new-repo", metavar="REPO_NAME", help="Create a new GitHub repository with the given name.")
500
+ parser.add_argument("--private", action="store_true", help="Make the new repository private.")
501
+ parser.add_argument("--description", help="Description for the new repository.")
502
+
503
+ args = parser.parse_args()
504
+
505
+ target_branch = args.branch
506
+ if not target_branch:
507
+ try:
508
+ branch_result = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True, check=True)
509
+ target_branch = branch_result.stdout.strip()
510
+ except subprocess.CalledProcessError:
511
+ target_branch = "main"
512
+
513
+ if args.new_repo:
514
+ # *** THIS IS THE MAIN FIX: Calling your orchestrator function ***
515
+ if not check_and_install_gh():
516
+ sys.exit(1)
517
+
518
+ if not gh_authenticated():
519
+ if not authenticate_with_gh():
520
+ sys.exit(1)
521
+
522
+ if not create_with_gh_cli(
523
+ args.new_repo,
524
+ private=args.private,
525
+ description=args.description or "",
526
+ commit_message=args.commit or "Initial commit"
527
+ ):
528
+ sys.exit(1)
529
+
530
+ elif args.init:
531
+ if initialize_git_repository():
532
+ print("✅ Git repository initialized successfully.")
533
+
534
+ else:
535
+ if not standard_git_push(
536
+ args.commit,
537
+ target_branch,
538
+ args.remote,
539
+ args.force,
540
+ args.tags
541
+ ):
542
+ sys.exit(1)
543
+
544
+ if __name__ == "__main__":
545
+ run()