Skip to content

Fix external file modification warnings during Git merge #13946

Merged
Siedlerchr merged 30 commits into
JabRef:mainfrom
wanling0000:fix/files-changed-externally
Oct 16, 2025
Merged

Fix external file modification warnings during Git merge #13946
Siedlerchr merged 30 commits into
JabRef:mainfrom
wanling0000:fix/files-changed-externally

Conversation

@wanling0000

@wanling0000 wanling0000 commented Sep 22, 2025

Copy link
Copy Markdown
Collaborator

Closes #12350

Previously, JabRef sometimes showed file has been externally altered warnings after a Git pull.
Possible causes:

  • Database writes were not triggered by the GUI.
  • Side-effects from GitHandler.MergeGuard modifying the index or working tree.

This PR changes the workflow:

  • Logic layer: only performs semantic analysis and orchestrates Git operations.
  • GUI layer: responsible for writing the merged database to disk.
  • Removed MergeCommand usage; now commits are created explicitly with specified parent commits.

Next steps

  • Review and refine test classes for coverage and stability.

Steps to test

  • Use “Share to GitHub” to create a remote BibTeX repository
  • Modify the file on the remote side
  • In JabRef, go to File → Git → Pull
  • Verify that the GUI no longer shows file being externally altered warnings

Mandatory checks

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • I added JUnit tests for changes (if applicable)
  • [/] I added screenshots in the PR description (if change is visible to the user)
  • [/] I described the change in CHANGELOG.md in a way that is understandable for the average user (if change is visible to the user)
  • I checked the user documentation: Is the information available and up to date? If not, I created an issue at https://github.com/JabRef/user-documentation/issues or, even better, I submitted a pull request updating file(s) in https://github.com/JabRef/user-documentation/tree/main/en.

@wanling0000

Copy link
Copy Markdown
Collaborator Author

Sorry, this PR has become too large...
Although most of the changes are class moves and bulk test case migrations, I will split it into smaller PRs for easier review.
I will push the split versions later.

