From e84326aaecf4850aab37861f1edf223dee2be674 Mon Sep 17 00:00:00 2001 From: John Olheiser <42128690+jolheiser@users.noreply.github.com> Date: Sun, 24 Nov 2019 11:57:52 -0600 Subject: [PATCH] Add git hooks and webhooks to template repositories; move to services (#8926) * Add git hooks and webhooks to template options Signed-off-by: jolheiser * Update models/repo.go Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Add tooltip if the user can't edit git hooks Signed-off-by: jolheiser * Close repositories after copying git hooks Signed-off-by: jolheiser * Wording Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com> * Restructure for services Signed-off-by: jolheiser * Return errors Signed-off-by: jolheiser * Move GenerateRepository to using a DBContext Signed-off-by: jolheiser * Wrap with models.WithTx Signed-off-by: jolheiser * Remove debug print Signed-off-by: jolheiser * Move if-error-delete-repo outside WithTx Signed-off-by: jolheiser * Return nil if no repo generated Signed-off-by: jolheiser --- models/repo.go | 104 +------------------ models/repo_generate.go | 162 ++++++++++++++++++++++++++++++ modules/auth/repo_form.go | 2 + options/locale/locale_en-US.ini | 3 + routers/repo/repo.go | 2 + services/repository/generate.go | 63 ++++++++++++ services/repository/repository.go | 15 --- templates/repo/create.tmpl | 8 ++ 8 files changed, 244 insertions(+), 115 deletions(-) create mode 100644 models/repo_generate.go create mode 100644 services/repository/generate.go diff --git a/models/repo.go b/models/repo.go index 1f544f1e8c..cbafe5d5a5 100644 --- a/models/repo.go +++ b/models/repo.go @@ -42,7 +42,6 @@ import ( "github.com/unknwon/com" ini "gopkg.in/ini.v1" "xorm.io/builder" - "xorm.io/xorm" ) var repoWorkingPool = sync.NewExclusivePool() @@ -1265,11 +1264,13 @@ type GenerateRepoOptions struct { Private bool GitContent bool Topics bool + GitHooks bool + Webhooks bool } // IsValid checks whether at least one option is chosen for generation func (gro GenerateRepoOptions) IsValid() bool { - return gro.GitContent || gro.Topics // or other items as they are added + return gro.GitContent || gro.Topics || gro.GitHooks || gro.Webhooks // or other items as they are added } func getRepoInitFile(tp, name string) ([]byte, error) { @@ -1483,37 +1484,6 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts C return nil } -// generateRepository initializes repository from template -func generateRepository(e Engine, repo, templateRepo *Repository) (err error) { - tmpDir := filepath.Join(os.TempDir(), "gitea-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond())) - - if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { - return fmt.Errorf("Failed to create dir %s: %v", tmpDir, err) - } - - defer func() { - if err := os.RemoveAll(tmpDir); err != nil { - log.Error("RemoveAll: %v", err) - } - }() - - if err = generateRepoCommit(e, repo, templateRepo, tmpDir); err != nil { - return fmt.Errorf("generateRepoCommit: %v", err) - } - - // re-fetch repo - if repo, err = getRepositoryByID(e, repo.ID); err != nil { - return fmt.Errorf("getRepositoryByID: %v", err) - } - - repo.DefaultBranch = "master" - if err = updateRepository(e, repo, false); err != nil { - return fmt.Errorf("updateRepository: %v", err) - } - - return nil -} - var ( reservedRepoNames = []string{".", ".."} reservedRepoPatterns = []string{"*.git", "*.wiki"} @@ -1524,7 +1494,7 @@ func IsUsableRepoName(name string) error { return isUsableName(reservedRepoNames, reservedRepoPatterns, name) } -func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err error) { +func createRepository(e Engine, doer, u *User, repo *Repository) (err error) { if err = IsUsableRepoName(repo.Name); err != nil { return err } @@ -2771,72 +2741,6 @@ func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) ( return repo, CopyLFS(repo, oldRepo) } -// GenerateRepository generates a repository from a template -func GenerateRepository(doer, owner *User, templateRepo *Repository, opts GenerateRepoOptions) (_ *Repository, err error) { - repo := &Repository{ - OwnerID: owner.ID, - Owner: owner, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - IsPrivate: opts.Private, - IsEmpty: !opts.GitContent || templateRepo.IsEmpty, - IsFsckEnabled: templateRepo.IsFsckEnabled, - TemplateID: templateRepo.ID, - } - - createSess := x.NewSession() - defer createSess.Close() - if err = createSess.Begin(); err != nil { - return nil, err - } - - if err = createRepository(createSess, doer, owner, repo); err != nil { - return nil, err - } - - //Commit repo to get created repo ID - err = createSess.Commit() - if err != nil { - return nil, err - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return repo, err - } - - repoPath := RepoPath(owner.Name, repo.Name) - if err = checkInitRepository(repoPath); err != nil { - return repo, err - } - - if opts.GitContent && !templateRepo.IsEmpty { - if err = generateRepository(sess, repo, templateRepo); err != nil { - return repo, err - } - - if err = repo.updateSize(sess); err != nil { - return repo, fmt.Errorf("failed to update size for repository: %v", err) - } - - if err = copyLFS(sess, repo, templateRepo); err != nil { - return repo, fmt.Errorf("failed to copy LFS: %v", err) - } - } - - if opts.Topics { - for _, topic := range templateRepo.Topics { - if _, err = addTopicByNameToRepo(sess, repo.ID, topic); err != nil { - return repo, err - } - } - } - - return repo, sess.Commit() -} - // GetForks returns all the forks of the repository func (repo *Repository) GetForks() ([]*Repository, error) { forks := make([]*Repository, 0, repo.NumForks) diff --git a/models/repo_generate.go b/models/repo_generate.go new file mode 100644 index 0000000000..6406180038 --- /dev/null +++ b/models/repo_generate.go @@ -0,0 +1,162 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package models + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + + "github.com/unknwon/com" +) + +// generateRepository initializes repository from template +func generateRepository(e Engine, repo, templateRepo *Repository) (err error) { + tmpDir := filepath.Join(os.TempDir(), "gitea-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond())) + + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return fmt.Errorf("Failed to create dir %s: %v", tmpDir, err) + } + + defer func() { + if err := os.RemoveAll(tmpDir); err != nil { + log.Error("RemoveAll: %v", err) + } + }() + + if err = generateRepoCommit(e, repo, templateRepo, tmpDir); err != nil { + return fmt.Errorf("generateRepoCommit: %v", err) + } + + // re-fetch repo + if repo, err = getRepositoryByID(e, repo.ID); err != nil { + return fmt.Errorf("getRepositoryByID: %v", err) + } + + repo.DefaultBranch = "master" + if err = updateRepository(e, repo, false); err != nil { + return fmt.Errorf("updateRepository: %v", err) + } + + return nil +} + +// GenerateRepository generates a repository from a template +func GenerateRepository(ctx DBContext, doer, owner *User, templateRepo *Repository, opts GenerateRepoOptions) (_ *Repository, err error) { + generateRepo := &Repository{ + OwnerID: owner.ID, + Owner: owner, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + IsPrivate: opts.Private, + IsEmpty: !opts.GitContent || templateRepo.IsEmpty, + IsFsckEnabled: templateRepo.IsFsckEnabled, + TemplateID: templateRepo.ID, + } + + if err = createRepository(ctx.e, doer, owner, generateRepo); err != nil { + return nil, err + } + + repoPath := RepoPath(owner.Name, generateRepo.Name) + if err = checkInitRepository(repoPath); err != nil { + return generateRepo, err + } + + return generateRepo, nil +} + +// GenerateGitContent generates git content from a template repository +func GenerateGitContent(ctx DBContext, templateRepo, generateRepo *Repository) error { + if err := generateRepository(ctx.e, generateRepo, templateRepo); err != nil { + return err + } + + if err := generateRepo.updateSize(ctx.e); err != nil { + return fmt.Errorf("failed to update size for repository: %v", err) + } + + if err := copyLFS(ctx.e, generateRepo, templateRepo); err != nil { + return fmt.Errorf("failed to copy LFS: %v", err) + } + return nil +} + +// GenerateTopics generates topics from a template repository +func GenerateTopics(ctx DBContext, templateRepo, generateRepo *Repository) error { + for _, topic := range templateRepo.Topics { + if _, err := addTopicByNameToRepo(ctx.e, generateRepo.ID, topic); err != nil { + return err + } + } + return nil +} + +// GenerateGitHooks generates git hooks from a template repository +func GenerateGitHooks(ctx DBContext, templateRepo, generateRepo *Repository) error { + generateGitRepo, err := git.OpenRepository(generateRepo.repoPath(ctx.e)) + if err != nil { + return err + } + defer generateGitRepo.Close() + + templateGitRepo, err := git.OpenRepository(templateRepo.repoPath(ctx.e)) + if err != nil { + return err + } + defer templateGitRepo.Close() + + templateHooks, err := templateGitRepo.Hooks() + if err != nil { + return err + } + + for _, templateHook := range templateHooks { + generateHook, err := generateGitRepo.GetHook(templateHook.Name()) + if err != nil { + return err + } + + generateHook.Content = templateHook.Content + if err := generateHook.Update(); err != nil { + return err + } + } + return nil +} + +// GenerateWebhooks generates webhooks from a template repository +func GenerateWebhooks(ctx DBContext, templateRepo, generateRepo *Repository) error { + templateWebhooks, err := GetWebhooksByRepoID(templateRepo.ID) + if err != nil { + return err + } + + for _, templateWebhook := range templateWebhooks { + generateWebhook := &Webhook{ + RepoID: generateRepo.ID, + URL: templateWebhook.URL, + HTTPMethod: templateWebhook.HTTPMethod, + ContentType: templateWebhook.ContentType, + Secret: templateWebhook.Secret, + HookEvent: templateWebhook.HookEvent, + IsActive: templateWebhook.IsActive, + HookTaskType: templateWebhook.HookTaskType, + OrgID: templateWebhook.OrgID, + Events: templateWebhook.Events, + Meta: templateWebhook.Meta, + } + if err := createWebhook(ctx.e, generateWebhook); err != nil { + return err + } + } + return nil +} diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 2602dc42eb..1ca9dd1de9 100644 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -40,6 +40,8 @@ type CreateRepoForm struct { RepoTemplate int64 GitContent bool Topics bool + GitHooks bool + Webhooks bool } // Validate validates the fields diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 60d0cfcd7e..3a209f6214 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -637,6 +637,9 @@ reactions_more = and %d more template.items = Template Items template.git_content = Git Content (Default Branch) +template.git_hooks = Git Hooks +template.git_hooks_tooltip = You are currently unable to modify or remove git hooks once added. Select this only if you trust the template repository. +template.webhooks = Webhooks template.topics = Topics template.one_item = Must select at least one template item template.invalid = Must select a template repository diff --git a/routers/repo/repo.go b/routers/repo/repo.go index e4bc6d2443..2c97216406 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -188,6 +188,8 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { Private: form.Private, GitContent: form.GitContent, Topics: form.Topics, + GitHooks: form.GitHooks, + Webhooks: form.Webhooks, } if !opts.IsValid() { diff --git a/services/repository/generate.go b/services/repository/generate.go new file mode 100644 index 0000000000..bd7fd0ce11 --- /dev/null +++ b/services/repository/generate.go @@ -0,0 +1,63 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/notification" +) + +// GenerateRepository generates a repository from a template +func GenerateRepository(doer, owner *models.User, templateRepo *models.Repository, opts models.GenerateRepoOptions) (_ *models.Repository, err error) { + var generateRepo *models.Repository + if err = models.WithTx(func(ctx models.DBContext) error { + generateRepo, err = models.GenerateRepository(ctx, doer, owner, templateRepo, opts) + if err != nil { + return err + } + + // Git Content + if opts.GitContent && !templateRepo.IsEmpty { + if err = models.GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { + return err + } + } + + // Topics + if opts.Topics { + if err = models.GenerateTopics(ctx, templateRepo, generateRepo); err != nil { + return err + } + } + + // Git Hooks + if opts.GitHooks { + if err = models.GenerateGitHooks(ctx, templateRepo, generateRepo); err != nil { + return err + } + } + + // Webhooks + if opts.Webhooks { + if err = models.GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { + return err + } + } + + return nil + }); err != nil { + if generateRepo != nil { + if errDelete := models.DeleteRepository(doer, owner.ID, generateRepo.ID); errDelete != nil { + log.Error("Rollback deleteRepository: %v", errDelete) + } + } + return nil, err + } + + notification.NotifyCreateRepository(doer, owner, generateRepo) + + return generateRepo, nil +} diff --git a/services/repository/repository.go b/services/repository/repository.go index b1156b41d5..5135435c78 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -44,21 +44,6 @@ func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc return repo, nil } -// GenerateRepository generates a repository from a template -func GenerateRepository(doer, u *models.User, oldRepo *models.Repository, opts models.GenerateRepoOptions) (*models.Repository, error) { - repo, err := models.GenerateRepository(doer, u, oldRepo, opts) - if err != nil { - if repo != nil { - if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { - log.Error("Rollback deleteRepository: %v", errDelete) - } - } - return nil, err - } - - return repo, nil -} - // DeleteRepository deletes a repository for a user or organization. func DeleteRepository(doer *models.User, repo *models.Repository) error { if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index f728a93631..69f2abb687 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -72,9 +72,17 @@ +
+ + +
+
+ + +