Skip to content

Commit 0221118

Browse files
authored
feat: implement Git pull & push with semantic merge support (#13744)
1 parent c877291 commit 0221118

25 files changed

+762
-344
lines changed

jabgui/src/main/java/org/jabref/gui/actions/ActionHelper.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import javafx.collections.ObservableList;
1111

1212
import org.jabref.gui.StateManager;
13+
import org.jabref.logic.git.GitHandler;
14+
import org.jabref.logic.git.util.GitHandlerRegistry;
1315
import org.jabref.logic.preferences.CliPreferences;
1416
import org.jabref.logic.shared.DatabaseLocation;
1517
import org.jabref.logic.util.io.FileUtil;
@@ -104,4 +106,21 @@ public static BooleanExpression hasLinkedFileForSelectedEntries(StateManager sta
104106
return BooleanExpression.booleanExpression(EasyBind.reduce(stateManager.getSelectedEntries(),
105107
entries -> entries.anyMatch(entry -> !entry.getFiles().isEmpty())));
106108
}
109+
110+
public static BooleanExpression needsGitRemoteConfigured(StateManager stateManager) {
111+
return BooleanExpression.booleanExpression(
112+
EasyBind.map(stateManager.activeDatabaseProperty(), contextOptional -> {
113+
if (contextOptional.isPresent()) {
114+
return contextOptional.get().getDatabasePath()
115+
.map(path -> {
116+
GitHandler handler = new GitHandlerRegistry().get(path.getParent());
117+
return handler != null && handler.hasRemote("origin");
118+
})
119+
.orElse(false);
120+
} else {
121+
return false;
122+
}
123+
})
124+
);
125+
}
107126
}

