Template Repositories (#8768)

* Start work on templates

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Continue work

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix IsTemplate vs IsGenerated

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix tabs vs spaces

* Tabs vs Spaces

* Add templates to API & start adding tests

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix integration tests

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Remove unused User

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Move template tests to existing repos

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Minor re-check updates and cleanup

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* make fmt

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Test cleanup

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix optionalbool

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* make fmt

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Test fixes and icon change

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Add new user and repo for tests

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix tests (finally)

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Update meta repo with env variables

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Move generation to create page

Combine with repo create template
Modify API search to prioritize owner for repo

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix tests and coverage

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix swagger and JS lint

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix API searching for own private repos

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Change wording

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix repo search test. User had a private repo that didn't show up

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Another search test fix

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Clarify git content

Co-Authored-By: guillep2k <18600385+guillep2k@users.noreply.github.com>

* Feedback updates

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Add topics WIP

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Finish adding topics

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Update locale

Signed-off-by: jolheiser <john.olheiser@gmail.com>
This commit is contained in:
John Olheiser 2019-11-11 09:15:29 -06:00 committed by Lunny Xiao
parent 74bb292fe3
commit 74a6add4d9
58 changed files with 1441 additions and 119 deletions

View file

@ -71,6 +71,11 @@ func Search(ctx *context.APIContext) {
// description: search only for repos that the user with the given id owns or contributes to
// type: integer
// format: int64
// - name: priority_owner_id
// in: query
// description: repo owner to prioritize in the results
// type: integer
// format: int64
// - name: starredBy
// in: query
// description: search only for repos that the user with the given id has starred
@ -80,6 +85,10 @@ func Search(ctx *context.APIContext) {
// in: query
// description: include private repositories this user has access to (defaults to true)
// type: boolean
// - name: template
// in: query
// description: include template repositories this user has access to (defaults to true)
// type: boolean
// - name: page
// in: query
// description: page number of results to return (1-based)
@ -116,17 +125,23 @@ func Search(ctx *context.APIContext) {
opts := &models.SearchRepoOptions{
Keyword: strings.Trim(ctx.Query("q"), " "),
OwnerID: ctx.QueryInt64("uid"),
PriorityOwnerID: ctx.QueryInt64("priority_owner_id"),
Page: ctx.QueryInt("page"),
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
TopicOnly: ctx.QueryBool("topic"),
Collaborate: util.OptionalBoolNone,
Private: ctx.IsSigned && (ctx.Query("private") == "" || ctx.QueryBool("private")),
Template: util.OptionalBoolNone,
UserIsAdmin: ctx.IsUserSiteAdmin(),
UserID: ctx.Data["SignedUserID"].(int64),
StarredByID: ctx.QueryInt64("starredBy"),
IncludeDescription: ctx.QueryBool("includeDesc"),
}
if ctx.Query("template") != "" {
opts.Template = util.OptionalBoolOf(ctx.QueryBool("template"))
}
if ctx.QueryBool("exclusive") {
opts.Collaborate = util.OptionalBoolFalse
}
@ -678,6 +693,10 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
repo.IsPrivate = *opts.Private
}
if opts.Template != nil {
repo.IsTemplate = *opts.Template
}
if err := models.UpdateRepository(repo, visibilityChanged); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepository", err)
return err

View file

@ -51,8 +51,8 @@ var (
}
)
func getForkRepository(ctx *context.Context) *models.Repository {
forkRepo, err := models.GetRepositoryByID(ctx.ParamsInt64(":repoid"))
func getRepository(ctx *context.Context, repoID int64) *models.Repository {
repo, err := models.GetRepositoryByID(repoID)
if err != nil {
if models.IsErrRepoNotExist(err) {
ctx.NotFound("GetRepositoryByID", nil)
@ -62,25 +62,33 @@ func getForkRepository(ctx *context.Context) *models.Repository {
return nil
}
perm, err := models.GetUserRepoPermission(forkRepo, ctx.User)
perm, err := models.GetUserRepoPermission(repo, ctx.User)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil
}
if forkRepo.IsEmpty || !perm.CanRead(models.UnitTypeCode) {
if log.IsTrace() {
if forkRepo.IsEmpty {
log.Trace("Empty fork repository %-v", forkRepo)
} else {
log.Trace("Permission Denied: User %-v cannot read %-v of forkRepo %-v\n"+
"User in forkRepo has Permissions: %-+v",
ctx.User,
models.UnitTypeCode,
ctx.Repo,
perm)
}
}
if !perm.CanRead(models.UnitTypeCode) {
log.Trace("Permission Denied: User %-v cannot read %-v of repo %-v\n"+
"User in repo has Permissions: %-+v",
ctx.User,
models.UnitTypeCode,
ctx.Repo,
perm)
ctx.NotFound("getRepository", nil)
return nil
}
return repo
}
func getForkRepository(ctx *context.Context) *models.Repository {
forkRepo := getRepository(ctx, ctx.ParamsInt64(":repoid"))
if ctx.Written() {
return nil
}
if forkRepo.IsEmpty {
log.Trace("Empty repository %-v", forkRepo)
ctx.NotFound("getForkRepository", nil)
return nil
}
@ -90,7 +98,7 @@ func getForkRepository(ctx *context.Context) *models.Repository {
ctx.Data["IsPrivate"] = forkRepo.IsPrivate
canForkToUser := forkRepo.OwnerID != ctx.User.ID && !ctx.User.HasForkedRepo(forkRepo.ID)
if err = forkRepo.GetOwner(); err != nil {
if err := forkRepo.GetOwner(); err != nil {
ctx.ServerError("GetOwner", err)
return nil
}
@ -109,6 +117,7 @@ func getForkRepository(ctx *context.Context) *models.Repository {
}
var traverseParentRepo = forkRepo
var err error
for {
if ctx.User.ID == traverseParentRepo.OwnerID {
canForkToUser = false

View file

@ -129,6 +129,16 @@ func Create(ctx *context.Context) {
}
ctx.Data["ContextUser"] = ctxUser
ctx.Data["repo_template_name"] = ctx.Tr("repo.template_select")
templateID := ctx.QueryInt64("template_id")
if templateID > 0 {
templateRepo, err := models.GetRepositoryByID(templateID)
if err == nil && templateRepo.CheckUnitUser(ctxUser.ID, ctxUser.IsAdmin, models.UnitTypeCode) {
ctx.Data["repo_template"] = templateID
ctx.Data["repo_template_name"] = templateRepo.Name
}
}
ctx.HTML(200, tplCreate)
}
@ -170,20 +180,53 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
return
}
repo, err := repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
Name: form.RepoName,
Description: form.Description,
Gitignores: form.Gitignores,
IssueLabels: form.IssueLabels,
License: form.License,
Readme: form.Readme,
IsPrivate: form.Private || setting.Repository.ForcePrivate,
AutoInit: form.AutoInit,
})
if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name)
return
var err error
if form.RepoTemplate > 0 {
opts := models.GenerateRepoOptions{
Name: form.RepoName,
Description: form.Description,
Private: form.Private,
GitContent: form.GitContent,
Topics: form.Topics,
}
if !opts.IsValid() {
ctx.RenderWithErr(ctx.Tr("repo.template.one_item"), tplCreate, form)
return
}
templateRepo := getRepository(ctx, form.RepoTemplate)
if ctx.Written() {
return
}
if !templateRepo.IsTemplate {
ctx.RenderWithErr(ctx.Tr("repo.template.invalid"), tplCreate, form)
return
}
repo, err := repo_service.GenerateRepository(ctx.User, ctxUser, templateRepo, opts)
if err == nil {
log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name)
return
}
} else {
repo, err := repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
Name: form.RepoName,
Description: form.Description,
Gitignores: form.Gitignores,
IssueLabels: form.IssueLabels,
License: form.License,
Readme: form.Readme,
IsPrivate: form.Private || setting.Repository.ForcePrivate,
AutoInit: form.AutoInit,
})
if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
ctx.Redirect(setting.AppSubURL + "/" + ctxUser.Name + "/" + repo.Name)
return
}
}
handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form)

View file

@ -101,6 +101,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
repo.LowerName = strings.ToLower(newRepoName)
repo.Description = form.Description
repo.Website = form.Website
repo.IsTemplate = form.Template
// Visibility of forked repository is forced sync with base repository.
if repo.IsFork {