11package main
22
33import (
4+ "encoding/json"
45 "fmt"
56 "net/url"
67 "os"
@@ -125,7 +126,38 @@ func (bm *BackupManager) ensureRepo(remoteURL string) error {
125126 return nil
126127}
127128
129+ // snapshotTransactionCount parses a JSON backup string and returns the number
130+ // of transactions it contains. Returns 0 on any parse error (treats corrupt/empty
131+ // JSON as having no transactions).
132+ func snapshotTransactionCount (jsonData string ) int {
133+ var s struct {
134+ Transactions []json.RawMessage `json:"transactions"`
135+ }
136+ if err := json .Unmarshal ([]byte (jsonData ), & s ); err != nil {
137+ return 0
138+ }
139+ return len (s .Transactions )
140+ }
141+
142+ // fetchRemoteJSON fetches the remote backup file content without modifying the
143+ // working tree. Returns "" if the remote does not exist yet or has no backup
144+ // file — that is not an error, it just means this is the first push.
145+ func (bm * BackupManager ) fetchRemoteJSON () string {
146+ // Update remote refs. Ignore errors (remote may not exist yet).
147+ bm .runGit ("fetch" , "origin" ) //nolint:errcheck
148+ out , err := bm .runGit ("show" , "FETCH_HEAD:self_ledger_backup.json" )
149+ if err != nil {
150+ return ""
151+ }
152+ return out
153+ }
154+
128155// BackupNow writes jsonData to the local repo and pushes to the remote.
156+ // It skips the commit/push when:
157+ // - the remote already contains identical content (no-op), OR
158+ // - local has no transactions but remote does — this prevents an empty-DB
159+ // force-push from overwriting real data when the app first launches on a
160+ // new device before the user has restored from backup.
129161func (bm * BackupManager ) BackupNow (jsonData string ) error {
130162 cfg , err := bm .LoadConfig ()
131163 if err != nil {
@@ -144,6 +176,18 @@ func (bm *BackupManager) BackupNow(jsonData string) error {
144176 return err
145177 }
146178
179+ // Fetch remote content and compare before writing anything.
180+ remoteJSON := bm .fetchRemoteJSON ()
181+ if remoteJSON == jsonData {
182+ // Remote is already up to date.
183+ return nil
184+ }
185+ if snapshotTransactionCount (jsonData ) == 0 && snapshotTransactionCount (remoteJSON ) > 0 {
186+ // Local DB is empty but remote has data. Skip to avoid overwriting a
187+ // real backup with a blank slate (typical on first launch of a new device).
188+ return nil
189+ }
190+
147191 backupFile := filepath .Join (bm .repoPath , "self_ledger_backup.json" )
148192 if err := os .WriteFile (backupFile , []byte (jsonData ), 0600 ); err != nil {
149193 return err
0 commit comments