Skip to content

Commit 2bf33ea

Browse files
author
djinni-hppro
committed
git sync
1 parent d55f284 commit 2bf33ea

3 files changed

Lines changed: 96 additions & 39 deletions

File tree

src/git.js

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ window.initGit = async (creds) => {
113113

114114
if (!isRepo) {
115115
// Clone
116-
console.log('Cloning Git Repo...');
116+
console.log('GIT: Cloning...');
117117
await git.clone({
118118
...getGitConfig(creds),
119119
url: creds.repoUrl,
@@ -123,16 +123,18 @@ window.initGit = async (creds) => {
123123
});
124124
} else {
125125
// Pull
126-
console.log('Pulling Git Repo...');
126+
console.log('GIT: Pulling...');
127127
try {
128128
await git.pull({
129129
...getGitConfig(creds),
130130
url: creds.repoUrl,
131131
ref: creds.branch || 'main',
132132
singleBranch: true,
133-
fastForward: true,
134-
author: getGitConfig(creds).author // author needed for merge commit if not ff
133+
// fastForward: true, // Removed to allow merge
134+
author: getGitConfig(creds).author
135135
});
136+
137+
console.log('GIT: Pulled (and Merged)');
136138
} catch (e) {
137139
console.warn('Git pull failed (might be offline or conflict), continuing with local state:', e);
138140
// We continue. Synchronization will happen against local FS state.
@@ -195,13 +197,13 @@ window.listNotesInGit = async (creds) => {
195197
source: 'git'
196198
});
197199
} catch (readErr) {
198-
console.error(`Error reading/parsing file ${file}:`, readErr.message);
200+
console.error(`GIT: Error reading/parsing file ${file}:`, readErr.message);
199201
}
200202
}
201203
}
202204
return notes;
203205
} catch (err) {
204-
console.error('Error listing notes in git:', err);
206+
console.error('GIT: Error listing notes in git:', err);
205207
return [];
206208
}
207209
};
@@ -222,7 +224,7 @@ window.deleteNoteFromGit = async (noteId, creds) => {
222224
try {
223225
await git.remove({ fs, dir: GIT_DIR, filepath: filename });
224226
} catch (e) {
225-
console.log('Git remove failed (file might not exist):', e);
227+
console.log('GIT: Git remove failed (file might not exist):', e);
226228
}
227229
try {
228230
// Ensure physical removal if git remove didn't do it (it should, but safety first)
@@ -233,30 +235,61 @@ window.deleteNoteFromGit = async (noteId, creds) => {
233235
window.finishGitSync = async (creds) => {
234236
if (!creds.repoUrl) return;
235237

236-
// Simple check: try to commit.
237238
try {
238-
const sha = await git.commit({
239-
...getGitConfig(creds),
240-
message: `Sync from FeatherNote: ${new Date().toISOString()}`,
241-
});
242-
console.log('Committed:', sha);
239+
const config = getGitConfig(creds);
240+
const remoteRef = creds.branch || 'main';
241+
242+
// 1. Commit
243+
try {
244+
const sha = await git.commit({
245+
...config,
246+
message: `Sync from FeatherNote: ${new Date().toISOString()}`,
247+
});
248+
console.log('GIT: Committed:', sha);
249+
} catch (e) {
250+
if (e.message && (e.message.includes('nothing to commit') || e.message.includes('no changes'))) {
251+
console.log('GIT: Nothing to commit.');
252+
return; // Nothing to push either
253+
}
254+
throw e; // Rethrow other errors
255+
}
243256

244-
// Push
245-
console.log('Pushing...');
246-
await git.push({
247-
...getGitConfig(creds),
248-
url: creds.repoUrl,
249-
ref: creds.branch || 'main',
250-
});
251-
console.log('Pushed successfully.');
257+
// 2. Push with Fallbacks
258+
const pushOptions = { ...config, url: creds.repoUrl, ref: remoteRef };
259+
260+
try {
261+
// Attempt 1: Standard Push
262+
console.log('GIT: Pushing...');
263+
await git.push(pushOptions);
264+
console.log('GIT: Pushed successfully.');
265+
266+
} catch (pushErr) {
267+
console.warn('GIT: Push failed, attempting to pull and retry:', pushErr);
268+
269+
try {
270+
// Attempt 2: Pull (Fetch + Merge) then Push
271+
await git.pull({
272+
...pushOptions,
273+
singleBranch: true,
274+
author: config.author
275+
});
276+
console.log('GIT: Pull successful. Retrying push...');
277+
278+
await git.push(pushOptions);
279+
console.log('GIT: Pushed successfully after merge.');
280+
281+
} catch (retryErr) {
282+
// Attempt 3: Force Push
283+
console.warn('GIT: Standard push failed after merge. Attempting force push as final fallback:', retryErr);
284+
await git.push({ ...pushOptions, force: true });
285+
console.log('GIT: Force push successful.');
286+
}
287+
}
252288
} catch (e) {
253289
if (e.code === 'MissingName') {
254-
console.error('Git Commit Failed: Missing Author Name/Email');
255-
} else if (e.message && (e.message.includes('nothing to commit') || e.message.includes('no changes'))) {
256-
// Ignore
257-
console.log('Nothing to commit.');
290+
console.error('GIT: Git Commit Failed: Missing Author Name/Email');
258291
} else {
259-
console.error('Git Commit/Push Error:', e);
292+
console.error('GIT: Git Sync Error:', e);
260293
}
261294
}
262295
};

src/helpers.js

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ async function synchronize({notes, deletedNoteIds, isSilent, credentials, nostrP
293293
};
294294

295295
// --- Step 1: Get remote state FIRST ---
296-
const remoteNoteMetadata = await listNotes({credentials, nostrPrivateKey, nostrRelays, lastSync, gitCredentials});
296+
const { mergedNotes: remoteNoteMetadata, s3Ids, gitIds, nostrIds } = await listNotes({credentials, nostrPrivateKey, nostrRelays, lastSync, gitCredentials});
297297
const remoteMetaMap = new Map(remoteNoteMetadata.map(m => [m.id, m]));
298298

299299
// --- Step 2: Determine which notes to upload ---
@@ -304,17 +304,30 @@ async function synchronize({notes, deletedNoteIds, isSilent, credentials, nostrP
304304

305305
const remoteMeta = remoteMetaMap.get(localNote.id);
306306
if (!remoteMeta) {
307-
// Note doesn't exist remotely, so it's new. Upload it.
307+
// Note doesn't exist remotely at all, so it's new.
308308
return true;
309309
}
310310

311-
// Note exists remotely. Only upload if local is newer.
312311
const remoteDate = new Date(remoteMeta.updatedAt);
313312
const localDate = new Date(localNote.updatedAt);
313+
314+
// If it's missing from any of our active sync providers, we need to upload.
315+
if (gitCredentials?.repoUrl && !gitIds.has(localNote.id)) return true;
316+
if (credentials.secretAccessKey && !s3Ids.has(localNote.id)) return true;
317+
if (nostrPrivateKey && !nostrIds.has(localNote.id)) return true;
318+
319+
// If the remote version is strictly newer, don't push the local version yet.
320+
// We'll download the remote version later in this sync cycle.
321+
if (localDate < remoteDate) return false;
322+
323+
// Note exists remotely. Only upload if local is newer than the newest remote.
314324
return localDate > remoteDate;
315325
});
316326

317-
const uploadPromises = notesToUpload.map(note => uploadNote({note, credentials, nostrPrivateKey, nostrRelays, gitCredentials}));
327+
const uploadPromises = notesToUpload.map(note => uploadNote({
328+
note, credentials, nostrPrivateKey, nostrRelays, gitCredentials,
329+
s3Ids, gitIds, nostrIds, remoteMetaMap
330+
}));
318331

319332
// --- Step 3: Determine which notes to delete from Remotes ---
320333
const deletePromises = deletedNoteIds.map(noteId => deleteNoteFromRemotes({noteId, credentials, nostrPrivateKey, nostrRelays, gdriveStore, gitCredentials}));
@@ -527,11 +540,15 @@ async function synchronizeImages({encryptedSettings, userId, nostrPrivateKey, no
527540
}
528541
}
529542

530-
async function uploadNote({note, credentials, nostrPrivateKey, nostrRelays, gitCredentials}) {
543+
async function uploadNote({note, credentials, nostrPrivateKey, nostrRelays, gitCredentials, s3Ids, gitIds, nostrIds, remoteMetaMap}) {
544+
const localDate = new Date(note.updatedAt);
545+
const remoteMeta = remoteMetaMap?.get(note.id);
546+
const isNewerLocally = remoteMeta && localDate > new Date(remoteMeta.updatedAt);
547+
531548
await Promise.allSettled([
532-
credentials.secretAccessKey ? uploadNoteToS3(note, credentials) : null,
533-
nostrPrivateKey ? window.publishNoteToRelays(nostrRelays.split(',').map(r => r.trim()), nostrPrivateKey, note) : null,
534-
(gitCredentials?.repoUrl && typeof window.uploadNoteToGit === 'function') ? window.uploadNoteToGit(note, gitCredentials) : null,
549+
(credentials.secretAccessKey && (isNewerLocally || !s3Ids?.has(note.id))) ? uploadNoteToS3(note, credentials) : null,
550+
(nostrPrivateKey && (isNewerLocally || !nostrIds?.has(note.id))) ? window.publishNoteToRelays(nostrRelays.split(',').map(r => r.trim()), nostrPrivateKey, note) : null,
551+
(gitCredentials?.repoUrl && typeof window.uploadNoteToGit === 'function' && (isNewerLocally || !gitIds?.has(note.id))) ? window.uploadNoteToGit(note, gitCredentials) : null,
535552
].filter(x => x));
536553
}
537554

@@ -611,7 +628,12 @@ async function listNotes({credentials, nostrPrivateKey, nostrRelays, lastSync, g
611628
}
612629
});
613630

614-
return Array.from(mergedNotes.values());
631+
return {
632+
mergedNotes: Array.from(mergedNotes.values()),
633+
s3Ids: new Set(s3Notes.map(n => n.id)),
634+
gitIds: new Set(gitNotes.map(n => n.id)),
635+
nostrIds: new Set(nostrNotes.map(n => n.id))
636+
};
615637
}
616638

617639
async function listImages({credentials, nostrPrivateKey, nostrRelays}) {

src/index.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,12 @@ <h3 class="text-lg font-bold">Settings <span class="text-sm pl-2 font-light text
356356
</div>
357357
<div class="grid grid-cols-4 items-center gap-2">
358358
<label for="secret-key" class="text-right">S3 Secret Key</label>
359-
<input id="secret-key" type="password" x-model="secretAccessKey" class="col-span-3 border rounded-md p-2" placeholder="Leave blank to keep existing" autocomplete="new-password" />
359+
<input id="secret-key" type="password" onmouseover="this.type='text'" onmouseout="this.type='password'" x-model="secretAccessKey" class="col-span-3 border rounded-md p-2" placeholder="Leave blank to keep existing" autocomplete="new-password" />
360360
</div>
361361
</fieldset>
362362

363+
364+
363365
<fieldset class="grid gap-2 border-t pt-4 text-sm">
364366
<div class="grid grid-cols-4 items-center gap-2">
365367
<label for="git-repo-url" class="text-right text-sm">Git Repo URL</label>
@@ -375,7 +377,7 @@ <h3 class="text-lg font-bold">Settings <span class="text-sm pl-2 font-light text
375377
</div>
376378
<div class="grid grid-cols-4 items-center gap-2">
377379
<label for="git-token" class="text-right text-sm">Token/Pass</label>
378-
<input id="git-token" type="password" x-model="gitToken" class="col-span-3 border rounded-md p-2" placeholder="Personal Access Token" autocomplete="new-password" />
380+
<input id="git-token" type="password" onmouseover="this.type='text'" onmouseout="this.type='password'" x-model="gitToken" class="col-span-3 border rounded-md p-2" placeholder="Personal Access Token" autocomplete="new-password" />
379381
</div>
380382
<!-- <div class="grid grid-cols-4 items-center gap-2">
381383
<label for="git-email" class="text-right text-sm">Email</label>
@@ -390,7 +392,7 @@ <h3 class="text-lg font-bold">Settings <span class="text-sm pl-2 font-light text
390392
<fieldset class="grid gap-2 border-t pt-4 text-sm">
391393
<div class="grid grid-cols-4 items-center gap-2">
392394
<label for="nostr-private-key" class="text-right">Nostr Private Key</label>
393-
<input id="nostr-private-key" type="password" x-model="nostrPrivateKey" class="col-span-3 border rounded-md p-2" placeholder="nsec..." autocomplete="new-password" />
395+
<input id="nostr-private-key" type="password" onmouseover="this.type='text'" onmouseout="this.type='password'" x-model="nostrPrivateKey" class="col-span-3 border rounded-md p-2" placeholder="nsec..." autocomplete="new-password" />
394396
</div>
395397
<div class="grid grid-cols-4 items-center gap-2">
396398
<label for="nostr-relays" class="text-right">Nostr Relays</label>
@@ -401,7 +403,7 @@ <h3 class="text-lg font-bold">Settings <span class="text-sm pl-2 font-light text
401403
<fieldset class="grid gap-2 border-t pt-4 text-sm">
402404
<div class="grid grid-cols-4 items-center gap-2">
403405
<label for="ai-api-key" class="text-right">AI API Key</label>
404-
<input id="ai-api-key" type="password" x-model="aiApiKey" class="col-span-3 border rounded-md p-2" placeholder="sk-B14h8lah..." autocomplete="new-password" />
406+
<input id="ai-api-key" type="password" onmouseover="this.type='text'" onmouseout="this.type='password'" x-model="aiApiKey" class="col-span-3 border rounded-md p-2" placeholder="sk-B14h8lah..." autocomplete="new-password" />
405407
</div>
406408
<div class="grid grid-cols-4 items-center gap-2">
407409
<label for="ai-api-route" class="text-right">AI API Endpoint</label>

0 commit comments

Comments
 (0)