git-sync-web 自动提交 2026-09-08 17:12:27
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// gitRun 在指定仓库目录执行 git 命令,返回合并后的输出。
|
||||
// GIT_TERMINAL_PROMPT=0 避免认证弹窗卡死后台进程。
|
||||
func gitRun(repoPath string, timeout time.Duration, args ...string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
full := append([]string{"-C", repoPath}, args...)
|
||||
cmd := exec.CommandContext(ctx, "git", full...)
|
||||
cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
|
||||
var buf bytes.Buffer
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &buf
|
||||
err := cmd.Run()
|
||||
out := strings.TrimSpace(buf.String())
|
||||
if err != nil {
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return out, fmt.Errorf("命令超时: git %s", strings.Join(args, " "))
|
||||
}
|
||||
msg := out
|
||||
if msg == "" {
|
||||
msg = err.Error()
|
||||
}
|
||||
return out, errors.New(msg)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func isRepo(path string) bool {
|
||||
if path == "" {
|
||||
return false
|
||||
}
|
||||
_, err := os.Stat(filepath.Join(path, ".git"))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func currentBranch(path string) (string, error) {
|
||||
return gitRun(path, 15*time.Second, "rev-parse", "--abbrev-ref", "HEAD")
|
||||
}
|
||||
|
||||
// aheadBehind 返回 (领先, 落后) 远程跟踪分支的提交数(需先 fetch 才准确)
|
||||
func aheadBehind(path, remote, branch string) (int, int, error) {
|
||||
out, err := gitRun(path, 30*time.Second, "rev-list", "--left-right", "--count", "HEAD..."+remote+"/"+branch)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
parts := strings.Fields(out)
|
||||
if len(parts) != 2 {
|
||||
return 0, 0, fmt.Errorf("无法解析 rev-list 输出: %q", out)
|
||||
}
|
||||
a, _ := strconv.Atoi(parts[0])
|
||||
b, _ := strconv.Atoi(parts[1])
|
||||
return a, b, nil
|
||||
}
|
||||
|
||||
// remoteRefExists 检查远程跟踪引用是否存在(远程仓库为空或分支名不一致时为 false)
|
||||
func remoteRefExists(path, remote, branch string) bool {
|
||||
_, err := gitRun(path, 15*time.Second, "rev-parse", "--verify", "-q", remote+"/"+branch)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func dirtyCount(path string) int {
|
||||
out, err := gitRun(path, 30*time.Second, "status", "--porcelain")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
n := 0
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
if strings.TrimSpace(line) != "" {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
var conflictCodes = map[string]bool{
|
||||
"DD": true, "AU": true, "UD": true, "UA": true, "DU": true, "AA": true, "UU": true,
|
||||
}
|
||||
|
||||
func conflictFiles(path string) ([]string, error) {
|
||||
out, err := gitRun(path, 30*time.Second, "status", "--porcelain")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var files []string
|
||||
seen := map[string]bool{}
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
if len(line) < 4 {
|
||||
continue
|
||||
}
|
||||
if !conflictCodes[line[:2]] {
|
||||
continue
|
||||
}
|
||||
p := strings.TrimSpace(line[3:])
|
||||
if i := strings.Index(p, "->"); i >= 0 {
|
||||
p = strings.TrimSpace(p[i+2:])
|
||||
}
|
||||
if p != "" && !seen[p] {
|
||||
seen[p] = true
|
||||
files = append(files, p)
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func abortMerge(path string) (string, error) {
|
||||
if out, err := gitRun(path, 30*time.Second, "rebase", "--abort"); err == nil {
|
||||
return out, nil
|
||||
}
|
||||
return gitRun(path, 30*time.Second, "merge", "--abort")
|
||||
}
|
||||
|
||||
// resolveDone 在用户网页上标记"已解决"后调用:暂存全部 → 继续 merge/rebase 流程
|
||||
func resolveDone(path string, rebase bool) error {
|
||||
if _, err := gitRun(path, 30*time.Second, "add", "-A"); err != nil {
|
||||
return err
|
||||
}
|
||||
if rebase {
|
||||
_, err := gitRun(path, 120*time.Second, "-c", "core.editor=true", "rebase", "--continue")
|
||||
return err
|
||||
}
|
||||
if _, err := gitRun(path, 15*time.Second, "rev-parse", "-q", "--verify", "MERGE_HEAD"); err == nil {
|
||||
_, err := gitRun(path, 120*time.Second, "commit", "--no-edit")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user