jabgui/src/main/java/org/jabref/gui/frame/MainMenu.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import org.jabref.gui.externalfiles.DownloadFullTextAction;
3939
import org.jabref.gui.externalfiles.FindUnlinkedFilesAction;
4040
import org.jabref.gui.git.GitCommitAction;
41+
import org.jabref.gui.git.GitPullAction;
42+
import org.jabref.gui.git.GitPushAction;
4143
import org.jabref.gui.git.GitShareToGitHubAction;
4244
import org.jabref.gui.help.AboutAction;
4345
import org.jabref.gui.help.ErrorConsoleAction;
@@ -180,10 +182,11 @@ private void createMenu() {
180182
new SeparatorMenuItem(),
181183

182184
// region: Sharing of the library
183-
184185
// TODO: Should be only enabled if not yet shared.
185186
factory.createSubMenu(StandardActions.GIT,
186187
factory.createMenuItem(StandardActions.GIT_COMMIT, new GitCommitAction(dialogService, stateManager)),
188+
factory.createMenuItem(StandardActions.GIT_PULL, new GitPullAction(dialogService, stateManager, preferences, taskExecutor)),
189+
factory.createMenuItem(StandardActions.GIT_PUSH, new GitPushAction(dialogService, stateManager, preferences, taskExecutor)),
187190
new SeparatorMenuItem(),
188191
factory.createMenuItem(StandardActions.GIT_SHARE, new GitShareToGitHubAction(dialogService, stateManager))
189192
),

jabgui/src/main/java/org/jabref/gui/git/GitPullAction.java

Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import java.io.IOException;
44
import java.nio.file.Path;
5+
import java.util.List;
56
import java.util.Optional;
67

78
import org.jabref.gui.DialogService;
89
import org.jabref.gui.StateManager;
10+
import org.jabref.gui.actions.ActionHelper;
911
import org.jabref.gui.actions.SimpleCommand;
1012
import org.jabref.gui.preferences.GuiPreferences;
1113
import org.jabref.logic.JabRefException;
@@ -14,12 +16,15 @@
1416
import org.jabref.logic.git.conflicts.GitConflictResolverStrategy;
1517
import org.jabref.logic.git.merge.GitSemanticMergeExecutor;
1618
import org.jabref.logic.git.merge.GitSemanticMergeExecutorImpl;
19+
import org.jabref.logic.git.model.PullResult;
1720
import org.jabref.logic.git.util.GitHandlerRegistry;
1821
import org.jabref.logic.l10n.Localization;
1922
import org.jabref.logic.util.BackgroundTask;
2023
import org.jabref.logic.util.TaskExecutor;
2124
import org.jabref.model.database.BibDatabaseContext;
25+
import org.jabref.model.entry.BibEntry;
2226

27+
import com.airhacks.afterburner.injection.Injector;
2328
import org.eclipse.jgit.api.errors.GitAPIException;
2429

2530
public class GitPullAction extends SimpleCommand {
@@ -28,18 +33,17 @@ public class GitPullAction extends SimpleCommand {
2833
private final StateManager stateManager;
2934
private final GuiPreferences guiPreferences;
3035
private final TaskExecutor taskExecutor;
31-
private final GitHandlerRegistry handlerRegistry;
3236

3337
public GitPullAction(DialogService dialogService,
3438
StateManager stateManager,
3539
GuiPreferences guiPreferences,
36-
TaskExecutor taskExecutor,
37-
GitHandlerRegistry handlerRegistry) {
40+
TaskExecutor taskExecutor) {
3841
this.dialogService = dialogService;
3942
this.stateManager = stateManager;
4043
this.guiPreferences = guiPreferences;
4144
this.taskExecutor = taskExecutor;
42-
this.handlerRegistry = handlerRegistry;
45+
46+
this.executable.bind(ActionHelper.needsDatabase(stateManager).and(ActionHelper.needsGitRemoteConfigured(stateManager)));
4347
}
4448

4549
@Override
@@ -64,57 +68,87 @@ public void execute() {
6468
}
6569

6670
Path bibFilePath = bibFilePathOpt.get();
67-
GitHandler handler = new GitHandler(bibFilePath.getParent());
68-
GitConflictResolverDialog dialog = new GitConflictResolverDialog(dialogService, guiPreferences);
69-
GitConflictResolverStrategy resolver = new GuiGitConflictResolverStrategy(dialog);
70-
GitSemanticMergeExecutor mergeExecutor = new GitSemanticMergeExecutorImpl(guiPreferences.getImportFormatPreferences());
7171

72-
GitSyncService syncService = new GitSyncService(guiPreferences.getImportFormatPreferences(), handlerRegistry, resolver, mergeExecutor);
73-
GitStatusViewModel statusViewModel = new GitStatusViewModel(stateManager, bibFilePath);
74-
GitPullViewModel viewModel = new GitPullViewModel(syncService, statusViewModel);
72+
GitHandlerRegistry registry = Injector.instantiateModelOrService(GitHandlerRegistry.class);
73+
GitStatusViewModel gitStatusViewModel = GitStatusViewModel.fromPathAndContext(stateManager, taskExecutor, registry, bibFilePath);
7574

7675
BackgroundTask
77-
.wrap(() -> viewModel.pull())
76+
.wrap(() -> doPull(activeDatabase, bibFilePath, stateManager, registry))
7877
.onSuccess(result -> {
79-
if (result.isSuccessful()) {
78+
if (result.noop()) {
8079
dialogService.showInformationDialogAndWait(
8180
Localization.lang("Git Pull"),
82-
Localization.lang("Successfully merged and updated.")
83-
);
84-
} else {
85-
dialogService.showWarningDialogAndWait(
86-
Localization.lang("Git Pull"),
87-
Localization.lang("Merge completed with conflicts.")
88-
);
89-
}
90-
})
91-
.onFailure(ex -> {
92-
if (ex instanceof JabRefException e) {
93-
dialogService.showErrorDialogAndWait(
94-
Localization.lang("Git Pull Failed"),
95-
e.getLocalizedMessage(),
96-
e
97-
);
98-
} else if (ex instanceof GitAPIException e) {
99-
dialogService.showErrorDialogAndWait(
100-
Localization.lang("Git Pull Failed"),
101-
Localization.lang("An unexpected Git error occurred: %0", e.getLocalizedMessage()),
102-
e
103-
);
104-
} else if (ex instanceof IOException e) {
105-
dialogService.showErrorDialogAndWait(
106-
Localization.lang("Git Pull Failed"),
107-
Localization.lang("I/O error: %0", e.getLocalizedMessage()),
108-
e
109-
);
110-
} else {
111-
dialogService.showErrorDialogAndWait(
112-
Localization.lang("Git Pull Failed"),
113-
Localization.lang("Unexpected error: %0", ex.getLocalizedMessage()),
114-
ex
81+
Localization.lang("Already up to date.")
11582
);
83+
} else if (result.isSuccessful()) {
84+
try {
85+
replaceWithMergedEntries(result.getMergedEntries(), activeDatabase);
86+
gitStatusViewModel.refresh(bibFilePath);
87+
dialogService.showInformationDialogAndWait(
88+
Localization.lang("Git Pull"),
89+
Localization.lang("Merged and updated."));
90+
} catch (IOException | JabRefException ex) {
91+
showPullError(ex);
92+
}
11693
}
11794
})
95+
.onFailure(exception -> showPullError(exception))
11896
.executeWith(taskExecutor);
11997
}
98+
99+
private PullResult doPull(BibDatabaseContext databaseContext, Path bibPath, StateManager stateManager, GitHandlerRegistry registry) throws IOException, GitAPIException, JabRefException {
100+
GitSyncService syncService = buildSyncService(bibPath, registry);
101+
GitHandler handler = registry.get(bibPath.getParent());
102+
String user = guiPreferences.getGitPreferences().getUsername();
103+
String pat = guiPreferences.getGitPreferences().getPat();
104+
handler.setCredentials(user, pat);
105+
return syncService.fetchAndMerge(databaseContext, bibPath);
106+
}
107+
108+
private GitSyncService buildSyncService(Path bibPath, GitHandlerRegistry handlerRegistry) throws JabRefException {
109+
GitConflictResolverDialog dialog = new GitConflictResolverDialog(dialogService, guiPreferences);
110+
GitConflictResolverStrategy resolver = new GuiGitConflictResolverStrategy(dialog);
111+
GitSemanticMergeExecutor mergeExecutor = new GitSemanticMergeExecutorImpl(guiPreferences.getImportFormatPreferences());
112+
113+
return new GitSyncService(guiPreferences.getImportFormatPreferences(), handlerRegistry, resolver, mergeExecutor);
114+
}
115+
116+
private void showPullError(Throwable exception) {
117+
if (exception instanceof JabRefException e) {
118+
dialogService.showErrorDialogAndWait(
119+
Localization.lang("Git Pull Failed"),
120+
e.getLocalizedMessage(),
121+
e
122+
);
123+
} else if (exception instanceof GitAPIException e) {
124+
dialogService.showErrorDialogAndWait(
125+
Localization.lang("Git Pull Failed"),
126+
Localization.lang("An unexpected Git error occurred: %0", e.getLocalizedMessage()),
127+
e
128+
);
129+
} else if (exception instanceof IOException e) {
130+
dialogService.showErrorDialogAndWait(
131+
Localization.lang("Git Pull Failed"),
132+
Localization.lang("I/O error: %0", e.getLocalizedMessage()),
133+
e
134+
);
135+
} else {
136+
dialogService.showErrorDialogAndWait(
137+
Localization.lang("Git Pull Failed"),
138+
Localization.lang("Unexpected error: %0", exception.getLocalizedMessage()),
139+
exception
140+
);
141+
}
142+
}
143+
144+
private void replaceWithMergedEntries(List<BibEntry> mergedEntries, BibDatabaseContext databaseContext) throws IOException, JabRefException {
145+
List<BibEntry> currentEntries = List.copyOf(databaseContext.getDatabase().getEntries());
146+
for (BibEntry entry : currentEntries) {
147+
databaseContext.getDatabase().removeEntry(entry);
148+
}
149+
150+
for (BibEntry entry : mergedEntries) {
151+
databaseContext.getDatabase().insertEntry(new BibEntry(entry));
152+
}
153+
}
120154
}

jabgui/src/main/java/org/jabref/gui/git/GitPullViewModel.java

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)