Skip to content

Commit e5a0e8a

Browse files
committed
Add commit and push on close option
1 parent 436e10d commit e5a0e8a

File tree

5 files changed

+103
-8
lines changed

5 files changed

+103
-8
lines changed

main.js

Lines changed: 9 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/git.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { execFile } from "child_process";
1+
import { execFile, execFileSync } from "child_process";
22
import { promises as fs } from "fs";
33
import * as path from "path";
44

@@ -8,6 +8,15 @@ interface GitRunOptions {
88
args: string[];
99
}
1010

11+
function runGitSync({ cwd, gitPath, args }: GitRunOptions): string {
12+
return execFileSync(gitPath, args, {
13+
cwd,
14+
windowsHide: true,
15+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
16+
encoding: "utf8",
17+
});
18+
}
19+
1120
function runGit({ cwd, gitPath, args }: GitRunOptions): Promise<string> {
1221
return new Promise((resolve, reject) => {
1322
execFile(
@@ -390,3 +399,33 @@ async function getCurrentBranch(cwd: string, gitPath: string): Promise<string> {
390399
const stdout = await runGit({ cwd, gitPath, args: ["rev-parse", "--abbrev-ref", "HEAD"] });
391400
return stdout.trim();
392401
}
402+
403+
// Synchronous version for use during app close
404+
export function getChangedFilesSync(cwd: string, gitPath: string): string[] {
405+
try {
406+
const stdout = runGitSync({ cwd, gitPath, args: ["status", "--porcelain=v1", "-z"] });
407+
if (!stdout) return [];
408+
const parts = stdout.split("\0").filter(Boolean);
409+
const files: string[] = [];
410+
for (let i = 0; i < parts.length; i++) {
411+
const entry = parts[i];
412+
const statusCode = entry.slice(0, 2);
413+
const filePath = entry.slice(3);
414+
if (filePath) files.push(filePath);
415+
if (statusCode.startsWith("R") || statusCode.startsWith("C")) i++;
416+
}
417+
return [...new Set(files)];
418+
} catch {
419+
return [];
420+
}
421+
}
422+
423+
export function commitAndPushSync(cwd: string, gitPath: string, message: string): void {
424+
try {
425+
runGitSync({ cwd, gitPath, args: ["add", "-A"] });
426+
runGitSync({ cwd, gitPath, args: ["commit", "-m", message] });
427+
runGitSync({ cwd, gitPath, args: ["push"] });
428+
} catch {
429+
// Ignore errors during close
430+
}
431+
}

src/i18n.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ type Translations = {
1717
autoPullOnOpenName: string;
1818
autoPullOnOpenDesc: string;
1919

20+
commitOnCloseName: string;
21+
commitOnCloseDesc: string;
22+
2023
templateName: string;
2124
templateDesc: string;
2225

@@ -130,6 +133,9 @@ const en: Translations = {
130133
autoPullOnOpenName: "Auto pull on open",
131134
autoPullOnOpenDesc: "Pull from remote when Obsidian opens.",
132135

136+
commitOnCloseName: "Commit and push on close",
137+
commitOnCloseDesc: "Commit all changes and push when Obsidian closes. Note: This may cause a brief delay when closing.",
138+
133139
templateName: "Commit message template",
134140
templateDesc: "Variables: {{date}}, {{time}}, {{files}}, {{count}}",
135141

@@ -239,6 +245,9 @@ const zhCN: Translations = {
239245
autoPullOnOpenName: "打开时自动拉取",
240246
autoPullOnOpenDesc: "打开 Obsidian 时自动从远程仓库拉取。",
241247

248+
commitOnCloseName: "关闭时提交并推送",
249+
commitOnCloseDesc: "关闭 Obsidian 时自动提交所有更改并推送。注意:这可能导致关闭时短暂卡顿。",
250+
242251
templateName: "提交消息模板",
243252
templateDesc: "变量: {{date}}, {{time}}, {{files}}, {{count}}",
244253

src/main.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EventRef, Menu, Notice, Platform, Plugin, TAbstractFile, TFile, FileSystemAdapter } from "obsidian";
22
import { AutoGitSettings, AutoGitSettingTab, DEFAULT_SETTINGS } from "./settings";
3-
import { getChangedFiles, commitAll, push, pull, getFileStatuses, getConflictFiles, markConflictsResolved, revertAll, FileStatus } from "./git";
3+
import { getChangedFiles, commitAll, push, pull, getFileStatuses, getConflictFiles, markConflictsResolved, revertAll, FileStatus, getChangedFilesSync, commitAndPushSync } from "./git";
44
import { renderTemplate } from "./template";
55
import { t } from "./i18n";
66

@@ -17,6 +17,7 @@ export default class AutoGitPlugin extends Plugin {
1717
private _hasConflicts = false;
1818
private resolveConflictCommand: { id: string } | null = null;
1919
private ribbonIconEl: HTMLElement | null = null;
20+
private beforeUnloadHandler: (() => void) | null = null;
2021

2122
async onload() {
2223
await this.loadSettings();
@@ -61,6 +62,33 @@ export default class AutoGitPlugin extends Plugin {
6162
window.setTimeout(() => { void this.doPull(); }, 1000);
6263
}
6364

65+
// Setup beforeunload handler for commit on close
66+
if (!Platform.isMobileApp) {
67+
this.beforeUnloadHandler = () => {
68+
if (this.settings.commitOnClose) {
69+
const cwd = this.getVaultPathSafe();
70+
if (cwd) {
71+
const changedFiles = getChangedFilesSync(cwd, this.settings.gitPath);
72+
if (changedFiles.length > 0) {
73+
const now = new Date();
74+
const subject = renderTemplate(this.settings.commitTemplate, {
75+
date: now.toISOString().slice(0, 10),
76+
time: now.toTimeString().slice(0, 8),
77+
files: changedFiles.slice(0, 5).join(", ") + (changedFiles.length > 5 ? "..." : ""),
78+
count: String(changedFiles.length),
79+
});
80+
let message = subject;
81+
if (this.settings.includeFileList) {
82+
message += "\n\n" + changedFiles.join("\n");
83+
}
84+
commitAndPushSync(cwd, this.settings.gitPath, message);
85+
}
86+
}
87+
}
88+
};
89+
window.addEventListener("beforeunload", this.beforeUnloadHandler);
90+
}
91+
6492
// Check for existing conflicts on load
6593
void this.checkConflicts();
6694
}
@@ -76,6 +104,10 @@ export default class AutoGitPlugin extends Plugin {
76104
this.ribbonIconEl.remove();
77105
this.ribbonIconEl = null;
78106
}
107+
if (this.beforeUnloadHandler) {
108+
window.removeEventListener("beforeunload", this.beforeUnloadHandler);
109+
this.beforeUnloadHandler = null;
110+
}
79111
}
80112

81113
async loadSettings() {

src/settings.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface AutoGitSettings {
1010
includeFileList: boolean;
1111
autoPush: boolean;
1212
autoPullOnOpen: boolean;
13+
commitOnClose: boolean;
1314
gitPath: string;
1415
ignoreObsidianDir: boolean;
1516
showStatusBadge: boolean;
@@ -23,6 +24,7 @@ export const DEFAULT_SETTINGS: AutoGitSettings = {
2324
includeFileList: true,
2425
autoPush: false,
2526
autoPullOnOpen: false,
27+
commitOnClose: false,
2628
gitPath: "git",
2729
ignoreObsidianDir: true,
2830
showStatusBadge: true,
@@ -70,6 +72,16 @@ export class AutoGitSettingTab extends PluginSettingTab {
7072
})
7173
);
7274

75+
new Setting(containerEl)
76+
.setName(i18n.commitOnCloseName)
77+
.setDesc(i18n.commitOnCloseDesc)
78+
.addToggle((toggle) =>
79+
toggle.setValue(this.plugin.settings.commitOnClose).onChange(async (value) => {
80+
this.plugin.settings.commitOnClose = value;
81+
await this.plugin.saveSettings();
82+
})
83+
);
84+
7385
// Container for auto-commit related settings (created after toggle for correct order)
7486
let autoCommitSettings: HTMLDivElement;
7587

0 commit comments

Comments
 (0)