Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b39eb17
Synchronize local libraries with their files
koppor Sep 1, 2026
bcd6236
Add changelog entry and null-marking for the file sync
koppor Sep 1, 2026
00a9d30
Apply CI formatting to LibraryBaseline
koppor Sep 1, 2026
8c23c9c
Harden synchronization against overlapping scans and concurrent edits
koppor Sep 1, 2026
cbb855a
Merge remote-tracking branch 'origin/main' into file-sync-main
koppor Sep 2, 2026
3224be5
Merge remote-tracking branch 'origin/main' into file-sync-main
koppor Sep 2, 2026
66d91c3
Invalidate in-flight scans and key baseline lookups by in-memory id
koppor Sep 2, 2026
33eb7d7
Wait for the library file to stop changing before parsing it
koppor Sep 3, 2026
ebaa829
Merge remote-tracking branch 'upstream/main' into file-sync-main
github-actions[bot] Sep 4, 2026
25453e9
Group an entry's merged changes into a named nested step
calixtus Sep 4, 2026
ce7b626
Merge remote-tracking branch 'upstream/main' into file-sync-main
github-actions[bot] Sep 4, 2026
90d28e9
Merge remote-tracking branch 'origin/main' into file-sync-main
koppor Sep 7, 2026
5d60bd3
Extract the entry classification and the scan callback into methods
koppor Sep 7, 2026
0d21f34
Remove the preference listener regardless of the library's current lo…
koppor Sep 7, 2026
c8c9bec
Stub the preferences in the unregister test
koppor Sep 7, 2026
e4e4b09
Keep the baseline as entry snapshots and index it once per scan
koppor Sep 7, 2026
4b200af
Apply IDEA formatting
koppor Sep 7, 2026
278f2c7
Move the synchronization policy into jablib
koppor Sep 7, 2026
e98762b
Treat entry comments as content, resolve duplicates only when unambig…
koppor Sep 7, 2026
b315245
Merge remote-tracking branch 'origin/main' into file-sync-main
koppor Sep 12, 2026
6687705
Detect conflicting comments and string renames, abort interrupted sca…
koppor Sep 12, 2026
0c78cbe
Make synchronization a per-library setting with a global default
koppor Sep 13, 2026
f3ff3ac
Describe the per-library synchronization option in the changelog
koppor Sep 13, 2026
eb7bc11
Fail closed on unsettled files, keep the setting out of the snapshot,…
koppor Sep 13, 2026
7053ddd
Withdraw reviews on unregister, retry failed reads, keep local settin…
koppor Sep 13, 2026
df61073
Merge remote-tracking branch 'origin/main' into file-sync-main
koppor Sep 13, 2026
247d27f
Populate the synchronization setting in the metadata copy test fixture
koppor Sep 13, 2026
cdecc57
Merge remote-tracking branch 'origin/main' into file-sync-main
koppor Sep 14, 2026
e2c805c
Merge remote-tracking branch 'origin/main' into file-sync-main
koppor Sep 18, 2026
53e16fa
Merge remote-tracking branch 'upstream/main' into file-sync-main
github-actions[bot] Sep 22, 2026
6fd1a66
Merge remote-tracking branch 'origin/main' into file-sync-main
koppor Sep 28, 2026
f1beda8
Rename the file synchronization option to "Automatically merge extern…
koppor Sep 28, 2026
960609e
Fix a typo in a test method name
koppor Sep 28, 2026
ed72541
Rename the synchronization requirement to match the option name
koppor Sep 28, 2026
d906395
Relate the file baseline to the Git merge and the shared-database syn…
koppor Sep 28, 2026
ff34175
Advance the sync baseline by disk facts after merges and reviews
koppor Sep 28, 2026
792e502
Keep the library modified after a review when it has unsaved edits el…
koppor Sep 28, 2026
34e9841
Use getFirst in a test, as OpenRewrite asks
koppor Sep 28, 2026
254295c
Merge remote-tracking branch 'origin/main' into file-sync-main
claude Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added `jabkit git merge-driver`, a Git merge driver that merges `.bib` files semantically. [#16838](https://github.com/JabRef/jabref/pull/16838)
- We added tooltips with action descriptions to menu items. [#9615](https://github.com/JabRef/jabref/issues/9615)
- We added "Contribute to JabRef" to the Help menu, linking to [contribute.jabref.org](https://contribute.jabref.org). [#17162](https://github.com/JabRef/jabref/pull/17162)
- We added an option to automatically merge changes made to the library file outside JabRef into the open library. [#8431](https://github.com/JabRef/jabref/issues/8431)
- We added EasyOCR, PaddleOCR, and AppleOCR as selectable OCR engines, alongside Tesseract and Docling. [#16866](https://github.com/JabRef/jabref/issues/16866)

### Changed
Expand All @@ -35,6 +36,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We fixed an issue where JabRef closed when a client of the integrated language server disconnected. [#17303](https://github.com/JabRef/jabref/pull/17303)
- We fixed menu items not showing why they are disabled when a menu was opened for the first time. [#17200](https://github.com/JabRef/jabref/pull/17200)
- We fixed an issue where entries of type Misc were penalized too strictly during duplicate detection. [#16578](https://github.com/JabRef/jabref/issues/16578)
- We fixed an issue where accepting an external change to an entry lost its table position, selection, and open entry editor. [#16813](https://github.com/JabRef/jabref/pull/16813)
- We fixed an issue where the "Simple HTML" export separated author initials with spaces. [#17306](https://github.com/JabRef/jabref/pull/17306)
- We fixed an issue where the Hayagriva YAML export wrote braced author names, such as company names, as invalid YAML. [#17316](https://github.com/JabRef/jabref/issues/17316)
- We fixed an issue where "Community forum" in the Help menu opened the donation page instead of the forum. [#17162](https://github.com/JabRef/jabref/pull/17162)
Expand Down
9 changes: 9 additions & 0 deletions docs/requirements/ux.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ Since inspecting a library file means parsing it completely, the inspection is s

Needs: impl

## Automatically merging external changes into a library
`req~ux.external-library-changes.automatic-merge~1`

While a local library is set to automatically merge external changes (a per-library setting with a global default, off unless enabled), changes made to its file outside JabRef must be merged into the in-memory library without asking, and unsaved in-memory changes must not be reported as external changes.
A review is only required for an item (entry field, entry type, metadata, preamble, string) that was changed differently in memory and in the file, or that was deleted on one side and changed on the other.
Fields changed only in the file are taken over even when other fields of the same entry were changed in memory.

Needs: impl

## Bulk entry deletion must keep main table responsive
`req~ux.large-library.bulk-entry-removal~1`

Expand Down
27 changes: 22 additions & 5 deletions jabgui/src/main/java/org/jabref/gui/collab/ChangeScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.function.BooleanSupplier;

import org.jabref.gui.DialogService;
import org.jabref.gui.StateManager;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.logic.importer.ImportFormatPreferences;
import org.jabref.logic.importer.OpenDatabase;
import org.jabref.logic.importer.ParserResult;
import org.jabref.logic.sync.LibraryBaseline;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.util.DummyFileUpdateMonitor;

Expand All @@ -34,18 +37,32 @@ public ChangeScanner(BibDatabaseContext database,
}

public List<DatabaseChange> scanForChanges() {
if (database.getDatabasePath().isEmpty()) {
return List.of();
}

try {
return getDatabaseChanges(database.getDatabasePath().get());
return scanForChanges(() -> true).orElse(List.of());
} catch (IOException e) {
LOGGER.warn("Error while parsing changed file.", e);
return List.of();
}
}

/// @param readyToParse consulted right before the file is parsed, e.g. to wait until a sync client has finished writing it; `false` abandons the scan
/// @return the changes, or empty when the scan was abandoned
/// @throws IOException when the file could not be read; unlike no changes, a failed read must be retried
public Optional<List<DatabaseChange>> scanForChanges(BooleanSupplier readyToParse) throws IOException {
if (database.getDatabasePath().isEmpty()) {
return Optional.of(List.of());
}
if (!readyToParse.getAsBoolean()) {
return Optional.empty();
}
return Optional.of(getDatabaseChanges(database.getDatabasePath().get()));
}

/// @return the given external changes sorted by the side they happened on, see [ChangeTriage#triage]
public ChangeTriage.Triage triage(LibraryBaseline baseline, List<DatabaseChange> changes) {
return ChangeTriage.triage(baseline, changes, database, databaseChangeResolverFactory);
}

public List<DatabaseChange> getDatabaseChanges(Path fileToCompare) throws IOException {
ImportFormatPreferences importFormatPreferences = preferences.getImportFormatPreferences();
ParserResult result = OpenDatabase.loadDatabase(fileToCompare, importFormatPreferences, new DummyFileUpdateMonitor());
Expand Down
Loading
Loading