git-sync-web 自动提交 2026-09-09 15:39:21
This commit is contained in:
@@ -28,6 +28,7 @@ type RepoConfig struct {
|
||||
ConflictStrategy string `json:"conflict_strategy"`
|
||||
PushStrategy string `json:"push_strategy"`
|
||||
AutoCommit bool `json:"auto_commit"`
|
||||
AutoSync bool `json:"auto_sync"`
|
||||
Cron string `json:"cron"`
|
||||
}
|
||||
|
||||
@@ -109,6 +110,8 @@ func loadRepoConfig(dir, name string) (*RepoConfig, error) {
|
||||
c.ConflictStrategy = orDefault(m["CONFLICT_STRATEGY"], "merge")
|
||||
c.PushStrategy = orDefault(m["PUSH_STRATEGY"], "manual")
|
||||
c.AutoCommit = strings.EqualFold(m["AUTO_COMMIT"], "1") || strings.EqualFold(m["AUTO_COMMIT"], "true")
|
||||
// 自动同步默认开启;仅在显式配置 0 / false 时关闭(关闭后不再自动触发,但手动同步仍可用)
|
||||
c.AutoSync = !(strings.EqualFold(m["AUTO_SYNC"], "0") || strings.EqualFold(m["AUTO_SYNC"], "false"))
|
||||
c.Cron = strings.TrimSpace(m["CRON"])
|
||||
if remotes := strings.TrimSpace(m["REMOTES"]); remotes != "" {
|
||||
for _, pair := range strings.Split(remotes, ",") {
|
||||
@@ -155,6 +158,11 @@ func saveRepoConfig(dir string, c *RepoConfig) error {
|
||||
ac = "1"
|
||||
}
|
||||
fmt.Fprintf(&b, "AUTO_COMMIT=%s\n", q(ac))
|
||||
as := "0"
|
||||
if c.AutoSync {
|
||||
as = "1"
|
||||
}
|
||||
fmt.Fprintf(&b, "AUTO_SYNC=%s\n", q(as))
|
||||
fmt.Fprintf(&b, "CRON=%s\n", q(c.Cron))
|
||||
return os.WriteFile(filepath.Join(dir, c.Name+".conf"), []byte(b.String()), 0o644)
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ type RepoSnapshot struct {
|
||||
PushStrategy string `json:"push_strategy"`
|
||||
LogFile string `json:"log_file"`
|
||||
AutoCommit bool `json:"auto_commit"`
|
||||
AutoSync bool `json:"auto_sync"`
|
||||
Cron string `json:"cron"`
|
||||
Remotes []Remote `json:"remotes"`
|
||||
Running bool `json:"running"`
|
||||
@@ -273,6 +274,7 @@ func (s *repoState) snapshot(live bool) RepoSnapshot {
|
||||
PushStrategy: cfg.PushStrategy,
|
||||
LogFile: cfg.LogFile,
|
||||
AutoCommit: cfg.AutoCommit,
|
||||
AutoSync: cfg.AutoSync,
|
||||
Cron: cfg.Cron,
|
||||
Remotes: cfg.Remotes,
|
||||
Running: s.running,
|
||||
@@ -350,7 +352,15 @@ func (e *Engine) loop(s *repoState, stopCh chan struct{}) {
|
||||
s.mu.Lock()
|
||||
interval := s.cfg.Interval
|
||||
cronStr := s.cfg.Cron
|
||||
autoSync := s.cfg.AutoSync
|
||||
s.mu.Unlock()
|
||||
// 自动同步关闭:不再自动触发,仅保留监控与手动同步能力
|
||||
if !autoSync {
|
||||
e.logf(s, "info", "已关闭自动同步,仅响应手动同步")
|
||||
e.pushStatus(s)
|
||||
<-stopCh
|
||||
return
|
||||
}
|
||||
if interval < 5 {
|
||||
interval = 5
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ function card(r) {
|
||||
${r.cron ? `<span class="badge" title="${esc(r.cron)}">cron</span>` : ''}
|
||||
${r.conflicts && r.conflicts.length ? `<span class="badge b-conflict">冲突 ${r.conflicts.length}</span>` : ''}
|
||||
${r.push_conflict ? `<span class="badge b-conflict">推送冲突</span>` : ''}
|
||||
${r.running && r.auto_sync === false ? `<span class="badge b-paused">手动模式</span>` : ''}
|
||||
${r.running && r.last_sync && isStale(r.last_sync.time) ? '<span class="badge b-stale">久未同步</span>' : ''}
|
||||
</div>
|
||||
<div class="last" title="${r.last_sync ? esc(r.last_sync.time) : ''}">上次同步:${esc(last)}</div>
|
||||
@@ -1024,6 +1025,7 @@ function fillForm(r) {
|
||||
$('#f-retry').value = r ? r.retry_count : 3;
|
||||
$('#f-retry-interval').value = r ? r.retry_interval : 5;
|
||||
$('#f-autocommit').checked = r ? !!r.auto_commit : false;
|
||||
$('#f-autosync').checked = r ? (r.auto_sync !== false) : true;
|
||||
$('#f-cron').value = r ? (r.cron || '') : '';
|
||||
$('#f-log').value = r ? (r.log_file || '') : '';
|
||||
previewCron();
|
||||
@@ -1050,6 +1052,7 @@ function readForm() {
|
||||
conflict_strategy: $('#f-conflict').value,
|
||||
push_strategy: $('#f-push').value,
|
||||
auto_commit: $('#f-autocommit').checked,
|
||||
auto_sync: $('#f-autosync').checked,
|
||||
cron: $('#f-cron').value.trim(),
|
||||
log_file: $('#f-log').value.trim(),
|
||||
};
|
||||
|
||||
+8
-4
@@ -128,13 +128,13 @@
|
||||
<button class="btn btn-sm" id="btn-push" data-page-node-id="qTA9vsiTSuWz4dwilu7Prf">推送</button>
|
||||
</div>
|
||||
<div class="conflict-actions" id="pull-conflict-actions" data-page-node-id="u1mYEBVApFghZDqR2RFg7V">
|
||||
<button class="btn btn-danger" id="btn-abort" data-page-node-id="iFRU44FOA3NXieww4PmbAR">放弃合并</button>
|
||||
<button class="btn btn-primary" id="btn-resolved" data-page-node-id="C4SMNurpXaOJ8jByGjS2zg">我已在本地解决,继续</button>
|
||||
<button class="btn btn-sm btn-danger" id="btn-abort" data-page-node-id="iFRU44FOA3NXieww4PmbAR">放弃合并</button>
|
||||
<button class="btn btn-sm btn-primary" id="btn-resolved" data-page-node-id="C4SMNurpXaOJ8jByGjS2zg">我已在本地解决,继续</button>
|
||||
</div>
|
||||
<div class="conflict-actions" id="push-conflict-actions" hidden data-page-node-id="C2kukXRbyT5HAgGznQBEOF">
|
||||
<span class="hint" id="push-conflict-hint" data-page-node-id="meqqutN8Y8BlHoIUOsDuc3"></span>
|
||||
<button class="btn btn-danger" id="btn-force-push" data-page-node-id="20FtTVRLlB3HrObTPUgS7I">强制推送(--force-with-lease)</button>
|
||||
<button class="btn" id="btn-abort-push" data-page-node-id="kr5vGtshzwTYewuRpjenau">放弃强制推送</button>
|
||||
<button class="btn btn-sm btn-danger" id="btn-force-push" data-page-node-id="20FtTVRLlB3HrObTPUgS7I">强制推送(--force-with-lease)</button>
|
||||
<button class="btn btn-sm" id="btn-abort-push" data-page-node-id="kr5vGtshzwTYewuRpjenau">放弃强制推送</button>
|
||||
</div>
|
||||
<div id="conflict-list" data-page-node-id="5cossjGefHTRs2BQmv7ZiE"></div>
|
||||
</div>
|
||||
@@ -197,6 +197,10 @@
|
||||
<input type="checkbox" id="f-autocommit" data-page-node-id="5ADMjJXNR5EGPDmhwiCBlm">
|
||||
<span data-page-node-id="z0tpoI6jpAbJCEwUJ4cSxO">自动提交模式:检测到未提交改动时自动 commit,并随同步推送到远程</span>
|
||||
</label>
|
||||
<label class="full check-row">
|
||||
<input type="checkbox" id="f-autosync">
|
||||
<span>自动同步:按间隔 / Cron 自动拉取推送。关闭后仅响应「立即同步」手动触发,不再自动运行</span>
|
||||
</label>
|
||||
<label class="full" data-page-node-id="tei066TFL8N9sqInjysFvD">Cron 表达式(可选,5 段:分 时 日 月 周;填写后优先于固定间隔)
|
||||
<input name="cron" id="f-cron" placeholder="*/30 * * * * 表示每 30 分钟,留空使用固定间隔" data-page-node-id="6W3Ua8pPoGLuaNh7rGcDOw">
|
||||
<span id="cron-next" class="cron-next" data-page-node-id="WgXuBnZPqGWvOBGRxzIVCn"></span>
|
||||
|
||||
+10
-8
@@ -23,21 +23,21 @@ header { display: flex; justify-content: space-between; align-items: center; pad
|
||||
.btn-danger:hover { background: var(--red-bg); }
|
||||
.btn-icon { border: none; background: none; font-size: 18px; cursor: pointer; color: var(--muted); }
|
||||
|
||||
main { max-width: 1320px; margin: 24px auto; padding: 0 24px; }
|
||||
.layout { display: flex; gap: 20px; align-items: flex-start; }
|
||||
main { max-width: 1320px; margin: 18px auto; padding: 0 18px; }
|
||||
.layout { display: flex; gap: 16px; align-items: flex-start; }
|
||||
.main-col { flex: 1; min-width: 0; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
|
||||
.empty { text-align: center; color: var(--muted); padding: 80px 0; }
|
||||
|
||||
/* 全局实时活动面板 */
|
||||
.live { width: 360px; flex: none; position: sticky; top: 80px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); display: flex; flex-direction: column; max-height: calc(100vh - 104px); }
|
||||
.live { width: 460px; flex: none; position: sticky; top: 78px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); display: flex; flex-direction: column; max-height: calc(100vh - 100px); }
|
||||
.live-head { display: flex; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--border); }
|
||||
.live-head h2 { margin: 0; font-size: 15px; font-weight: 500; flex: 1; }
|
||||
.live-head .conn { margin: 0; }
|
||||
.btn-sm { border: 1px solid var(--border); background: #fff; border-radius: 7px; padding: 3px 10px; font-size: 12px; cursor: pointer; color: var(--muted); }
|
||||
.btn-sm:hover { border-color: #b6bcc4; background: #f3f4f6; }
|
||||
.live-hint { font-size: 11px; color: var(--muted); padding: 8px 14px; border-bottom: 1px solid var(--border); }
|
||||
.live-log { flex: 1; overflow-y: auto; padding: 8px 10px; font-family: Consolas, "Cascadia Code", monospace; font-size: 11.5px; line-height: 1.5; }
|
||||
.live-log { flex: 1; overflow-y: auto; padding: 8px 10px; font-family: Consolas, "Cascadia Code", monospace; font-size: 12px; line-height: 1.45; }
|
||||
.live-line { display: flex; gap: 6px; padding: 2px 0; border-bottom: 1px solid #f2f2f2; }
|
||||
.live-line .ll-time { color: #9aa0a6; flex: none; }
|
||||
.live-line .ll-repo { color: var(--blue); flex: none; font-weight: 600; max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
@@ -53,7 +53,7 @@ main { max-width: 1320px; margin: 24px auto; padding: 0 24px; }
|
||||
.live { width: 100%; position: static; max-height: 360px; }
|
||||
}
|
||||
|
||||
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; display: flex; flex-direction: column; gap: 8px; }
|
||||
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; display: flex; flex-direction: column; gap: 6px; }
|
||||
.card.st-conflict { border-color: #e8c98a; background: #fffdf6; }
|
||||
.card.st-error { border-color: #eec9c9; }
|
||||
.card-head { display: flex; align-items: center; gap: 8px; }
|
||||
@@ -89,7 +89,8 @@ main { max-width: 1320px; margin: 24px auto; padding: 0 24px; }
|
||||
.tab-pane[hidden] { display: none; }
|
||||
|
||||
.logbox { margin: 0; background: #f6f7f9; border: 1px solid var(--border); padding: 12px; border-radius: 8px; font: 12px/1.5 Consolas, monospace; white-space: pre-wrap; word-break: break-all; max-height: 55vh; overflow: auto; }
|
||||
.conflict-actions { display: flex; gap: 8px; margin-bottom: 12px; }
|
||||
.conflict-actions { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
|
||||
.manual-actions { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
|
||||
.conflict-file { margin-bottom: 14px; }
|
||||
.conflict-file h4 { margin: 0 0 6px; font-size: 13px; font-family: Consolas, monospace; }
|
||||
.conflict-file pre { margin: 0; background: #f6f7f9; border: 1px solid var(--border); border-radius: 8px; padding: 10px; font: 12px/1.5 Consolas, monospace; white-space: pre-wrap; word-break: break-all; max-height: 300px; overflow: auto; }
|
||||
@@ -201,6 +202,7 @@ html.dark .file-editor { background: #12151a; }
|
||||
|
||||
/* ---------- 卡片:久未同步 ---------- */
|
||||
.badge.b-stale { background: var(--amber-bg); color: var(--amber); }
|
||||
.badge.b-paused { background: var(--gray-bg); color: var(--muted); }
|
||||
|
||||
/* ---------- 配置:cron 下次执行预览 ---------- */
|
||||
.cron-next { font-size: 12px; color: var(--muted); min-height: 16px; }
|
||||
@@ -244,7 +246,7 @@ html.dark .file-editor { background: #12151a; }
|
||||
.cf-head h4 { margin: 0; flex: 1; }
|
||||
.cf-ta { width: 100%; min-height: 260px; border: 1px solid var(--border); border-radius: 8px; padding: 10px; font: 12.5px/1.55 Consolas, monospace; color: var(--text); background: #fff; white-space: pre; overflow: auto; }
|
||||
html.dark .cf-ta { background: #12151a; }
|
||||
.cf-actions { display: flex; gap: 8px; margin-top: 6px; }
|
||||
.cf-actions { display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
|
||||
.cf-hint { font-size: 12px; color: var(--muted); margin-top: 6px; line-height: 1.5; }
|
||||
.cf-edit { margin-bottom: 10px; }
|
||||
.conflict-file { border: 1px solid var(--border); border-radius: 8px; padding: 10px; margin-bottom: 10px; }
|
||||
|
||||
Reference in New Issue
Block a user