diff --git a/integrations/pull_update_test.go b/integrations/pull_update_test.go
index 484390001..251051582 100644
--- a/integrations/pull_update_test.go
+++ b/integrations/pull_update_test.go
@@ -58,7 +58,7 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullReq
assert.NoError(t, err)
assert.NotEmpty(t, baseRepo)
- headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc")
+ headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc", "")
assert.NoError(t, err)
assert.NotEmpty(t, headRepo)
diff --git a/models/action.go b/models/action.go
index 003dc1b20..4821910db 100755
--- a/models/action.go
+++ b/models/action.go
@@ -164,12 +164,24 @@ func (a *Action) GetRepoName() string {
return a.Repo.Name
}
+// GetRepoName returns the name of the action repository.
+func (a *Action) GetRepoDisplayName() string {
+ a.loadRepo()
+ return a.Repo.DisplayName()
+}
+
// ShortRepoName returns the name of the action repository
// trimmed to max 33 chars.
func (a *Action) ShortRepoName() string {
return base.EllipsisString(a.GetRepoName(), 33)
}
+// ShortRepoName returns the name of the action repository
+// trimmed to max 33 chars.
+func (a *Action) ShortRepoDisplayName() string {
+ return base.EllipsisString(a.GetRepoDisplayName(), 33)
+}
+
// GetRepoPath returns the virtual path to the action repository.
func (a *Action) GetRepoPath() string {
return path.Join(a.GetRepoUserName(), a.GetRepoName())
@@ -181,6 +193,12 @@ func (a *Action) ShortRepoPath() string {
return path.Join(a.ShortRepoUserName(), a.ShortRepoName())
}
+// ShortRepoPath returns the virtual path to the action repository
+// trimmed to max 20 + 1 + 33 chars.
+func (a *Action) ShortRepoFullDisplayName() string {
+ return path.Join(a.ShortRepoUserName(), a.ShortRepoDisplayName())
+}
+
// GetRepoLink returns relative link to action repository.
func (a *Action) GetRepoLink() string {
if len(setting.AppSubURL) > 0 {
diff --git a/models/repo.go b/models/repo.go
index f393b51b2..93e2cb140 100755
--- a/models/repo.go
+++ b/models/repo.go
@@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"html/template"
+ "math/rand"
"xorm.io/xorm"
"code.gitea.io/gitea/modules/blockchain"
@@ -139,6 +140,7 @@ func NewRepoContext() {
// RepositoryStatus defines the status of repository
type RepositoryStatus int
type RepoBlockChainStatus int
+type RepoType int
// all kinds of RepositoryStatus
const (
@@ -152,6 +154,11 @@ const (
RepoBlockChainFailed
)
+const (
+ RepoNormal RepoType = iota
+ RepoCourse
+)
+
// Repository represents a git repository.
type Repository struct {
ID int64 `xorm:"pk autoincr"`
@@ -165,7 +172,8 @@ type Repository struct {
OriginalServiceType api.GitServiceType `xorm:"index"`
OriginalURL string `xorm:"VARCHAR(2048)"`
DefaultBranch string
-
+ CreatorID int64 `xorm:"INDEX NOT NULL DEFAULT 0"`
+ Creator *User `xorm:"-"`
NumWatches int
NumStars int
NumForks int
@@ -174,11 +182,12 @@ type Repository struct {
NumOpenIssues int `xorm:"-"`
NumPulls int
NumClosedPulls int
- NumOpenPulls int `xorm:"-"`
- NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
- NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
- NumOpenMilestones int `xorm:"-"`
- NumCommit int64 `xorm:"NOT NULL DEFAULT 0"`
+ NumOpenPulls int `xorm:"-"`
+ NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
+ NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
+ NumOpenMilestones int `xorm:"-"`
+ NumCommit int64 `xorm:"NOT NULL DEFAULT 0"`
+ RepoType RepoType `xorm:"NOT NULL DEFAULT 0"`
IsPrivate bool `xorm:"INDEX"`
IsEmpty bool `xorm:"INDEX"`
@@ -223,6 +232,7 @@ type Repository struct {
Hot int64 `xorm:"-"`
Active int64 `xorm:"-"`
+ Alias string
}
// SanitizedOriginalURL returns a sanitized OriginalURL
@@ -233,6 +243,14 @@ func (repo *Repository) SanitizedOriginalURL() string {
return util.SanitizeURLCredentials(repo.OriginalURL, false)
}
+// GetAlias returns a sanitized OriginalURL
+func (repo *Repository) DisplayName() string {
+ if repo.Alias == "" {
+ return repo.Name
+ }
+ return repo.Alias
+}
+
// ColorFormat returns a colored string to represent this repo
func (repo *Repository) ColorFormat(s fmt.State) {
var ownerName interface{}
@@ -286,6 +304,11 @@ func (repo *Repository) FullName() string {
return repo.OwnerName + "/" + repo.Name
}
+// FullDisplayName returns the repository full display name
+func (repo *Repository) FullDisplayName() string {
+ return repo.OwnerName + "/" + repo.DisplayName()
+}
+
// HTMLURL returns the repository HTML URL
func (repo *Repository) HTMLURL() string {
return setting.AppURL + repo.FullName()
@@ -386,6 +409,7 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool)
Owner: repo.Owner.APIFormat(),
Name: repo.Name,
FullName: repo.FullName(),
+ FullDisplayName: repo.FullDisplayName(),
Description: repo.Description,
Private: repo.IsPrivate,
Template: repo.IsTemplate,
@@ -921,17 +945,23 @@ func (repo *Repository) DescriptionHTML() template.HTML {
return template.HTML(markup.Sanitize(string(desc)))
}
-func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) {
- has, err := e.Get(&Repository{
- OwnerID: u.ID,
- LowerName: strings.ToLower(repoName),
- })
- return has && com.IsDir(RepoPath(u.Name, repoName)), err
+func isRepositoryExist(e Engine, u *User, repoName string, alias string) (bool, error) {
+ var cond = builder.NewCond()
+ cond = cond.And(builder.Eq{"owner_id": u.ID})
+ if alias != "" {
+ subCon := builder.NewCond()
+ subCon = subCon.Or(builder.Eq{"alias": alias}, builder.Eq{"lower_name": repoName})
+ cond = cond.And(subCon)
+ } else {
+ cond = cond.And(builder.Eq{"lower_name": repoName})
+ }
+ count, err := e.Where(cond).Count(&Repository{})
+ return count > 0 || com.IsDir(RepoPath(u.Name, repoName)), err
}
// IsRepositoryExist returns true if the repository with given name under user has already existed.
-func IsRepositoryExist(u *User, repoName string) (bool, error) {
- return isRepositoryExist(x, u, repoName)
+func IsRepositoryExist(u *User, repoName string, alias string) (bool, error) {
+ return isRepositoryExist(x, u, repoName, alias)
}
// CloneLink represents different types of clone URLs of repository.
@@ -975,20 +1005,24 @@ func (repo *Repository) CloneLink() (cl *CloneLink) {
}
// CheckCreateRepository check if could created a repository
-func CheckCreateRepository(doer, u *User, name string) error {
+func CheckCreateRepository(doer, u *User, repoName, alias string) error {
if !doer.CanCreateRepo() {
return ErrReachLimitOfRepo{u.MaxRepoCreation}
}
- if err := IsUsableRepoName(name); err != nil {
+ if err := IsUsableRepoName(repoName); err != nil {
return err
}
- has, err := isRepositoryExist(x, u, name)
+ if err := IsUsableRepoAlias(alias); err != nil {
+ return err
+ }
+
+ has, err := isRepositoryExist(x, u, repoName, alias)
if err != nil {
return fmt.Errorf("IsRepositoryExist: %v", err)
} else if has {
- return ErrRepoAlreadyExist{u.Name, name}
+ return ErrRepoAlreadyExist{u.Name, repoName}
}
return nil
}
@@ -996,6 +1030,7 @@ func CheckCreateRepository(doer, u *User, name string) error {
// CreateRepoOptions contains the create repository options
type CreateRepoOptions struct {
Name string
+ Alias string
Description string
OriginalURL string
GitServiceType api.GitServiceType
@@ -1008,6 +1043,8 @@ type CreateRepoOptions struct {
IsMirror bool
AutoInit bool
Status RepositoryStatus
+ IsCourse bool
+ Topics []string
}
// GetRepoInitFile returns repository init files
@@ -1036,8 +1073,10 @@ func GetRepoInitFile(tp, name string) ([]byte, error) {
}
var (
- reservedRepoNames = []string{".", ".."}
- reservedRepoPatterns = []string{"*.git", "*.wiki"}
+ reservedRepoNames = []string{".", ".."}
+ reservedRepoPatterns = []string{"*.git", "*.wiki"}
+ reservedRepoAliasNames = []string{}
+ reservedRepoAliasPatterns = []string{}
)
// IsUsableRepoName returns true when repository is usable
@@ -1045,19 +1084,30 @@ func IsUsableRepoName(name string) error {
return isUsableName(reservedRepoNames, reservedRepoPatterns, name)
}
+// IsUsableRepoAlias returns true when repository alias is usable
+func IsUsableRepoAlias(name string) error {
+ return isUsableName(reservedRepoAliasNames, reservedRepoAliasPatterns, name)
+}
+
// CreateRepository creates a repository for the user/organization.
-func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error) {
+func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, opts ...CreateRepoOptions) (err error) {
if err = IsUsableRepoName(repo.Name); err != nil {
return err
}
- has, err := isRepositoryExist(ctx.e, u, repo.Name)
+ if err := IsUsableRepoAlias(repo.Alias); err != nil {
+ return err
+ }
+ has, err := isRepositoryExist(ctx.e, u, repo.Name, repo.Alias)
if err != nil {
return fmt.Errorf("IsRepositoryExist: %v", err)
} else if has {
return ErrRepoAlreadyExist{u.Name, repo.Name}
}
-
+ isCourse := isCourse(opts)
+ if isCourse {
+ repo.CreatorID = doer.ID
+ }
if _, err = ctx.e.Insert(repo); err != nil {
return err
}
@@ -1091,17 +1141,23 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true},
})
} else if tp == UnitTypeDatasets {
- units = append(units, RepoUnit{
- RepoID: repo.ID,
- Type: tp,
- Config: &DatasetConfig{EnableDataset: true},
- })
+ if !isCourse {
+ units = append(units, RepoUnit{
+ RepoID: repo.ID,
+ Type: tp,
+ Config: &DatasetConfig{EnableDataset: true},
+ })
+ }
+
} else if tp == UnitTypeCloudBrain {
- units = append(units, RepoUnit{
- RepoID: repo.ID,
- Type: tp,
- Config: &CloudBrainConfig{EnableCloudBrain: true},
- })
+ if !isCourse {
+ units = append(units, RepoUnit{
+ RepoID: repo.ID,
+ Type: tp,
+ Config: &CloudBrainConfig{EnableCloudBrain: true},
+ })
+ }
+
} else if tp == UnitTypeBlockChain {
units = append(units, RepoUnit{
RepoID: repo.ID,
@@ -1109,11 +1165,13 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
Config: &BlockChainConfig{EnableBlockChain: true},
})
} else if tp == UnitTypeModelManage {
- units = append(units, RepoUnit{
- RepoID: repo.ID,
- Type: tp,
- Config: &ModelManageConfig{EnableModelManage: true},
- })
+ if !isCourse {
+ units = append(units, RepoUnit{
+ RepoID: repo.ID,
+ Type: tp,
+ Config: &ModelManageConfig{EnableModelManage: true},
+ })
+ }
} else {
units = append(units, RepoUnit{
RepoID: repo.ID,
@@ -1183,6 +1241,14 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error
return nil
}
+func isCourse(opts []CreateRepoOptions) bool {
+ var isCourse = false
+ if len(opts) > 0 {
+ isCourse = opts[0].IsCourse
+ }
+ return isCourse
+}
+
func countRepositories(userID int64, private bool) int64 {
sess := x.Where("id > 0")
@@ -1233,7 +1299,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
}
// Check if new owner has repository with same name.
- has, err := IsRepositoryExist(newOwner, repo.Name)
+ has, err := IsRepositoryExist(newOwner, repo.Name, repo.Alias)
if err != nil {
return fmt.Errorf("IsRepositoryExist: %v", err)
} else if has {
@@ -1366,7 +1432,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err
return err
}
- has, err := IsRepositoryExist(repo.Owner, newRepoName)
+ has, err := IsRepositoryExist(repo.Owner, newRepoName, "")
if err != nil {
return fmt.Errorf("IsRepositoryExist: %v", err)
} else if has {
@@ -2521,6 +2587,14 @@ func UpdateRepositoryCommitNum(repo *Repository) error {
return nil
}
+func GenerateDefaultRepoName(ownerName string) string {
+ if len(ownerName) > 5 {
+ ownerName = ownerName[:5]
+ }
+ now := time.Now().Format("20060102150405")
+ return ownerName + now + fmt.Sprint(rand.Intn(10))
+}
+
type RepoFile struct {
CommitId string
Content []byte
diff --git a/models/repo_generate.go b/models/repo_generate.go
index 480683cd4..08bb1463d 100644
--- a/models/repo_generate.go
+++ b/models/repo_generate.go
@@ -19,6 +19,7 @@ import (
// GenerateRepoOptions contains the template units to generate
type GenerateRepoOptions struct {
Name string
+ Alias string
Description string
Private bool
GitContent bool
diff --git a/models/repo_list.go b/models/repo_list.go
index c4d8ee823..6fb9380de 100755
--- a/models/repo_list.go
+++ b/models/repo_list.go
@@ -48,9 +48,12 @@ func (repos RepositoryList) loadAttributes(e Engine) error {
set := make(map[int64]struct{})
repoIDs := make([]int64, len(repos))
+ setCreator := make(map[int64]struct{})
for i := range repos {
set[repos[i].OwnerID] = struct{}{}
repoIDs[i] = repos[i].ID
+ setCreator[repos[i].CreatorID] = struct{}{}
+
}
// Load owners.
@@ -61,8 +64,18 @@ func (repos RepositoryList) loadAttributes(e Engine) error {
Find(&users); err != nil {
return fmt.Errorf("find users: %v", err)
}
+ //Load creator
+ creators := make(map[int64]*User, len(set))
+ if err := e.
+ Where("id > 0").
+ In("id", keysInt64(setCreator)).
+ Find(&creators); err != nil {
+ return fmt.Errorf("find create repo users: %v", err)
+ }
+
for i := range repos {
repos[i].Owner = users[repos[i].OwnerID]
+ repos[i].Creator = creators[repos[i].CreatorID]
}
// Load primary language.
@@ -174,6 +187,10 @@ type SearchRepoOptions struct {
// True -> include just has milestones
// False -> include just has no milestone
HasMilestones util.OptionalBool
+ // None -> include all repos
+ // True -> include just courses
+ // False -> include just no courses
+ Course util.OptionalBool
}
//SearchOrderBy is used to sort the result
@@ -200,8 +217,8 @@ const (
SearchOrderByForks SearchOrderBy = "num_forks ASC"
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC"
- SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC"
- SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC"
+ SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC"
+ SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC"
)
// SearchRepositoryCondition creates a query condition according search repository options
@@ -321,6 +338,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
var likes = builder.NewCond()
for _, v := range strings.Split(opts.Keyword, ",") {
likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)})
+ likes = likes.Or(builder.Like{"alias", v})
if opts.IncludeDescription {
likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)})
}
@@ -350,6 +368,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue})
}
+ if opts.Course == util.OptionalBoolTrue {
+ cond = cond.And(builder.Eq{"repo_type": RepoCourse})
+ }
+
if opts.Actor != nil && opts.Actor.IsRestricted {
cond = cond.And(accessibleRepositoryCondition(opts.Actor))
}
diff --git a/modules/auth/auth.go b/modules/auth/auth.go
index 352e50ca0..6a156491d 100644
--- a/modules/auth/auth.go
+++ b/modules/auth/auth.go
@@ -186,6 +186,8 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro
data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field))
case validation.ErrGlobPattern:
data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message)
+ case validation.ErrAlphaDashDotChinese:
+ data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_chinese_error")
default:
data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification
}
diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go
index 8061c6469..c113aa890 100755
--- a/modules/auth/repo_form.go
+++ b/modules/auth/repo_form.go
@@ -29,6 +29,7 @@ import (
type CreateRepoForm struct {
UID int64 `binding:"Required"`
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Alias string `binding:"Required;MaxSize(100);AlphaDashDotChinese"`
Private bool
Description string `binding:"MaxSize(1024)"`
DefaultBranch string `binding:"GitRefName;MaxSize(100)"`
@@ -62,6 +63,7 @@ type MigrateRepoForm struct {
UID int64 `json:"uid" binding:"Required"`
// required: true
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Alias string `json:"alias" binding:"Required;AlphaDashDotChinese;MaxSize(100)"`
Mirror bool `json:"mirror"`
Private bool `json:"private"`
Description string `json:"description" binding:"MaxSize(255)"`
@@ -109,6 +111,7 @@ func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) {
// RepoSettingForm form for changing repository settings
type RepoSettingForm struct {
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Alias string `binding:"Required;AlphaDashDotChinese;MaxSize(100)"`
Description string `binding:"MaxSize(255)"`
Website string `binding:"ValidUrl;MaxSize(255)"`
Interval string
@@ -725,3 +728,15 @@ type DeadlineForm struct {
func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
+
+type CreateCourseForm struct {
+ RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Alias string `binding:"Required;MaxSize(100);AlphaDashDotChinese"`
+ Topics string
+ Description string `binding:"MaxSize(1024)"`
+}
+
+// Validate validates the fields
+func (f *CreateCourseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
+ return validate(errs, ctx.Data, f, ctx.Locale)
+}
diff --git a/modules/repository/create.go b/modules/repository/create.go
index d740c58b1..2a24cb708 100644
--- a/modules/repository/create.go
+++ b/modules/repository/create.go
@@ -22,12 +22,17 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
Limit: u.MaxRepoCreation,
}
}
+ var RepoType = models.RepoNormal
+ if opts.IsCourse {
+ RepoType = models.RepoCourse
+ }
repo := &models.Repository{
OwnerID: u.ID,
Owner: u,
OwnerName: u.Name,
Name: opts.Name,
+ Alias: opts.Alias,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
OriginalURL: opts.OriginalURL,
@@ -37,10 +42,14 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
Status: opts.Status,
IsEmpty: !opts.AutoInit,
+ RepoType: RepoType,
}
err = models.WithTx(func(ctx models.DBContext) error {
- if err = models.CreateRepository(ctx, doer, u, repo); err != nil {
+ if err = models.CreateRepository(ctx, doer, u, repo, opts); err != nil {
+ return err
+ }
+ if err = models.SaveTopics(repo.ID, opts.Topics...); err != nil {
return err
}
diff --git a/modules/repository/fork.go b/modules/repository/fork.go
index 2ed2a0eb7..da9039d00 100644
--- a/modules/repository/fork.go
+++ b/modules/repository/fork.go
@@ -15,7 +15,7 @@ import (
)
// ForkRepository forks a repository
-func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc string) (_ *models.Repository, err error) {
+func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc, alias string) (_ *models.Repository, err error) {
forkedRepo, err := oldRepo.GetUserFork(owner.ID)
if err != nil {
return nil, err
@@ -33,6 +33,7 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name,
Owner: owner,
OwnerName: owner.Name,
Name: name,
+ Alias: alias,
LowerName: strings.ToLower(name),
Description: desc,
DefaultBranch: oldRepo.DefaultBranch,
diff --git a/modules/repository/fork_test.go b/modules/repository/fork_test.go
index cb3526bcc..f599ead68 100644
--- a/modules/repository/fork_test.go
+++ b/modules/repository/fork_test.go
@@ -18,7 +18,7 @@ func TestForkRepository(t *testing.T) {
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository)
- fork, err := ForkRepository(user, user, repo, "test", "test")
+ fork, err := ForkRepository(user, user, repo, "test", "test", "test")
assert.Nil(t, fork)
assert.Error(t, err)
assert.True(t, models.IsErrForkAlreadyExist(err))
diff --git a/modules/repository/generate.go b/modules/repository/generate.go
index 6d80488de..86c9a5c28 100644
--- a/modules/repository/generate.go
+++ b/modules/repository/generate.go
@@ -236,6 +236,7 @@ func GenerateRepository(ctx models.DBContext, doer, owner *models.User, template
Owner: owner,
OwnerName: owner.Name,
Name: opts.Name,
+ Alias: opts.Alias,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
IsPrivate: opts.Private,
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index e7ab0b7d2..3d2bd91c5 100755
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -568,6 +568,11 @@ var (
}{}
Warn_Notify_Mails []string
+
+ Course = struct {
+ OrgName string
+ TeamName string
+ }{}
)
// DateLang transforms standard language locale name to corresponding value in datetime plugin.
@@ -1331,6 +1336,10 @@ func NewContext() {
sec = Cfg.Section("warn_mail")
Warn_Notify_Mails = strings.Split(sec.Key("mails").MustString(""), ",")
+
+ sec = Cfg.Section("course")
+ Course.OrgName = sec.Key("org_name").MustString("")
+ Course.TeamName = sec.Key("team_name").MustString("")
}
func SetRadarMapConfig() {
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index 70de9b746..b824813bb 100755
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -46,31 +46,32 @@ type ExternalWiki struct {
// Repository represents a repository
type Repository struct {
- ID int64 `json:"id"`
- Owner *User `json:"owner"`
- Name string `json:"name"`
- FullName string `json:"full_name"`
- Description string `json:"description"`
- Empty bool `json:"empty"`
- Private bool `json:"private"`
- Fork bool `json:"fork"`
- Template bool `json:"template"`
- Parent *Repository `json:"parent"`
- Mirror bool `json:"mirror"`
- Size int `json:"size"`
- HTMLURL string `json:"html_url"`
- SSHURL string `json:"ssh_url"`
- CloneURL string `json:"clone_url"`
- OriginalURL string `json:"original_url"`
- Website string `json:"website"`
- Stars int `json:"stars_count"`
- Forks int `json:"forks_count"`
- Watchers int `json:"watchers_count"`
- OpenIssues int `json:"open_issues_count"`
- OpenPulls int `json:"open_pr_counter"`
- Releases int `json:"release_counter"`
- DefaultBranch string `json:"default_branch"`
- Archived bool `json:"archived"`
+ ID int64 `json:"id"`
+ Owner *User `json:"owner"`
+ Name string `json:"name"`
+ FullName string `json:"full_name"`
+ FullDisplayName string `json:"full_display_name"`
+ Description string `json:"description"`
+ Empty bool `json:"empty"`
+ Private bool `json:"private"`
+ Fork bool `json:"fork"`
+ Template bool `json:"template"`
+ Parent *Repository `json:"parent"`
+ Mirror bool `json:"mirror"`
+ Size int `json:"size"`
+ HTMLURL string `json:"html_url"`
+ SSHURL string `json:"ssh_url"`
+ CloneURL string `json:"clone_url"`
+ OriginalURL string `json:"original_url"`
+ Website string `json:"website"`
+ Stars int `json:"stars_count"`
+ Forks int `json:"forks_count"`
+ Watchers int `json:"watchers_count"`
+ OpenIssues int `json:"open_issues_count"`
+ OpenPulls int `json:"open_pr_counter"`
+ Releases int `json:"release_counter"`
+ DefaultBranch string `json:"default_branch"`
+ Archived bool `json:"archived"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
@@ -217,6 +218,7 @@ type MigrateRepoOption struct {
UID int `json:"uid" binding:"Required"`
// required: true
RepoName string `json:"repo_name" binding:"Required"`
+ Alias string `json:"alias" binding:"Required"`
Mirror bool `json:"mirror"`
Private bool `json:"private"`
Description string `json:"description"`
diff --git a/modules/task/task.go b/modules/task/task.go
index 72f111ecc..722e39bec 100644
--- a/modules/task/task.go
+++ b/modules/task/task.go
@@ -84,6 +84,7 @@ func CreateMigrateTask(doer, u *models.User, opts base.MigrateOptions) (*models.
repo, err := repo_module.CreateRepository(doer, u, models.CreateRepoOptions{
Name: opts.RepoName,
+ Alias: opts.Alias,
Description: opts.Description,
OriginalURL: opts.OriginalURL,
GitServiceType: opts.GitServiceType,
diff --git a/modules/validation/binding.go b/modules/validation/binding.go
index 1c67878ea..b608cdea2 100644
--- a/modules/validation/binding.go
+++ b/modules/validation/binding.go
@@ -19,6 +19,8 @@ const (
// ErrGlobPattern is returned when glob pattern is invalid
ErrGlobPattern = "GlobPattern"
+
+ ErrAlphaDashDotChinese = "AlphaDashDotChineseError"
)
var (
@@ -26,6 +28,8 @@ var (
// They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
// They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
GitRefNamePatternInvalid = regexp.MustCompile(`[\000-\037\177 \\~^:?*[]+`)
+
+ AlphaDashDotChinese = regexp.MustCompile("^[\u4e00-\u9fa5\\.\\-_A-Za-z0-9]+$")
)
// CheckGitRefAdditionalRulesValid check name is valid on additional rules
@@ -53,6 +57,7 @@ func AddBindingRules() {
addGitRefNameBindingRule()
addValidURLBindingRule()
addGlobPatternRule()
+ addAlphaDashDotChineseRule()
}
func addGitRefNameBindingRule() {
@@ -117,6 +122,21 @@ func addGlobPatternRule() {
})
}
+func addAlphaDashDotChineseRule() {
+ binding.AddRule(&binding.Rule{
+ IsMatch: func(rule string) bool {
+ return strings.HasPrefix(rule, "AlphaDashDotChinese")
+ },
+ IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) {
+ if !ValidAlphaDashDotChinese(fmt.Sprintf("%v", val)) {
+ errs.Add([]string{name}, ErrAlphaDashDotChinese, "ErrAlphaDashDotChinese")
+ return false, errs
+ }
+ return true, errs
+ },
+ })
+}
+
func portOnly(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 {
@@ -139,3 +159,7 @@ func validPort(p string) bool {
}
return true
}
+
+func ValidAlphaDashDotChinese(value string) bool {
+ return AlphaDashDotChinese.MatchString(value)
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 21890babe..3b57b22f5 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -50,6 +50,7 @@ repository = Repository
organization = Organization
mirror = Mirror
new_repo = New Repository
+new_course=Publish Course
new_migrate = New Migration
new_dataset = New Dataset
edit_dataset = Edit Dataset
@@ -346,7 +347,9 @@ modify = Update
[form]
UserName = Username
-RepoName = Repository name
+Alias = Repository name
+RepoPath = Repository path
+RepoAdress = Repository Adress
Email = Email address
Password = Password
Retype = Re-Type Password
@@ -370,7 +373,10 @@ SSPIDefaultLanguage = Default Language
require_error = ` cannot be empty.`
alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.`
alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.`
+reponame_dash_dot_error=` Please enter Chinese, alphanumeric, dash ('-') ,underscore ('_') and dot ('.')characters, up to 100 characters. `
+repoadd_dash_dot_error=` Path only allows input alphanumeric, dash ('-') ,underscore ('_') and dot ('.')characters, up to 100 characters. `
git_ref_name_error = ` must be a well-formed Git reference name.`
+alpha_dash_dot_chinese_error= ` should contain only alphanumeric, chinese, dash ('-') and underscore ('_') characters.`
size_error = ` must be size %s.`
min_size_error = ` must contain at least %s characters.`
max_size_error = ` must contain at most %s characters.`
@@ -384,7 +390,8 @@ password_not_match = The passwords do not match.
lang_select_error = Select a language from the list.
username_been_taken = The username is already taken.
-repo_name_been_taken = The repository name is already used.
+repo_name_been_taken = The repository name or path is already used.
+course_name_been_taken=The course path is already used.
visit_rate_limit = Remote visit addressed rate limitation.
2fa_auth_required = Remote visit required two factors authentication.
org_name_been_taken = The organization name is already taken.
@@ -797,6 +804,8 @@ readme = README
readme_helper = Select a README file template.
auto_init = Initialize Repository (Adds .gitignore, License and README)
create_repo = Create Repository
+create_course = Publish Course
+failed_to_create_course=Fail to publish course, please try again later.
default_branch = Default Branch
mirror_prune = Prune
mirror_prune_desc = Remove obsolete remote-tracking references
@@ -894,7 +903,7 @@ modelarts.train_job.description=Description
modelarts.train_job.parameter_setting=Parameter setting
modelarts.train_job.parameter_setting_info=Parameter Info
modelarts.train_job.fast_parameter_setting=fast_parameter_setting
-modelarts.train_job.fast_parameter_setting_config=fast_parameter_setting_config
+modelarts.train_job.fast_parameter_setting_config=fast_parameter_setting_config
modelarts.train_job.fast_parameter_setting_config_link=fast_parameter_setting_config_link
modelarts.train_job.frames=frames
modelarts.train_job.algorithm_origin=Algorithm Origin
@@ -954,14 +963,21 @@ template.avatar = Avatar
template.issue_labels = Issue Labels
template.one_item = Must select at least one template item
template.invalid = Must select a template repository
+template.repo_adress=Adress
+template.repo_path=path
+template.repo_name=Name
archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
archive.issue.nocomment = This repo is archived. You cannot comment on issues.
archive.pull.nocomment = This repo is archived. You cannot comment on pull requests.
form.reach_limit_of_creation = You have already reached your limit of %d repositories.
+form.reach_limit_of_course_creation=You have already reached your limit of %d courses or repositories.
form.name_reserved = The repository name '%s' is reserved.
+form.course_name_reserved=The course name '%s' is reserved.
form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
+form.course_name_pattern_not_allowed=The pattern '%s' is not allowed in a course name.
+add_course_org_fail=Fail to add organization, please try again later.
need_auth = Clone Authorization
migrate_type = Migration Type
@@ -1174,7 +1190,7 @@ issues.filter_label_exclude = `Use alt
+ click/enter
t
issues.filter_label_no_select = All labels
issues.filter_milestone = Milestone
issues.filter_milestone_no_select = All milestones
-issues.filter_milestone_no_add = Not add milestones
+issues.filter_milestone_no_add = Not add milestones
issues.filter_assignee = Assignee
issues.filter_assginee_no_select = All assignees
issues.filter_type = Type
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 6dd44d848..d034b1722 100755
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -50,6 +50,7 @@ repository=项目
organization=组织
mirror=镜像
new_repo=创建项目
+new_course=发布课程
new_dataset=创建数据集
new_migrate=迁移外部项目
edit_dataset = Edit Dataset
@@ -350,7 +351,10 @@ modify=更新
[form]
UserName=用户名
-RepoName=项目名称
+RepoName=项目路径
+Alias=项目名称
+RepoPath=项目路径
+RepoAdress=项目地址
Email=邮箱地址
Password=密码
Retype=重新输入密码
@@ -374,7 +378,10 @@ SSPIDefaultLanguage=默认语言
require_error=不能为空。
alpha_dash_error=应该只包含字母数字、破折号 ('-') 和下划线 ('_') 字符。
alpha_dash_dot_error=应该只包含字母数字, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。
+reponame_dash_dot_error=请输入中文、字母、数字和-_.、最多100个字符。
+repoadd_dash_dot_error=路径只允许字母、数字和-_.,最多100个字符。
git_ref_name_error=` 必须是格式良好的 git 引用名称。`
+alpha_dash_dot_chinese_error=应该只包含字母数字中文, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。
size_error=长度必须为 %s。
min_size_error=长度最小为 %s 个字符。
max_size_error=长度最大为 %s 个字符。
@@ -388,7 +395,8 @@ password_not_match=密码不匹配。
lang_select_error=从列表中选出语言
username_been_taken=用户名已被使用。
-repo_name_been_taken=项目名称已被使用。
+repo_name_been_taken=项目名称或项目路径已被使用。
+course_name_been_taken=课程名称或路径已被使用。
visit_rate_limit=远程访问达到速度限制。
2fa_auth_required=远程访问需要双重验证。
org_name_been_taken=组织名称已被使用。
@@ -802,6 +810,8 @@ readme=自述
readme_helper=选择自述文件模板。
auto_init=初始化存储库 (添加. gitignore、许可证和自述文件)
create_repo=创建项目
+create_course=发布课程
+failed_to_create_course=发布课程失败,请稍后再试。
default_branch=默认分支
mirror_prune=修剪
mirror_prune_desc=删除过时的远程跟踪引用
@@ -965,14 +975,21 @@ template.avatar=头像
template.issue_labels=任务标签
template.one_item=必须至少选择一个模板项
template.invalid=必须选择一个模板项目
+template.repo_adress=项目地址
+template.repo_path=项目地址
+template.repo_name=项目名称
archive.title=此项目已存档。您可以查看文件和克隆,但不能推送或创建任务/合并请求。
archive.issue.nocomment=此项目已存档,您不能在此任务添加评论。
archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。
form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。
+form.reach_limit_of_course_creation=你已经达到了您的 %d 课程的限制。
form.name_reserved=项目名称 '%s' 是被保留的。
+form.course_name_reserved=课程名称 '%s' 是被保留的。
form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。
+form.course_name_pattern_not_allowed=课程名称中不允许使用模式 "%s"。
+add_course_org_fail=加入组织失败,请稍后重试。
need_auth=需要授权验证
migrate_type=迁移类型
diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go
index 3536b7f43..a753f192d 100644
--- a/routers/api/v1/repo/fork.go
+++ b/routers/api/v1/repo/fork.go
@@ -118,7 +118,7 @@ func CreateFork(ctx *context.APIContext, form api.CreateForkOption) {
forker = org
}
- fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description)
+ fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description, repo.Alias)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ForkRepository", err)
return
diff --git a/routers/home.go b/routers/home.go
index 24de1a10c..397e1990d 100755
--- a/routers/home.go
+++ b/routers/home.go
@@ -7,11 +7,11 @@ package routers
import (
"bytes"
- "fmt"
- "io/ioutil"
"net/http"
"strings"
+ "code.gitea.io/gitea/services/repository"
+
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@@ -133,6 +133,7 @@ type RepoSearchOptions struct {
Restricted bool
PageSize int
TplName base.TplName
+ Course util.OptionalBool
}
var (
@@ -211,6 +212,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
AllLimited: true,
TopicName: topic,
IncludeDescription: setting.UI.SearchRepoDescription,
+ Course: opts.Course,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
@@ -559,7 +561,7 @@ func NotFound(ctx *context.Context) {
func RecommendOrgFromPromote(ctx *context.Context) {
url := setting.RecommentRepoAddr + "organizations"
- result, err := recommendFromPromote(url)
+ result, err := repository.RecommendFromPromote(url)
if err != nil {
ctx.ServerError("500", err)
return
@@ -586,62 +588,11 @@ func RecommendOrgFromPromote(ctx *context.Context) {
ctx.JSON(200, resultOrg)
}
-func recommendFromPromote(url string) ([]string, error) {
- resp, err := http.Get(url)
- if err != nil || resp.StatusCode != 200 {
- log.Info("Get organizations url error=" + err.Error())
- return nil, err
- }
- bytes, err := ioutil.ReadAll(resp.Body)
- resp.Body.Close()
- if err != nil {
- log.Info("Get organizations url error=" + err.Error())
- return nil, err
- }
-
- allLineStr := string(bytes)
- lines := strings.Split(allLineStr, "\n")
- result := make([]string, len(lines))
- for i, line := range lines {
- log.Info("i=" + fmt.Sprint(i) + " line=" + line)
- result[i] = strings.Trim(line, " ")
- }
- return result, nil
-}
-
func RecommendRepoFromPromote(ctx *context.Context) {
- url := setting.RecommentRepoAddr + "projects"
- result, err := recommendFromPromote(url)
+ result, err := repository.GetRecommendRepoFromPromote("projects")
if err != nil {
ctx.ServerError("500", err)
- return
- }
- resultRepo := make([]map[string]interface{}, 0)
- //resultRepo := make([]*models.Repository, 0)
- for _, repoName := range result {
- tmpIndex := strings.Index(repoName, "/")
- if tmpIndex == -1 {
- log.Info("error repo name format.")
- } else {
- ownerName := strings.Trim(repoName[0:tmpIndex], " ")
- repoName := strings.Trim(repoName[tmpIndex+1:], " ")
- repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
- if err == nil {
- repoMap := make(map[string]interface{})
- repoMap["ID"] = fmt.Sprint(repo.ID)
- repoMap["Name"] = repo.Name
- repoMap["OwnerName"] = repo.OwnerName
- repoMap["NumStars"] = repo.NumStars
- repoMap["NumForks"] = repo.NumForks
- repoMap["Description"] = repo.Description
- repoMap["NumWatchs"] = repo.NumWatches
- repoMap["Topics"] = repo.Topics
- repoMap["Avatar"] = repo.RelAvatarLink()
- resultRepo = append(resultRepo, repoMap)
- } else {
- log.Info("query repo error," + err.Error())
- }
- }
+ } else {
+ ctx.JSON(200, result)
}
- ctx.JSON(200, resultRepo)
}
diff --git a/routers/org/home.go b/routers/org/home.go
index df600d96d..4c350d352 100755
--- a/routers/org/home.go
+++ b/routers/org/home.go
@@ -7,6 +7,10 @@ package org
import (
"strings"
+ "code.gitea.io/gitea/services/repository"
+
+ "code.gitea.io/gitea/modules/util"
+
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@@ -14,7 +18,8 @@ import (
)
const (
- tplOrgHome base.TplName = "org/home"
+ tplOrgHome base.TplName = "org/home"
+ tplOrgCourseHome base.TplName = "org/home_courses"
)
// Home show organization home page
@@ -59,10 +64,16 @@ func Home(ctx *context.Context) {
case "fewestforks":
orderBy = models.SearchOrderByForks
default:
- ctx.Data["SortType"] = "recentupdate"
- orderBy = models.SearchOrderByRecentUpdated
- }
+ if setting.Course.OrgName == org.Name {
+ ctx.Data["SortType"] = "newest"
+ orderBy = models.SearchOrderByNewest
+ } else {
+ ctx.Data["SortType"] = "recentupdate"
+ orderBy = models.SearchOrderByRecentUpdated
+ }
+ }
+ orderBy = orderBy + ",id"
keyword := strings.Trim(ctx.Query("q"), " ")
ctx.Data["Keyword"] = keyword
@@ -76,9 +87,21 @@ func Home(ctx *context.Context) {
count int64
err error
)
+ pageSize := setting.UI.User.RepoPagingNum
+ var CourseOptional util.OptionalBool = util.OptionalBoolNone
+ if setting.Course.OrgName == org.Name {
+ pageSize = 15
+ CourseOptional = util.OptionalBoolTrue
+ recommendCourses, _ := repository.GetRecommendRepoFromPromote("courses")
+ ctx.Data["RecommendCourses"] = recommendCourses
+ recommendCourseKeyWords, _ := repository.GetRecommendCourseKeyWords()
+ ctx.Data["CoursesKeywords"] = recommendCourseKeyWords
+
+ }
+
repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
ListOptions: models.ListOptions{
- PageSize: setting.UI.User.RepoPagingNum,
+ PageSize: pageSize,
Page: page,
},
Keyword: keyword,
@@ -87,6 +110,7 @@ func Home(ctx *context.Context) {
Private: ctx.IsSigned,
Actor: ctx.User,
IncludeDescription: setting.UI.SearchRepoDescription,
+ Course: CourseOptional,
})
if err != nil {
ctx.ServerError("SearchRepository", err)
@@ -138,5 +162,10 @@ func Home(ctx *context.Context) {
}
ctx.Data["tags"] = tags
- ctx.HTML(200, tplOrgHome)
+ if setting.Course.OrgName == org.Name {
+ ctx.HTML(200, tplOrgCourseHome)
+ } else {
+ ctx.HTML(200, tplOrgHome)
+ }
+
}
diff --git a/routers/org/members.go b/routers/org/members.go
index 9f13d1be3..39df692d2 100755
--- a/routers/org/members.go
+++ b/routers/org/members.go
@@ -17,7 +17,8 @@ import (
const (
// tplMembers template for organization members page
- tplMembers base.TplName = "org/member/members"
+ tplMembers base.TplName = "org/member/members"
+ tplCourseMembers base.TplName = "org/member/course_members"
)
// Members render organization users page
@@ -64,8 +65,11 @@ func Members(ctx *context.Context) {
ctx.Data["MembersIsPublicMember"] = membersIsPublic
ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID)
ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus()
-
- ctx.HTML(200, tplMembers)
+ if setting.Course.OrgName == org.Name {
+ ctx.HTML(200, tplCourseMembers)
+ } else {
+ ctx.HTML(200, tplMembers)
+ }
}
// MembersAction response for operation to a member of organization
diff --git a/routers/org/teams.go b/routers/org/teams.go
index 03fbf068d..8aa3e3947 100644
--- a/routers/org/teams.go
+++ b/routers/org/teams.go
@@ -10,6 +10,8 @@ import (
"path"
"strings"
+ "code.gitea.io/gitea/modules/setting"
+
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
@@ -22,7 +24,8 @@ import (
const (
// tplTeams template path for teams list page
- tplTeams base.TplName = "org/team/teams"
+ tplTeams base.TplName = "org/team/teams"
+ tplCourseTeams base.TplName = "org/team/courseTeams"
// tplTeamNew template path for create new team page
tplTeamNew base.TplName = "org/team/new"
// tplTeamMembers template path for showing team members page
@@ -44,8 +47,12 @@ func Teams(ctx *context.Context) {
}
}
ctx.Data["Teams"] = org.Teams
+ if setting.Course.OrgName == org.Name {
+ ctx.HTML(200, tplCourseTeams)
+ } else {
+ ctx.HTML(200, tplTeams)
+ }
- ctx.HTML(200, tplTeams)
}
// TeamsAction response for join, leave, remove, add operations to team
diff --git a/routers/repo/course.go b/routers/repo/course.go
new file mode 100644
index 000000000..b8f39817b
--- /dev/null
+++ b/routers/repo/course.go
@@ -0,0 +1,189 @@
+package repo
+
+import (
+ "net/http"
+ "strings"
+
+ "code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/modules/auth"
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ repo_service "code.gitea.io/gitea/services/repository"
+)
+
+const (
+ tplCreateCourse base.TplName = "repo/createCourse"
+)
+
+func CreateCourse(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("new_course")
+
+ ctx.Data["Owner"] = setting.Course.OrgName
+
+ ctx.HTML(200, tplCreateCourse)
+
+}
+
+func CreateCoursePost(ctx *context.Context, form auth.CreateCourseForm) {
+ ctx.Data["Title"] = ctx.Tr("new_course")
+
+ if ctx.Written() {
+ return
+ }
+
+ var topics = make([]string, 0)
+ var topicsStr = strings.TrimSpace(form.Topics)
+ if len(topicsStr) > 0 {
+ topics = strings.Split(topicsStr, ",")
+ }
+
+ validTopics, invalidTopics := models.SanitizeAndValidateTopics(topics)
+
+ if len(validTopics) > 25 {
+ ctx.RenderWithErr(ctx.Tr("repo.topic.count_prompt"), tplCreateCourse, form)
+ return
+ }
+
+ if len(invalidTopics) > 0 {
+ ctx.RenderWithErr(ctx.Tr("repo.topic.format_prompt"), tplCreateCourse, form)
+ return
+ }
+
+ var repo *models.Repository
+ var err error
+
+ if setting.Course.OrgName == "" || setting.Course.TeamName == "" {
+ log.Error("then organization name or team name of course is empty.")
+ ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
+ return
+ }
+
+ org, team, err := getOrgAndTeam()
+
+ if err != nil {
+ log.Error("Failed to get team from db.", err)
+ ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
+ return
+ }
+ isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID})
+ if err != nil {
+ log.Error("Failed to get user in team from db.")
+ ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
+ return
+ }
+
+ if !isInTeam {
+ err = models.AddTeamMember(team, ctx.User.ID)
+ if err != nil {
+ log.Error("Failed to add user to team.")
+ ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form)
+ return
+ }
+ }
+
+ if ctx.HasError() {
+ ctx.HTML(200, tplCreateCourse)
+ return
+ }
+
+ repo, err = repo_service.CreateRepository(ctx.User, org, models.CreateRepoOptions{
+ Name: form.RepoName,
+ Alias: form.Alias,
+ Description: form.Description,
+ Gitignores: "",
+ IssueLabels: "",
+ License: "",
+ Readme: "Default",
+ IsPrivate: false,
+ DefaultBranch: "master",
+ AutoInit: true,
+ IsCourse: true,
+ Topics: validTopics,
+ })
+ if err == nil {
+ log.Trace("Repository created [%d]: %s/%s", repo.ID, org.Name, repo.Name)
+ ctx.Redirect(setting.AppSubURL + "/" + org.Name + "/" + repo.Name)
+ return
+ }
+
+ handleCreateCourseError(ctx, org, err, "CreateCoursePost", tplCreateCourse, &form)
+}
+
+func AddCourseOrg(ctx *context.Context) {
+
+ _, team, err := getOrgAndTeam()
+
+ if err != nil {
+ log.Error("Failed to get team from db.", err)
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "code": 1,
+ "message": ctx.Tr("repo.addCourseOrgFail"),
+ })
+ return
+ }
+ isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID})
+ if err != nil {
+ log.Error("Failed to get user in team from db.", err)
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "code": 1,
+ "message": ctx.Tr("repo.add_course_org_fail"),
+ })
+ return
+ }
+
+ if !isInTeam {
+ err = models.AddTeamMember(team, ctx.User.ID)
+ if err != nil {
+ log.Error("Failed to add user to team.", err)
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "code": 1,
+ "message": ctx.Tr("repo.add_course_org_fail"),
+ })
+ return
+ }
+ }
+
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "code": 0,
+ "message": "",
+ })
+
+}
+
+func getOrgAndTeam() (*models.User, *models.Team, error) {
+ org, err := models.GetUserByName(setting.Course.OrgName)
+
+ if err != nil {
+ log.Error("Failed to get organization from db.", err)
+ return nil, nil, err
+ }
+
+ team, err := models.GetTeam(org.ID, setting.Course.TeamName)
+
+ if err != nil {
+ log.Error("Failed to get team from db.", err)
+
+ return nil, nil, err
+ }
+ return org, team, nil
+}
+
+func handleCreateCourseError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) {
+ switch {
+ case models.IsErrReachLimitOfRepo(err):
+ ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_course_creation", owner.MaxCreationLimit()), tpl, form)
+ case models.IsErrRepoAlreadyExist(err):
+ ctx.Data["Err_RepoName"] = true
+ ctx.RenderWithErr(ctx.Tr("form.course_name_been_taken"), tpl, form)
+ case models.IsErrNameReserved(err):
+ ctx.Data["Err_RepoName"] = true
+ ctx.RenderWithErr(ctx.Tr("repo.form.course_name_reserved", err.(models.ErrNameReserved).Name), tpl, form)
+ case models.IsErrNamePatternNotAllowed(err):
+ ctx.Data["Err_RepoName"] = true
+ ctx.RenderWithErr(ctx.Tr("repo.form.course_name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form)
+ default:
+ ctx.ServerError(name, err)
+ }
+}
diff --git a/routers/repo/pull.go b/routers/repo/pull.go
index 8841e4755..22ea3a54c 100755
--- a/routers/repo/pull.go
+++ b/routers/repo/pull.go
@@ -105,6 +105,7 @@ func getForkRepository(ctx *context.Context) *models.Repository {
return nil
}
ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name
+ ctx.Data["ForkDisplayName"] = forkRepo.FullDisplayName()
ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID
if err := ctx.User.GetOwnedOrganizations(); err != nil {
@@ -221,7 +222,7 @@ func ForkPost(ctx *context.Context, form auth.CreateRepoForm) {
}
}
- repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description)
+ repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description, form.Alias)
if err != nil {
ctx.Data["Err_RepoName"] = true
switch {
diff --git a/routers/repo/repo.go b/routers/repo/repo.go
index a182e9087..a509cb52e 100644
--- a/routers/repo/repo.go
+++ b/routers/repo/repo.go
@@ -6,10 +6,12 @@
package repo
import (
+ "code.gitea.io/gitea/modules/validation"
"fmt"
"net/url"
"os"
"path"
+ "regexp"
"strings"
"code.gitea.io/gitea/models"
@@ -201,6 +203,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
if form.RepoTemplate > 0 {
opts := models.GenerateRepoOptions{
Name: form.RepoName,
+ Alias: form.Alias,
Description: form.Description,
Private: form.Private,
GitContent: form.GitContent,
@@ -235,6 +238,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
} else {
repo, err = repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{
Name: form.RepoName,
+ Alias: form.Alias,
Description: form.Description,
Gitignores: form.Gitignores,
IssueLabels: form.IssueLabels,
@@ -358,6 +362,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
GitServiceType: gitServiceType,
CloneAddr: remoteAddr,
RepoName: form.RepoName,
+ Alias: form.Alias,
Description: form.Description,
Private: form.Private || setting.Repository.ForcePrivate,
Mirror: form.Mirror,
@@ -380,7 +385,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
opts.Releases = false
}
- err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName)
+ err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, opts.Alias)
if err != nil {
handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form)
return
@@ -552,3 +557,27 @@ func Status(ctx *context.Context) {
"err": task.Errors,
})
}
+
+var repoNamePattern = regexp.MustCompile("^[0-9a-zA-Z\\.\\-_]{1,100}$")
+var repoAliasPattern = regexp.MustCompile("^[\u4e00-\u9fa5\\.\\-_A-Za-z0-9]{1,100}$")
+
+// CheckName returns repository's default name(by given alias)
+func CheckName(ctx *context.Context) {
+ var r = make(map[string]string, 1)
+ q := ctx.Query("q")
+ owner := ctx.Query("owner")
+ if q == "" || owner == "" || len(q) > 100 || !validation.ValidAlphaDashDotChinese(q) {
+ r["name"] = ""
+ ctx.JSON(200, r)
+ return
+ }
+ if repoNamePattern.MatchString(q) {
+ r["name"] = q
+ ctx.JSON(200, r)
+ return
+ }
+ n := models.GenerateDefaultRepoName(owner)
+ r["name"] = n
+ ctx.JSON(200, r)
+ return
+}
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
index f7da8f4a8..055627fc1 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -98,6 +98,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
repo.Description = form.Description
repo.Website = form.Website
repo.IsTemplate = form.Template
+ repo.Alias = form.Alias
// Visibility of forked repository is forced sync with base repository.
if repo.IsFork {
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index 439c17a92..5f2237dd8 100755
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -708,6 +708,13 @@ func RegisterRoutes(m *macaron.Macaron) {
}, reqSignIn)
// ***** END: Organization *****
+ m.Group("/course", func() {
+ m.Get("/create", repo.CreateCourse)
+ m.Post("/create", bindIgnErr(auth.CreateCourseForm{}), repo.CreateCoursePost)
+ m.Get("/addOrg", repo.AddCourseOrg)
+
+ }, reqSignIn)
+
// ***** START: Repository *****
m.Group("/repo", func() {
m.Get("/create", repo.Create)
@@ -718,6 +725,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Combo("/:repoid").Get(repo.Fork).
Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost)
}, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader)
+ m.Get("/check_name", repo.CheckName)
}, reqSignIn)
// ***** Release Attachment Download without Signin
diff --git a/services/repository/repository.go b/services/repository/repository.go
index eafad988e..86ee9370e 100644
--- a/services/repository/repository.go
+++ b/services/repository/repository.go
@@ -5,12 +5,17 @@
package repository
import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "strings"
+
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/setting"
pull_service "code.gitea.io/gitea/services/pull"
- "fmt"
)
// CreateRepository creates a repository for the user/organization.
@@ -31,8 +36,8 @@ func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) (
}
// ForkRepository forks a repository
-func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc string) (*models.Repository, error) {
- repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc)
+func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc, alias string) (*models.Repository, error) {
+ repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc, alias)
if err != nil {
if repo != nil {
if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil {
@@ -86,3 +91,79 @@ func PushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repo
return repo, nil
}
+
+func GetRecommendCourseKeyWords() ([]string, error) {
+
+ url := setting.RecommentRepoAddr + "course_keywords"
+ result, err := RecommendFromPromote(url)
+
+ if err != nil {
+ return []string{}, err
+ }
+ return result, err
+
+}
+
+func GetRecommendRepoFromPromote(filename string) ([]map[string]interface{}, error) {
+ resultRepo := make([]map[string]interface{}, 0)
+ url := setting.RecommentRepoAddr + filename
+ result, err := RecommendFromPromote(url)
+
+ if err != nil {
+
+ return resultRepo, err
+ }
+
+ //resultRepo := make([]*models.Repository, 0)
+ for _, repoName := range result {
+ tmpIndex := strings.Index(repoName, "/")
+ if tmpIndex == -1 {
+ log.Info("error repo name format.")
+ } else {
+ ownerName := strings.Trim(repoName[0:tmpIndex], " ")
+ repoName := strings.Trim(repoName[tmpIndex+1:], " ")
+ repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
+ if err == nil {
+ repoMap := make(map[string]interface{})
+ repoMap["ID"] = fmt.Sprint(repo.ID)
+ repoMap["Name"] = repo.Name
+ repoMap["Alias"] = repo.Alias
+ repoMap["Creator"] = repo.Creator
+ repoMap["OwnerName"] = repo.OwnerName
+ repoMap["NumStars"] = repo.NumStars
+ repoMap["NumForks"] = repo.NumForks
+ repoMap["Description"] = repo.Description
+ repoMap["NumWatchs"] = repo.NumWatches
+ repoMap["Topics"] = repo.Topics
+ repoMap["Avatar"] = repo.RelAvatarLink()
+ resultRepo = append(resultRepo, repoMap)
+ } else {
+ log.Info("query repo error," + err.Error())
+ }
+ }
+ }
+ return resultRepo, nil
+}
+
+func RecommendFromPromote(url string) ([]string, error) {
+ resp, err := http.Get(url)
+ if err != nil || resp.StatusCode != 200 {
+ log.Info("Get organizations url error=" + err.Error())
+ return nil, err
+ }
+ bytes, err := ioutil.ReadAll(resp.Body)
+ resp.Body.Close()
+ if err != nil {
+ log.Info("Get organizations url error=" + err.Error())
+ return nil, err
+ }
+
+ allLineStr := string(bytes)
+ lines := strings.Split(allLineStr, "\n")
+ result := make([]string, len(lines))
+ for i, line := range lines {
+ log.Info("i=" + fmt.Sprint(i) + " line=" + line)
+ result[i] = strings.Trim(line, " ")
+ }
+ return result, nil
+}
diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl
index d7560837f..0e01186b0 100755
--- a/templates/explore/repo_list.tmpl
+++ b/templates/explore/repo_list.tmpl
@@ -5,7 +5,7 @@
border-radius: 0.8rem;
margin-bottom: 1.0rem;
padding: 1.0rem !important;
- }
+ }
.ui.repository.list>.item .header {
font-size: 1.4rem !important;
font-weight: 200;
@@ -24,7 +24,7 @@
content: "";
height: 1px;
background-color: #E1E3E6;
- bottom: 2.8rem;
+ bottom: 2.8rem;
}
.repository .ui.mini.menu{
font-size: .6rem;
@@ -43,13 +43,13 @@
+
热门{{.i18n.Tr "explore.repos"}}
+
活跃{{.i18n.Tr "explore.repos"}}
{{end}}
@@ -93,7 +93,7 @@