Added a dialog to configure git sync with pass.
This commit is contained in:
@@ -17,6 +17,7 @@ from gi.repository import Gtk, Gdk, GLib, Atk
|
||||
import subprocess
|
||||
import shutil
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
class PassManager(Gtk.Window):
|
||||
@@ -63,6 +64,10 @@ class PassManager(Gtk.Window):
|
||||
# Populate password list
|
||||
self.populate_password_list()
|
||||
|
||||
# Auto-pull from remote if git is configured
|
||||
if self.isGitRepo and self.has_git_remote():
|
||||
GLib.idle_add(self.auto_pull_on_startup)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def show_error_and_exit(self, message):
|
||||
@@ -140,11 +145,73 @@ class PassManager(Gtk.Window):
|
||||
gitDir = Path.home() / '.password-store' / '.git'
|
||||
return gitDir.exists()
|
||||
|
||||
def has_git_remote(self):
|
||||
"""Check if git repository has a remote configured"""
|
||||
if not self.isGitRepo:
|
||||
return False
|
||||
|
||||
result = subprocess.run(
|
||||
['git', '-C', str(Path.home() / '.password-store'), 'remote'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
return result.returncode == 0 and result.stdout.strip() != ""
|
||||
|
||||
def validate_git_url(self, url):
|
||||
"""Validate git URL format"""
|
||||
if not url or not url.strip():
|
||||
return False
|
||||
|
||||
url = url.strip()
|
||||
|
||||
# Common git URL patterns
|
||||
patterns = [
|
||||
r'^git@[\w\.-]+:[\w\-/\.]+\.git$', # git@host:path/repo.git
|
||||
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'^ssh://[\w@\.-]+/[\w\-/\.~]+$', # ssh://user@host/path
|
||||
r'^[\w]+@[\w\.-]+:[\w\-/\.~]+$', # user@host:path
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
if re.match(pattern, url):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def test_git_url_connectivity(self, url):
|
||||
"""Test if git URL is accessible"""
|
||||
result = subprocess.run(
|
||||
['git', 'ls-remote', url],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
return result.returncode == 0
|
||||
|
||||
def build_browse_tab(self):
|
||||
"""Build the browse and view tab"""
|
||||
browseBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
||||
self.notebook.append_page(browseBox, Gtk.Label(label="Browse"))
|
||||
|
||||
# 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()
|
||||
|
||||
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)
|
||||
|
||||
browseBox.pack_start(infoBanner, False, False, 0)
|
||||
|
||||
# Search box
|
||||
searchBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||
searchLabel = Gtk.Label(label="_Search:")
|
||||
@@ -437,7 +504,7 @@ class PassManager(Gtk.Window):
|
||||
self.gitStatusLabel.set_xalign(0)
|
||||
gitBox.pack_start(self.gitStatusLabel, False, False, 0)
|
||||
|
||||
# Button box
|
||||
# Button box for sync operations
|
||||
buttonBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||
gitBox.pack_start(buttonBox, False, False, 0)
|
||||
|
||||
@@ -453,6 +520,14 @@ class PassManager(Gtk.Window):
|
||||
refreshButton.connect("clicked", self.on_git_refresh_clicked)
|
||||
buttonBox.pack_start(refreshButton, True, True, 0)
|
||||
|
||||
# Button box for configuration
|
||||
configBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||
gitBox.pack_start(configBox, False, False, 0)
|
||||
|
||||
configRemoteButton = Gtk.Button(label="Configure Remote")
|
||||
configRemoteButton.connect("clicked", self.on_git_configure_remote_clicked)
|
||||
configBox.pack_start(configRemoteButton, True, True, 0)
|
||||
|
||||
# Log display
|
||||
logFrame = Gtk.Frame(label="Commit History")
|
||||
gitBox.pack_start(logFrame, True, True, 0)
|
||||
@@ -856,9 +931,11 @@ class PassManager(Gtk.Window):
|
||||
if not self.isGitRepo:
|
||||
return
|
||||
|
||||
passwordStoreDir = str(Path.home() / '.password-store')
|
||||
|
||||
# Get current branch
|
||||
result = subprocess.run(
|
||||
['git', '-C', str(Path.home() / '.password-store'), 'branch', '--show-current'],
|
||||
['git', '-C', passwordStoreDir, 'branch', '--show-current'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
@@ -866,17 +943,50 @@ class PassManager(Gtk.Window):
|
||||
|
||||
# Get status
|
||||
result = subprocess.run(
|
||||
['git', '-C', str(Path.home() / '.password-store'), 'status', '--porcelain', '--branch'],
|
||||
['git', '-C', passwordStoreDir, 'status', '--porcelain', '--branch'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
statusText = f"Branch: {branch}"
|
||||
|
||||
# Check for uncommitted changes
|
||||
if result.returncode == 0:
|
||||
lines = result.stdout.strip().split('\n')
|
||||
if len(lines) > 1:
|
||||
statusText += f" ({len(lines) - 1} changes)"
|
||||
|
||||
# Check ahead/behind status if remote exists
|
||||
if self.has_git_remote():
|
||||
# Fetch to get latest remote status (quietly)
|
||||
subprocess.run(
|
||||
['git', '-C', passwordStoreDir, 'fetch'],
|
||||
capture_output=True,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
|
||||
# Get ahead/behind counts
|
||||
result = subprocess.run(
|
||||
['git', '-C', passwordStoreDir, 'rev-list', '--left-right', '--count', f'{branch}...@{{u}}'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
counts = result.stdout.strip().split()
|
||||
if len(counts) == 2:
|
||||
ahead = int(counts[0])
|
||||
behind = int(counts[1])
|
||||
|
||||
if ahead > 0 and behind > 0:
|
||||
statusText += f" (↑{ahead} ↓{behind})"
|
||||
elif ahead > 0:
|
||||
statusText += f" (↑{ahead} ahead)"
|
||||
elif behind > 0:
|
||||
statusText += f" (↓{behind} behind)"
|
||||
else:
|
||||
statusText += " (up to date)"
|
||||
|
||||
self.gitStatusLabel.set_text(statusText)
|
||||
|
||||
# Update log
|
||||
@@ -898,6 +1008,22 @@ class PassManager(Gtk.Window):
|
||||
else:
|
||||
self.gitLogBuffer.set_text("Failed to retrieve log")
|
||||
|
||||
def auto_pull_on_startup(self):
|
||||
"""Automatically pull from remote on startup"""
|
||||
result = subprocess.run(
|
||||
['pass', 'git', 'pull'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
# Update UI if pull was successful
|
||||
self.update_git_status()
|
||||
self.populate_password_list()
|
||||
# Silently fail if pull fails - user can manually pull later
|
||||
|
||||
return False # Don't repeat this idle callback
|
||||
|
||||
def play_sound_effect(self, frequency=800, duration=0.1):
|
||||
"""Play sound effect"""
|
||||
if not shutil.which('play'):
|
||||
@@ -1148,6 +1274,395 @@ class PassManager(Gtk.Window):
|
||||
"""Handle git refresh button"""
|
||||
self.update_git_status()
|
||||
|
||||
def on_git_configure_remote_clicked(self, button):
|
||||
"""Handle configure remote button"""
|
||||
passwordStoreDir = str(Path.home() / '.password-store')
|
||||
|
||||
# Get current remote URL if exists
|
||||
result = subprocess.run(
|
||||
['git', '-C', passwordStoreDir, 'remote', 'get-url', 'origin'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
currentRemote = result.stdout.strip() if result.returncode == 0 else ""
|
||||
|
||||
# Show dialog
|
||||
dialog = Gtk.Dialog(
|
||||
title="Configure Git Remote",
|
||||
parent=self,
|
||||
flags=0
|
||||
)
|
||||
dialog.add_buttons(
|
||||
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
Gtk.STOCK_SAVE, Gtk.ResponseType.OK
|
||||
)
|
||||
dialog.set_default_size(500, 150)
|
||||
|
||||
contentArea = dialog.get_content_area()
|
||||
contentArea.set_spacing(10)
|
||||
contentArea.set_border_width(10)
|
||||
|
||||
# Instructions
|
||||
infoLabel = Gtk.Label(label="Enter the URL of your password store git repository:")
|
||||
infoLabel.set_line_wrap(True)
|
||||
infoLabel.set_xalign(0)
|
||||
contentArea.pack_start(infoLabel, False, False, 0)
|
||||
|
||||
# Remote URL entry
|
||||
urlBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||
urlLabel = Gtk.Label(label="_Remote URL:")
|
||||
urlLabel.set_use_underline(True)
|
||||
urlLabel.set_size_request(100, -1)
|
||||
urlLabel.set_xalign(0)
|
||||
urlEntry = Gtk.Entry()
|
||||
urlEntry.set_text(currentRemote)
|
||||
urlEntry.set_placeholder_text("git@example.com:username/pass-store.git")
|
||||
urlLabel.set_mnemonic_widget(urlEntry)
|
||||
urlBox.pack_start(urlLabel, False, False, 0)
|
||||
urlBox.pack_start(urlEntry, True, True, 0)
|
||||
contentArea.pack_start(urlBox, False, False, 0)
|
||||
|
||||
# Example label
|
||||
exampleLabel = Gtk.Label(label="Examples:\n • git@github.com:user/pass-store.git\n • https://github.com/user/pass-store.git\n • user@server.com:~/pass-store")
|
||||
exampleLabel.set_xalign(0)
|
||||
exampleLabel.set_markup("<small>Examples:\n • git@github.com:user/pass-store.git\n • https://github.com/user/pass-store.git\n • user@server.com:~/pass-store</small>")
|
||||
contentArea.pack_start(exampleLabel, False, False, 0)
|
||||
|
||||
dialog.show_all()
|
||||
response = dialog.run()
|
||||
|
||||
if response == Gtk.ResponseType.OK:
|
||||
remoteUrl = urlEntry.get_text().strip()
|
||||
if remoteUrl:
|
||||
# Remove existing remote if present
|
||||
subprocess.run(
|
||||
['git', '-C', passwordStoreDir, 'remote', 'remove', 'origin'],
|
||||
capture_output=True,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
|
||||
# Add new remote
|
||||
result = subprocess.run(
|
||||
['git', '-C', passwordStoreDir, 'remote', 'add', 'origin', remoteUrl],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
self.play_sound_effect(1000, 0.1)
|
||||
self.update_git_status()
|
||||
|
||||
# Show success message
|
||||
successDialog = Gtk.MessageDialog(
|
||||
transient_for=self,
|
||||
flags=0,
|
||||
message_type=Gtk.MessageType.INFO,
|
||||
buttons=Gtk.ButtonsType.OK,
|
||||
text="Remote configured successfully"
|
||||
)
|
||||
successDialog.format_secondary_text(
|
||||
f"Git remote 'origin' set to:\n{remoteUrl}\n\n"
|
||||
"You can now use Pull and Push buttons to sync."
|
||||
)
|
||||
successDialog.run()
|
||||
successDialog.destroy()
|
||||
else:
|
||||
self.show_error(f"Failed to configure remote: {result.stderr}")
|
||||
|
||||
dialog.destroy()
|
||||
|
||||
def on_setup_git_sync_clicked(self, button):
|
||||
"""Handle Set Up Git Sync button click"""
|
||||
# Show explanation dialog
|
||||
dialog = Gtk.Dialog(
|
||||
title="Set Up Git Sync",
|
||||
parent=self,
|
||||
flags=0
|
||||
)
|
||||
dialog.add_buttons(
|
||||
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
"Continue", Gtk.ResponseType.OK
|
||||
)
|
||||
dialog.set_default_size(550, 300)
|
||||
|
||||
contentArea = dialog.get_content_area()
|
||||
contentArea.set_spacing(10)
|
||||
contentArea.set_border_width(10)
|
||||
|
||||
# Explanation
|
||||
explanationText = (
|
||||
"Git sync allows you to back up and synchronize your passwords across multiple devices.\n\n"
|
||||
"How it works:\n"
|
||||
"• Your passwords will be stored in a git repository\n"
|
||||
"• Each change is automatically committed\n"
|
||||
"• You can push/pull to sync with a remote server\n"
|
||||
"• All passwords remain encrypted with your GPG key\n\n"
|
||||
"You'll need:\n"
|
||||
"• A git repository URL (GitHub, GitLab, self-hosted, etc.)\n"
|
||||
"• SSH keys or HTTPS credentials configured for access"
|
||||
)
|
||||
|
||||
explanationLabel = Gtk.Label(label=explanationText)
|
||||
explanationLabel.set_line_wrap(True)
|
||||
explanationLabel.set_xalign(0)
|
||||
contentArea.pack_start(explanationLabel, False, False, 0)
|
||||
|
||||
dialog.show_all()
|
||||
response = dialog.run()
|
||||
dialog.destroy()
|
||||
|
||||
if response != Gtk.ResponseType.OK:
|
||||
return
|
||||
|
||||
# Show URL configuration dialog
|
||||
self.show_git_url_dialog()
|
||||
|
||||
def show_git_url_dialog(self):
|
||||
"""Show dialog to configure git remote URL"""
|
||||
dialog = Gtk.Dialog(
|
||||
title="Configure Git Repository",
|
||||
parent=self,
|
||||
flags=0
|
||||
)
|
||||
dialog.add_buttons(
|
||||
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||
"Set Up", Gtk.ResponseType.OK
|
||||
)
|
||||
dialog.set_default_size(550, 250)
|
||||
|
||||
contentArea = dialog.get_content_area()
|
||||
contentArea.set_spacing(10)
|
||||
contentArea.set_border_width(10)
|
||||
|
||||
# Instructions
|
||||
infoLabel = Gtk.Label(label="Enter your git repository URL:")
|
||||
infoLabel.set_xalign(0)
|
||||
contentArea.pack_start(infoLabel, False, False, 0)
|
||||
|
||||
# URL entry
|
||||
urlBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||
urlLabel = Gtk.Label(label="_Repository URL:")
|
||||
urlLabel.set_use_underline(True)
|
||||
urlLabel.set_size_request(120, -1)
|
||||
urlLabel.set_xalign(0)
|
||||
urlEntry = Gtk.Entry()
|
||||
urlEntry.set_placeholder_text("git@github.com:username/pass-store.git")
|
||||
urlLabel.set_mnemonic_widget(urlEntry)
|
||||
urlBox.pack_start(urlLabel, False, False, 0)
|
||||
urlBox.pack_start(urlEntry, True, True, 0)
|
||||
contentArea.pack_start(urlBox, False, False, 0)
|
||||
|
||||
# Test connectivity checkbox
|
||||
testCheck = Gtk.CheckButton(label="Test connectivity before setup")
|
||||
testCheck.set_active(True)
|
||||
contentArea.pack_start(testCheck, False, False, 0)
|
||||
|
||||
# Examples
|
||||
exampleLabel = Gtk.Label()
|
||||
exampleLabel.set_markup(
|
||||
"<small><b>Examples:</b>\n"
|
||||
" • git@github.com:username/pass-store.git\n"
|
||||
" • https://github.com/username/pass-store.git\n"
|
||||
" • ssh://user@server.com/~/pass-store.git\n"
|
||||
" • user@server.com:pass-store</small>"
|
||||
)
|
||||
exampleLabel.set_xalign(0)
|
||||
contentArea.pack_start(exampleLabel, False, False, 0)
|
||||
|
||||
# Status label for validation feedback
|
||||
statusLabel = Gtk.Label()
|
||||
statusLabel.set_xalign(0)
|
||||
statusLabel.set_line_wrap(True)
|
||||
contentArea.pack_start(statusLabel, False, False, 0)
|
||||
|
||||
# Make status label accessible as a live region
|
||||
statusAccessible = statusLabel.get_accessible()
|
||||
statusAccessible.set_name("Validation status")
|
||||
|
||||
dialog.show_all()
|
||||
statusLabel.hide()
|
||||
|
||||
# Helper function to show error and announce to screen reader
|
||||
def show_status_message(message, messageType='error'):
|
||||
"""Show status message and make it accessible"""
|
||||
if messageType == 'error':
|
||||
statusLabel.set_markup(f"<span foreground='red'>{message}</span>")
|
||||
elif messageType == 'info':
|
||||
statusLabel.set_markup(f"<span foreground='blue'>{message}</span>")
|
||||
elif messageType == 'success':
|
||||
statusLabel.set_markup(f"<span foreground='green'>{message}</span>")
|
||||
else:
|
||||
statusLabel.set_text(message)
|
||||
|
||||
statusLabel.show()
|
||||
|
||||
# Use Orca's notification system by showing a simple dialog
|
||||
# This ensures the message is read aloud
|
||||
if messageType == 'error':
|
||||
# For errors, show an accessible message dialog briefly
|
||||
errorDialog = Gtk.MessageDialog(
|
||||
transient_for=dialog,
|
||||
flags=Gtk.DialogFlags.MODAL,
|
||||
message_type=Gtk.MessageType.ERROR,
|
||||
buttons=Gtk.ButtonsType.OK,
|
||||
text=message
|
||||
)
|
||||
errorDialog.run()
|
||||
errorDialog.destroy()
|
||||
urlEntry.grab_focus()
|
||||
|
||||
while True:
|
||||
response = dialog.run()
|
||||
|
||||
if response != Gtk.ResponseType.OK:
|
||||
dialog.destroy()
|
||||
return
|
||||
|
||||
remoteUrl = urlEntry.get_text().strip()
|
||||
|
||||
# Validate URL format
|
||||
if not self.validate_git_url(remoteUrl):
|
||||
show_status_message("Invalid URL format. Please check the examples above.", 'error')
|
||||
continue
|
||||
|
||||
# Test connectivity if requested
|
||||
if testCheck.get_active():
|
||||
# Show testing message
|
||||
testingDialog = Gtk.MessageDialog(
|
||||
transient_for=dialog,
|
||||
flags=Gtk.DialogFlags.MODAL,
|
||||
message_type=Gtk.MessageType.INFO,
|
||||
buttons=Gtk.ButtonsType.NONE,
|
||||
text="Testing connectivity..."
|
||||
)
|
||||
testingDialog.show()
|
||||
|
||||
# Process events to update UI
|
||||
while Gtk.events_pending():
|
||||
Gtk.main_iteration()
|
||||
|
||||
try:
|
||||
connectionSuccess = self.test_git_url_connectivity(remoteUrl)
|
||||
testingDialog.destroy()
|
||||
|
||||
if not connectionSuccess:
|
||||
show_status_message("Cannot connect to repository. Check URL and network access.", 'error')
|
||||
continue
|
||||
except subprocess.TimeoutExpired:
|
||||
testingDialog.destroy()
|
||||
show_status_message("Connection timeout. Check URL and network access.", 'error')
|
||||
continue
|
||||
except Exception as e:
|
||||
testingDialog.destroy()
|
||||
show_status_message(f"Error: {str(e)}", 'error')
|
||||
continue
|
||||
|
||||
# Show success message
|
||||
successDialog = Gtk.MessageDialog(
|
||||
transient_for=dialog,
|
||||
flags=Gtk.DialogFlags.MODAL,
|
||||
message_type=Gtk.MessageType.INFO,
|
||||
buttons=Gtk.ButtonsType.OK,
|
||||
text="Connection successful!"
|
||||
)
|
||||
successDialog.format_secondary_text("The repository is accessible and ready to use.")
|
||||
successDialog.run()
|
||||
successDialog.destroy()
|
||||
|
||||
# URL is valid, proceed with setup
|
||||
dialog.destroy()
|
||||
self.initialize_git_sync(remoteUrl)
|
||||
break
|
||||
|
||||
def initialize_git_sync(self, remoteUrl):
|
||||
"""Initialize git repository and set up remote"""
|
||||
passwordStoreDir = str(Path.home() / '.password-store')
|
||||
|
||||
# Initialize git
|
||||
result = subprocess.run(
|
||||
['pass', 'git', 'init'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
self.show_error(f"Failed to initialize git: {result.stderr}")
|
||||
return
|
||||
|
||||
# Add remote
|
||||
result = subprocess.run(
|
||||
['git', '-C', passwordStoreDir, 'remote', 'add', 'origin', remoteUrl],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
self.show_error(f"Failed to add remote: {result.stderr}")
|
||||
return
|
||||
|
||||
# Ask if user wants to push now
|
||||
pushDialog = Gtk.MessageDialog(
|
||||
transient_for=self,
|
||||
flags=0,
|
||||
message_type=Gtk.MessageType.QUESTION,
|
||||
buttons=Gtk.ButtonsType.YES_NO,
|
||||
text="Git sync set up successfully!"
|
||||
)
|
||||
pushDialog.format_secondary_text(
|
||||
f"Repository initialized and remote configured:\n{remoteUrl}\n\n"
|
||||
"Would you like to push your passwords to the remote now?"
|
||||
)
|
||||
|
||||
response = pushDialog.run()
|
||||
pushDialog.destroy()
|
||||
|
||||
if response == Gtk.ResponseType.YES:
|
||||
# Push to remote
|
||||
result = subprocess.run(
|
||||
['pass', 'git', 'push', '-u', 'origin', 'master'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
# Try 'main' branch if 'master' fails
|
||||
result = subprocess.run(
|
||||
['pass', 'git', 'push', '-u', 'origin', 'main'],
|
||||
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)
|
||||
|
||||
# Update the UI to reflect git is now initialized
|
||||
self.isGitRepo = True
|
||||
self.rebuild_ui_with_git()
|
||||
|
||||
def rebuild_ui_with_git(self):
|
||||
"""Rebuild the UI to include the Git tab"""
|
||||
# Clear existing notebook
|
||||
for i in range(self.notebook.get_n_pages()):
|
||||
self.notebook.remove_page(0)
|
||||
|
||||
# Rebuild all tabs
|
||||
self.build_browse_tab()
|
||||
self.build_add_tab()
|
||||
self.build_organize_tab()
|
||||
self.build_git_tab()
|
||||
|
||||
# Reconnect signals
|
||||
self.setup_accessibility()
|
||||
|
||||
# Show all and switch to Git tab
|
||||
self.show_all()
|
||||
self.notebook.set_current_page(3) # Git tab
|
||||
|
||||
# Update git status
|
||||
self.update_git_status()
|
||||
|
||||
if __name__ == "__main__":
|
||||
win = PassManager()
|
||||
Gtk.main()
|
||||
|
||||
Reference in New Issue
Block a user