| @@ -5,7 +5,9 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language | |||
|  | |||
| ##### Current version: 0.1.9 Alpha | |||
| ##### Current version: 0.2.0 Alpha | |||
| #### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in March 29, 2014 and will reset multiple times after. Please do NOT put your important data on the site. | |||
| #### Other language version | |||
| @@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。 | |||
|  | |||
| ##### 当前版本:0.1.9 Alpha | |||
| ##### 当前版本:0.2.0 Alpha | |||
| ## 开发目的 | |||
| @@ -19,7 +19,7 @@ import ( | |||
| // Test that go1.2 tag above is included in builds. main.go refers to this definition. | |||
| const go12tag = true | |||
| const APP_VER = "0.1.9.0329 Alpha" | |||
| const APP_VER = "0.2.0.0329 Alpha" | |||
| func init() { | |||
| base.AppVer = APP_VER | |||
| @@ -70,9 +70,12 @@ func GetTargetFile(userName, repoName, branchName, commitId, rpath string) (*Rep | |||
| return nil, err | |||
| } | |||
| commit, err := repo.GetCommit(branchName, commitId) | |||
| commit, err := repo.GetCommitOfBranch(branchName) | |||
| if err != nil { | |||
| return nil, err | |||
| commit, err = repo.GetCommit(commitId) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| } | |||
| parts := strings.Split(path.Clean(rpath), "/") | |||
| @@ -110,13 +113,22 @@ func GetTargetFile(userName, repoName, branchName, commitId, rpath string) (*Rep | |||
| } | |||
| // GetReposFiles returns a list of file object in given directory of repository. | |||
| func GetReposFiles(userName, repoName, branchName, commitId, rpath string) ([]*RepoFile, error) { | |||
| // func GetReposFilesOfBranch(userName, repoName, branchName, rpath string) ([]*RepoFile, error) { | |||
| // return getReposFiles(userName, repoName, commitId, rpath) | |||
| // } | |||
| // GetReposFiles returns a list of file object in given directory of repository. | |||
| func GetReposFiles(userName, repoName, commitId, rpath string) ([]*RepoFile, error) { | |||
| return getReposFiles(userName, repoName, commitId, rpath) | |||
| } | |||
| func getReposFiles(userName, repoName, commitId string, rpath string) ([]*RepoFile, error) { | |||
| repo, err := git.OpenRepository(RepoPath(userName, repoName)) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| commit, err := repo.GetCommit(branchName, commitId) | |||
| commit, err := repo.GetCommit(commitId) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -216,13 +228,13 @@ func GetReposFiles(userName, repoName, branchName, commitId, rpath string) ([]*R | |||
| return append(repodirs, repofiles...), nil | |||
| } | |||
| func GetCommit(userName, repoName, branchname, commitid string) (*git.Commit, error) { | |||
| func GetCommit(userName, repoName, commitId string) (*git.Commit, error) { | |||
| repo, err := git.OpenRepository(RepoPath(userName, repoName)) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return repo.GetCommit(branchname, commitid) | |||
| return repo.GetCommit(commitId) | |||
| } | |||
| // GetCommitsByBranch returns all commits of given branch of repository. | |||
| @@ -397,7 +409,7 @@ func GetDiff(repoPath, commitid string) (*Diff, error) { | |||
| return nil, err | |||
| } | |||
| commit, err := repo.GetCommit("", commitid) | |||
| commit, err := repo.GetCommit(commitid) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| @@ -18,23 +18,24 @@ var ( | |||
| // Issue represents an issue or pull request of repository. | |||
| type Issue struct { | |||
| Id int64 | |||
| Index int64 // Index in one repository. | |||
| Name string | |||
| RepoId int64 `xorm:"index"` | |||
| Repo *Repository `xorm:"-"` | |||
| PosterId int64 | |||
| Poster *User `xorm:"-"` | |||
| MilestoneId int64 | |||
| AssigneeId int64 | |||
| IsPull bool // Indicates whether is a pull request or not. | |||
| IsClosed bool | |||
| Labels string `xorm:"TEXT"` | |||
| Mentions string `xorm:"TEXT"` | |||
| Content string `xorm:"TEXT"` | |||
| NumComments int | |||
| Created time.Time `xorm:"created"` | |||
| Updated time.Time `xorm:"updated"` | |||
| Id int64 | |||
| Index int64 // Index in one repository. | |||
| Name string | |||
| RepoId int64 `xorm:"index"` | |||
| Repo *Repository `xorm:"-"` | |||
| PosterId int64 | |||
| Poster *User `xorm:"-"` | |||
| MilestoneId int64 | |||
| AssigneeId int64 | |||
| IsPull bool // Indicates whether is a pull request or not. | |||
| IsClosed bool | |||
| Labels string `xorm:"TEXT"` | |||
| Mentions string `xorm:"TEXT"` | |||
| Content string `xorm:"TEXT"` | |||
| RenderedContent string `xorm:"-"` | |||
| NumComments int | |||
| Created time.Time `xorm:"created"` | |||
| Updated time.Time `xorm:"updated"` | |||
| } | |||
| // CreateIssue creates new issue for repository. | |||
| @@ -169,9 +170,17 @@ type Milestone struct { | |||
| Created time.Time `xorm:"created"` | |||
| } | |||
| // Issue types. | |||
| const ( | |||
| IT_PLAIN = iota // Pure comment. | |||
| IT_REOPEN // Issue reopen status change prompt. | |||
| IT_CLOSE // Issue close status change prompt. | |||
| ) | |||
| // Comment represents a comment in commit and issue page. | |||
| type Comment struct { | |||
| Id int64 | |||
| Type int | |||
| PosterId int64 | |||
| Poster *User `xorm:"-"` | |||
| IssueId int64 | |||
| @@ -182,21 +191,37 @@ type Comment struct { | |||
| } | |||
| // CreateComment creates comment of issue or commit. | |||
| func CreateComment(userId, issueId, commitId, line int64, content string) error { | |||
| func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error { | |||
| sess := orm.NewSession() | |||
| defer sess.Close() | |||
| sess.Begin() | |||
| if _, err := orm.Insert(&Comment{PosterId: userId, IssueId: issueId, | |||
| if _, err := orm.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId, | |||
| CommitId: commitId, Line: line, Content: content}); err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?" | |||
| if _, err := sess.Exec(rawSql, issueId); err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| // Check comment type. | |||
| switch cmtType { | |||
| case IT_PLAIN: | |||
| rawSql := "UPDATE `issue` SET num_comments = num_comments + 1 WHERE id = ?" | |||
| if _, err := sess.Exec(rawSql, issueId); err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| case IT_REOPEN: | |||
| rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues - 1 WHERE id = ?" | |||
| if _, err := sess.Exec(rawSql, repoId); err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| case IT_CLOSE: | |||
| rawSql := "UPDATE `repository` SET num_closed_issues = num_closed_issues + 1 WHERE id = ?" | |||
| if _, err := sess.Exec(rawSql, repoId); err != nil { | |||
| sess.Rollback() | |||
| return err | |||
| } | |||
| } | |||
| return sess.Commit() | |||
| } | |||
| @@ -34,8 +34,7 @@ func LoadModelsConfig() { | |||
| DbCfg.Path = base.Cfg.MustValue("database", "PATH", "data/gogs.db") | |||
| } | |||
| func SetEngine() { | |||
| var err error | |||
| func SetEngine() (err error) { | |||
| switch DbCfg.Type { | |||
| case "mysql": | |||
| orm, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", | |||
| @@ -47,12 +46,10 @@ func SetEngine() { | |||
| os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm) | |||
| orm, err = xorm.NewEngine("sqlite3", DbCfg.Path) | |||
| default: | |||
| fmt.Printf("Unknown database type: %s\n", DbCfg.Type) | |||
| os.Exit(2) | |||
| return fmt.Errorf("Unknown database type: %s\n", DbCfg.Type) | |||
| } | |||
| if err != nil { | |||
| fmt.Printf("models.init(fail to conntect database): %v\n", err) | |||
| os.Exit(2) | |||
| return fmt.Errorf("models.init(fail to conntect database): %v\n", err) | |||
| } | |||
| // WARNNING: for serv command, MUST remove the output to os.stdout, | |||
| @@ -62,20 +59,21 @@ func SetEngine() { | |||
| //orm.ShowErr = true | |||
| f, err := os.Create("xorm.log") | |||
| if err != nil { | |||
| fmt.Printf("models.init(fail to create xorm.log): %v\n", err) | |||
| os.Exit(2) | |||
| return fmt.Errorf("models.init(fail to create xorm.log): %v\n", err) | |||
| } | |||
| orm.Logger = f | |||
| orm.ShowSQL = true | |||
| return nil | |||
| } | |||
| func NewEngine() { | |||
| SetEngine() | |||
| if err := orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch), | |||
| func NewEngine() (err error) { | |||
| if err = SetEngine(); err != nil { | |||
| return err | |||
| } else if err = orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch), | |||
| new(Action), new(Access), new(Issue), new(Comment)); err != nil { | |||
| fmt.Printf("sync database struct error: %v\n", err) | |||
| os.Exit(2) | |||
| return fmt.Errorf("sync database struct error: %v\n", err) | |||
| } | |||
| return nil | |||
| } | |||
| type Statistic struct { | |||
| @@ -12,7 +12,6 @@ import ( | |||
| "os/exec" | |||
| "path" | |||
| "path/filepath" | |||
| "regexp" | |||
| "strings" | |||
| "time" | |||
| "unicode/utf8" | |||
| @@ -60,15 +59,6 @@ func NewRepoContext() { | |||
| os.Exit(2) | |||
| } | |||
| } | |||
| // Initialize illegal patterns. | |||
| for i := range illegalPatterns[1:] { | |||
| pattern := "" | |||
| for j := range illegalPatterns[i+1] { | |||
| pattern += "[" + string(illegalPatterns[i+1][j]-32) + string(illegalPatterns[i+1][j]) + "]" | |||
| } | |||
| illegalPatterns[i+1] = pattern | |||
| } | |||
| } | |||
| // Repository represents a git repository. | |||
| @@ -106,15 +96,20 @@ func IsRepositoryExist(user *User, repoName string) (bool, error) { | |||
| } | |||
| var ( | |||
| // Define as all lower case!! | |||
| illegalPatterns = []string{"[.][Gg][Ii][Tt]", "raw", "user", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin"} | |||
| illegalEquals = []string{"raw", "install", "api", "avatar", "user", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin"} | |||
| illegalSuffixs = []string{".git"} | |||
| ) | |||
| // IsLegalName returns false if name contains illegal characters. | |||
| func IsLegalName(repoName string) bool { | |||
| for _, pattern := range illegalPatterns { | |||
| has, _ := regexp.MatchString(pattern, repoName) | |||
| if has { | |||
| repoName = strings.ToLower(repoName) | |||
| for _, char := range illegalEquals { | |||
| if repoName == char { | |||
| return false | |||
| } | |||
| } | |||
| for _, char := range illegalSuffixs { | |||
| if strings.HasSuffix(repoName, char) { | |||
| return false | |||
| } | |||
| } | |||
| @@ -199,12 +194,19 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv | |||
| c := exec.Command("git", "update-server-info") | |||
| c.Dir = repoPath | |||
| err = c.Run() | |||
| if err != nil { | |||
| if err = c.Run(); err != nil { | |||
| log.Error("repo.CreateRepository(exec update-server-info): %v", err) | |||
| } | |||
| return repo, NewRepoAction(user, repo) | |||
| if err = NewRepoAction(user, repo); err != nil { | |||
| log.Error("repo.CreateRepository(NewRepoAction): %v", err) | |||
| } | |||
| if err = WatchRepo(user.Id, repo.Id, true); err != nil { | |||
| log.Error("repo.CreateRepository(WatchRepo): %v", err) | |||
| } | |||
| return repo, nil | |||
| } | |||
| // extractGitBareZip extracts git-bare.zip to repository path. | |||
| @@ -363,7 +365,7 @@ func GetRepos(num, offset int) ([]UserRepo, error) { | |||
| } | |||
| func RepoPath(userName, repoName string) string { | |||
| return filepath.Join(UserPath(userName), repoName+".git") | |||
| return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git") | |||
| } | |||
| func UpdateRepository(repo *Repository) error { | |||
| @@ -172,6 +172,7 @@ type InstallForm struct { | |||
| DatabasePath string `form:"database_path"` | |||
| RepoRootPath string `form:"repo_path"` | |||
| RunUser string `form:"run_user"` | |||
| Domain string `form:"domain"` | |||
| AppUrl string `form:"app_url"` | |||
| AdminName string `form:"admin_name" binding:"Required"` | |||
| AdminPasswd string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(30)"` | |||
| @@ -272,18 +272,19 @@ func NewConfigContext() { | |||
| Domain = Cfg.MustValue("server", "DOMAIN") | |||
| SecretKey = Cfg.MustValue("security", "SECRET_KEY") | |||
| InstallLock = Cfg.MustBool("security", "INSTALL_LOCK", false) | |||
| RunUser = Cfg.MustValue("", "RUN_USER") | |||
| curUser := os.Getenv("USERNAME") | |||
| if len(curUser) == 0 { | |||
| curUser = os.Getenv("USER") | |||
| } | |||
| if RunUser != curUser { | |||
| // Does not check run user when the install lock is off. | |||
| if InstallLock && RunUser != curUser { | |||
| fmt.Printf("Expect user(%s) but current user is: %s\n", RunUser, curUser) | |||
| os.Exit(2) | |||
| } | |||
| InstallLock = Cfg.MustBool("security", "INSTALL_LOCK", false) | |||
| LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS") | |||
| CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME") | |||
| CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME") | |||
| @@ -33,6 +33,13 @@ func List(l *list.List) chan interface{} { | |||
| return c | |||
| } | |||
| func ShortSha(sha1 string) string { | |||
| if len(sha1) == 40 { | |||
| return sha1[:10] | |||
| } | |||
| return sha1 | |||
| } | |||
| var mailDomains = map[string]string{ | |||
| "gmail.com": "gmail.com", | |||
| } | |||
| @@ -72,4 +79,5 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{ | |||
| }, | |||
| "DiffTypeToStr": DiffTypeToStr, | |||
| "DiffLineTypeToStr": DiffLineTypeToStr, | |||
| "ShortSha": ShortSha, | |||
| } | |||
| @@ -15,13 +15,14 @@ var ( | |||
| ) | |||
| func init() { | |||
| logger = logs.NewLogger(10000) | |||
| logger.SetLogger("console", `{"level": 0}`) | |||
| NewLogger(10000, "console", `{"level": 0}`) | |||
| } | |||
| func NewLogger(bufLen int64, mode, config string) { | |||
| Mode, Config = mode, config | |||
| logger = logs.NewLogger(bufLen) | |||
| logger.EnableFuncCallDepth(true) | |||
| logger.SetLogFuncCallDepth(4) | |||
| logger.SetLogger(mode, config) | |||
| } | |||
| @@ -18,6 +18,7 @@ import ( | |||
| "github.com/codegangsta/martini" | |||
| "github.com/gogits/cache" | |||
| "github.com/gogits/git" | |||
| "github.com/gogits/session" | |||
| "github.com/gogits/gogs/models" | |||
| @@ -41,11 +42,18 @@ type Context struct { | |||
| csrfToken string | |||
| Repo struct { | |||
| IsValid bool | |||
| IsOwner bool | |||
| IsWatching bool | |||
| IsBranch bool | |||
| IsTag bool | |||
| IsCommit bool | |||
| Repository *models.Repository | |||
| Owner *models.User | |||
| Commit *git.Commit | |||
| GitRepo *git.Repository | |||
| BranchName string | |||
| CommitId string | |||
| RepoLink string | |||
| CloneLink struct { | |||
| SSH string | |||
| HTTPS string | |||
| @@ -98,6 +106,10 @@ func (ctx *Context) Handle(status int, title string, err error) { | |||
| ctx.HTML(status, fmt.Sprintf("status/%d", status)) | |||
| } | |||
| func (ctx *Context) Debug(msg string, args ...interface{}) { | |||
| log.Debug(msg, args...) | |||
| } | |||
| func (ctx *Context) GetCookie(name string) string { | |||
| cookie, err := ctx.Req.Cookie(name) | |||
| if err != nil { | |||
| @@ -11,6 +11,8 @@ import ( | |||
| "github.com/codegangsta/martini" | |||
| "github.com/gogits/git" | |||
| "github.com/gogits/gogs/models" | |||
| "github.com/gogits/gogs/modules/base" | |||
| ) | |||
| @@ -25,8 +27,12 @@ func RepoAssignment(redirect bool) martini.Handler { | |||
| err error | |||
| ) | |||
| userName := params["username"] | |||
| repoName := params["reponame"] | |||
| branchName := params["branchname"] | |||
| // get repository owner | |||
| ctx.Repo.IsOwner = ctx.IsSigned && ctx.User.LowerName == strings.ToLower(params["username"]) | |||
| ctx.Repo.IsOwner = ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) | |||
| if !ctx.Repo.IsOwner { | |||
| user, err = models.GetUserByName(params["username"]) | |||
| @@ -51,10 +57,8 @@ func RepoAssignment(redirect bool) martini.Handler { | |||
| return | |||
| } | |||
| ctx.Repo.Owner = user | |||
| // get repository | |||
| repo, err := models.GetRepositoryByName(user.Id, params["reponame"]) | |||
| repo, err := models.GetRepositoryByName(user.Id, repoName) | |||
| if err != nil { | |||
| if err == models.ErrRepoNotExist { | |||
| ctx.Handle(404, "RepoAssignment", err) | |||
| @@ -62,29 +66,69 @@ func RepoAssignment(redirect bool) martini.Handler { | |||
| ctx.Redirect("/") | |||
| return | |||
| } | |||
| ctx.Handle(200, "RepoAssignment", err) | |||
| ctx.Handle(404, "RepoAssignment", err) | |||
| return | |||
| } | |||
| ctx.Repo.Repository = repo | |||
| gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName)) | |||
| if err != nil { | |||
| ctx.Handle(404, "RepoAssignment Invalid repo", err) | |||
| return | |||
| } | |||
| ctx.Repo.GitRepo = gitRepo | |||
| detect: | |||
| if len(branchName) > 0 { | |||
| // TODO check tag | |||
| if models.IsBranchExist(user.Name, repoName, branchName) { | |||
| ctx.Repo.IsBranch = true | |||
| ctx.Repo.BranchName = branchName | |||
| ctx.Repo.Commit, err = gitRepo.GetCommitOfBranch(branchName) | |||
| if err != nil { | |||
| ctx.Handle(404, "RepoAssignment invalid branch", nil) | |||
| return | |||
| } | |||
| ctx.Repo.CommitId = ctx.Repo.Commit.Oid.String() | |||
| } else if len(branchName) == 40 { | |||
| ctx.Repo.IsCommit = true | |||
| ctx.Repo.CommitId = branchName | |||
| ctx.Repo.BranchName = branchName | |||
| ctx.Repo.Commit, err = gitRepo.GetCommit(branchName) | |||
| if err != nil { | |||
| ctx.Handle(404, "RepoAssignment invalid commit", nil) | |||
| return | |||
| } | |||
| } else { | |||
| ctx.Handle(404, "RepoAssignment invalid repo", nil) | |||
| return | |||
| } | |||
| } else { | |||
| branchName = "master" | |||
| goto detect | |||
| } | |||
| ctx.Repo.IsValid = true | |||
| if ctx.User != nil { | |||
| if ctx.IsSigned { | |||
| ctx.Repo.IsWatching = models.IsWatching(ctx.User.Id, repo.Id) | |||
| } | |||
| ctx.Repo.Repository = repo | |||
| ctx.Repo.Owner = user | |||
| ctx.Repo.CloneLink.SSH = fmt.Sprintf("%s@%s:%s/%s.git", base.RunUser, base.Domain, user.LowerName, repo.LowerName) | |||
| ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", base.AppUrl, user.LowerName, repo.LowerName) | |||
| ctx.Repo.RepoLink = "/" + user.Name + "/" + repo.Name | |||
| if len(params["branchname"]) == 0 { | |||
| params["branchname"] = "master" | |||
| } | |||
| ctx.Data["Branchname"] = params["branchname"] | |||
| ctx.Data["IsRepositoryValid"] = true | |||
| ctx.Data["BranchName"] = ctx.Repo.BranchName | |||
| ctx.Data["CommitId"] = ctx.Repo.CommitId | |||
| ctx.Data["Repository"] = repo | |||
| ctx.Data["Owner"] = user | |||
| ctx.Data["Title"] = user.Name + "/" + repo.Name | |||
| ctx.Data["CloneLink"] = ctx.Repo.CloneLink | |||
| ctx.Data["RepositoryLink"] = ctx.Data["Title"] | |||
| ctx.Data["RepoLink"] = ctx.Repo.RepoLink | |||
| ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner | |||
| ctx.Data["IsRepositoryWatching"] = ctx.Repo.IsWatching | |||
| } | |||
| @@ -1197,13 +1197,13 @@ html, body { | |||
| line-height: 42px; | |||
| } | |||
| #issue .issue-closed{ | |||
| border-bottom: 3px solid #CCC; | |||
| #issue .issue-closed, #issue .issue-opened { | |||
| border-bottom: 2px solid #CCC; | |||
| margin-bottom: 24px; | |||
| padding-bottom: 24px; | |||
| } | |||
| #issue .issue-closed .btn-danger,#issue .issue-opened .btn-success{ | |||
| #issue .issue-closed .label-danger,#issue .issue-opened .label-success{ | |||
| margin: 0 .8em; | |||
| } | |||
| @@ -56,6 +56,43 @@ var Gogits = { | |||
| }, | |||
| toggleShow: function () { | |||
| $(this).removeClass("hidden"); | |||
| }, | |||
| toggleAjax: function (successCallback) { | |||
| var url = $(this).data("ajax"); | |||
| var method = $(this).data('ajax-method') || 'get'; | |||
| var ajaxName = $(this).data('ajax-name'); | |||
| var data = {}; | |||
| $('[data-ajax-rel=' + ajaxName + ']').each(function () { | |||
| var field = $(this).data("ajax-field"); | |||
| var t = $(this).data("ajax-val"); | |||
| if (t == "val") { | |||
| data[field] = $(this).val(); | |||
| return true; | |||
| } | |||
| if (t == "txt") { | |||
| data[field] = $(this).text(); | |||
| return true; | |||
| } | |||
| if (t == "html") { | |||
| data[field] = $(this).html(); | |||
| return true; | |||
| } | |||
| if (t == "data") { | |||
| data[field] = $(this).data("ajax-data"); | |||
| return true; | |||
| } | |||
| return true; | |||
| }); | |||
| $.ajax({ | |||
| url: url, | |||
| method: method.toUpperCase(), | |||
| data: data, | |||
| success: function (d) { | |||
| if (successCallback) { | |||
| successCallback(d); | |||
| } | |||
| } | |||
| }) | |||
| } | |||
| }) | |||
| }(jQuery)); | |||
| @@ -362,21 +399,24 @@ function initRepository() { | |||
| function initInstall() { | |||
| // database type change | |||
| $('#install-database').on("change", function () { | |||
| var val = $(this).val(); | |||
| if (val != "sqlite") { | |||
| $('.server-sql').show(); | |||
| $('.sqlite-setting').addClass("hide"); | |||
| if (val == "pgsql") { | |||
| $('.pgsql-setting').removeClass("hide"); | |||
| (function () { | |||
| $('#install-database').on("change", function () { | |||
| var val = $(this).val(); | |||
| if (val != "sqlite") { | |||
| $('.server-sql').show(); | |||
| $('.sqlite-setting').addClass("hide"); | |||
| if (val == "pgsql") { | |||
| $('.pgsql-setting').removeClass("hide"); | |||
| } else { | |||
| $('.pgsql-setting').addClass("hide"); | |||
| } | |||
| } else { | |||
| $('.pgsql-setting').addClass("hide"); | |||
| $('.server-sql').hide(); | |||
| $('.sqlite-setting').removeClass("hide"); | |||
| } | |||
| } else { | |||
| $('.server-sql').hide(); | |||
| $('.sqlite-setting').removeClass("hide"); | |||
| } | |||
| }); | |||
| }); | |||
| }()); | |||
| } | |||
| function initIssue() { | |||
| @@ -386,11 +426,11 @@ function initIssue() { | |||
| var $openBtn = $('#issue-open-btn'); | |||
| $('#issue-reply-content').on("keyup", function () { | |||
| if ($(this).val().length) { | |||
| $closeBtn.text($closeBtn.data("text")); | |||
| $openBtn.text($openBtn.data("text")); | |||
| $closeBtn.val($closeBtn.data("text")); | |||
| $openBtn.val($openBtn.data("text")); | |||
| } else { | |||
| $closeBtn.text($closeBtn.data("origin")); | |||
| $openBtn.text($openBtn.data("origin")); | |||
| $closeBtn.val($closeBtn.data("origin")); | |||
| $openBtn.val($openBtn.data("origin")); | |||
| } | |||
| }); | |||
| }()); | |||
| @@ -406,6 +446,35 @@ function initIssue() { | |||
| $('#issue-edit-title,#issue-edit-content,.issue-edit-cancel,.issue-edit-save').toggleHide(); | |||
| }) | |||
| }()); | |||
| // issue ajax update | |||
| (function () { | |||
| $('.issue-edit-save').on("click", function () { | |||
| $(this).toggleAjax(function (json) { | |||
| if (json.ok) { | |||
| $('.issue-head h1.title').text(json.title); | |||
| $('.issue-main > .issue-content .content').html(json.content); | |||
| $('.issue-edit-cancel').trigger("click"); | |||
| } | |||
| }); | |||
| }); | |||
| }()); | |||
| // issue ajax preview | |||
| (function () { | |||
| $('[data-ajax-name=issue-preview]').on("click", function () { | |||
| var $this = $(this); | |||
| $this.toggleAjax(function (json) { | |||
| if (json.ok) { | |||
| $($this.data("preview")).html(json.content); | |||
| } | |||
| }) | |||
| }); | |||
| $('.issue-write a[data-toggle]').on("click", function () { | |||
| $('.issue-preview-content').html("loading..."); | |||
| }); | |||
| }()) | |||
| } | |||
| (function ($) { | |||
| @@ -0,0 +1,18 @@ | |||
| // Copyright 2014 The Gogs 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 v1 | |||
| import ( | |||
| "github.com/gogits/gogs/modules/base" | |||
| "github.com/gogits/gogs/modules/middleware" | |||
| ) | |||
| func Markdown(ctx *middleware.Context) { | |||
| content := ctx.Query("content") | |||
| ctx.Render.JSON(200, map[string]interface{}{ | |||
| "ok": true, | |||
| "content": string(base.RenderMarkdown([]byte(content), "")), | |||
| }) | |||
| } | |||
| @@ -6,13 +6,46 @@ package routers | |||
| import ( | |||
| "errors" | |||
| "os" | |||
| "strings" | |||
| "github.com/Unknwon/goconfig" | |||
| "github.com/codegangsta/martini" | |||
| "github.com/gogits/gogs/models" | |||
| "github.com/gogits/gogs/modules/auth" | |||
| "github.com/gogits/gogs/modules/base" | |||
| "github.com/gogits/gogs/modules/log" | |||
| "github.com/gogits/gogs/modules/mailer" | |||
| "github.com/gogits/gogs/modules/middleware" | |||
| ) | |||
| // Check run mode(Default of martini is Dev). | |||
| func checkRunMode() { | |||
| switch base.Cfg.MustValue("", "RUN_MODE") { | |||
| case "prod": | |||
| martini.Env = martini.Prod | |||
| case "test": | |||
| martini.Env = martini.Test | |||
| } | |||
| log.Info("Run Mode: %s", strings.Title(martini.Env)) | |||
| } | |||
| // GlobalInit is for global configuration reload-able. | |||
| func GlobalInit() { | |||
| base.NewConfigContext() | |||
| mailer.NewMailerContext() | |||
| models.LoadModelsConfig() | |||
| models.LoadRepoConfig() | |||
| models.NewRepoContext() | |||
| if err := models.NewEngine(); err != nil && base.InstallLock { | |||
| log.Error("%v", err) | |||
| os.Exit(2) | |||
| } | |||
| base.NewServices() | |||
| checkRunMode() | |||
| } | |||
| func Install(ctx *middleware.Context, form auth.InstallForm) { | |||
| if base.InstallLock { | |||
| ctx.Handle(404, "install.Install", errors.New("Installation is prohibited")) | |||
| @@ -20,14 +53,114 @@ func Install(ctx *middleware.Context, form auth.InstallForm) { | |||
| } | |||
| ctx.Data["Title"] = "Install" | |||
| ctx.Data["DbCfg"] = models.DbCfg | |||
| ctx.Data["RepoRootPath"] = base.RepoRootPath | |||
| ctx.Data["RunUser"] = base.RunUser | |||
| ctx.Data["AppUrl"] = base.AppUrl | |||
| ctx.Data["PageIsInstall"] = true | |||
| // Get and assign value to install form. | |||
| if len(form.Host) == 0 { | |||
| form.Host = models.DbCfg.Host | |||
| } | |||
| if len(form.User) == 0 { | |||
| form.User = models.DbCfg.User | |||
| } | |||
| if len(form.Passwd) == 0 { | |||
| form.Passwd = models.DbCfg.Pwd | |||
| } | |||
| if len(form.DatabaseName) == 0 { | |||
| form.DatabaseName = models.DbCfg.Name | |||
| } | |||
| if len(form.DatabasePath) == 0 { | |||
| form.DatabasePath = models.DbCfg.Path | |||
| } | |||
| if len(form.RepoRootPath) == 0 { | |||
| form.RepoRootPath = base.RepoRootPath | |||
| } | |||
| if len(form.RunUser) == 0 { | |||
| form.RunUser = base.RunUser | |||
| } | |||
| if len(form.Domain) == 0 { | |||
| form.Domain = base.Domain | |||
| } | |||
| if len(form.AppUrl) == 0 { | |||
| form.AppUrl = base.AppUrl | |||
| } | |||
| auth.AssignForm(form, ctx.Data) | |||
| if ctx.Req.Method == "GET" { | |||
| ctx.HTML(200, "install") | |||
| return | |||
| } | |||
| if ctx.HasError() { | |||
| ctx.HTML(200, "install") | |||
| return | |||
| } | |||
| // Pass basic check, now test configuration. | |||
| // Test database setting. | |||
| dbTypes := map[string]string{"mysql": "mysql", "pgsql": "postgres", "sqlite": "sqlite3"} | |||
| models.DbCfg.Type = dbTypes[form.Database] | |||
| models.DbCfg.Host = form.Host | |||
| models.DbCfg.User = form.User | |||
| models.DbCfg.Pwd = form.Passwd | |||
| models.DbCfg.Name = form.DatabaseName | |||
| models.DbCfg.SslMode = form.SslMode | |||
| models.DbCfg.Path = form.DatabasePath | |||
| if err := models.NewEngine(); err != nil { | |||
| ctx.RenderWithErr("Database setting is not correct: "+err.Error(), "install", &form) | |||
| return | |||
| } | |||
| // Test repository root path. | |||
| if err := os.MkdirAll(form.RepoRootPath, os.ModePerm); err != nil { | |||
| ctx.RenderWithErr("Repository root path is invalid: "+err.Error(), "install", &form) | |||
| return | |||
| } | |||
| // Create admin account. | |||
| if _, err := models.RegisterUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd, | |||
| IsAdmin: true, IsActive: true}); err != nil { | |||
| if err != models.ErrUserAlreadyExist { | |||
| ctx.RenderWithErr("Admin account setting is invalid: "+err.Error(), "install", &form) | |||
| return | |||
| } | |||
| } | |||
| // Save settings. | |||
| base.Cfg.SetValue("database", "DB_TYPE", models.DbCfg.Type) | |||
| base.Cfg.SetValue("database", "HOST", models.DbCfg.Host) | |||
| base.Cfg.SetValue("database", "NAME", models.DbCfg.Name) | |||
| base.Cfg.SetValue("database", "USER", models.DbCfg.User) | |||
| base.Cfg.SetValue("database", "PASSWD", models.DbCfg.Pwd) | |||
| base.Cfg.SetValue("database", "SSL_MODE", models.DbCfg.SslMode) | |||
| base.Cfg.SetValue("database", "PATH", models.DbCfg.Path) | |||
| base.Cfg.SetValue("repository", "ROOT", form.RepoRootPath) | |||
| base.Cfg.SetValue("", "RUN_USER", form.RunUser) | |||
| base.Cfg.SetValue("server", "DOMAIN", form.Domain) | |||
| base.Cfg.SetValue("server", "ROOT_URL", form.AppUrl) | |||
| if len(form.Host) > 0 { | |||
| base.Cfg.SetValue("mailer", "ENABLED", "true") | |||
| base.Cfg.SetValue("mailer", "HOST", form.SmtpHost) | |||
| base.Cfg.SetValue("mailer", "USER", form.SmtpEmail) | |||
| base.Cfg.SetValue("mailer", "PASSWD", form.SmtpPasswd) | |||
| base.Cfg.SetValue("service", "REGISTER_EMAIL_CONFIRM", base.ToStr(form.RegisterConfirm == "on")) | |||
| base.Cfg.SetValue("service", "ENABLE_NOTIFY_MAIL", base.ToStr(form.MailNotify == "on")) | |||
| } | |||
| base.Cfg.SetValue("security", "INSTALL_LOCK", "true") | |||
| if err := goconfig.SaveConfigFile(base.Cfg, "custom/conf/app.ini"); err != nil { | |||
| ctx.RenderWithErr("Fail to save configuration: "+err.Error(), "install", &form) | |||
| return | |||
| } | |||
| GlobalInit() | |||
| log.Info("First-time run install finished!") | |||
| ctx.Redirect("/user/login") | |||
| } | |||
| @@ -11,21 +11,15 @@ import ( | |||
| ) | |||
| func Branches(ctx *middleware.Context, params martini.Params) { | |||
| if !ctx.Repo.IsValid { | |||
| return | |||
| } | |||
| brs, err := models.GetBranches(params["username"], params["reponame"]) | |||
| brs, err := models.GetBranches(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) | |||
| if err != nil { | |||
| ctx.Handle(200, "repo.Branches", err) | |||
| ctx.Handle(404, "repo.Branches", err) | |||
| return | |||
| } else if len(brs) == 0 { | |||
| ctx.Handle(404, "repo.Branches", nil) | |||
| return | |||
| } | |||
| ctx.Data["Username"] = params["username"] | |||
| ctx.Data["Reponame"] = params["reponame"] | |||
| ctx.Data["Branches"] = brs | |||
| ctx.Data["IsRepoToolbarBranches"] = true | |||
| @@ -50,16 +50,12 @@ func Commits(ctx *middleware.Context, params martini.Params) { | |||
| } | |||
| func Diff(ctx *middleware.Context, params martini.Params) { | |||
| userName := params["username"] | |||
| repoName := params["reponame"] | |||
| branchName := params["branchname"] | |||
| commitId := params["commitid"] | |||
| userName := ctx.Repo.Owner.Name | |||
| repoName := ctx.Repo.Repository.Name | |||
| branchName := ctx.Repo.BranchName | |||
| commitId := ctx.Repo.CommitId | |||
| commit, err := models.GetCommit(userName, repoName, branchName, commitId) | |||
| if err != nil { | |||
| ctx.Handle(404, "repo.Diff", err) | |||
| return | |||
| } | |||
| commit := ctx.Repo.Commit | |||
| diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId) | |||
| if err != nil { | |||
| @@ -85,11 +81,9 @@ func Diff(ctx *middleware.Context, params martini.Params) { | |||
| return isImage | |||
| } | |||
| shortSha := params["commitid"][:10] | |||
| ctx.Data["IsImageFile"] = isImageFile | |||
| ctx.Data["Title"] = commit.Message() + " · " + shortSha | |||
| ctx.Data["Title"] = commit.Message() + " · " + base.ShortSha(commitId) | |||
| ctx.Data["Commit"] = commit | |||
| ctx.Data["ShortSha"] = shortSha | |||
| ctx.Data["Diff"] = diff | |||
| ctx.Data["IsRepoToolbarCommits"] = true | |||
| ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", commitId) | |||
| @@ -7,6 +7,7 @@ package repo | |||
| import ( | |||
| "fmt" | |||
| "net/url" | |||
| "strings" | |||
| "github.com/codegangsta/martini" | |||
| @@ -19,10 +20,6 @@ import ( | |||
| ) | |||
| func Issues(ctx *middleware.Context) { | |||
| if !ctx.Repo.IsValid { | |||
| ctx.Handle(404, "issue.Issues(invalid repo):", nil) | |||
| } | |||
| ctx.Data["Title"] = "Issues" | |||
| ctx.Data["IsRepoToolbarIssues"] = true | |||
| ctx.Data["IsRepoToolbarIssuesList"] = true | |||
| @@ -79,10 +76,6 @@ func Issues(ctx *middleware.Context) { | |||
| } | |||
| func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) { | |||
| if !ctx.Repo.IsValid { | |||
| ctx.Handle(404, "issue.CreateIssue(invalid repo):", nil) | |||
| } | |||
| ctx.Data["Title"] = "Create issue" | |||
| ctx.Data["IsRepoToolbarIssues"] = true | |||
| ctx.Data["IsRepoToolbarIssuesList"] = false | |||
| @@ -125,10 +118,6 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat | |||
| } | |||
| func ViewIssue(ctx *middleware.Context, params martini.Params) { | |||
| if !ctx.Repo.IsValid { | |||
| ctx.Handle(404, "issue.ViewIssue(invalid repo):", nil) | |||
| } | |||
| index, err := base.StrTo(params["index"]).Int() | |||
| if err != nil { | |||
| ctx.Handle(404, "issue.ViewIssue", err) | |||
| @@ -152,7 +141,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) { | |||
| return | |||
| } | |||
| issue.Poster = u | |||
| issue.Content = string(base.RenderMarkdown([]byte(issue.Content), "")) | |||
| issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), "")) | |||
| // Get comments. | |||
| comments, err := models.GetIssueComments(issue.Id) | |||
| @@ -175,16 +164,13 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) { | |||
| ctx.Data["Title"] = issue.Name | |||
| ctx.Data["Issue"] = issue | |||
| ctx.Data["Comments"] = comments | |||
| ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id | |||
| ctx.Data["IsRepoToolbarIssues"] = true | |||
| ctx.Data["IsRepoToolbarIssuesList"] = false | |||
| ctx.HTML(200, "issue/view") | |||
| } | |||
| func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) { | |||
| if !ctx.Repo.IsValid { | |||
| ctx.Handle(404, "issue.UpdateIssue(invalid repo):", nil) | |||
| } | |||
| index, err := base.StrTo(params["index"]).Int() | |||
| if err != nil { | |||
| ctx.Handle(404, "issue.UpdateIssue", err) | |||
| @@ -216,29 +202,21 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat | |||
| return | |||
| } | |||
| ctx.Data["Title"] = issue.Name | |||
| ctx.Data["Issue"] = issue | |||
| ctx.JSON(200, map[string]interface{}{ | |||
| "ok": true, | |||
| "title": issue.Name, | |||
| "content": string(base.RenderMarkdown([]byte(issue.Content), "")), | |||
| }) | |||
| } | |||
| func Comment(ctx *middleware.Context, params martini.Params) { | |||
| fmt.Println(ctx.Query("change_status")) | |||
| if !ctx.Repo.IsValid { | |||
| ctx.Handle(404, "issue.Comment(invalid repo):", nil) | |||
| } | |||
| index, err := base.StrTo(ctx.Query("issueIndex")).Int() | |||
| index, err := base.StrTo(ctx.Query("issueIndex")).Int64() | |||
| if err != nil { | |||
| ctx.Handle(404, "issue.Comment", err) | |||
| ctx.Handle(404, "issue.Comment(get index)", err) | |||
| return | |||
| } | |||
| content := ctx.Query("content") | |||
| if len(content) == 0 { | |||
| ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", ctx.User.Name, ctx.Repo.Repository.Name, index)) | |||
| return | |||
| } | |||
| issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, int64(index)) | |||
| issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index) | |||
| if err != nil { | |||
| if err == models.ErrIssueNotExist { | |||
| ctx.Handle(404, "issue.Comment", err) | |||
| @@ -248,16 +226,46 @@ func Comment(ctx *middleware.Context, params martini.Params) { | |||
| return | |||
| } | |||
| switch params["action"] { | |||
| case "new": | |||
| if err = models.CreateComment(ctx.User.Id, issue.Id, 0, 0, content); err != nil { | |||
| ctx.Handle(500, "issue.Comment(create comment)", err) | |||
| // Check if issue owner changes the status of issue. | |||
| var newStatus string | |||
| if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id { | |||
| newStatus = ctx.Query("change_status") | |||
| } | |||
| if len(newStatus) > 0 { | |||
| if (strings.Contains(newStatus, "Reopen") && issue.IsClosed) || | |||
| (strings.Contains(newStatus, "Close") && !issue.IsClosed) { | |||
| issue.IsClosed = !issue.IsClosed | |||
| if err = models.UpdateIssue(issue); err != nil { | |||
| ctx.Handle(200, "issue.Comment(update issue status)", err) | |||
| return | |||
| } | |||
| cmtType := models.IT_CLOSE | |||
| if !issue.IsClosed { | |||
| cmtType = models.IT_REOPEN | |||
| } | |||
| if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, ""); err != nil { | |||
| ctx.Handle(200, "issue.Comment(create status change comment)", err) | |||
| return | |||
| } | |||
| log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed) | |||
| } | |||
| } | |||
| content := ctx.Query("content") | |||
| if len(content) > 0 { | |||
| switch params["action"] { | |||
| case "new": | |||
| if err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.IT_PLAIN, content); err != nil { | |||
| ctx.Handle(500, "issue.Comment(create comment)", err) | |||
| return | |||
| } | |||
| log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id) | |||
| default: | |||
| ctx.Handle(404, "issue.Comment", err) | |||
| return | |||
| } | |||
| log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id) | |||
| default: | |||
| ctx.Handle(404, "issue.Comment", err) | |||
| return | |||
| } | |||
| ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", ctx.User.Name, ctx.Repo.Repository.Name, index)) | |||
| @@ -53,20 +53,20 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) { | |||
| } | |||
| func Single(ctx *middleware.Context, params martini.Params) { | |||
| if !ctx.Repo.IsValid { | |||
| return | |||
| } | |||
| branchName := ctx.Repo.BranchName | |||
| commitId := ctx.Repo.CommitId | |||
| userName := ctx.Repo.Owner.Name | |||
| repoName := ctx.Repo.Repository.Name | |||
| branchName := params["branchname"] | |||
| userName := params["username"] | |||
| repoName := params["reponame"] | |||
| repoLink := ctx.Repo.RepoLink | |||
| branchLink := ctx.Repo.RepoLink + "/src/" + branchName | |||
| rawLink := ctx.Repo.RepoLink + "/raw/" + branchName | |||
| // Get tree path | |||
| treename := params["_1"] | |||
| if len(treename) > 0 && treename[len(treename)-1] == '/' { | |||
| ctx.Redirect("/" + ctx.Repo.Owner.LowerName + "/" + | |||
| ctx.Repo.Repository.Name + "/src/" + branchName + "/" + treename[:len(treename)-1]) | |||
| ctx.Redirect(repoLink + "/src/" + branchName + "/" + treename[:len(treename)-1]) | |||
| return | |||
| } | |||
| @@ -84,23 +84,17 @@ func Single(ctx *middleware.Context, params martini.Params) { | |||
| } | |||
| ctx.Data["Branches"] = brs | |||
| var commitId string | |||
| isViewBranch := models.IsBranchExist(userName, repoName, branchName) | |||
| if !isViewBranch { | |||
| commitId = branchName | |||
| } | |||
| isViewBranch := ctx.Repo.IsBranch | |||
| ctx.Data["IsViewBranch"] = isViewBranch | |||
| repoFile, err := models.GetTargetFile(userName, repoName, | |||
| branchName, commitId, treename) | |||
| if err != nil && err != models.ErrRepoFileNotExist { | |||
| ctx.Handle(404, "repo.Single(GetTargetFile)", err) | |||
| return | |||
| } | |||
| branchLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/src/" + branchName | |||
| rawLink := "/" + ctx.Repo.Owner.LowerName + "/" + ctx.Repo.Repository.Name + "/raw/" + branchName | |||
| if len(treename) != 0 && repoFile == nil { | |||
| ctx.Handle(404, "repo.Single", nil) | |||
| return | |||
| @@ -142,8 +136,7 @@ func Single(ctx *middleware.Context, params martini.Params) { | |||
| } else { | |||
| // Directory and file list. | |||
| files, err := models.GetReposFiles(userName, repoName, | |||
| branchName, commitId, treename) | |||
| files, err := models.GetReposFiles(userName, repoName, ctx.Repo.CommitId, treename) | |||
| if err != nil { | |||
| ctx.Handle(404, "repo.Single(GetReposFiles)", err) | |||
| return | |||
| @@ -200,18 +193,7 @@ func Single(ctx *middleware.Context, params martini.Params) { | |||
| } | |||
| } | |||
| // Get latest commit according username and repo name. | |||
| commit, err := models.GetCommit(userName, repoName, | |||
| branchName, commitId) | |||
| if err != nil { | |||
| log.Error("repo.Single(GetCommit): %v", err) | |||
| ctx.Handle(404, "repo.Single(GetCommit)", err) | |||
| return | |||
| } | |||
| ctx.Data["LastCommit"] = commit | |||
| ctx.Data["CommitId"] = commitId | |||
| ctx.Data["LastCommit"] = ctx.Repo.Commit | |||
| ctx.Data["Paths"] = Paths | |||
| ctx.Data["Treenames"] = treenames | |||
| ctx.Data["BranchLink"] = branchLink | |||
| @@ -219,11 +201,6 @@ func Single(ctx *middleware.Context, params martini.Params) { | |||
| } | |||
| func SingleDownload(ctx *middleware.Context, params martini.Params) { | |||
| if !ctx.Repo.IsValid { | |||
| ctx.Handle(404, "repo.SingleDownload", nil) | |||
| return | |||
| } | |||
| // Get tree path | |||
| treename := params["_1"] | |||
| @@ -263,10 +240,6 @@ func SingleDownload(ctx *middleware.Context, params martini.Params) { | |||
| } | |||
| func Http(ctx *middleware.Context, params martini.Params) { | |||
| /*if !ctx.Repo.IsValid { | |||
| return | |||
| }*/ | |||
| // TODO: access check | |||
| username := params["username"] | |||
| @@ -120,7 +120,7 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) { | |||
| return | |||
| } | |||
| if hasErr, ok := ctx.Data["HasError"]; ok && hasErr.(bool) { | |||
| if ctx.HasError() { | |||
| ctx.HTML(200, "user/signin") | |||
| return | |||
| } | |||
| @@ -308,17 +308,19 @@ func Issues(ctx *middleware.Context) { | |||
| showRepos := make([]models.Repository, 0, len(repos)) | |||
| var closedIssueCount, createdByCount int | |||
| isShowClosed := ctx.Query("state") == "closed" | |||
| var closedIssueCount, createdByCount, allIssueCount int | |||
| // Get all issues. | |||
| allIssues := make([]models.Issue, 0, 5*len(repos)) | |||
| for i, repo := range repos { | |||
| issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, false, false, "", "") | |||
| issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, false, "", "") | |||
| if err != nil { | |||
| ctx.Handle(200, "user.Issues(get issues)", err) | |||
| return | |||
| } | |||
| allIssueCount += repo.NumIssues | |||
| closedIssueCount += repo.NumClosedIssues | |||
| // Set repository information to issues. | |||
| @@ -330,12 +332,10 @@ func Issues(ctx *middleware.Context) { | |||
| repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues | |||
| if repos[i].NumOpenIssues > 0 { | |||
| showRepos = append(showRepos, repos[i]) | |||
| } | |||
| } | |||
| showIssues := make([]models.Issue, 0, len(allIssues)) | |||
| isShowClosed := ctx.Query("state") == "closed" | |||
| ctx.Data["IsShowClosed"] = isShowClosed | |||
| // Get posters and filter issues. | |||
| @@ -361,9 +361,9 @@ func Issues(ctx *middleware.Context) { | |||
| ctx.Data["Repos"] = showRepos | |||
| ctx.Data["Issues"] = showIssues | |||
| ctx.Data["AllIssueCount"] = len(allIssues) | |||
| ctx.Data["AllIssueCount"] = allIssueCount | |||
| ctx.Data["ClosedIssueCount"] = closedIssueCount | |||
| ctx.Data["OpenIssueCount"] = len(allIssues) - closedIssueCount | |||
| ctx.Data["OpenIssueCount"] = allIssueCount - closedIssueCount | |||
| ctx.Data["CreatedByCount"] = createdByCount | |||
| ctx.HTML(200, "issue/user") | |||
| } | |||
| @@ -3,9 +3,8 @@ | |||
| <form action="/install" method="post" class="form-horizontal card" id="install-card"> | |||
| {{.CsrfTokenHtml}} | |||
| <h3>Install Steps For First-time Run</h3> | |||
| <div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div> | |||
| <p class="help-block text-center">Gogs requires MySQL or PostgreSQL based on your choice</p> | |||
| <p class="help-block text-center">Gogs requires MySQL or PostgreSQL, SQLite3 only available for official binary version</p> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">Database Type: </label> | |||
| <div class="col-md-8"> | |||
| @@ -16,26 +15,28 @@ | |||
| </select> | |||
| </div> | |||
| </div> | |||
| <div class="server-sql"> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">Host: </label> | |||
| <div class="col-md-8"> | |||
| <input name="host" class="form-control" placeholder="Type database server host, leave blank to keep default" value="{{.DbCfg.Host}}" required="required"> | |||
| <input name="host" class="form-control" placeholder="Type database server host" value="{{.host}}" required="required"> | |||
| </div> | |||
| </div> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">User: </label> | |||
| <div class="col-md-8"> | |||
| <input name="user" class="form-control" placeholder="Type database username" required="required" value="{{.DbCfg.User}}"> | |||
| <input name="user" class="form-control" placeholder="Type database username" required="required" value="{{.user}}"> | |||
| </div> | |||
| </div> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">Password: </label> | |||
| <div class="col-md-8"> | |||
| <input name="passwd" type="password" class="form-control" placeholder="Type database password" required="required" value="{{.DbCfg.Pwd}}"> | |||
| <input name="passwd" type="password" class="form-control" placeholder="Type database password" required="required" value="{{.passwd}}"> | |||
| </div> | |||
| </div> | |||
| @@ -43,7 +44,7 @@ | |||
| <label class="col-md-3 control-label">Database Name: </label> | |||
| <div class="col-md-8"> | |||
| <input name="database_name" type="text" class="form-control" placeholder="Type mysql database name" value="{{.DbCfg.Name}}" required="required"> | |||
| <input name="database_name" type="text" class="form-control" placeholder="Type mysql database name" value="{{.database_name}}" required="required"> | |||
| <p class="help-block">Recommend use INNODB engine with utf8_general_ci charset.</p> | |||
| </div> | |||
| </div> | |||
| @@ -59,12 +60,13 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="sqlite-setting hide"> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">Path: </label> | |||
| <div class="col-md-8"> | |||
| <input name="database_path" class="form-control" placeholder="Type sqlite3 file path" value="{{.DbCfg.Path}}"> | |||
| <input name="database_path" class="form-control" placeholder="Type sqlite3 file path" value="{{.database_path}}"> | |||
| <p class="help-block">The file path of SQLite3 database.</p> | |||
| </div> | |||
| </div> | |||
| @@ -73,12 +75,11 @@ | |||
| <hr/> | |||
| <p class="help-block text-center">General Settings of Gogs</p> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">Repository Path: </label> | |||
| <div class="col-md-8"> | |||
| <input name="repo_path" type="text" class="form-control" placeholder="Type your repository directory" value="{{.RepoRootPath}}" required="required"> | |||
| <input name="repo_path" type="text" class="form-control" placeholder="Type your repository directory" value="{{.repo_path}}" required="required"> | |||
| <p class="help-block">The git copy of each repository is saved in this directory.</p> | |||
| </div> | |||
| @@ -88,16 +89,25 @@ | |||
| <label class="col-md-3 control-label">Run User: </label> | |||
| <div class="col-md-8"> | |||
| <input name="run_user" type="text" class="form-control" placeholder="Type system user name" value="{{.RunUser}}" required="required"> | |||
| <input name="run_user" type="text" class="form-control" placeholder="Type system user name" value="{{.run_user}}" required="required"> | |||
| <p class="help-block">The user has access to visit and run Gogs.</p> | |||
| </div> | |||
| </div> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">Domain: </label> | |||
| <div class="col-md-8"> | |||
| <input name="domain" type="text" class="form-control" placeholder="Type your domain name" value="{{.domain}}" required="required"> | |||
| <p class="help-block">This affects SSH clone URL.</p> | |||
| </div> | |||
| </div> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">App URL: </label> | |||
| <div class="col-md-8"> | |||
| <input name="app_url" type="text" class="form-control" placeholder="Type app root URL " value="{{.AppUrl}}" required="required"> | |||
| <input name="app_url" type="text" class="form-control" placeholder="Type app root URL" value="{{.app_url}}" required="required"> | |||
| <p class="help-block">This affects HTTP/HTTPS clone URL and somewhere in e-mail.</p> | |||
| </div> | |||
| </div> | |||
| @@ -105,35 +115,30 @@ | |||
| <hr/> | |||
| <p class="help-block text-center">Admin Account Settings</p> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">Username: </label> | |||
| <div class="col-md-8"> | |||
| <input name="admin_name" type="text" class="form-control" placeholder="Type admin user name" value="admin" required="required"> | |||
| <input name="admin_name" type="text" class="form-control" placeholder="Type admin user name" value="{{.admin_name}}" required="required"> | |||
| </div> | |||
| </div> | |||
| <div class="form-group"> | |||
| <div class="form-group {{if .Err_AdminPasswd}}has-error has-feedback{{end}}"> | |||
| <label class="col-md-3 control-label">Password: </label> | |||
| <div class="col-md-8"> | |||
| <input name="admin_pwd" type="password" class="form-control" placeholder="Type admin user password" required="required"> | |||
| <input name="admin_pwd" type="password" class="form-control" placeholder="Type admin user password" value="{{.admin_pwd}}" required="required"> | |||
| </div> | |||
| </div> | |||
| <div class="form-group"> | |||
| <div class="form-group {{if .Err_AdminEmail}}has-error has-feedback{{end}}"> | |||
| <label class="col-md-3 control-label">E-mail: </label> | |||
| <div class="col-md-8"> | |||
| <input name="admin_email" type="text" class="form-control" placeholder="Type admin user e-mail" required="required"> | |||
| <input name="admin_email" type="text" class="form-control" placeholder="Type admin user e-mail" value="{{.admin_email}}" required="required"> | |||
| </div> | |||
| </div> | |||
| <hr/> | |||
| <div class="form-group text-center"> | |||
| <button class="btn btn-primary btn-lg">Test Configuration</button> | |||
| <button class="btn btn-danger btn-lg">Install Gogs</button> | |||
| <button class="btn btn-default btn-sm" type="button" data-toggle="modal" data-target="#advance-options-modal"> | |||
| Advanced Options | |||
| @@ -151,21 +156,21 @@ | |||
| <label class="col-md-3 control-label">SMTP Host: </label> | |||
| <div class="col-md-8"> | |||
| <input name="smtp_host" type="text" class="form-control" placeholder="Type SMTP host address"> | |||
| <input name="smtp_host" type="text" class="form-control" placeholder="Type SMTP host address" value="{{.smtp_host}}"> | |||
| </div> | |||
| </div> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">Email: </label> | |||
| <div class="col-md-8"> | |||
| <input name="mailer_user" type="text" class="form-control" placeholder="Type SMTP user e-mail address"> | |||
| <input name="mailer_user" type="text" class="form-control" placeholder="Type SMTP user e-mail address" value="{{.mailer_user}}"> | |||
| </div> | |||
| </div> | |||
| <div class="form-group"> | |||
| <label class="col-md-3 control-label">Password: </label> | |||
| <div class="col-md-8"> | |||
| <input name="mailer_pwd" type="password" class="form-control" placeholder="Type SMTP user password"> | |||
| <input name="mailer_pwd" type="password" class="form-control" placeholder="Type SMTP user password" value="{{.mailer_pwd}}"> | |||
| </div> | |||
| </div> | |||
| <hr/> | |||
| @@ -175,7 +180,7 @@ | |||
| <div class="col-md-offset-3 col-md-7"> | |||
| <div class="checkbox"> | |||
| <label> | |||
| <input name="register_confirm" type="checkbox"> | |||
| <input name="register_confirm" type="checkbox" {{if .register_confirm}}checked{{end}}> | |||
| <strong>Enable Register Confirmation</strong> | |||
| </label> | |||
| </div> | |||
| @@ -186,7 +191,7 @@ | |||
| <div class="col-md-offset-3 col-md-7"> | |||
| <div class="checkbox"> | |||
| <label> | |||
| <input name="mail_notify" type="checkbox"> | |||
| <input name="mail_notify" type="checkbox" {{if .mail_notify}}checked{{end}}> | |||
| <strong>Enable Mail Notification</strong> | |||
| </label> | |||
| </div> | |||
| @@ -4,7 +4,7 @@ | |||
| {{template "repo/toolbar" .}} | |||
| <div id="body" class="container"> | |||
| <div id="issue"> | |||
| <form class="form" action="/{{.RepositoryLink}}/issues/new" method="post" id="issue-create-form"> | |||
| <form class="form" action="{{.RepoLink}}/issues/new" method="post" id="issue-create-form"> | |||
| {{.CsrfTokenHtml}} | |||
| <div class="col-md-1"> | |||
| <img class="avatar" src="{{.SignedUser.AvatarLink}}" alt=""/> | |||
| @@ -15,19 +15,19 @@ | |||
| </div> | |||
| <div class="form-group panel-body"> | |||
| <div class="md-help pull-right"><!-- todo help link --> | |||
| Content with <a href="#">Markdown</a> | |||
| Content with <a href="https://help.github.com/articles/markdown-basics">Markdown</a> | |||
| </div> | |||
| <ul class="nav nav-tabs" data-init="tabs"> | |||
| <li class="active"><a href="#issue-textarea" data-toggle="tab">Write</a></li> | |||
| <li><a href="#issue-preview" data-toggle="tab">Preview</a></li> | |||
| <li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li> | |||
| <li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&issue=new" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li> | |||
| </ul> | |||
| <div class="tab-content"> | |||
| <div class="tab-pane" id="issue-textarea"> | |||
| <div class="form-group"> | |||
| <textarea class="form-control" name="content" id="issue-content" rows="10" placeholder="Write some content">{{.content}}</textarea> | |||
| <textarea class="form-control" name="content" id="issue-content" rows="10" placeholder="Write some content" data-ajax-rel="issue-preview" data-ajax-val="val" data-ajax-field="content">{{.content}}</textarea> | |||
| </div> | |||
| </div> | |||
| <div class="tab-pane" id="issue-preview">preview</div> | |||
| <div class="tab-pane issue-preview-content" id="issue-preview">loading...</div> | |||
| </div> | |||
| </div> | |||
| <div class="text-right panel-body"> | |||
| @@ -40,4 +40,4 @@ | |||
| </form> | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| {{template "base/footer" .}} | |||
| @@ -6,24 +6,24 @@ | |||
| <div id="issue"> | |||
| <div class="col-md-3 filter-list"> | |||
| <ul class="list-unstyled"> | |||
| <li><a href="/{{.RepositoryLink}}/issues"{{if eq .ViewType "all"}} class="active"{{end}}>All Issues <strong class="pull-right">{{.IssueCount}}</strong></a></li> | |||
| <li><a href="{{.RepoLink}}/issues"{{if eq .ViewType "all"}} class="active"{{end}}>All Issues <strong class="pull-right">{{.IssueCount}}</strong></a></li> | |||
| <!-- <li><a href="#">Assigned to you</a></li> --> | |||
| <li><a href="/{{.RepositoryLink}}/issues?type=created_by"{{if eq .ViewType "created_by"}} class="active"{{end}}>Created by you <strong class="pull-right">{{.IssueCreatedCount}}</strong></a></li> | |||
| <li><a href="{{.RepoLink}}/issues?type=created_by"{{if eq .ViewType "created_by"}} class="active"{{end}}>Created by you <strong class="pull-right">{{.IssueCreatedCount}}</strong></a></li> | |||
| <!-- <li><a href="#">Mentioned</a></li> --> | |||
| </ul> | |||
| </div> | |||
| <div class="col-md-9"> | |||
| <div class="filter-option"> | |||
| <div class="btn-group"> | |||
| <a class="btn btn-default issue-open{{if not .IsShowClosed}} active{{end}}" href="/{{.RepositoryLink}}/issues?type={{.ViewType}}">{{.OpenCount}} Open</a> | |||
| <a class="btn btn-default issue-close{{if .IsShowClosed}} active{{end}}" href="/{{.RepositoryLink}}/issues?state=closed&type={{.ViewType}}">{{.ClosedCount}} Closed</a> | |||
| <a class="btn btn-default issue-open{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/issues?type={{.ViewType}}">{{.OpenCount}} Open</a> | |||
| <a class="btn btn-default issue-close{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/issues?state=closed&type={{.ViewType}}">{{.ClosedCount}} Closed</a> | |||
| </div> | |||
| </div> | |||
| <div class="issues list-group"> | |||
| {{range .Issues}} | |||
| <div class="list-group-item issue-item" id="issue-{{.Id}}"> | |||
| <span class="number pull-right">#{{.Index}}</span> | |||
| <h5 class="title"><a href="/{{$.RepositoryLink}}/issues/{{.Index}}">{{.Name}}</a></h5> | |||
| <h5 class="title"><a href="{{$.RepoLink}}/issues/{{.Index}}">{{.Name}}</a></h5> | |||
| <p class="info"> | |||
| <span class="author"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" width="20"/> | |||
| <a href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a></span> | |||
| @@ -37,4 +37,4 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| {{template "base/footer" .}} | |||
| @@ -4,16 +4,17 @@ | |||
| {{template "repo/toolbar" .}} | |||
| <div id="body" class="container"> | |||
| <div id="issue"> | |||
| <div id="issue-{issue.id}" class="issue-whole issue-is-opening"> | |||
| <div id="issue-{{.Issue.Id}}" class="issue-whole issue-is-opening"> | |||
| <div class="issue-head clearfix"> | |||
| <div class="number pull-right">#{{.Issue.Index}}</div> | |||
| <a class="author pull-left" href="/user/{{.Issue.Poster.Name}}"><img class="avatar" src="{{.Issue.Poster.AvatarLink}}" alt="" width="30"/></a> | |||
| <h1 class="title pull-left">{{.Issue.Name}}</h1> | |||
| <input id="issue-edit-title" class="form-control input-lg pull-left hidden" type="text" value="{issue.title}" data-ajax-rel="issue-save"/> | |||
| <input id="issue-edit-title" class="form-control input-lg pull-left hidden" type="text" value="{{.Issue.Name}}" data-ajax-rel="issue-edit-save" data-ajax-val="val" data-ajax-field="title"/> | |||
| <input type="hidden" value="{{.Issue.Id}}" data-ajax-rel="issue-edit-save" data-ajax-val="val" data-ajax-field="issue_id"/> | |||
| <p class="info pull-left"> | |||
| <!-- <a class="btn btn-default pull-right issue-edit" href="#" id="issue-edit-btn">Edit</a> --> | |||
| {{if .IsIssueOwner}}<a class="btn btn-default pull-right issue-edit" href="#" id="issue-edit-btn">Edit</a> | |||
| <a class="btn btn-danger pull-right issue-edit-cancel hidden" href="#">Cancel</a> | |||
| <a class="btn btn-primary pull-right issue-edit-save hidden" href="#" data-ajax="{issue.save.link}" data-ajax-name="issue-save">Save</a> | |||
| <a class="btn btn-primary pull-right issue-edit-save hidden" href="#" data-ajax="/{{.RepositoryLink}}/issues/{{.Issue.Index}}" data-ajax-name="issue-edit-save" data-ajax-method="post">Save</a>{{end}} | |||
| <span class="status label label-{{if .Issue.IsClosed}}danger{{else}}success{{end}}">{{if .Issue.IsClosed}}Closed{{else}}Open{{end}}</span> | |||
| <a href="/user/{{.Issue.Poster.Name}}" class="author"><strong>{{.Issue.Poster.Name}}</strong></a> opened this issue | |||
| <span class="time">{{TimeSince .Issue.Created}}</span> · {{.Issue.NumComments}} comments | |||
| @@ -23,78 +24,77 @@ | |||
| <div class="panel panel-default issue-content"> | |||
| <div class="panel-body markdown"> | |||
| <div class="content"> | |||
| {{str2html .Issue.Content}} | |||
| {{str2html .Issue.RenderedContent}} | |||
| </div> | |||
| <textarea class="form-control hidden" name="content" id="issue-edit-content" rows="10" data-ajax-rel="issue-save">content</textarea> | |||
| <textarea class="form-control hidden" name="content" id="issue-edit-content" rows="10" data-ajax-rel="issue-edit-save" data-ajax-val="val" data-ajax-field="content">{{.Issue.Content}}</textarea> | |||
| </div> | |||
| </div> | |||
| {{range .Comments}} | |||
| <div class="issue-child" id="issue-comment-{issue.comment.id}"> | |||
| <a class="user pull-left" href="/user/{{.Poster.Name}}"><img class="avatar" src="{{.Poster.AvatarLink}}" alt=""/></a> | |||
| <div class="issue-content panel panel-default"> | |||
| <div class="panel-heading"> | |||
| <a href="/user/{{.Poster.Name}}" class="user">{{.Poster.Name}}</a> commented <span class="time">{{TimeSince .Created}}</span> | |||
| <!-- <a class="issue-comment-del pull-right issue-action" href="#" title="Edit Comment"><i class="fa fa-times-circle"></i></a> | |||
| <a class="issue-comment-edit pull-right issue-action" href="#" title="Remove Comment" data-url="{remove-link}"><i class="fa fa-edit"></i></a> --> | |||
| <span class="role label label-default pull-right">Owner</span> | |||
| </div> | |||
| <div class="panel-body markdown"> | |||
| {{str2html .Content}} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{end}} | |||
| <!-- <div class="issue-child issue-closed"> | |||
| <a class="user pull-left" href="{user.link}"><img class="avatar" src="{user.avatar}" alt=""/></a> | |||
| {{if eq .Type 0}} | |||
| <div class="issue-child" id="issue-comment-{{.Id}}"> | |||
| <a class="user pull-left" href="/user/{{.Poster.Name}}"><img class="avatar" src="{{.Poster.AvatarLink}}" alt=""/></a> | |||
| <div class="issue-content panel panel-default"> | |||
| <div class="panel-heading"> | |||
| <a href="/user/{{.Poster.Name}}" class="user">{{.Poster.Name}}</a> commented <span class="time">{{TimeSince .Created}}</span> | |||
| <!-- <a class="issue-comment-del pull-right issue-action" href="#" title="Edit Comment"><i class="fa fa-times-circle"></i></a> | |||
| <a class="issue-comment-edit pull-right issue-action" href="#" title="Remove Comment" data-url="{remove-link}"><i class="fa fa-edit"></i></a> --> | |||
| <span class="role label label-default pull-right">Owner</span> | |||
| </div> | |||
| <div class="panel-body markdown"> | |||
| {{str2html .Content}} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{else if eq .Type 1}} | |||
| <div class="issue-child issue-opened"> | |||
| <a class="user pull-left" href="/user/{{.Poster.Name}}"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" /></a> | |||
| <div class="issue-content"> | |||
| <a class="user pull-left" href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a> <span class="label label-success">Reopened</span> this issue <span class="time">{{TimeSince .Created}}</span> | |||
| </div> | |||
| </div> | |||
| {{else if eq .Type 2}} | |||
| <div class="issue-child issue-closed"> | |||
| <a class="user pull-left" href="/user/{{.Poster.Name}}"><img class="avatar" src="{{.Poster.AvatarLink}}" alt=""/></a> | |||
| <div class="issue-content"> | |||
| <a class="user pull-left" href="{user.link}">{user.name}</a> | |||
| <span class="btn btn-danger">Closed</span> this | |||
| <span class="time">{close.time}</span> | |||
| <a class="user pull-left" href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a> <span class="label label-danger">Closed</span> this issue <span class="time">{{TimeSince .Created}}</span> | |||
| </div> | |||
| </div> | |||
| <div class="issue-child issue-opened"> | |||
| <a class="user pull-left" href="{user.link}"><img class="avatar" src="{user.avatar}" alt=""/></a> | |||
| <div class="issue-content"> | |||
| <a class="user pull-left" href="{user.link}">{user.name}</a> | |||
| <span class="btn btn-success">Reopened</span> this | |||
| <span class="time">{close.time}</span> | |||
| </div> | |||
| </div> --> | |||
| </div> | |||
| {{end}} | |||
| {{end}} | |||
| <hr class="issue-line"/> | |||
| <div class="issue-child issue-reply"> | |||
| {{if .SignedUser}}<div class="issue-child issue-reply"> | |||
| <a class="user pull-left" href="/user/{{.SignedUser.Name}}"><img class="avatar" src="{{.SignedUser.AvatarLink}}" alt=""/></a> | |||
| <form class="panel panel-default issue-content" action="/{{.RepositoryLink}}/comment/new" method="post"> | |||
| <form class="panel panel-default issue-content" action="{{.RepoLink}}/comment/new" method="post"> | |||
| {{.CsrfTokenHtml}} | |||
| <div class="panel-body"> | |||
| <div class="form-group"> | |||
| <div class="md-help pull-right"><!-- todo help link --> | |||
| Content with <a href="#">Markdown</a> | |||
| <div class="md-help pull-right">Content with <a href="https://help.github.com/articles/markdown-basics">Markdown</a> | |||
| </div> | |||
| <ul class="nav nav-tabs" data-init="tabs"> | |||
| <li class="active"><a href="#issue-textarea" data-toggle="tab">Write</a></li> | |||
| <li><a href="#issue-preview" data-toggle="tab">Preview</a></li> | |||
| <li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li> | |||
| <li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&issue=issue_id&comment=new" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li> | |||
| </ul> | |||
| <div class="tab-content"> | |||
| <div class="tab-pane" id="issue-textarea"> | |||
| <div class="form-group"> | |||
| <input type="hidden" value="{{.Issue.Index}}" name="issueIndex"/> | |||
| <textarea class="form-control" name="content" id="issue-reply-content" rows="10" placeholder="Write some content">{{.content}}</textarea> | |||
| <textarea class="form-control" name="content" id="issue-reply-content" rows="10" placeholder="Write some content" data-ajax-rel="issue-preview" data-ajax-val="val" data-ajax-field="content">{{.content}}</textarea> | |||
| </div> | |||
| </div> | |||
| <div class="tab-pane" id="issue-preview">preview</div> | |||
| <div class="tab-pane issue-preview-content" id="issue-preview">Loading...</div> | |||
| </div> | |||
| </div> | |||
| <div class="text-right"> | |||
| <div class="form-group"> | |||
| {{if .Issue.IsClosed}} | |||
| <input type="submit" class="btn-default btn issue-open" id="issue-open-btn" data-origin="Re-Open" data-text="Re-Open & Comment" name="change_status" value="Reopen"/>{{else}} | |||
| <input type="submit" class="btn-default btn issue-open" id="issue-open-btn" data-origin="Reopen" data-text="Reopen & Comment" name="change_status" value="Reopen"/>{{else}} | |||
| <input type="submit" class="btn-default btn issue-close" id="issue-close-btn" data-origin="Close" data-text="Close & Comment" name="change_status" value="Close"/>{{end}} | |||
| <button class="btn-success btn" id="issue-reply-btn">Comment</button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div>{{else}}<div class="alert alert-warning"><a class="btn btn-success btn-lg" href="/user/sign_up">Sign up for free</a> to join this conversation. Already have an account? <a href="/user/login">Sign in to comment</a></div>{{end}} | |||
| </div><!-- | |||
| <div class="col-md-3"> | |||
| label assignment milestone dashboard | |||
| @@ -102,4 +102,4 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| {{template "base/footer" .}} | |||
| @@ -1,7 +1,6 @@ | |||
| {{template "base/head" .}} | |||
| {{template "base/navbar" .}} | |||
| {{template "repo/nav" .}} | |||
| {{template "repo/toolbar" .}} | |||
| <div id="body" class="container" data-page="repo"> | |||
| <div id="source"> | |||
| <div class="panel panel-info diff-box diff-head-box"> | |||
| @@ -11,7 +10,7 @@ | |||
| </div> | |||
| <div class="panel-body"> | |||
| <span class="pull-right"> | |||
| commit <span class="label label-default sha">{{.ShortSha}}</span> | |||
| commit <span class="label label-default sha">{{ShortSha .CommitId}}</span> | |||
| </span> | |||
| <p class="author"> | |||
| <img class="avatar" src="{{AvatarLink .Commit.Author.Email}}" alt=""/> | |||
| @@ -11,11 +11,11 @@ | |||
| {{ $n := len .Treenames}} | |||
| {{if not .IsFile}}<button class="btn btn-default pull-right hidden"><i class="fa fa-plus-square"></i>Add File</button>{{end}} | |||
| <div class="dropdown branch-switch"> | |||
| <a href="#" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><i class="fa fa-chain"></i>{{if .CommitId}}{{SubStr .CommitId 0 10}}{{else}}{{.Branchname}}{{end}} | |||
| <a href="#" class="btn btn-success dropdown-toggle" data-toggle="dropdown"><i class="fa fa-chain"></i>{{if .CommitId}}{{ShortSha .CommitId}}{{else}}{{.BranchName}}{{end}} | |||
| <b class="caret"></b></a> | |||
| <ul class="dropdown-menu"> | |||
| {{range .Branches}} | |||
| <li><a {{if eq . $.Branchname}}class="current" {{end}}href="/{{$.Username}}/{{$.Reponame}}/src/{{.}}">{{.}}</a></li> | |||
| <li><a {{if eq . $.BranchName}}class="current" {{end}}href="/{{$.Username}}/{{$.Reponame}}/src/{{.}}">{{.}}</a></li> | |||
| {{end}} | |||
| </ul> | |||
| </div> | |||
| @@ -3,24 +3,24 @@ | |||
| <nav class="navbar navbar-toolbar navbar-default" role="navigation"> | |||
| <div class="collapse navbar-collapse"> | |||
| <ul class="nav navbar-nav"> | |||
| <li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="/{{.RepositoryLink}}">Source</a></li> | |||
| <li class="{{if .IsRepoToolbarSource}}active{{end}}"><a href="{{.RepoLink}}{{if ne .BranchName `master`}}/src/{{.BranchName}}{{end}}">Source</a></li> | |||
| {{if not .IsBareRepo}} | |||
| <li class="{{if .IsRepoToolbarCommits}}active{{end}}"><a href="/{{.RepositoryLink}}/commits/{{.Branchname}}">Commits</a></li> | |||
| <!-- <li class="{{if .IsRepoToolbarBranches}}active{{end}}"><a href="/{{.RepositoryLink}}/branches">Branches</a></li> --> | |||
| <!-- <li class="{{if .IsRepoToolbarPulls}}active{{end}}"><a href="/{{.RepositoryLink}}/pulls">Pull Requests</a></li> --> | |||
| <li class="{{if .IsRepoToolbarIssues}}active{{end}}"><a href="/{{.RepositoryLink}}/issues">Issues <!--<span class="badge">42</span>--></a></li> | |||
| <li class="{{if .IsRepoToolbarCommits}}active{{end}}"><a href="{{.RepoLink}}/commits/{{.BranchName}}">Commits</a></li> | |||
| <!-- <li class="{{if .IsRepoToolbarBranches}}active{{end}}"><a href="{{.RepoLink}}/branches">Branches</a></li> --> | |||
| <!-- <li class="{{if .IsRepoToolbarPulls}}active{{end}}"><a href="{{.RepoLink}}/pulls">Pull Requests</a></li> --> | |||
| <li class="{{if .IsRepoToolbarIssues}}active{{end}}"><a href="{{.RepoLink}}/issues">Issues <!--<span class="badge">42</span>--></a></li> | |||
| {{if .IsRepoToolbarIssues}} | |||
| <li class="tmp">{{if .IsRepoToolbarIssuesList}}<a href="/{{.RepositoryLink}}/issues/new"> | |||
| <li class="tmp">{{if .IsRepoToolbarIssuesList}}<a href="{{.RepoLink}}/issues/new"> | |||
| <button class="btn btn-primary btn-sm">New Issue</button> | |||
| </a>{{else}}<a href="/{{.RepositoryLink}}/issues"> | |||
| </a>{{else}}<a href="{{.RepoLink}}/issues"> | |||
| <button class="btn btn-primary btn-sm">Issues List</button> | |||
| </a>{{end}}</li> | |||
| {{end}} | |||
| <!-- <li class="dropdown"> | |||
| <a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a> | |||
| <ul class="dropdown-menu"> | |||
| <li><a href="/{{.RepositoryLink}}/release">Release</a></li> | |||
| <li><a href="//{{.RepositoryLink}}/wiki">Wiki</a></li> | |||
| <li><a href="{{.RepoLink}}/release">Release</a></li> | |||
| <li><a href="{{.RepoLink}}/wiki">Wiki</a></li> | |||
| </ul> | |||
| </li> -->{{end}} | |||
| </ul> | |||
| @@ -34,7 +34,7 @@ | |||
| <li><a href="#">Network</a></li> | |||
| </ul> | |||
| </li> -->{{end}}{{if .IsRepositoryOwner}} | |||
| <li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="/{{.RepositoryLink}}/settings">Settings</a> | |||
| <li class="{{if .IsRepoToolbarSetting}}active{{end}}"><a href="{{.RepoLink}}/settings">Settings</a> | |||
| </li>{{end}} | |||
| </ul> | |||
| </div> | |||
| @@ -44,7 +44,7 @@ | |||
| </div> | |||
| <div class="form-group text-center" id="social-login"> | |||
| <a class="btn btn-danger btn-lg">Register new account</a> | |||
| <a class="btn btn-danger btn-lg" href="/user/sign_up">Register new account</a> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| @@ -8,22 +8,20 @@ import ( | |||
| "fmt" | |||
| "html/template" | |||
| "net/http" | |||
| "strings" | |||
| "github.com/codegangsta/cli" | |||
| "github.com/codegangsta/martini" | |||
| "github.com/gogits/binding" | |||
| "github.com/gogits/gogs/models" | |||
| "github.com/gogits/gogs/modules/auth" | |||
| "github.com/gogits/gogs/modules/avatar" | |||
| "github.com/gogits/gogs/modules/base" | |||
| "github.com/gogits/gogs/modules/log" | |||
| "github.com/gogits/gogs/modules/mailer" | |||
| "github.com/gogits/gogs/modules/middleware" | |||
| "github.com/gogits/gogs/routers" | |||
| "github.com/gogits/gogs/routers/admin" | |||
| "github.com/gogits/gogs/routers/api/v1" | |||
| "github.com/gogits/gogs/routers/dev" | |||
| "github.com/gogits/gogs/routers/repo" | |||
| "github.com/gogits/gogs/routers/user" | |||
| @@ -39,27 +37,6 @@ and it takes care of all the other things for you`, | |||
| Flags: []cli.Flag{}, | |||
| } | |||
| // globalInit is for global configuration reload-able. | |||
| func globalInit() { | |||
| base.NewConfigContext() | |||
| mailer.NewMailerContext() | |||
| models.LoadModelsConfig() | |||
| models.LoadRepoConfig() | |||
| models.NewRepoContext() | |||
| models.NewEngine() | |||
| } | |||
| // Check run mode(Default of martini is Dev). | |||
| func checkRunMode() { | |||
| switch base.Cfg.MustValue("", "RUN_MODE") { | |||
| case "prod": | |||
| martini.Env = martini.Prod | |||
| case "test": | |||
| martini.Env = martini.Test | |||
| } | |||
| log.Info("Run Mode: %s", strings.Title(martini.Env)) | |||
| } | |||
| func newMartini() *martini.ClassicMartini { | |||
| r := martini.NewRouter() | |||
| m := martini.New() | |||
| @@ -72,9 +49,8 @@ func newMartini() *martini.ClassicMartini { | |||
| } | |||
| func runWeb(*cli.Context) { | |||
| globalInit() | |||
| base.NewServices() | |||
| checkRunMode() | |||
| fmt.Println("Server is running...") | |||
| routers.GlobalInit() | |||
| log.Info("%s %s", base.AppName, base.AppVer) | |||
| m := newMartini() | |||
| @@ -90,12 +66,16 @@ func runWeb(*cli.Context) { | |||
| // Routers. | |||
| m.Get("/", ignSignIn, routers.Home) | |||
| m.Any("/install", routers.Install) | |||
| m.Any("/install", binding.BindIgnErr(auth.InstallForm{}), routers.Install) | |||
| m.Get("/issues", reqSignIn, user.Issues) | |||
| m.Get("/pulls", reqSignIn, user.Pulls) | |||
| m.Get("/stars", reqSignIn, user.Stars) | |||
| m.Get("/help", routers.Help) | |||
| m.Group("/api/v1", func(r martini.Router) { | |||
| r.Post("/markdown", v1.Markdown) | |||
| }) | |||
| avt := avatar.CacheServer("public/img/avatar/", "public/img/avatar_default.jpg") | |||
| m.Get("/avatar/:hash", avt.ServeHTTP) | |||
| @@ -150,8 +130,8 @@ func runWeb(*cli.Context) { | |||
| r.Post("/issues/:index", binding.BindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue) | |||
| r.Post("/comment/:action", repo.Comment) | |||
| }, reqSignIn, middleware.RepoAssignment(true)) | |||
| m.Group("/:username/:reponame", func(r martini.Router) { | |||
| r.Get("/commits/:branchname", repo.Commits) | |||
| r.Get("/issues", repo.Issues) | |||
| r.Get("/issues/:index", repo.ViewIssue) | |||
| r.Get("/pulls", repo.Pulls) | |||
| @@ -160,11 +140,10 @@ func runWeb(*cli.Context) { | |||
| r.Get("/src/:branchname/**", repo.Single) | |||
| r.Get("/raw/:branchname/**", repo.SingleDownload) | |||
| r.Get("/commits/:branchname", repo.Commits) | |||
| r.Get("/commits/:branchname", repo.Commits) | |||
| }, ignSignIn, middleware.RepoAssignment(true)) | |||
| m.Get("/:username/:reponame/commit/:commitid/**", ignSignIn, middleware.RepoAssignment(true), repo.Diff) | |||
| m.Get("/:username/:reponame/commit/:commitid", ignSignIn, middleware.RepoAssignment(true), repo.Diff) | |||
| m.Get("/:username/:reponame/commit/:branchname/**", ignSignIn, middleware.RepoAssignment(true), repo.Diff) | |||
| m.Get("/:username/:reponame/commit/:branchname", ignSignIn, middleware.RepoAssignment(true), repo.Diff) | |||
| m.Group("/:username", func(r martini.Router) { | |||
| r.Get("/:reponame", middleware.RepoAssignment(true), repo.Single) | |||