From 68512a67c8b383c593d4e119a965391dcd7d3ca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=9F=E5=AE=81?= Date: Sat, 9 Aug 2025 07:30:07 +0000 Subject: [PATCH 1/3] =?UTF-8?q?!93=20add=20DevStar=E6=A8=A1=E6=9D=BF=20in?= =?UTF-8?q?=20/repo/create=20*=20=E5=87=8F=E5=B0=91=E4=B8=8D=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E6=9B=B4=E6=94=B9=EF=BC=8C=E6=81=A2=E5=A4=8DGenerateG?= =?UTF-8?q?itContent=20*=20=E6=B8=85=E7=90=86=E4=B8=80=E4=BA=9B=E4=B8=8D?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84=E6=A0=BC=E5=BC=8F=E5=B7=AE=E5=BC=82?= =?UTF-8?q?=20*=20add=20DevStar=E6=A8=A1=E6=9D=BF=20in=20/repo/create?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/web/repo/repo.go | 127 +++++++++++++++++- services/forms/repo_form.go | 2 + services/repository/create.go | 2 +- services/repository/generate.go | 78 ++++++++++- services/repository/init.go | 6 +- services/repository/template.go | 14 ++ templates/repo/create.tmpl | 21 ++- web_src/js/features/repo-template.ts | 187 +++++++++++++++++++++++++++ web_src/js/index-domready.ts | 2 + 9 files changed, 429 insertions(+), 10 deletions(-) create mode 100644 web_src/js/features/repo-template.ts diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 828ec08a8a..11aa820e67 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net/http" + "regexp" "slices" "strings" @@ -169,13 +170,14 @@ func Create(ctx *context.Context) { ctx.Data["private"] = getRepoPrivate(ctx) ctx.Data["default_branch"] = setting.Repository.DefaultBranch ctx.Data["repo_template_name"] = ctx.Tr("repo.template_select") - + ctx.Data["devstar_template_name"] = ctx.Tr("repo.template_select") templateID := ctx.FormInt64("template_id") if templateID > 0 { templateRepo, err := repo_model.GetRepositoryByID(ctx, templateID) if err == nil && access_model.CheckRepoUnitUser(ctx, templateRepo, ctxUser, unit.TypeCode) { ctx.Data["repo_template"] = templateID - ctx.Data["repo_template_name"] = templateRepo.Name + ctx.Data["repo_template_name"] = templateRepo.FullName + ctx.Data["devstar_template_name"] = false } } @@ -214,6 +216,79 @@ func handleCreateError(ctx *context.Context, owner *user_model.User, err error, } } +// isValidCommitUrl checks whether a given URL is a valid Git URL with a commit reference. +func isValidCommitUrl(url string) bool { + // 正则表达式用于匹配包含 commit 的 Git URL + regex := `^https?:\/\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)(\/commit\/([a-f0-9]{40}))?$` + + // 编译正则表达式 + pattern, err := regexp.Compile(regex) + if err != nil { + fmt.Println("Invalid regex for commit URL:", err) + return false + } + + // 测试 URL 是否匹配正则表达式 + return pattern.MatchString(url) +} + +// isValidGitPath checks whether a given URL is a valid Git URL ending with .git. +func isValidGitPath(url string) bool { + // 正则表达式用于匹配以 .git 结尾的 Git URL + regexWithGit := `^https?:\/\/([a-zA-Z0-9.-]+)\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+)\.git$` + + // 编译正则表达式 + pattern, err := regexp.Compile(regexWithGit) + if err != nil { + fmt.Println("Invalid regex for .git URL:", err) + return false + } + + // 测试 URL 是否匹配正则表达式 + return pattern.MatchString(url) +} + +// ExtractCommitID 从给定的 URL 中提取 commitID +func ExtractCommitID(url string) (string, error) { + // 正则表达式匹配 commit URL 中的 commitID + regex := `^https?:\/\/[a-zA-Z0-9.-]+\/[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+\/commit\/([a-f0-9]{40})$` + + // 编译正则表达式 + commitPattern, err := regexp.Compile(regex) + if err != nil { + return "", err + } + + // 尝试匹配 URL + if commitPattern.MatchString(url) { + // 提取 commitID + matches := commitPattern.FindStringSubmatch(url) + if len(matches) >= 2 { + return matches[1], nil // 返回 commitID + } + } + + // 如果匹配失败,返回错误 + return "", fmt.Errorf("URL does not match expected format: %s", url) +} + +// ReplaceCommitWithGit 将 commit URL 中的 /commit/ 替换为 .git +func ReplaceCommitWithGit(url string) (string, error) { + // 定义正则表达式匹配 URL 中的 /commit/ 部分 + regex := `(/commit/[a-f0-9]{40})$` + + // 编译正则表达式 + commitPattern, err := regexp.Compile(regex) + if err != nil { + return "", err + } + + // 替换为 .git + replacedURL := commitPattern.ReplaceAllString(url, ".git") + + return replacedURL, nil +} + // CreatePost response for creating repository func CreatePost(ctx *context.Context) { createCommon(ctx) @@ -292,6 +367,54 @@ func CreatePost(ctx *context.Context) { }) if err == nil { log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) + + if strings.TrimSpace(repo.DefaultBranch) == "" { + repo.DefaultBranch = setting.Repository.DefaultBranch + } + + if isValidGitPath(form.DevstarTemplate) { + log.Info("init repo from %s", form.DevstarTemplate) + repo, err = repo_service.InitRepoFromGitURL(ctx, ctx.Doer, ctxUser, form.DevstarTemplate, "", repo) + if err != nil { + log.Error("Error initializing repo from DevstarTemplate: %v", err) + return + } + if repo == nil { + log.Error("repo is nil after initializing from DevstarTemplate") + } + } + + if isValidGitPath(form.GitUrlTemplate) { + log.Info("init repo from %s", form.GitUrlTemplate) + repo, err = repo_service.InitRepoFromGitURL(ctx, ctx.Doer, ctxUser, form.GitUrlTemplate, "", repo) + if err != nil { + log.Error("Error initializing repo from GitUrlTemplate: %v", err) + return + } + if repo == nil { + log.Error("repo is nil after initializing from GitUrlTemplate") + } + } + + if isValidCommitUrl(form.GitUrlTemplate) { + log.Info("init repo from %s", form.GitUrlTemplate) + commitID, err := ExtractCommitID(form.GitUrlTemplate) + if err != nil { + log.Error("Error:", err) + } + gitURL, err := ReplaceCommitWithGit(form.GitUrlTemplate) + if err != nil { + log.Error("Error:", err) + } + repo, err = repo_service.InitRepoFromGitURL(ctx, ctx.Doer, ctxUser, gitURL, commitID, repo) + if err != nil { + log.Error("Error initializing repo from GitUrlTemplate (commit): %v", err) + return + } + if repo == nil { + log.Error("repo is nil after initializing from GitUrlTemplate (commit)") + } + } ctx.Redirect(repo.Link()) return } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index cb267f891c..dd89d6618b 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -32,6 +32,8 @@ type CreateRepoForm struct { Readme string Template bool + DevstarTemplate string + GitUrlTemplate string RepoTemplate int64 GitContent bool Topics bool diff --git a/services/repository/create.go b/services/repository/create.go index bed02e5d7e..75fec0a453 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -160,7 +160,7 @@ func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Re } // Apply changes and commit. - if err = initRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch); err != nil { + if err = initRepoCommit(ctx, tmpDir, repo, u, opts.DefaultBranch, "Initial commit"); err != nil { return fmt.Errorf("initRepoCommit: %w", err) } } diff --git a/services/repository/generate.go b/services/repository/generate.go index 867b5d7855..ece4b6d17e 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -9,6 +9,7 @@ import ( "context" "fmt" "os" + "path" "path/filepath" "regexp" "strconv" @@ -250,7 +251,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r defaultBranch = templateRepo.DefaultBranch } - return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch) + return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch, "Initial commit") } // GenerateGitContent generates git content from a template repository @@ -292,6 +293,81 @@ func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_mo return nil } +// GenerateGitContent generates git content from a Git URL +func GenerateGitContentFromGitURL(ctx context.Context, gitURL, commitID string, repo *repo_model.Repository) error { + tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) + if err != nil { + return fmt.Errorf("Failed to create temp dir for repository %s: %w", gitURL, err) + } + + defer func() { + if err := util.RemoveAll(tmpDir); err != nil { + log.Error("RemoveAll: %v", err) + } + }() + // generateRepoCommit + commitTimeStr := time.Now().Format(time.RFC3339) + authorSig := repo.Owner.NewGitSig() + + // Because this may call hooks we should pass in the environment + env := append(os.Environ(), + "GIT_AUTHOR_NAME="+authorSig.Name, + "GIT_AUTHOR_EMAIL="+authorSig.Email, + "GIT_AUTHOR_DATE="+commitTimeStr, + "GIT_COMMITTER_NAME="+authorSig.Name, + "GIT_COMMITTER_EMAIL="+authorSig.Email, + "GIT_COMMITTER_DATE="+commitTimeStr, + ) + + // Clone to temporary path with a specified depth. + if err := git.Clone(ctx, gitURL, tmpDir, git.CloneRepoOptions{ + Depth: 1, // 仍然可以保持深度克隆以优化性能 + }); err != nil { + return fmt.Errorf("git clone: %w", err) + } + // Change to the specified commit ID after cloning + if commitID != "" { + if err := git.NewCommand("checkout").AddDynamicArguments(commitID).Run(ctx, &git.RunOpts{Dir: tmpDir}); err != nil { + return fmt.Errorf("git checkout %s: %w", commitID, err) + } + } + // Get the current SHA1 version of the working directory + var sha1Buffer bytes.Buffer + if err := git.NewCommand("rev-parse", "HEAD").Run(ctx, &git.RunOpts{ + Dir: tmpDir, + Stdout: &sha1Buffer, // 使用 bytes.Buffer + }); err != nil { + return fmt.Errorf("git rev-parse HEAD: %w", err) + } + // 获取SHA1字符串并修整空白 + sha1 := strings.TrimSpace(sha1Buffer.String()) + + if err := util.RemoveAll(path.Join(tmpDir, ".git")); err != nil { + return fmt.Errorf("remove git dir: %w", err) + } + + if err := git.InitRepository(ctx, tmpDir, false, repo.ObjectFormatName); err != nil { + return err + } + + repoPath := repo.RepoPath() + if stdout, _, err := git.NewCommand("remote", "add", "origin").AddDynamicArguments(repoPath). + //SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", gitURL, tmpDir)). + RunStdString(ctx, &git.RunOpts{Dir: tmpDir, Env: env}); err != nil { + log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err) + return fmt.Errorf("git remote add: %w", err) + } + commitMessage := "Initial commit from " + gitURL + " ( " + sha1 + " ) " + initRepoCommit(ctx, tmpDir, repo, repo.Owner, repo.DefaultBranch, commitMessage) + + if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { + return fmt.Errorf("failed to update size for repository: %w", err) + } + + log.Info("GenerateGitContentFromGitURL init repo from %s : %s", gitURL, commitID) + return nil +} + // GenerateRepoOptions contains the template units to generate type GenerateRepoOptions struct { Name string diff --git a/services/repository/init.go b/services/repository/init.go index 1eeeb4aa4f..72f7c6c1b1 100644 --- a/services/repository/init.go +++ b/services/repository/init.go @@ -19,9 +19,9 @@ import ( ) // initRepoCommit temporarily changes with work directory. -func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) { +func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string, commitMessage string) (err error) { commitTimeStr := time.Now().Format(time.RFC3339) - + commitMsg := "--message=" + commitMessage sig := u.NewGitSig() // Because this may call hooks we should pass in the environment env := append(os.Environ(), @@ -39,7 +39,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi return fmt.Errorf("git add --all: %w", err) } - cmd := git.NewCommand("commit", "--message=Initial commit"). + cmd := git.NewCommand("commit").AddOptionFormat(commitMsg). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email) sign, key, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) diff --git a/services/repository/template.go b/services/repository/template.go index 6906a60083..9c462bc528 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -192,3 +192,17 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ return generateRepo, nil } + +// InitRepoFromGitURL for a repository from a Git URL +func InitRepoFromGitURL(ctx context.Context, doer, owner *user_model.User, gitURL, commitID string, repo *repo_model.Repository) (_ *repo_model.Repository, err error) { + if !doer.IsAdmin { + return nil, nil + } + + err = GenerateGitContentFromGitURL(ctx, gitURL, commitID, repo) + if err != nil { + return nil, err + } + + return repo, nil +} diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index ada7e0c092..55f7ca2f32 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -61,17 +61,31 @@ + + {{if .devstar_template_name}} +
+ + + {{ctx.Locale.Tr "repo.devstar_template_desc" "https://DevStar.cn/"}} +
+ {{end}} + +
-
+
@@ -113,8 +127,9 @@
+
-
+
+
+ + +
+
diff --git a/templates/shared/actions/runner_list.tmpl b/templates/shared/actions/runner_list.tmpl index 43321a8dc5..9b2b90874c 100644 --- a/templates/shared/actions/runner_list.tmpl +++ b/templates/shared/actions/runner_list.tmpl @@ -9,6 +9,9 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}}