if (localIsAncestorOfRemote) { // BEHIND (we know remote is ahead of local)
if (bibEqualsRemote) {
gitHandler.fastForwardTo(remote);
return FinalizeResult.fastForward();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method should not return null. Consider using java.util.Optional to handle cases where a result might not be present.


private static boolean blobEqualsCommitPath(Git git, RevCommit commit, String relPath, Path file) {
try {
Optional<String> content = GitFileReader.readFileFromCommit(git, commit, Path.of(relPath));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use modern Java best practices such as Path.of() instead of Paths.get() to improve readability and maintainability.

Comment thread jablib/src/main/java/org/jabref/logic/git/merge/MergeBookkeeper.java Outdated
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While Optional is used correctly, ensure that new public methods do not return null and instead use Optional consistently.

Comment on lines +3 to +4
import java.util.List;
import java.util.Map;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports for List and Map should be replaced with modern Java data structures like Set.of() for better readability and maintainability.

Comment on lines +25 to +27
public static MergePlan empty() {
return new MergePlan(Map.of(), List.of(), List.of());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method 'empty' should not return null and should use java.util.Optional to handle empty states more effectively.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, here it returns a valid MergePlan instance with empty fields rather than a missing value, which does not refer to Optional...?

}

public record PushResult(boolean successful, boolean noop) {
/// Created when local branch has commits to push and the push succeeded.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is trivial and restates the obvious, which goes against the guideline that comments should add new information and not be plainly derived from the code itself.

return new PushResult(true, false);
}

/// Created when there is nothing to push (local is up to date with remote).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is trivial and restates the obvious, which goes against the guideline that comments should add new information and not be plainly derived from the code itself.

@trag-bot

trag-bot Bot commented Sep 29, 2025

Copy link
Copy Markdown

@trag-bot didn't find any issues in the code! ✅✨

tsantalis added a commit to tsantalis/RefactoringMiner that referenced this pull request Oct 1, 2025
JabRef/jabref#13946
jablib/src/main/java/org/jabref/logic/git/conflicts/GitConflictResolverStrategy.java
koppor
koppor previously requested changes Oct 2, 2025

@koppor koppor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small comments

I think, this really needs to be tested manually 😅. Maybe now - but if no one has time, we merge it into main and see what users say.

Localization.lang("Fast-forwarded to remote.")
);
} else {
String stats = Localization.lang(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally, one uses StringJoiner with " ") as separator.


private PullResult doPull(BibDatabaseContext databaseContext, Path bibPath, GitHandlerRegistry registry) throws IOException, GitAPIException, JabRefException {
GitSyncService syncService = buildSyncService(bibPath, registry);
private Optional<PullPlan> prepareMergeResult(BibDatabaseContext databaseContext, Path bibPath, GitHandlerRegistry registry) throws IOException, GitAPIException, JabRefException {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add JavaDoc when the optional is empty.

PullPlan pullPlan)
throws IOException, GitAPIException, JabRefException {
GitFileWriter.write(bibPath, databaseContext, guiPreferences.getImportFormatPreferences());
// Git bookkeeping

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment can be remoed, can't it? --> The line is self-explanatory, isn't it?

return gitSyncService.finalizeMerge(bibPath, pullPlan);
}

// ------------------- helpers: memory mutations -------------------

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this comment means.

Since the class is not long - just remove it.

(Otherwise: // region: xyz and then // endregion would be thing to group methods)

return new GitSyncService(guiPreferences.getImportFormatPreferences(), handlerRegistry, resolver, mergeExecutor);
/// Apply (remote - base) patches safely into the in-memory DB, plus safe new/deleted entries.
private static void applyAutoPlan(BibDatabaseContext bibDatabaseContext, MergePlan plan) {
// new entries

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the comment - the loop says it.

import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;

public final class DefaultMergeBookkeeper implements MergeBookkeeper {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add JavaDoc about the intention of this class.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is only one implementation of "MergeBookkeeper", I would merge these two classes - or document at "MergeBookkeeper" about the possible implementation variants.

Comment on lines +13 to +23
/**
* Record the GUI-produced merge result into Git history.
* Creates the right commit shape based on the merge graph:
* - BEHIND: fast-forward if content equals remote;
* otherwise create a new commit on top of `remote`.
* - DIVERGED: create a merge commit with parents [localHead, remote].
*
* Preconditions:
* - GUI has already saved the final .bib file to disk.
* - No unrelated unstaged changes (defensive check recommended).
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert to /// because the content is Markdown

Comment on lines +358 to +359
author = {author-a},
doi = {xya},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please indent by two to have it consistently formatted. - a non-indent is very strange and seems like an issue

(at "normalize", you do remove the spaces, therefore, the tests work))

* Apply user-resolved entries into MEMORY: replace or insert by citation key.
* (Aligned with MergeEntriesAction’s “edit the in-memory database first” philosophy.)
*/
private static void applyResolved(BibDatabaseContext bibDatabaseContext, List<BibEntry> resolved) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not duplice methods - you can use "package private" - meaning just leaving out "private" in the method to make it usable for for tests (Annote with @VisibleForTesting)

}

/// Apply (remote - base) patches safely into the in-memory DB, plus safe new/deleted entries.
private static void applyAutoPlan(BibDatabaseContext bibDatabaseContext, MergePlan plan) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not duplice methods - you can use "package private" - meaning just leaving out "private" in the method to make it usable for for tests (Annote with @VisibleForTesting)

@subhramit subhramit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny suggestions, code reads very pleasant.

Comment thread jablib/src/main/java/org/jabref/logic/git/merge/execution/MergeBookkeeper.java Outdated
Comment thread jablib/src/main/java/org/jabref/logic/git/merge/execution/MergeBookkeeper.java Outdated
@subhramit subhramit requested a review from koppor October 14, 2025 23:03
rename variables

Co-authored-by: Subhramit Basu <subhramit.bb@live.in>
subhramit
subhramit previously approved these changes Oct 15, 2025

@subhramit subhramit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a looot for continuing :)

@calixtus calixtus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just five small comments. Please forgive me if some of them were already discussed, I did not follow the reviews before.

Comment on lines +119 to +147
.onSuccess(finalizeResult -> {
gitStatusViewModel.refresh(bibFilePath);
if (finalizeResult.isFastForward()) {
dialogService.showInformationDialogAndWait(
Localization.lang("Git Pull"),
Localization.lang("Fast-forwarded to remote.")
);
} else {
StringJoiner joiner = new StringJoiner(" ");
joiner.add(Localization.lang(
"Auto-applied changes: %0 new, %1 modified, %2 deleted.",
String.valueOf(autoNewCount),
String.valueOf(autoModifiedCount),
String.valueOf(autoDeletedCount)
));
if (manualResolvedCount > 0) {
joiner.add(Localization.lang(
"%0 conflicts resolved.",
String.valueOf(manualResolvedCount)
));
}
String stats = joiner.toString();

dialogService.showInformationDialogAndWait(
Localization.lang("Git Pull"),
Localization.lang("Merged and updated.") + " " + stats
);
}
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sake of clean code this code be extracted to a separate method.

boolean inMerging = (state == RepositoryState.MERGING) || (state == RepositoryState.MERGING_RESOLVED);

if (dirty) {
if (!status.isClean()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clean code nesting could be reduced by failing fast - meaning `if (status.isClean()) { return false; }

Comment thread jablib/src/main/java/org/jabref/logic/git/merge/execution/GitMergeApplier.java Outdated
Comment thread jablib/src/main/java/org/jabref/logic/git/merge/execution/GitMergeApplier.java Outdated
Comment thread jablib/src/main/java/org/jabref/logic/git/merge/execution/MergeBookkeeper.java Outdated
@calixtus

Copy link
Copy Markdown
Member

Looks really great so far. Very excited to have this merged.

@subhramit subhramit dismissed their stale review October 15, 2025 17:53

comments by carl to be addressed

@Siedlerchr Siedlerchr enabled auto-merge October 16, 2025 08:50
@Siedlerchr Siedlerchr added this pull request to the merge queue Oct 16, 2025
Merged via the queue into JabRef:main with commit 5a91a7f Oct 16, 2025
52 checks passed
bblhd pushed a commit to bblhd/jabref that referenced this pull request Oct 16, 2025
* Changes by koppor

* Changes by subhramit

* update the JabRef code style

* fix git pull action

* fix git push action

* test: migrate semantic conflict cases to SemanticMergeAnalyzerTest and add unit tests

* fix: remove incorrect mention of `computeMergePlan`

* fix: remove incorrect mention of `computeMergePlan`

* Revert unintended renaming caused by IDE auto-formatting

* Address part of trag-bot's suggestions in JabRef#13946

* Add missing English localization keys

* remove obsolete inMerging branch in createCommitOnCurrentBranch

* test: replace assertTrue with assertEquals

* Clean up comments and variable names + Modify a test assertion

* Change prepareMerge() to return Optional<PullPlan> for no-op cases

* Fix failed tests

* Update comments

* remove exception control flow, assert presence of PullPlan instead

* Handle the optional PullPlan

* refactor(git): address review feedback

* chore: fix OpenRewrite config and apply code style cleanup

* Apply suggestions from code review

rename variables

Co-authored-by: Subhramit Basu <subhramit.bb@live.in>

* fix:remove unnecessary annotation and avoid throwing RuntimeException

---------

Co-authored-by: Subhramit Basu <subhramit.bb@live.in>
merlinymy pushed a commit to merlinymy/jabref that referenced this pull request Nov 19, 2025
* Changes by koppor

* Changes by subhramit

* update the JabRef code style

* fix git pull action

* fix git push action

* test: migrate semantic conflict cases to SemanticMergeAnalyzerTest and add unit tests

* fix: remove incorrect mention of `computeMergePlan`

* fix: remove incorrect mention of `computeMergePlan`

* Revert unintended renaming caused by IDE auto-formatting

* Address part of trag-bot's suggestions in JabRef#13946

* Add missing English localization keys

* remove obsolete inMerging branch in createCommitOnCurrentBranch

* test: replace assertTrue with assertEquals

* Clean up comments and variable names + Modify a test assertion

* Change prepareMerge() to return Optional<PullPlan> for no-op cases

* Fix failed tests

* Update comments

* remove exception control flow, assert presence of PullPlan instead

* Handle the optional PullPlan

* refactor(git): address review feedback

* chore: fix OpenRewrite config and apply code style cleanup

* Apply suggestions from code review

rename variables

Co-authored-by: Subhramit Basu <subhramit.bb@live.in>

* fix:remove unnecessary annotation and avoid throwing RuntimeException

---------

Co-authored-by: Subhramit Basu <subhramit.bb@live.in>
maorethians pushed a commit to maorethians/RefactoringMiner that referenced this pull request Jun 8, 2026
JabRef/jabref#13946
jablib/src/main/java/org/jabref/logic/git/conflicts/GitConflictResolverStrategy.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add (simple) git support

5 participants