gopass History

Version history navigation and secret recovery.

Git-Backed Password Store

gopass stores everything in git. Every change creates a commit with: - What changed (the encrypted .gpg file) - When it changed (commit timestamp) - Who changed it (GPG signature if enabled)

BENEFIT: Full audit trail, full rollback, full disaster recovery.

View History

# Store commit history (default store)
gopass git log

# Specific store history
gopass git log --store v3

# Last 10 commits
gopass git log --oneline -10

# What changed in a commit
gopass git show <commit-hash>

# Full git log (in store directory)
cd ~/.local/share/gopass/stores/v3
git log --oneline
git log --oneline --graph

# Changes to specific entry
git log --oneline -- path/to/entry.gpg

# Who changed what
git log --format="%h %an %s" -10

# Changes in time range
git log --since="2026-02-01" --until="2026-02-28" --oneline

Find Deleted Entries

# List ALL deleted entries in store
cd ~/.local/share/gopass/stores/v3
git log --diff-filter=D --summary --oneline

# Extract just the deleted paths
git log --diff-filter=D --summary --oneline | grep 'delete mode' | awk '{print $NF}'

# Find when specific entry was deleted
git log --all --full-history -- path/to/entry.gpg

# Find entries deleted in last 30 days
git log --diff-filter=D --summary --oneline --since="30 days ago"

# Search for deleted entry by keyword
git log --all --full-history --diff-filter=D --name-only --oneline | grep -i keyword

Recover Deleted Entry

# Step 1: Find the deletion commit
cd ~/.local/share/gopass/stores/v3
git log --all --full-history -- path/to/entry.gpg
# Note: Find commit BEFORE the deletion (the ^ means parent)

# Step 2: Restore the entry
# The ^ after commit hash means "parent of this commit" (state before deletion)
git checkout <deletion-commit-hash>^ -- path/to/entry.gpg

# Step 3: Verify recovery
gopass show path/to/entry

# Step 4: Commit the restoration
git add path/to/entry.gpg
git commit -m "Restore deleted entry: path/to/entry"

# Alternative: Restore entire directory
git checkout <commit-hash>^ -- path/to/directory/

# Alternative: Restore to different path
git show <commit-hash>^:path/to/entry.gpg > new/path/entry.gpg
git add new/path/entry.gpg
git commit -m "Restore entry to new location"

TIP: The caret ^ is crucial. Without it, you get the state AFTER deletion (empty).

Revert Changes

# Undo last commit (creates new revert commit)
gopass git revert HEAD

# Undo last commit in specific store
gopass git revert HEAD --store v3

# Revert specific commit
gopass git revert <commit-hash>

# Revert multiple commits (interactive)
cd ~/.local/share/gopass/stores/v3
git revert <oldest-hash>^..<newest-hash>

# Hard reset to previous state (DANGEROUS - rewrites history)
# Only use if NOT pushed to remote yet
cd ~/.local/share/gopass/stores/v3
git reset --hard HEAD~1

# Reset to specific commit
git reset --hard <commit-hash>

REVERT vs RESET: - revert: Creates new commit that undoes changes (safe, preserves history) - reset --hard: Rewrites history, loses commits (DANGEROUS)

Compare Versions

# See what changed between commits
cd ~/.local/share/gopass/stores/v3
git diff <old-hash> <new-hash>

# Changes in specific entry
git diff <old-hash> <new-hash> -- path/to/entry.gpg
# Note: Shows encrypted binary diff (not useful)

# To actually compare content, decrypt both versions
git show <old-hash>:path/to/entry.gpg | gpg -d > /tmp/old.txt
git show <new-hash>:path/to/entry.gpg | gpg -d > /tmp/new.txt
diff /tmp/old.txt /tmp/new.txt
rm /tmp/old.txt /tmp/new.txt

# Show entry at specific point in time
git show <commit-hash>:path/to/entry.gpg | gpg -d

Audit Trail

# When was entry last modified
cd ~/.local/share/gopass/stores/v3
git log -1 --format="%ai %s" -- path/to/entry.gpg

# All modifications to an entry
git log --format="%ai %h %s" -- path/to/entry.gpg

# Who modified entry
git log --format="%an <%ae>" -- path/to/entry.gpg | sort -u

# Changes by specific author
git log --author="username" --oneline

# Generate audit report for path
echo "=== Audit Report for path/to/entry ==="
echo "Created: $(git log --reverse --format='%ai' -- path/to/entry.gpg | head -1)"
echo "Last Modified: $(git log -1 --format='%ai' -- path/to/entry.gpg)"
echo "Total Changes: $(git log --oneline -- path/to/entry.gpg | wc -l)"
echo "Modified By:"
git log --format='  - %an (%ai)' -- path/to/entry.gpg

# Export full audit for compliance
git log --format='%ai|%an|%h|%s' > audit-$(date +%Y%m%d).csv

Safe Migration Pattern

# Before any migration: create bookmark
cd ~/.local/share/gopass/stores/v3
git log --oneline -1  # Note this commit hash

