1
1
import { spawnSync } from "child_process" ;
2
- import { FileSystemAdapter , FuzzySuggestModal , Notice , Plugin , PluginSettingTab , Setting , SuggestModal } from "obsidian" ;
2
+ import { FileSystemAdapter , FuzzySuggestModal , Notice , Plugin , PluginSettingTab , Setting , SuggestModal , TFile } from "obsidian" ;
3
3
import simpleGit , { FileStatusResult , SimpleGit } from "simple-git" ;
4
4
5
5
enum PluginState {
@@ -39,11 +39,13 @@ export default class ObsidianGit extends Plugin {
39
39
intervalID : number ;
40
40
lastUpdate : number ;
41
41
gitReady = false ;
42
+ conflictOutputFile = "conflict-files-obsidian-git.md" ;
42
43
43
44
setState ( state : PluginState ) {
44
45
this . state = state ;
45
46
this . statusBar . display ( ) ;
46
47
}
48
+
47
49
async onload ( ) {
48
50
console . log ( 'loading ' + this . manifest . name + " plugin" ) ;
49
51
await this . loadSettings ( ) ;
@@ -59,7 +61,7 @@ export default class ObsidianGit extends Plugin {
59
61
this . addCommand ( {
60
62
id : "push" ,
61
63
name : "Commit *all* changes and push to remote repository" ,
62
- callback : ( ) => this . createBackup ( )
64
+ callback : ( ) => this . createBackup ( false )
63
65
} ) ;
64
66
65
67
this . addCommand ( {
@@ -138,64 +140,91 @@ export default class ObsidianGit extends Plugin {
138
140
}
139
141
140
142
if ( ! this . gitReady ) return ;
141
- await this . pull ( ) . then ( ( filesUpdated ) => {
142
- if ( filesUpdated > 0 ) {
143
- this . displayMessage (
144
- `Pulled new changes. ${ filesUpdated } files updated`
145
- ) ;
146
- } else {
147
- this . displayMessage ( "Everything is up-to-date" ) ;
148
- }
149
- } ) ;
143
+
144
+ const filesUpdated = await this . pull ( ) ;
145
+ if ( filesUpdated > 0 ) {
146
+ this . displayMessage ( `Pulled new changes. ${ filesUpdated } files updated` ) ;
147
+ } else {
148
+ this . displayMessage ( "Everything is up-to-date" ) ;
149
+ }
150
+
151
+ const status = await this . git . status ( ) ;
152
+ if ( status . conflicted . length > 0 ) {
153
+ this . displayError ( `You have ${ status . conflicted . length } conflict files` ) ;
154
+ }
150
155
151
156
this . lastUpdate = Date . now ( ) ;
152
157
this . setState ( PluginState . idle ) ;
153
158
}
154
159
155
- async createBackup ( commitMessage ?: string ) : Promise < void > {
160
+ async createBackup ( fromAutoBackup : boolean , commitMessage ?: string ) : Promise < void > {
156
161
if ( ! this . gitReady ) {
157
162
await this . init ( ) ;
158
163
}
159
164
if ( ! this . gitReady ) return ;
160
165
161
166
this . setState ( PluginState . status ) ;
162
- const status = await this . git . status ( ) ;
167
+ let status = await this . git . status ( ) ;
163
168
164
- const currentBranch = status . current ;
165
169
166
- const changedFiles = status . files ;
170
+ if ( ! fromAutoBackup ) {
171
+ const file = this . app . vault . getAbstractFileByPath ( this . conflictOutputFile ) ;
172
+ await this . app . vault . delete ( file ) ;
173
+ }
174
+
175
+ // check for conflict files on auto backup
176
+ if ( fromAutoBackup && status . conflicted . length > 0 ) {
177
+ this . setState ( PluginState . idle ) ;
178
+ this . displayError ( `Did not commit, because you have ${ status . conflicted . length } conflict files. Please resolve them and commit per command.` ) ;
179
+ this . handleConflict ( status . conflicted ) ;
180
+ return ;
181
+ }
182
+
183
+ const changedFiles = ( await this . git . status ( ) ) . files ;
167
184
168
185
if ( changedFiles . length !== 0 ) {
169
186
await this . add ( ) ;
187
+ status = await this . git . status ( ) ;
170
188
await this . commit ( commitMessage ) ;
189
+
171
190
this . lastUpdate = Date . now ( ) ;
172
- this . displayMessage ( `Committed ${ changedFiles . length } files` ) ;
191
+ this . displayMessage ( `Committed ${ status . staged . length } files` ) ;
173
192
} else {
174
193
this . displayMessage ( "No changes to commit" ) ;
175
194
}
176
195
177
196
if ( ! this . settings . disablePush ) {
178
197
const trackingBranch = status . tracking ;
198
+ const currentBranch = status . current ;
179
199
180
200
if ( ! trackingBranch ) {
181
201
this . displayError ( "Did not push. No upstream branch is set! See README for instructions" , 10000 ) ;
182
202
this . setState ( PluginState . idle ) ;
183
203
return ;
184
204
}
185
205
186
- const allChangedFiles = ( await this . git . diffSummary ( [ currentBranch , trackingBranch ] ) ) . files ;
206
+ const remoteChangedFiles = ( await this . git . diffSummary ( [ currentBranch , trackingBranch ] ) ) . changed ;
187
207
188
208
// Prevent plugin to pull/push at every call of createBackup. Only if unpushed commits are present
189
- if ( allChangedFiles . length > 0 ) {
209
+ if ( remoteChangedFiles > 0 ) {
190
210
if ( this . settings . pullBeforePush ) {
191
211
const pulledFilesLength = await this . pull ( ) ;
192
212
if ( pulledFilesLength > 0 ) {
193
213
this . displayMessage ( `Pulled ${ pulledFilesLength } files from remote` ) ;
194
214
}
195
215
}
196
- await this . push ( ) ;
197
- this . lastUpdate = Date . now ( ) ;
198
- this . displayMessage ( `Pushed ${ allChangedFiles . length } files to remote` ) ;
216
+
217
+ // Refresh because of pull
218
+ status = await this . git . status ( ) ;
219
+
220
+ if ( status . conflicted . length > 0 ) {
221
+ this . displayError ( `Cannot push. You have ${ status . conflicted . length } conflict files` ) ;
222
+ } else {
223
+ const remoteChangedFiles = ( await this . git . diffSummary ( [ currentBranch , trackingBranch ] ) ) . changed ;
224
+
225
+ await this . push ( ) ;
226
+ this . displayMessage ( `Pushed ${ remoteChangedFiles } files to remote` ) ;
227
+ }
199
228
} else {
200
229
this . displayMessage ( "No changes to push" ) ;
201
230
}
@@ -251,8 +280,15 @@ export default class ObsidianGit extends Plugin {
251
280
async pull ( ) : Promise < number > {
252
281
this . setState ( PluginState . pull ) ;
253
282
const pullResult = await this . git . pull ( [ "--no-rebase" ] ,
254
- ( err : Error | null ) =>
255
- err && this . displayError ( `Pull failed ${ err . message } ` )
283
+ async ( err : Error | null ) => {
284
+ if ( err ) {
285
+ this . displayError ( `Pull failed ${ err . message } ` ) ;
286
+ const status = await this . git . status ( ) ;
287
+ if ( status . conflicted . length >= 0 ) {
288
+ this . handleConflict ( status . conflicted ) ;
289
+ }
290
+ }
291
+ }
256
292
) ;
257
293
this . lastUpdate = Date . now ( ) ;
258
294
return pullResult . files . length ;
@@ -263,7 +299,7 @@ export default class ObsidianGit extends Plugin {
263
299
enableAutoBackup ( ) {
264
300
const minutes = this . settings . autoSaveInterval ;
265
301
this . intervalID = window . setInterval (
266
- async ( ) => await this . createBackup ( ) ,
302
+ async ( ) => await this . createBackup ( true ) ,
267
303
minutes * 60000
268
304
) ;
269
305
this . registerInterval ( this . intervalID ) ;
@@ -278,6 +314,37 @@ export default class ObsidianGit extends Plugin {
278
314
return false ;
279
315
}
280
316
317
+ async handleConflict ( conflicted : string [ ] ) : Promise < void > {
318
+ const lines = [
319
+ "# Conflict files" ,
320
+ "Please resolve them and commit per command (This file will be deleted before the commit)." ,
321
+ ...conflicted . map ( e => {
322
+ const file = this . app . vault . getAbstractFileByPath ( e ) ;
323
+ if ( file instanceof TFile ) {
324
+ const link = this . app . metadataCache . fileToLinktext ( file , "/" ) ;
325
+ return `- [[${ link } ]]` ;
326
+ } else {
327
+ return `- Not a file: ${ e } ` ;
328
+ }
329
+ } )
330
+ ] ;
331
+ this . writeAndOpenFile ( lines . join ( "\n" ) ) ;
332
+ }
333
+
334
+ async writeAndOpenFile ( text : string ) {
335
+ await this . app . vault . adapter . write ( this . conflictOutputFile , text ) ;
336
+
337
+ let fileIsAlreadyOpened = false ;
338
+ this . app . workspace . iterateAllLeaves ( leaf => {
339
+ if ( leaf . getDisplayText ( ) != "" && this . conflictOutputFile . startsWith ( leaf . getDisplayText ( ) ) ) {
340
+ fileIsAlreadyOpened = true ;
341
+ }
342
+ } ) ;
343
+ if ( ! fileIsAlreadyOpened ) {
344
+ this . app . workspace . openLinkText ( this . conflictOutputFile , "/" , true ) ;
345
+ }
346
+ }
347
+
281
348
// region: displaying / formatting messages
282
349
displayMessage ( message : string , timeout : number = 4 * 1000 ) : void {
283
350
this . statusBar . displayMessage ( message . toLowerCase ( ) , timeout ) ;
@@ -601,7 +668,7 @@ class CustomMessageModal extends SuggestModal<string> {
601
668
}
602
669
603
670
onChooseSuggestion ( item : string , _ : MouseEvent | KeyboardEvent ) : void {
604
- this . plugin . createBackup ( item ) ;
671
+ this . plugin . createBackup ( false , item ) ;
605
672
}
606
673
607
674
}
0 commit comments