A couple fixes to git management for pass.
This commit is contained in:
+98
-24
@@ -171,7 +171,10 @@ class PassManager(Gtk.Window):
|
|||||||
r'^git@[\w\.-]+:[\w\-/\.]+$', # git@host:path/repo
|
r'^git@[\w\.-]+:[\w\-/\.]+$', # git@host:path/repo
|
||||||
r'^https?://[\w\.-]+/[\w\-/\.]+\.git$', # https://host/path/repo.git
|
r'^https?://[\w\.-]+/[\w\-/\.]+\.git$', # https://host/path/repo.git
|
||||||
r'^https?://[\w\.-]+/[\w\-/\.]+$', # https://host/path/repo
|
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@\.-]+/[\w\-/\.~]+$', # ssh://user@host/path
|
||||||
|
r'^ssh://[\w@\.-]+:\d+/[\w\-/\.~]+$', # ssh://user@host:port/path
|
||||||
r'^[\w]+@[\w\.-]+:[\w\-/\.~]+$', # user@host: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)
|
# Git sync info banner (only show if git is not initialized)
|
||||||
if not self.isGitRepo:
|
if not self.isGitRepo:
|
||||||
infoBanner = Gtk.InfoBar()
|
self.infoBanner = Gtk.InfoBar()
|
||||||
infoBanner.set_message_type(Gtk.MessageType.INFO)
|
self.infoBanner.set_message_type(Gtk.MessageType.INFO)
|
||||||
contentArea = infoBanner.get_content_area()
|
contentArea = self.infoBanner.get_content_area()
|
||||||
|
|
||||||
infoLabel = Gtk.Label(label="Git sync is not enabled. Passwords are stored locally only.")
|
infoLabel = Gtk.Label(label="Git sync is not enabled. Passwords are stored locally only.")
|
||||||
contentArea.pack_start(infoLabel, False, False, 0)
|
contentArea.pack_start(infoLabel, False, False, 0)
|
||||||
|
|
||||||
setupButton = Gtk.Button(label="Set Up Git Sync")
|
setupButton = Gtk.Button(label="Set Up Git Sync")
|
||||||
setupButton.connect("clicked", self.on_setup_git_sync_clicked)
|
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
|
# Search box
|
||||||
searchBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
searchBox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||||
@@ -961,7 +964,7 @@ class PassManager(Gtk.Window):
|
|||||||
# Fetch to get latest remote status (quietly)
|
# Fetch to get latest remote status (quietly)
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
['git', '-C', passwordStoreDir, 'fetch'],
|
['git', '-C', passwordStoreDir, 'fetch'],
|
||||||
capture_output=True,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL
|
stderr=subprocess.DEVNULL
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1258,12 +1261,51 @@ class PassManager(Gtk.Window):
|
|||||||
|
|
||||||
def on_git_push_clicked(self, button):
|
def on_git_push_clicked(self, button):
|
||||||
"""Handle git push button"""
|
"""Handle git push button"""
|
||||||
result = subprocess.run(
|
passwordStoreDir = str(Path.home() / '.password-store')
|
||||||
['pass', 'git', 'push'],
|
|
||||||
capture_output=True,
|
# Check if there are any commits
|
||||||
text=True
|
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:
|
if result.returncode == 0:
|
||||||
self.play_sound_effect(1000, 0.1)
|
self.play_sound_effect(1000, 0.1)
|
||||||
self.update_git_status()
|
self.update_git_status()
|
||||||
@@ -1337,7 +1379,7 @@ class PassManager(Gtk.Window):
|
|||||||
# Remove existing remote if present
|
# Remove existing remote if present
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
['git', '-C', passwordStoreDir, 'remote', 'remove', 'origin'],
|
['git', '-C', passwordStoreDir, 'remote', 'remove', 'origin'],
|
||||||
capture_output=True,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL
|
stderr=subprocess.DEVNULL
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1454,7 +1496,7 @@ class PassManager(Gtk.Window):
|
|||||||
|
|
||||||
# Test connectivity checkbox
|
# Test connectivity checkbox
|
||||||
testCheck = Gtk.CheckButton(label="Test connectivity before setup")
|
testCheck = Gtk.CheckButton(label="Test connectivity before setup")
|
||||||
testCheck.set_active(True)
|
testCheck.set_active(False)
|
||||||
contentArea.pack_start(testCheck, False, False, 0)
|
contentArea.pack_start(testCheck, False, False, 0)
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
@@ -1525,8 +1567,11 @@ class PassManager(Gtk.Window):
|
|||||||
show_status_message("Invalid URL format. Please check the examples above.", 'error')
|
show_status_message("Invalid URL format. Please check the examples above.", 'error')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Test connectivity if requested
|
# Skip connectivity test for SSH URLs (can't prompt for auth in GUI)
|
||||||
if testCheck.get_active():
|
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
|
# Show testing message
|
||||||
testingDialog = Gtk.MessageDialog(
|
testingDialog = Gtk.MessageDialog(
|
||||||
transient_for=dialog,
|
transient_for=dialog,
|
||||||
@@ -1617,28 +1662,57 @@ class PassManager(Gtk.Window):
|
|||||||
pushDialog.destroy()
|
pushDialog.destroy()
|
||||||
|
|
||||||
if response == Gtk.ResponseType.YES:
|
if response == Gtk.ResponseType.YES:
|
||||||
# Push to remote
|
# Detect current branch name
|
||||||
result = subprocess.run(
|
branchResult = subprocess.run(
|
||||||
['pass', 'git', 'push', '-u', 'origin', 'master'],
|
['git', '-C', passwordStoreDir, 'branch', '--show-current'],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode != 0:
|
currentBranch = branchResult.stdout.strip() if branchResult.returncode == 0 else 'master'
|
||||||
# Try 'main' branch if 'master' fails
|
|
||||||
|
# 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(
|
result = subprocess.run(
|
||||||
['pass', 'git', 'push', '-u', 'origin', 'main'],
|
['pass', 'git', 'push', '-u', 'origin', currentBranch],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
self.show_error(f"Push failed: {result.stderr}\n\nYou can push manually later from the Git tab.")
|
self.show_error(f"Push failed: {result.stderr}\n\nYou can push manually later from the Git tab.")
|
||||||
else:
|
else:
|
||||||
self.play_sound_effect(1000, 0.1)
|
self.play_sound_effect(1000, 0.1)
|
||||||
|
|
||||||
# Update the UI to reflect git is now initialized
|
# Update the UI to reflect git is now initialized
|
||||||
self.isGitRepo = True
|
self.isGitRepo = True
|
||||||
|
|
||||||
|
# Hide the info banner if it exists
|
||||||
|
if hasattr(self, 'infoBanner'):
|
||||||
|
self.infoBanner.hide()
|
||||||
|
|
||||||
self.rebuild_ui_with_git()
|
self.rebuild_ui_with_git()
|
||||||
|
|
||||||
def rebuild_ui_with_git(self):
|
def rebuild_ui_with_git(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user