# Migration: Copy to new location
gopass show old/path/entry | gopass insert -m new/path/entry

# Verify new entry works
gopass show new/path/entry

# Remove old entry
gopass rm old/path/entry

# Verify migration
gopass ls new/path/
gopass show new/path/entry

# If something went wrong, restore from bookmark
git checkout <bookmark-hash> -- old/path/entry.gpg
git add old/path/entry.gpg
git commit -m "Restore entry after failed migration"

# Bulk migration with verification
for entry in $(gopass ls -f old-store/path/); do
    new_entry=$(echo "$entry" | sed 's|old-store/|new-store/|')

    # Copy
    gopass show "$entry" | gopass insert -m "$new_entry"

    # Verify (compare passwords)
    old_pass=$(gopass show -o "$entry")
    new_pass=$(gopass show -o "$new_entry")

    if [[ "$old_pass" == "$new_pass" ]]; then
        echo "✓ Migrated: $entry"
    else
        echo "✗ FAILED: $entry"
    fi
done

Branch Operations

# Create branch for experimental changes
cd ~/.local/share/gopass/stores/v3
git checkout -b reorganization

# Make changes
gopass mv old/structure new/structure
gopass rm legacy/entries

# If happy, merge to main
git checkout main
git merge reorganization

# If not happy, abandon branch
git checkout main
git branch -D reorganization

# Compare branches
git log main..reorganization --oneline  # What's in branch but not main
git diff main reorganization            # Actual differences

# Cherry-pick specific commit
git cherry-pick <commit-hash>

USE CASE: Test store reorganization before committing to it.

Handling Sync Conflicts

# When gopass sync fails due to conflict

# Step 1: Check status
cd ~/.local/share/gopass/stores/v3
git status

# Step 2: Pull with rebase (usually works)
git pull --rebase origin main

# Step 3: If conflicts, resolve manually
git status  # Shows conflicted files
# For encrypted files, you usually want one or the other:
git checkout --ours path/to/entry.gpg    # Keep local version
git checkout --theirs path/to/entry.gpg  # Keep remote version

# Step 4: Complete the rebase
git add .
git rebase --continue

# Step 5: Push
git push origin main

# Nuclear option: Force local to match remote
git fetch origin
git reset --hard origin/main

# Or force remote to match local (DANGEROUS)
git push --force origin main

Git Housekeeping

# Check store health
gopass fsck

# Git garbage collection
cd ~/.local/share/gopass/stores/v3
git gc

# Aggressive cleanup (after large migrations)
git gc --aggressive --prune=now

# Check repository size
du -sh .git

# Find large objects (unusual for gopass)
git rev-list --objects --all | \
    git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
    awk '/^blob/ {print $3, $4}' | sort -rn | head -10

# Verify repository integrity
git fsck --full

Disaster Recovery

# Primary recovery: Clone from remote
rm -rf ~/.local/share/gopass/stores/v3  # CAREFUL
git clone git@github.com:user/gopass-v3.git ~/.local/share/gopass/stores/v3
gopass mounts add v3 ~/.local/share/gopass/stores/v3

# Recovery from local backup
tar xzf gopass-backup.tar.gz -C ~/.local/share/gopass/
gopass mounts add v3 ~/.local/share/gopass/stores/v3

# Recovery from reflog (if commits were lost locally)
cd ~/.local/share/gopass/stores/v3
git reflog
# Find the commit before disaster
git checkout <reflog-hash>
git checkout -b recovery
git checkout main
git reset --hard recovery

# Export all entries to plaintext (for migration)
for entry in $(gopass ls -f v3/); do
    mkdir -p "export/$(dirname "$entry")"
    gopass show "$entry" > "export/$entry.txt"
done
# WARNING: This creates unencrypted files!

# Re-import after setup
for file in $(find export -name "*.txt"); do
    entry=$(echo "$file" | sed 's|export/||; s|\.txt$||')
    cat "$file" | gopass insert -m "$entry"
done
rm -rf export

CRITICAL REQUIREMENTS: 1. GPG private key (without this, nothing can be decrypted) 2. Git remote URL (for clone recovery) 3. Mount configuration (recreate with gopass mounts add)

Common Gotchas

# GOTCHA: Forgetting ^ when restoring
git checkout <commit>^ -- file  # CORRECT - before deletion
git checkout <commit> -- file   # WRONG - after deletion (empty)

# GOTCHA: reset --hard loses data
# ALWAYS use revert for shared branches
gopass git revert HEAD          # Safe, creates revert commit

# GOTCHA: Encrypted files can't be diffed meaningfully
# Git shows binary diff - decrypt to compare

# GOTCHA: GPG signatures may prevent push
# If GPG signing fails, commits can't be made
gpg --list-secret-keys         # Verify key exists
gpgconf --kill gpg-agent       # Restart agent

# GOTCHA: Reflog expires
# Old commits eventually get garbage collected
git gc --prune=now             # Forces immediate cleanup
# Recovery only works if reflog entries exist