feat: add git pull to update cached repositories from remote
Deploy PR-Helper / deploy (push) Successful in 34s
Deploy PR-Helper / deploy (push) Successful in 34s
- Replace dead FetchRemote with PullRepo (fetch + merge) in services/git.go - Add POST /api/repos/:id/pull endpoint with DB size/last_used update - Add '更新' button next to each cached repo in the index page
This commit is contained in:
@@ -140,6 +140,41 @@ func (h *ReposHandler) CleanupRepos(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
// PullRepo handles POST /api/repos/:id/pull — fetches and merges latest changes.
|
||||
func (h *ReposHandler) PullRepo(c *gin.Context) {
|
||||
user := GetCurrentUser(c)
|
||||
if user == nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthenticated"})
|
||||
return
|
||||
}
|
||||
|
||||
id := c.Param("id")
|
||||
var localPath string
|
||||
err := h.db.QueryRow(`SELECT local_path FROM repositories WHERE id = ? AND user_id = ?`, id, user.ID).Scan(&localPath)
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "repository not found"})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
result, err := services.PullRepo(localPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
h.db.Exec(`UPDATE repositories SET size_bytes = ?, last_used = NOW() WHERE id = ?`, result.SizeBytes, id)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"ok": true,
|
||||
"repo_id": id,
|
||||
"size_bytes": result.SizeBytes,
|
||||
})
|
||||
}
|
||||
|
||||
// CloneRepo handles POST /api/repos with SSE progress events.
|
||||
func (h *ReposHandler) CloneRepo(c *gin.Context) {
|
||||
user := GetCurrentUser(c)
|
||||
|
||||
@@ -114,6 +114,7 @@ func main() {
|
||||
r.POST("/api/repos", authMw, repos.CloneRepo)
|
||||
r.DELETE("/api/repos/:id", authMw, repos.DeleteRepo)
|
||||
r.POST("/api/repos/:id/cleanup", authMw, repos.CleanupRepos)
|
||||
r.POST("/api/repos/:id/pull", authMw, repos.PullRepo)
|
||||
r.GET("/api/repos/:id/graph", authMw, repos.GetGraph)
|
||||
r.GET("/api/repos/:id/refs", authMw, repos.GetRefs)
|
||||
r.GET("/api/repos/:id/commits", authMw, repos.GetCommits)
|
||||
|
||||
+40
-8
@@ -441,19 +441,51 @@ func GetBranchCommits(repoPath, branchName string, maxCommits int) ([]CommitInfo
|
||||
return GetCommitLog(repo, branchName, maxCommits)
|
||||
}
|
||||
|
||||
// FetchRemote fetches latest changes for a repo.
|
||||
func FetchRemote(repoPath string) error {
|
||||
// PullRepo fetches and merges latest changes from origin into the current branch.
|
||||
func PullRepo(repoPath string) (*CloneResult, error) {
|
||||
repo, err := OpenRepo(repoPath)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
remote, err := repo.Remote("origin")
|
||||
|
||||
w, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, fmt.Errorf("worktree: %w", err)
|
||||
}
|
||||
return remote.Fetch(&git.FetchOptions{
|
||||
Force: true,
|
||||
})
|
||||
|
||||
err = w.Pull(&git.PullOptions{})
|
||||
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||
return nil, fmt.Errorf("git pull: %w", err)
|
||||
}
|
||||
|
||||
result := &CloneResult{RepoPath: repoPath}
|
||||
|
||||
iter, err := repo.CommitObjects()
|
||||
if err == nil {
|
||||
_ = iter.ForEach(func(c *object.Commit) error {
|
||||
result.CommitNum++
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
branches, err := repo.Branches()
|
||||
if err == nil {
|
||||
_ = branches.ForEach(func(ref *plumbing.Reference) error {
|
||||
result.Branches = append(result.Branches, ref.Name().Short())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
tags, err := repo.Tags()
|
||||
if err == nil {
|
||||
_ = tags.ForEach(func(ref *plumbing.Reference) error {
|
||||
result.Tags = append(result.Tags, ref.Name().Short())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
result.SizeBytes = dirSize(repoPath)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// dirSize returns the total size of files in a directory.
|
||||
|
||||
@@ -52,9 +52,15 @@
|
||||
<a href="/repo/{{.ID}}" class="text-sm font-medium text-indigo-600 hover:text-indigo-800">{{.URL}}</a>
|
||||
<p class="text-xs text-gray-400 mt-1">最后使用: {{.LastUsed}}</p>
|
||||
</div>
|
||||
<button hx-delete="/api/repos/{{.ID}}" hx-confirm="确定删除此仓库缓存?"
|
||||
hx-swap="none" hx-on::after-request="window.location.reload()"
|
||||
class="text-xs text-red-500 hover:text-red-700">删除</button>
|
||||
<div class="flex gap-2">
|
||||
<button hx-post="/api/repos/{{.ID}}/pull" hx-swap="none"
|
||||
hx-on::before-request="this.disabled=true;this.textContent='更新中...'"
|
||||
hx-on::after-request="window.location.reload()"
|
||||
class="text-xs text-indigo-500 hover:text-indigo-700">更新</button>
|
||||
<button hx-delete="/api/repos/{{.ID}}" hx-confirm="确定删除此仓库缓存?"
|
||||
hx-swap="none" hx-on::after-request="window.location.reload()"
|
||||
class="text-xs text-red-500 hover:text-red-700">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user