16
16
import org .jabref .logic .git .merge .MergePlan ;
17
17
import org .jabref .logic .git .merge .SemanticMerger ;
18
18
import org .jabref .logic .git .model .MergeResult ;
19
+ import org .jabref .logic .git .status .GitStatusChecker ;
20
+ import org .jabref .logic .git .status .GitStatusSnapshot ;
21
+ import org .jabref .logic .git .status .SyncStatus ;
19
22
import org .jabref .logic .importer .ImportFormatPreferences ;
20
23
import org .jabref .model .database .BibDatabaseContext ;
21
24
import org .jabref .model .entry .BibEntry ;
27
30
import org .slf4j .LoggerFactory ;
28
31
29
32
/**
30
- * Orchestrator for git sync service
33
+ * GitSyncService currently serves as an orchestrator for Git pull/push logic.
31
34
* if (hasConflict)
32
35
* → UI merge;
33
36
* else
34
37
* → autoMerge := local + remoteDiff
38
+ *
39
+ * NOTICE:
40
+ * - TODO:This class will be **deprecated** in the near future to avoid architecture violation (logic → gui)!
41
+ * - The underlying business logic will not change significantly.
42
+ * - Only the coordination responsibilities will shift to GUI/ViewModel layer.
43
+ *
44
+ * PLAN:
45
+ * - All orchestration logic (pull/push, merge, resolve, commit)
46
+ * will be **moved into corresponding ViewModels**, such as:
47
+ * - GitPullViewModel
48
+ * - GitPushViewModel
49
+ * - GitStatusViewModel
35
50
*/
36
51
public class GitSyncService {
37
52
private static final Logger LOGGER = LoggerFactory .getLogger (GitSyncService .class );
@@ -49,24 +64,42 @@ public GitSyncService(ImportFormatPreferences importFormatPreferences, GitHandle
49
64
* Called when user clicks Pull
50
65
*/
51
66
public MergeResult fetchAndMerge (Path bibFilePath ) throws GitAPIException , IOException , JabRefException {
52
- Git git = Git . open (bibFilePath . getParent (). toFile () );
67
+ GitStatusSnapshot status = GitStatusChecker . checkStatus (bibFilePath );
53
68
54
- // 1. fetch latest remote branch
55
- gitHandler .fetchOnCurrentBranch ();
56
-
57
- // 2. Locating the base / local / remote versions
58
- GitRevisionLocator locator = new GitRevisionLocator ();
59
- RevisionTriple triple = locator .locateMergeCommits (git );
69
+ if (!status .tracking ()) {
70
+ LOGGER .warn ("Pull aborted: The file is not under Git version control." );
71
+ return MergeResult .failure ();
72
+ }
60
73
61
- // 3. Calling semantic merge logic
62
- MergeResult result = performSemanticMerge (git , triple .base (), triple .local (), triple .remote (), bibFilePath );
74
+ if (status .conflict ()) {
75
+ LOGGER .warn ("Pull aborted: Local repository has unresolved merge conflicts." );
76
+ return MergeResult .failure ();
77
+ }
63
78
64
- // 4. Automatic merge
65
- if ( result . isSuccessful ()) {
66
- gitHandler . createCommitOnCurrentBranch ( "Auto-merged by JabRef" , ! AMEND );
79
+ if ( status . syncStatus () == SyncStatus . UP_TO_DATE || status . syncStatus () == SyncStatus . AHEAD ) {
80
+ LOGGER . info ( "Pull skipped: Local branch is already up to date with remote." );
81
+ return MergeResult . success ( );
67
82
}
68
83
69
- return result ;
84
+ // Status is BEHIND or DIVERGED
85
+ try (Git git = gitHandler .open ()) {
86
+ // 1. Fetch latest remote branch
87
+ gitHandler .fetchOnCurrentBranch ();
88
+
89
+ // 2. Locate base / local / remote commits
90
+ GitRevisionLocator locator = new GitRevisionLocator ();
91
+ RevisionTriple triple = locator .locateMergeCommits (git );
92
+
93
+ // 3. Perform semantic merge
94
+ MergeResult result = performSemanticMerge (git , triple .base (), triple .local (), triple .remote (), bibFilePath );
95
+
96
+ // 4. Auto-commit merge result if successful
97
+ if (result .isSuccessful ()) {
98
+ gitHandler .createCommitOnCurrentBranch ("Auto-merged by JabRef" , !AMEND );
99
+ }
100
+
101
+ return result ;
102
+ }
70
103
}
71
104
72
105
public MergeResult performSemanticMerge (Git git ,
@@ -80,7 +113,6 @@ public MergeResult performSemanticMerge(Git git,
80
113
Path relativePath ;
81
114
82
115
// TODO: Validate that the .bib file is inside the Git repository earlier in the workflow.
83
- // This check might be better placed before calling performSemanticMerge.
84
116
if (!bibPath .startsWith (workTree )) {
85
117
throw new IllegalStateException ("Given .bib file is not inside repository" );
86
118
}
@@ -122,43 +154,61 @@ public MergeResult performSemanticMerge(Git git,
122
154
return MergeResult .success ();
123
155
}
124
156
125
- // WIP
126
- public void push (Path bibFilePath ) throws GitAPIException , IOException , JabRefException {
127
- // 1. 1: Fetch remote changes
128
- gitHandler .fetchOnCurrentBranch ();
129
-
130
- // 2: Check if local branch is behind remote
131
- if (gitHandler .isBehindRemote ()) {
132
- LOGGER .info ("Remote changes detected — performing semantic merge" );
133
-
134
- // 2.1: Resolve commit identifiers
135
- RevCommit baseCommit = gitHandler .findMergeBaseWithRemote ();
136
- RevCommit localCommit = gitHandler .resolveHead ();
137
- RevCommit remoteCommit = gitHandler .resolveRemoteHead ();
138
-
139
- // 2.2: Perform semantic merge of the .bib file
140
- MergeResult mergeResult = performSemanticMerge (
141
- gitHandler .getGit (),
142
- baseCommit ,
143
- localCommit ,
144
- remoteCommit ,
145
- bibFilePath
146
- );
147
-
148
- if (!mergeResult .isSuccessful ()) {
149
- LOGGER .warn ("Semantic merge failed — aborting push" );
150
- return ;
151
- }
157
+ public void push (Path bibFilePath ) throws GitAPIException , IOException , JabRefException {
158
+ GitStatusSnapshot status = GitStatusChecker .checkStatus (bibFilePath );
159
+
160
+ if (!status .tracking ()) {
161
+ LOGGER .warn ("Push aborted: file is not tracked by Git" );
162
+ return ;
152
163
}
153
164
154
- // 3: Commit changes if there are any
155
- boolean committed = gitHandler .createCommitOnCurrentBranch ("Changes committed by JabRef" , !AMEND );
165
+ switch (status .syncStatus ()) {
166
+ case UP_TO_DATE -> {
167
+ boolean committed = gitHandler .createCommitOnCurrentBranch ("Changes committed by JabRef" , !AMEND );
168
+ if (committed ) {
169
+ gitHandler .pushCommitsToRemoteRepository ();
170
+ } else {
171
+ LOGGER .info ("No changes to commit — skipping push" );
172
+ }
173
+ }
156
174
157
- // 4: Push to remote only if a commit was made
158
- if (committed ) {
159
- gitHandler .pushCommitsToRemoteRepository ();
160
- } else {
161
- LOGGER .info ("No changes to commit — skipping push" );
175
+ case AHEAD -> {
176
+ gitHandler .pushCommitsToRemoteRepository ();
177
+ }
178
+
179
+ case BEHIND -> {
180
+ LOGGER .warn ("Push aborted: Local branch is behind remote. Please pull first." );
181
+ }
182
+
183
+ case DIVERGED -> {
184
+ try (Git git = gitHandler .open ()) {
185
+ GitRevisionLocator locator = new GitRevisionLocator ();
186
+ RevisionTriple triple = locator .locateMergeCommits (git );
187
+
188
+ MergeResult mergeResult = performSemanticMerge (git , triple .base (), triple .local (), triple .remote (), bibFilePath );
189
+
190
+ if (!mergeResult .isSuccessful ()) {
191
+ LOGGER .warn ("Semantic merge failed — aborting push" );
192
+ return ;
193
+ }
194
+
195
+ boolean committed = gitHandler .createCommitOnCurrentBranch ("Merged changes" , !AMEND );
196
+
197
+ if (committed ) {
198
+ gitHandler .pushCommitsToRemoteRepository ();
199
+ } else {
200
+ LOGGER .info ("Nothing to commit after semantic merge — skipping push" );
201
+ }
202
+ }
203
+ }
204
+
205
+ case CONFLICT -> {
206
+ LOGGER .warn ("Push aborted: Local repository has unresolved merge conflicts." );
207
+ }
208
+
209
+ case UNTRACKED , UNKNOWN -> {
210
+ LOGGER .warn ("Push aborted: Untracked or unknown Git status." );
211
+ }
162
212
}
163
213
}
164
214
}
0 commit comments