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)
|
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.
|
// CloneRepo handles POST /api/repos with SSE progress events.
|
||||||
func (h *ReposHandler) CloneRepo(c *gin.Context) {
|
func (h *ReposHandler) CloneRepo(c *gin.Context) {
|
||||||
user := GetCurrentUser(c)
|
user := GetCurrentUser(c)
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ func main() {
|
|||||||
r.POST("/api/repos", authMw, repos.CloneRepo)
|
r.POST("/api/repos", authMw, repos.CloneRepo)
|
||||||
r.DELETE("/api/repos/:id", authMw, repos.DeleteRepo)
|
r.DELETE("/api/repos/:id", authMw, repos.DeleteRepo)
|
||||||
r.POST("/api/repos/:id/cleanup", authMw, repos.CleanupRepos)
|
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/graph", authMw, repos.GetGraph)
|
||||||
r.GET("/api/repos/:id/refs", authMw, repos.GetRefs)
|
r.GET("/api/repos/:id/refs", authMw, repos.GetRefs)
|
||||||
r.GET("/api/repos/:id/commits", authMw, repos.GetCommits)
|
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)
|
return GetCommitLog(repo, branchName, maxCommits)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchRemote fetches latest changes for a repo.
|
// PullRepo fetches and merges latest changes from origin into the current branch.
|
||||||
func FetchRemote(repoPath string) error {
|
func PullRepo(repoPath string) (*CloneResult, error) {
|
||||||
repo, err := OpenRepo(repoPath)
|
repo, err := OpenRepo(repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
remote, err := repo.Remote("origin")
|
|
||||||
|
w, err := repo.Worktree()
|
||||||
if err != nil {
|
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.
|
// 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>
|
<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>
|
<p class="text-xs text-gray-400 mt-1">最后使用: {{.LastUsed}}</p>
|
||||||
</div>
|
</div>
|
||||||
<button hx-delete="/api/repos/{{.ID}}" hx-confirm="确定删除此仓库缓存?"
|
<div class="flex gap-2">
|
||||||
hx-swap="none" hx-on::after-request="window.location.reload()"
|
<button hx-post="/api/repos/{{.ID}}/pull" hx-swap="none"
|
||||||
class="text-xs text-red-500 hover:text-red-700">删除</button>
|
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>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user