|
4 | 4 | import java.nio.file.Path; |
5 | 5 | import java.util.List; |
6 | 6 | import java.util.Optional; |
| 7 | +import java.util.StringJoiner; |
7 | 8 |
|
8 | 9 | import org.jabref.gui.DialogService; |
9 | 10 | import org.jabref.gui.StateManager; |
|
14 | 15 | import org.jabref.logic.git.GitHandler; |
15 | 16 | import org.jabref.logic.git.GitSyncService; |
16 | 17 | import org.jabref.logic.git.conflicts.GitConflictResolverStrategy; |
17 | | -import org.jabref.logic.git.merge.GitSemanticMergeExecutor; |
18 | | -import org.jabref.logic.git.merge.GitSemanticMergeExecutorImpl; |
19 | | -import org.jabref.logic.git.model.PullResult; |
| 18 | +import org.jabref.logic.git.conflicts.ThreeWayEntryConflict; |
| 19 | +import org.jabref.logic.git.io.GitFileWriter; |
| 20 | +import org.jabref.logic.git.model.BookkeepingResult; |
| 21 | +import org.jabref.logic.git.model.MergePlan; |
| 22 | +import org.jabref.logic.git.model.PullPlan; |
20 | 23 | import org.jabref.logic.git.util.GitHandlerRegistry; |
21 | 24 | import org.jabref.logic.l10n.Localization; |
22 | 25 | import org.jabref.logic.util.BackgroundTask; |
|
26 | 29 |
|
27 | 30 | import org.eclipse.jgit.api.errors.GitAPIException; |
28 | 31 |
|
| 32 | +import static org.jabref.logic.git.merge.execution.GitMergeApplier.applyAutoPlan; |
| 33 | +import static org.jabref.logic.git.merge.execution.GitMergeApplier.applyResolved; |
| 34 | + |
29 | 35 | public class GitPullAction extends SimpleCommand { |
30 | 36 |
|
31 | 37 | private final DialogService dialogService; |
@@ -74,44 +80,95 @@ public void execute() { |
74 | 80 | GitStatusViewModel gitStatusViewModel = GitStatusViewModel.fromPathAndContext(stateManager, taskExecutor, gitHandlerRegistry, bibFilePath); |
75 | 81 |
|
76 | 82 | BackgroundTask |
77 | | - .wrap(() -> doPull(activeDatabase, bibFilePath, gitHandlerRegistry)) |
78 | | - .onSuccess(result -> { |
79 | | - if (result.noop()) { |
| 83 | + .wrap(() -> prepareMergeResult(activeDatabase, bibFilePath, gitHandlerRegistry)) |
| 84 | + .onSuccess(pullPlanOpt -> { |
| 85 | + if (pullPlanOpt.isEmpty()) { |
80 | 86 | dialogService.showInformationDialogAndWait( |
81 | 87 | Localization.lang("Git Pull"), |
82 | | - Localization.lang("Already up to date.") |
| 88 | + Localization.lang("Already up to date or local branch is ahead.") |
83 | 89 | ); |
84 | | - } else if (result.isSuccessful()) { |
85 | | - try { |
86 | | - replaceWithMergedEntries(result.getMergedEntries(), activeDatabase); |
87 | | - gitStatusViewModel.refresh(bibFilePath); |
88 | | - dialogService.showInformationDialogAndWait( |
89 | | - Localization.lang("Git Pull"), |
90 | | - Localization.lang("Merged and updated.")); |
91 | | - } catch (IOException | JabRefException ex) { |
92 | | - showPullError(ex); |
| 90 | + return; |
| 91 | + } |
| 92 | + |
| 93 | + PullPlan pullPlan = pullPlanOpt.get(); |
| 94 | + MergePlan autoMergePlan = pullPlan.autoPlan(); |
| 95 | + List<ThreeWayEntryConflict> conflicts = pullPlan.conflicts(); |
| 96 | + |
| 97 | + int autoNewCount = autoMergePlan.newEntries().size(); |
| 98 | + int autoModifiedCount = autoMergePlan.fieldPatches().size(); |
| 99 | + int autoDeletedCount = autoMergePlan.deletedEntryKeys().size(); |
| 100 | + |
| 101 | + applyAutoPlan(activeDatabase, autoMergePlan); |
| 102 | + |
| 103 | + int manualResolvedCount; |
| 104 | + if (!conflicts.isEmpty()) { |
| 105 | + // resolve via GUI (strategy jumps to FX thread internally; safe to call from background) |
| 106 | + GitConflictResolverStrategy resolver = new GuiGitConflictResolverStrategy(new GitConflictResolverDialog(dialogService, guiPreferences)); |
| 107 | + List<BibEntry> resolved = resolver.resolveConflicts(conflicts); |
| 108 | + if (resolved.isEmpty()) { |
| 109 | + dialogService.notify(Localization.lang("Pull canceled.")); |
| 110 | + return; |
93 | 111 | } |
| 112 | + manualResolvedCount = resolved.size(); |
| 113 | + applyResolved(activeDatabase, resolved); |
| 114 | + } else { |
| 115 | + manualResolvedCount = 0; |
94 | 116 | } |
| 117 | + |
| 118 | + BackgroundTask.wrap(() -> saveAndFinalize(bibFilePath, activeDatabase, pullPlan)) |
| 119 | + .onSuccess(finalizeResult -> { |
| 120 | + gitStatusViewModel.refresh(bibFilePath); |
| 121 | + if (finalizeResult.isFastForward()) { |
| 122 | + dialogService.showInformationDialogAndWait( |
| 123 | + Localization.lang("Git Pull"), |
| 124 | + Localization.lang("Fast-forwarded to remote.") |
| 125 | + ); |
| 126 | + } else { |
| 127 | + StringJoiner joiner = new StringJoiner(" "); |
| 128 | + joiner.add(Localization.lang( |
| 129 | + "Auto-applied changes: %0 new, %1 modified, %2 deleted.", |
| 130 | + String.valueOf(autoNewCount), |
| 131 | + String.valueOf(autoModifiedCount), |
| 132 | + String.valueOf(autoDeletedCount) |
| 133 | + )); |
| 134 | + if (manualResolvedCount > 0) { |
| 135 | + joiner.add(Localization.lang( |
| 136 | + "%0 conflicts resolved.", |
| 137 | + String.valueOf(manualResolvedCount) |
| 138 | + )); |
| 139 | + } |
| 140 | + String stats = joiner.toString(); |
| 141 | + |
| 142 | + dialogService.showInformationDialogAndWait( |
| 143 | + Localization.lang("Git Pull"), |
| 144 | + Localization.lang("Merged and updated.") + " " + stats |
| 145 | + ); |
| 146 | + } |
| 147 | + }) |
| 148 | + .onFailure(this::showPullError) |
| 149 | + .executeWith(taskExecutor); |
95 | 150 | }) |
96 | 151 | .onFailure(this::showPullError) |
97 | 152 | .executeWith(taskExecutor); |
98 | 153 | } |
99 | 154 |
|
100 | | - private PullResult doPull(BibDatabaseContext databaseContext, Path bibPath, GitHandlerRegistry registry) throws IOException, GitAPIException, JabRefException { |
101 | | - GitSyncService syncService = buildSyncService(bibPath, registry); |
| 155 | + /// Prepares a merge plan for the given library and file path. |
| 156 | + /// |
| 157 | + /// @return An Optional containing the PullPlan if a merge is needed, |
| 158 | + /// or Optional.empty() if the local library is already up-to-date or ahead of the remote branch. |
| 159 | + private Optional<PullPlan> prepareMergeResult(BibDatabaseContext databaseContext, Path bibPath, GitHandlerRegistry registry) throws IOException, GitAPIException, JabRefException { |
| 160 | + GitSyncService gitSyncService = GitSyncService.create(guiPreferences.getImportFormatPreferences(), registry); |
102 | 161 | GitHandler handler = registry.get(bibPath.getParent()); |
103 | 162 | String user = guiPreferences.getGitPreferences().getUsername(); |
104 | 163 | String pat = guiPreferences.getGitPreferences().getPat(); |
105 | 164 | handler.setCredentials(user, pat); |
106 | | - return syncService.fetchAndMerge(databaseContext, bibPath); |
| 165 | + return gitSyncService.prepareMerge(databaseContext, bibPath); |
107 | 166 | } |
108 | 167 |
|
109 | | - private GitSyncService buildSyncService(Path bibPath, GitHandlerRegistry handlerRegistry) { |
110 | | - GitConflictResolverDialog dialog = new GitConflictResolverDialog(dialogService, guiPreferences); |
111 | | - GitConflictResolverStrategy resolver = new GuiGitConflictResolverStrategy(dialog); |
112 | | - GitSemanticMergeExecutor mergeExecutor = new GitSemanticMergeExecutorImpl(guiPreferences.getImportFormatPreferences()); |
113 | | - |
114 | | - return new GitSyncService(guiPreferences.getImportFormatPreferences(), handlerRegistry, resolver, mergeExecutor); |
| 168 | + private BookkeepingResult saveAndFinalize(Path bibPath, BibDatabaseContext databaseContext, PullPlan pullPlan) throws IOException, GitAPIException, JabRefException { |
| 169 | + GitFileWriter.write(bibPath, databaseContext, guiPreferences.getImportFormatPreferences()); |
| 170 | + GitSyncService gitSyncService = GitSyncService.create(guiPreferences.getImportFormatPreferences(), gitHandlerRegistry); |
| 171 | + return gitSyncService.finalizeMerge(bibPath, pullPlan); |
115 | 172 | } |
116 | 173 |
|
117 | 174 | private void showPullError(Throwable exception) { |
@@ -141,15 +198,4 @@ private void showPullError(Throwable exception) { |
141 | 198 | ); |
142 | 199 | } |
143 | 200 | } |
144 | | - |
145 | | - private void replaceWithMergedEntries(List<BibEntry> mergedEntries, BibDatabaseContext databaseContext) throws IOException, JabRefException { |
146 | | - List<BibEntry> currentEntries = List.copyOf(databaseContext.getDatabase().getEntries()); |
147 | | - for (BibEntry entry : currentEntries) { |
148 | | - databaseContext.getDatabase().removeEntry(entry); |
149 | | - } |
150 | | - |
151 | | - for (BibEntry entry : mergedEntries) { |
152 | | - databaseContext.getDatabase().insertEntry(new BibEntry(entry)); |
153 | | - } |
154 | | - } |
155 | 201 | } |
0 commit comments