Skip to content

Commit 358dc6e

Browse files
committed
feat: conflict files support
close #38
1 parent 3b5987c commit 358dc6e

File tree

1 file changed

+92
-25
lines changed

1 file changed

+92
-25
lines changed

main.ts

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
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";
33
import simpleGit, { FileStatusResult, SimpleGit } from "simple-git";
44

55
enum PluginState {
@@ -39,11 +39,13 @@ export default class ObsidianGit extends Plugin {
3939
intervalID: number;
4040
lastUpdate: number;
4141
gitReady = false;
42+
conflictOutputFile = "conflict-files-obsidian-git.md";
4243

4344
setState(state: PluginState) {
4445
this.state = state;
4546
this.statusBar.display();
4647
}
48+
4749
async onload() {
4850
console.log('loading ' + this.manifest.name + " plugin");
4951
await this.loadSettings();
@@ -59,7 +61,7 @@ export default class ObsidianGit extends Plugin {
5961
this.addCommand({
6062
id: "push",
6163
name: "Commit *all* changes and push to remote repository",
62-
callback: () => this.createBackup()
64+
callback: () => this.createBackup(false)
6365
});
6466

6567
this.addCommand({
@@ -138,64 +140,91 @@ export default class ObsidianGit extends Plugin {
138140
}
139141

140142
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+
}
150155

151156
this.lastUpdate = Date.now();
152157
this.setState(PluginState.idle);
153158
}
154159

155-
async createBackup(commitMessage?: string): Promise<void> {
160+
async createBackup(fromAutoBackup: boolean, commitMessage?: string): Promise<void> {
156161
if (!this.gitReady) {
157162
await this.init();
158163
}
159164
if (!this.gitReady) return;
160165

161166
this.setState(PluginState.status);
162-
const status = await this.git.status();
167+
let status = await this.git.status();
163168

164-
const currentBranch = status.current;
165169

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;
167184

168185
if (changedFiles.length !== 0) {
169186
await this.add();
187+
status = await this.git.status();
170188
await this.commit(commitMessage);
189+
171190
this.lastUpdate = Date.now();
172-
this.displayMessage(`Committed ${changedFiles.length} files`);
191+
this.displayMessage(`Committed ${status.staged.length} files`);
173192
} else {
174193
this.displayMessage("No changes to commit");
175194
}
176195

177196
if (!this.settings.disablePush) {
178197
const trackingBranch = status.tracking;
198+
const currentBranch = status.current;
179199

180200
if (!trackingBranch) {
181201
this.displayError("Did not push. No upstream branch is set! See README for instructions", 10000);
182202
this.setState(PluginState.idle);
183203
return;
184204
}
185205

186-
const allChangedFiles = (await this.git.diffSummary([currentBranch, trackingBranch])).files;
206+
const remoteChangedFiles = (await this.git.diffSummary([currentBranch, trackingBranch])).changed;
187207

188208
// 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) {
190210
if (this.settings.pullBeforePush) {
191211
const pulledFilesLength = await this.pull();
192212
if (pulledFilesLength > 0) {
193213
this.displayMessage(`Pulled ${pulledFilesLength} files from remote`);
194214
}
195215
}
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+
}
199228
} else {
200229
this.displayMessage("No changes to push");
201230
}
@@ -251,8 +280,15 @@ export default class ObsidianGit extends Plugin {
251280
async pull(): Promise<number> {
252281
this.setState(PluginState.pull);
253282
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+
}
256292
);
257293
this.lastUpdate = Date.now();
258294
return pullResult.files.length;
@@ -263,7 +299,7 @@ export default class ObsidianGit extends Plugin {
263299
enableAutoBackup() {
264300
const minutes = this.settings.autoSaveInterval;
265301
this.intervalID = window.setInterval(
266-
async () => await this.createBackup(),
302+
async () => await this.createBackup(true),
267303
minutes * 60000
268304
);
269305
this.registerInterval(this.intervalID);
@@ -278,6 +314,37 @@ export default class ObsidianGit extends Plugin {
278314
return false;
279315
}
280316

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+
281348
// region: displaying / formatting messages
282349
displayMessage(message: string, timeout: number = 4 * 1000): void {
283350
this.statusBar.displayMessage(message.toLowerCase(), timeout);
@@ -601,7 +668,7 @@ class CustomMessageModal extends SuggestModal<string> {
601668
}
602669

603670
onChooseSuggestion(item: string, _: MouseEvent | KeyboardEvent): void {
604-
this.plugin.createBackup(item);
671+
this.plugin.createBackup(false, item);
605672
}
606673

607674
}

0 commit comments

Comments
 (0)