165 lines
10 KiB
Go
165 lines
10 KiB
Go
package gitclone
|
||
|
||
/*
|
||
package gitclone
|
||
|
||
import (
|
||
"compress/gzip"
|
||
"ghproxy/config"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"os"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/go-git/go-billy/v5/osfs"
|
||
"github.com/go-git/go-git/v5/plumbing/format/pktline"
|
||
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
|
||
"github.com/go-git/go-git/v5/plumbing/transport"
|
||
"github.com/go-git/go-git/v5/plumbing/transport/server"
|
||
)
|
||
|
||
// MIT https://github.com/erred/gitreposerver
|
||
|
||
// httpInfoRefs 函数处理 /info/refs 请求,用于 Git 客户端获取仓库的引用信息。
|
||
// 返回一个 gin.HandlerFunc 类型的处理函数。
|
||
func HttpInfoRefs(cfg *config.Config) gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
|
||
repo := c.Param("repo") // 从 Gin 上下文中获取路由参数 "repo",即仓库名
|
||
username := c.Param("username")
|
||
repoName := repo
|
||
dir := cfg.GitClone.Dir + "/" + username + "/" + repo
|
||
url := "https://github.com/" + username + "/" + repo
|
||
|
||
// 输出 repo user dir url
|
||
logInfo("Repo: %s, User: %s, Dir: %s, Url: %s\n", repoName, username, dir, url)
|
||
|
||
_, err := os.Stat(dir) // 检查目录是否存在
|
||
if os.IsNotExist(err) {
|
||
CloneRepo(dir, repoName, url)
|
||
}
|
||
|
||
// 检查请求参数 "service" 是否为 "git-upload-pack"。
|
||
// 这是为了确保只处理 smart git 的 upload-pack 服务请求。
|
||
if c.Query("service") != "git-upload-pack" {
|
||
c.String(http.StatusForbidden, "only smart git") // 如果 service 参数不正确,返回 403 Forbidden 状态码和错误信息
|
||
log.Printf("Request to /info/refs with invalid service: %s, repo: %s\n", c.Query("service"), repoName) // 记录无效 service 参数的日志
|
||
return // 结束处理
|
||
}
|
||
|
||
c.Header("content-type", "application/x-git-upload-pack-advertisement") // 设置 HTTP 响应头的 Content-Type 为 advertisement 类型。
|
||
// 这种类型用于告知客户端服务器支持的 Git 服务。
|
||
|
||
ep, err := transport.NewEndpoint("/") // 创建一个新的传输端点 (Endpoint)。这里使用根路径 "/" 作为端点,表示本地文件系统。
|
||
if err != nil { // 检查创建端点是否出错
|
||
log.Printf("Error creating endpoint: %v, repo: %s\n", err, repoName) // 记录创建端点错误日志
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
return // 结束处理
|
||
}
|
||
|
||
bfs := osfs.New(dir) // 创建一个基于本地文件系统的 billy Filesystem (bfs)。dir 变量指定了仓库的根目录。
|
||
ld := server.NewFilesystemLoader(bfs) // 创建一个基于文件系统的仓库加载器 (Loader)。Loader 负责从文件系统中加载仓库。
|
||
svr := server.NewServer(ld) // 创建一个新的 Git 服务器 (Server)。Server 负责处理 Git 服务请求。
|
||
sess, err := svr.NewUploadPackSession(ep, nil) // 创建一个新的 upload-pack 会话 (Session)。Session 用于处理客户端的 upload-pack 请求。
|
||
if err != nil { // 检查创建会话是否出错
|
||
log.Printf("Error creating upload pack session: %v, repo: %s\n", err, repoName) // 记录创建会话错误日志
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
return // 结束处理
|
||
}
|
||
|
||
ar, err := sess.AdvertisedReferencesContext(c.Request.Context()) // 获取已通告的引用 (Advertised References)。Advertised References 包含了仓库的分支、标签等信息。
|
||
if err != nil { // 检查获取 Advertised References 是否出错
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
log.Printf("Error getting advertised references: %v, repo: %s\n", err, repoName) // 记录获取 Advertised References 错误日志
|
||
return // 结束处理
|
||
}
|
||
|
||
// 设置 Advertised References 的前缀 (Prefix)。
|
||
// Prefix 通常包含 # service=git-upload-pack 和 pktline.Flush。
|
||
// # service=git-upload-pack 用于告知客户端服务器提供的是 upload-pack 服务。
|
||
// pktline.Flush 用于在 pkt-line 格式中发送 flush-pkt。
|
||
ar.Prefix = [][]byte{
|
||
[]byte("# service=git-upload-pack"), // 服务类型声明
|
||
pktline.Flush, // pkt-line flush 信号
|
||
}
|
||
err = ar.Encode(c.Writer) // 将 Advertised References 编码并写入 HTTP 响应。使用 pkt-line 格式进行编码。
|
||
if err != nil { // 检查编码和写入是否出错
|
||
log.Printf("Error encoding advertised references: %v, repo: %s\n", err, repoName) // 记录编码错误日志
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
return // 结束处理
|
||
}
|
||
}
|
||
}
|
||
|
||
// httpGitUploadPack 函数处理 /git-upload-pack 请求,用于处理 Git 客户端的推送 (push) 操作。
|
||
// 返回一个 gin.HandlerFunc 类型的处理函数。
|
||
func HttpGitUploadPack(cfg *config.Config) gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
|
||
repo := c.Param("repo") // 从 Gin 上下文中获取路由参数 "repo",即仓库名
|
||
username := c.Param("username")
|
||
repoName := repo
|
||
dir := cfg.GitClone.Dir + "/" + username + "/" + repo
|
||
|
||
c.Header("content-type", "application/x-git-upload-pack-result") // 设置 HTTP 响应头的 Content-Type 为 result 类型。
|
||
// 这种类型用于返回 upload-pack 操作的结果。
|
||
|
||
var bodyReader io.Reader = c.Request.Body // 初始化 bodyReader 为 HTTP 请求的 body。用于读取客户端发送的数据。
|
||
// 检查请求头 "Content-Encoding" 是否为 "gzip"。
|
||
// 如果是 gzip,则需要使用 gzip 解压缩请求 body。
|
||
if c.GetHeader("Content-Encoding") == "gzip" {
|
||
gzipReader, err := gzip.NewReader(c.Request.Body) // 创建一个新的 gzip Reader,用于解压缩请求 body。
|
||
if err != nil { // 检查创建 gzip Reader 是否出错
|
||
log.Printf("Error creating gzip reader: %v, repo: %s\n", err, repoName) // 记录创建 gzip Reader 错误日志
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
return // 结束处理
|
||
}
|
||
defer gzipReader.Close() // 延迟关闭 gzip Reader,确保资源释放
|
||
bodyReader = gzipReader // 将 bodyReader 替换为 gzip Reader,后续从 gzip Reader 中读取数据
|
||
}
|
||
|
||
upr := packp.NewUploadPackRequest() // 创建一个新的 UploadPackRequest 对象。UploadPackRequest 用于解码客户端发送的 upload-pack 请求数据。
|
||
err := upr.Decode(bodyReader) // 解码请求 body 中的数据到 UploadPackRequest 对象中。使用 packp 协议格式进行解码。
|
||
if err != nil { // 检查解码是否出错
|
||
log.Printf("Error decoding upload pack request: %v, repo: %s\n", err, repoName) // 记录解码错误日志
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
return // 结束处理
|
||
}
|
||
|
||
ep, err := transport.NewEndpoint("/") // 创建一个新的传输端点 (Endpoint)。这里使用根路径 "/" 作为端点,表示本地文件系统。
|
||
if err != nil { // 检查创建端点是否出错
|
||
log.Printf("Error creating endpoint: %v, repo: %s\n", err, repoName) // 记录创建端点错误日志
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
return // 结束处理
|
||
}
|
||
|
||
bfs := osfs.New(dir) // 创建一个基于本地文件系统的 billy Filesystem (bfs)。dir 变量指定了仓库的根目录。
|
||
ld := server.NewFilesystemLoader(bfs) // 创建一个基于文件系统的仓库加载器 (Loader)。Loader 负责从文件系统中加载仓库。
|
||
svr := server.NewServer(ld) // 创建一个新的 Git 服务器 (Server)。Server 负责处理 Git 服务请求。
|
||
sess, err := svr.NewUploadPackSession(ep, nil) // 创建一个新的 upload-pack 会话 (Session)。Session 用于处理客户端的 upload-pack 请求。
|
||
if err != nil { // 检查创建会话是否出错
|
||
log.Printf("Error creating upload pack session: %v, repo: %s\n", err, repoName) // 记录创建会话错误日志
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
return // 结束处理
|
||
}
|
||
|
||
res, err := sess.UploadPack(c.Request.Context(), upr) // 处理 upload-pack 请求,执行实际的仓库推送操作。
|
||
// sess.UploadPack 函数接收 context 和 UploadPackRequest 对象作为参数,返回 UploadPackResult 和 error。
|
||
if err != nil { // 检查 UploadPack 操作是否出错
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
log.Printf("Error during upload pack: %v, repo: %s\n", err, repoName) // 记录 UploadPack 操作错误日志
|
||
return // 结束处理
|
||
}
|
||
|
||
err = res.Encode(c.Writer) // 将 UploadPackResult 编码并写入 HTTP 响应。使用 pkt-line 格式进行编码。
|
||
if err != nil { // 检查编码和写入是否出错
|
||
log.Printf("Error encoding upload pack result: %v, repo: %s\n", err, repoName) // 记录编码错误日志
|
||
c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息
|
||
return // 结束处理
|
||
}
|
||
}
|
||
}
|
||
|
||
*/
|