package push import ( "encoding/base64" "fmt" "sync" "time" "code.gitea.io/sdk/gitea" ) var ( // 全局客户端实例(单例) defaultClient *GiteaClient once sync.Once initErr error ) // GiteaClient Gitea 客户端封装 type GiteaClient struct { client *gitea.Client } // RepoConfig 仓库配置 type RepoConfig struct { Name string // 仓库名称 Description string // 仓库描述 Private bool // 是否私有,false 为公共仓库 AutoInit bool // 是否自动初始化 Gitignores string // .gitignore 模板,如 "Go", "Python" License string // 许可证,如 "MIT", "Apache-2.0" Readme string // README 模板,如 "Default" } // FileConfig 文件配置 type FileConfig struct { Path string // 文件路径,如 "main.go" Content string // 文件内容(原始字符串) Message string // 提交信息 Branch string // 分支名,为空则使用默认分支 } // TagConfig Tag 配置 type TagConfig struct { TagName string // tag 名称,如 "v1.0.0" Message string // tag 说明 } // GetClient 获取全局客户端实例(单例模式) func GetClient() (*GiteaClient, error) { //if defaultClient == nil { // once.Do(func() { // defaultClient, initErr = NewGiteaClient( // "https://gitea.cdlsxd.cn/api/v1", // "rzy_lsxd_2026", // ) // }) // if initErr != nil { // return nil, initErr // } //} return defaultClient, nil } // NewGiteaClient 创建 Gitea 客户端实例 func NewGiteaClient(url, token string) (*GiteaClient, error) { client, err := gitea.NewClient(url, gitea.SetToken(token)) if err != nil { return nil, fmt.Errorf("创建 Gitea 客户端失败: %v", err) } defaultClient = &GiteaClient{ client: client, } return defaultClient, nil } // CreateRepo 创建仓库(支持个人和组织) func (g *GiteaClient) CreateRepo(cfg RepoConfig, orgName ...string) (*gitea.Repository, error) { if g.client == nil { return nil, fmt.Errorf("客户端未初始化") } opts := gitea.CreateRepoOption{ Name: cfg.Name, Description: cfg.Description, Private: cfg.Private, AutoInit: cfg.AutoInit, Gitignores: cfg.Gitignores, License: cfg.License, //Readme: cfg.Readme, } var repo *gitea.Repository var err error // 如果传入了组织名,则为组织创建仓库 if len(orgName) > 0 && orgName[0] != "" { repo, _, err = g.client.CreateOrgRepo(orgName[0], opts) if err != nil { return nil, fmt.Errorf("为组织 %s 创建仓库失败: %v", orgName[0], err) } } else { repo, _, err = g.client.CreateRepo(opts) if err != nil { return nil, fmt.Errorf("创建仓库失败: %v", err) } } return repo, nil } // PushFile 推送单个文件 func (g *GiteaClient) PushFile(owner, repoName string, fileCfg FileConfig) error { if g.client == nil { return fmt.Errorf("客户端未初始化") } // Base64 编码文件内容 contentBase64 := base64.StdEncoding.EncodeToString([]byte(fileCfg.Content)) opts := gitea.CreateFileOptions{ FileOptions: gitea.FileOptions{ Message: fileCfg.Message, BranchName: fileCfg.Branch, }, Content: contentBase64, } _, _, err := g.client.CreateFile(owner, repoName, fileCfg.Path, opts) if err != nil { return fmt.Errorf("推送文件 %s 失败: %v", fileCfg.Path, err) } return nil } // PushFiles 批量推送多个文件 func (g *GiteaClient) PushFiles(owner, repoName string, files []FileConfig) error { if g.client == nil { return fmt.Errorf("客户端未初始化") } for _, file := range files { if err := g.PushFile(owner, repoName, file); err != nil { return err } } return nil } // CreateTag 创建标签 func (g *GiteaClient) CreateTag(owner, repoName string, tagCfg TagConfig) (*gitea.Tag, error) { if g.client == nil { return nil, fmt.Errorf("客户端未初始化") } opts := gitea.CreateTagOption{ TagName: tagCfg.TagName, Message: tagCfg.Message, } tag, _, err := g.client.CreateTag(owner, repoName, opts) if err != nil { return nil, fmt.Errorf("创建标签 %s 失败: %v", tagCfg.TagName, err) } return tag, nil } // CreateRepoAndPush 创建仓库并推送代码(完整流程) func (g *GiteaClient) CreateRepoAndPush(cfg RepoConfig, files []FileConfig, tagCfg *TagConfig, orgName ...string) error { if g.client == nil { return fmt.Errorf("客户端未初始化") } // 1. 创建仓库 repo, err := g.CreateRepo(cfg, orgName...) if err != nil { return err } // 等待仓库初始化完成 if cfg.AutoInit { fmt.Println("⏳ 等待仓库初始化...") time.Sleep(2 * time.Second) } // 2. 推送代码 if len(files) > 0 { fmt.Println("📤 开始推送文件...") if err := g.PushFiles(repo.Owner.UserName, repo.Name, files); err != nil { return err } } // 3. 创建标签(如果需要) if tagCfg != nil && tagCfg.TagName != "" { fmt.Println("🏷️ 创建标签...") if _, err := g.CreateTag(repo.Owner.UserName, repo.Name, *tagCfg); err != nil { return err } } fmt.Println("🎉 全部操作完成!") return nil } // ==================== 便捷函数(使用全局单例) ==================== // CreateRepoQuick 使用全局客户端快速创建仓库 func CreateRepoQuick(cfg RepoConfig, orgName ...string) (*gitea.Repository, error) { client, err := GetClient() if err != nil { return nil, err } return client.CreateRepo(cfg, orgName...) } // PushFileQuick 使用全局客户端快速推送文件 func PushFileQuick(owner, repoName string, fileCfg FileConfig) error { client, err := GetClient() if err != nil { return err } return client.PushFile(owner, repoName, fileCfg) } // PushFilesQuick 使用全局客户端快速批量推送文件 func PushFilesQuick(owner, repoName string, files []FileConfig) error { client, err := GetClient() if err != nil { return err } return client.PushFiles(owner, repoName, files) } // CreateTagQuick 使用全局客户端快速创建标签 func CreateTagQuick(owner, repoName string, tagCfg TagConfig) (*gitea.Tag, error) { client, err := GetClient() if err != nil { return nil, err } return client.CreateTag(owner, repoName, tagCfg) } // PushQuick 使用全局客户端快速完整推送 func PushQuick(repoName, description string, files []FileConfig, tagCfg *TagConfig, orgName ...string) error { client, err := GetClient() if err != nil { return err } cfg := RepoConfig{ Name: repoName, Description: description, Private: false, AutoInit: true, Gitignores: "Go", License: "MIT", Readme: "Default", } return client.CreateRepoAndPush(cfg, files, tagCfg, orgName...) } // ==================== 示例 ==================== // ExampleUsage 使用示例 func ExampleUsage() { // 准备文件 files := []FileConfig{ { Path: "main.go", Content: `package main\n\nimport "fmt"\n\nfunc main() {\n\tfmt.Println("Hello, Gitea!")\n}\n`, Message: "添加主程序", Branch: "main", }, { Path: "README.md", Content: `# Auto Created Repo\n\nThis repo is created by Gitea API.`, Message: "添加 README", Branch: "main", }, } tagCfg := &TagConfig{ TagName: "v1.0.0", Message: "首次发布", } // 方式1:使用快捷函数(自动使用全局单例) err := PushQuick("my-auto-repo", "通过 API 自动创建的公共仓库", files, tagCfg) if err != nil { fmt.Printf("❌ 操作失败: %v\n", err) } // 方式2:手动获取客户端实例(更灵活) client, err := GetClient() if err != nil { fmt.Printf("获取客户端失败: %v\n", err) return } // 创建私有仓库 cfg := RepoConfig{ Name: "private-repo", Description: "私有仓库", Private: true, AutoInit: true, } // 为组织创建仓库 err = client.CreateRepoAndPush(cfg, files, nil, "my-org") if err != nil { fmt.Printf("❌ 操作失败: %v\n", err) } // 方式3:单独使用各个功能 // 只创建仓库 repoCfg := RepoConfig{ Name: "test-repo", Private: false, AutoInit: true, } repo, err := CreateRepoQuick(repoCfg) if err != nil { fmt.Printf("创建仓库失败: %v\n", err) } // 只推送文件 err = PushFileQuick(repo.Owner.UserName, repo.Name, FileConfig{ Path: "test.txt", Content: "Hello World", Message: "添加测试文件", Branch: "main", }) if err != nil { fmt.Printf("推送文件失败: %v\n", err) } // 只创建标签 _, err = CreateTagQuick(repo.Owner.UserName, repo.Name, TagConfig{ TagName: "v0.1.0", Message: "测试版本", }) if err != nil { fmt.Printf("创建标签失败: %v\n", err) } }