diff --git a/scripts/passmanager.py b/scripts/passmanager.py index 5576c99..1fba0ba 100755 --- a/scripts/passmanager.py +++ b/scripts/passmanager.py @@ -171,7 +171,10 @@ class PassManager(Gtk.Window): r'^git@[\w\.-]+:[\w\-/\.]+$', # git@host:path/repo r'^https?://[\w\.-]+/[\w\-/\.]+\.git$', # https://host/path/repo.git r'^https?://[\w\.-]+/[\w\-/\.]+$', # https://host/path/repo + r'^https?://[\w\.-]+:\d+/[\w\-/\.]+\.git$', # https://host:port/path/repo.git + r'^https?://[\w\.-]+:\d+/[\w\-/\.]+$', # https://host:port/path/repo r'^ssh://[\w@\.-]+/[\w\-/\.~]+$', # ssh://user@host/path + r'^ssh://[\w@\.-]+:\d+/[\w\-/\.~]+$', # ssh://user@host:port/path r'^[\w]+@[\w\.-]+:[\w\-/\.~]+$', # user@host:path ] @@ -199,18 +202,18 @@ class PassManager(Gtk.Window): # Git sync info banner (only show if git is not initialized) if not self.isGitRepo: - infoBanner = Gtk.InfoBar() - infoBanner.set_message_type(Gtk.MessageType.INFO) - contentArea = infoBanner.get_content_area() + self.infoBanner = Gtk.InfoBar() + self.infoBanner.set_message_type(Gtk.MessageType.INFO) + contentArea = self.infoBanner.get_content_area() infoLabel = Gtk.Label(label="Git sync is not enabled. Passwords are stored locally only.") contentArea.pack_start(infoLabel, False, False, 0) setupButton = Gtk.Button(label="Set Up Git Sync") setupButton.connect("clicked", self.on_setup_git_sync_clicked) - infoBanner.add_action_widget(setupButton, Gtk.ResponseType.NONE) + self.infoBanner.add_action_widget(setupButton, Gtk.ResponseType.NONE) - browseBox.pack_start(infoBanner, False, False, 0) + browseBox.pack_start(self.infoBanner, False, False, 0) # Search box searchBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5) @@ -961,7 +964,7 @@ class PassManager(Gtk.Window): # Fetch to get latest remote status (quietly) subprocess.run( ['git', '-C', passwordStoreDir, 'fetch'], - capture_output=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) @@ -1258,12 +1261,51 @@ class PassManager(Gtk.Window): def on_git_push_clicked(self, button): """Handle git push button""" - result = subprocess.run( - ['pass', 'git', 'push'], - capture_output=True, - text=True + passwordStoreDir = str(Path.home() / '.password-store') + + # Check if there are any commits + hasCommits = subprocess.run( + ['git', '-C', passwordStoreDir, 'rev-parse', 'HEAD'], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ).returncode == 0 + + if not hasCommits: + self.show_error( + "No commits to push yet.\n\n" + "Add some passwords first, then they will be automatically committed and you can push." + ) + return + + # Check if upstream is set + upstreamResult = subprocess.run( + ['git', '-C', passwordStoreDir, 'rev-parse', '--abbrev-ref', '@{u}'], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL ) + if upstreamResult.returncode != 0: + # No upstream set - need to set it with -u + branchResult = subprocess.run( + ['git', '-C', passwordStoreDir, 'branch', '--show-current'], + capture_output=True, + text=True + ) + currentBranch = branchResult.stdout.strip() if branchResult.returncode == 0 else 'master' + + result = subprocess.run( + ['pass', 'git', 'push', '-u', 'origin', currentBranch], + capture_output=True, + text=True + ) + else: + # Upstream already set, just push + result = subprocess.run( + ['pass', 'git', 'push'], + capture_output=True, + text=True + ) + if result.returncode == 0: self.play_sound_effect(1000, 0.1) self.update_git_status() @@ -1337,7 +1379,7 @@ class PassManager(Gtk.Window): # Remove existing remote if present subprocess.run( ['git', '-C', passwordStoreDir, 'remote', 'remove', 'origin'], - capture_output=True, + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) @@ -1454,7 +1496,7 @@ class PassManager(Gtk.Window): # Test connectivity checkbox testCheck = Gtk.CheckButton(label="Test connectivity before setup") - testCheck.set_active(True) + testCheck.set_active(False) contentArea.pack_start(testCheck, False, False, 0) # Examples @@ -1525,8 +1567,11 @@ class PassManager(Gtk.Window): show_status_message("Invalid URL format. Please check the examples above.", 'error') continue - # Test connectivity if requested - if testCheck.get_active(): + # Skip connectivity test for SSH URLs (can't prompt for auth in GUI) + isSshUrl = remoteUrl.startswith('ssh://') or remoteUrl.startswith('git@') + + # Test connectivity if requested and not SSH + if testCheck.get_active() and not isSshUrl: # Show testing message testingDialog = Gtk.MessageDialog( transient_for=dialog, @@ -1617,28 +1662,57 @@ class PassManager(Gtk.Window): pushDialog.destroy() if response == Gtk.ResponseType.YES: - # Push to remote - result = subprocess.run( - ['pass', 'git', 'push', '-u', 'origin', 'master'], + # Detect current branch name + branchResult = subprocess.run( + ['git', '-C', passwordStoreDir, 'branch', '--show-current'], capture_output=True, text=True ) - if result.returncode != 0: - # Try 'main' branch if 'master' fails + currentBranch = branchResult.stdout.strip() if branchResult.returncode == 0 else 'master' + + # Check if there are any commits + hasCommits = subprocess.run( + ['git', '-C', passwordStoreDir, 'rev-parse', 'HEAD'], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ).returncode == 0 + + if not hasCommits: + # No commits yet - explain to user + noCommitsDialog = Gtk.MessageDialog( + transient_for=self, + flags=0, + message_type=Gtk.MessageType.INFO, + buttons=Gtk.ButtonsType.OK, + text="No commits to push yet" + ) + noCommitsDialog.format_secondary_text( + "Your password store doesn't have any commits yet.\n\n" + "Add some passwords first, then use the Git tab to push them to the remote." + ) + noCommitsDialog.run() + noCommitsDialog.destroy() + else: + # Push to remote with detected branch result = subprocess.run( - ['pass', 'git', 'push', '-u', 'origin', 'main'], + ['pass', 'git', 'push', '-u', 'origin', currentBranch], capture_output=True, text=True ) - if result.returncode != 0: - self.show_error(f"Push failed: {result.stderr}\n\nYou can push manually later from the Git tab.") - else: - self.play_sound_effect(1000, 0.1) + if result.returncode != 0: + self.show_error(f"Push failed: {result.stderr}\n\nYou can push manually later from the Git tab.") + else: + self.play_sound_effect(1000, 0.1) # Update the UI to reflect git is now initialized self.isGitRepo = True + + # Hide the info banner if it exists + if hasattr(self, 'infoBanner'): + self.infoBanner.hide() + self.rebuild_ui_with_git() def rebuild_ui_with_git(self):