aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--README_ZH.md5
-rw-r--r--cmd/web.go8
-rw-r--r--conf/app.ini9
-rw-r--r--gogs.go2
-rw-r--r--models/git_diff.go2
-rw-r--r--models/issue.go188
-rw-r--r--models/login.go7
-rw-r--r--models/models.go6
-rw-r--r--models/repo.go153
-rw-r--r--modules/auth/authentication.go2
-rw-r--r--modules/auth/issue.go35
-rw-r--r--modules/auth/publickey.go (renamed from modules/auth/setting.go)0
-rw-r--r--modules/auth/release.go36
-rw-r--r--modules/auth/repo.go96
-rw-r--r--modules/base/conf.go103
-rw-r--r--modules/base/markdown.go29
-rw-r--r--modules/log/log.go41
-rw-r--r--modules/middleware/repo.go7
-rwxr-xr-xpublic/css/gogs.css49
-rw-r--r--public/js/app.js62
-rw-r--r--routers/admin/admin.go10
-rw-r--r--routers/admin/auths.go8
-rw-r--r--routers/repo/commit.go66
-rw-r--r--routers/repo/issue.go280
-rw-r--r--routers/repo/repo.go1
-rw-r--r--routers/repo/setting.go3
-rw-r--r--routers/user/home.go30
-rw-r--r--templates/admin/auths/edit.tmpl41
-rw-r--r--templates/admin/auths/new.tmpl8
-rw-r--r--templates/admin/config.tmpl6
-rw-r--r--templates/admin/dashboard.tmpl2
-rw-r--r--templates/issue/create.tmpl50
-rw-r--r--templates/issue/milestone.tmpl43
-rw-r--r--templates/issue/milestone_edit.tmpl61
-rw-r--r--templates/issue/milestone_new.tmpl4
-rw-r--r--templates/issue/user.tmpl2
-rw-r--r--templates/issue/view.tmpl62
-rw-r--r--templates/repo/commits.tmpl4
-rw-r--r--templates/repo/diff.tmpl4
-rw-r--r--templates/repo/setting.tmpl3
-rw-r--r--templates/repo/single_file.tmpl2
-rw-r--r--templates/user/dashboard.tmpl13
43 files changed, 1256 insertions, 292 deletions
diff --git a/README.md b/README.md
index 7e903abd..e50af352 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
![Demo](http://gowalker.org/public/gogs_demo.gif)
-##### Current version: 0.3.3 Alpha
+##### Current version: 0.3.4 Alpha
### NOTICES
@@ -52,10 +52,11 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
Make sure you install [Prerequirements](http://gogs.io/docs/installation/) first.
-There are 4 ways to install Gogs:
+There are 5 ways to install Gogs:
- [Install from binary](http://gogs.io/docs/installation/install_from_binary.md): **STRONGLY RECOMMENDED**
- [Install from source](http://gogs.io/docs/installation/install_from_source.md)
+- [Install from packages](http://gogs.io/docs/installation/install_from_packages.md)
- [Ship with Docker](https://github.com/gogits/gogs/tree/master/dockerfiles)
- [Install with Vagrant](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
diff --git a/README_ZH.md b/README_ZH.md
index d1f742d4..928d43f7 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
![Demo](http://gowalker.org/public/gogs_demo.gif)
-##### 当前版本:0.3.3 Alpha
+##### 当前版本:0.3.4 Alpha
## 开发目的
@@ -44,10 +44,11 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
在安装 Gogs 之前,您需要先安装 [基本环境](http://gogs.io/docs/installation/)。
-然后,您可以通过以下 4 种方式来安装 Gogs:
+然后,您可以通过以下 5 种方式来安装 Gogs:
- [二进制安装](http://gogs.io/docs/installation/install_from_binary.md): **强烈推荐**
- [源码安装](http://gogs.io/docs/installation/install_from_source.md)
+- [包管理安装](http://gogs.io/docs/installation/install_from_packages.md)
- [采用 Docker 部署](https://github.com/gogits/gogs/tree/master/dockerfiles)
- [通过 Vagrant 安装](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
diff --git a/cmd/web.go b/cmd/web.go
index 9a42b27a..5a0bd167 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -185,8 +185,13 @@ func runWeb(*cli.Context) {
r.Post("/issues/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost)
r.Post("/issues/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
r.Post("/issues/:index/assignee", repo.UpdateAssignee)
+ r.Post("/issues/:index/milestone", repo.UpdateIssueMilestone)
r.Get("/issues/milestones", repo.Milestones)
- r.Get("/issues/milestones/new", repo.NewMilestones)
+ r.Get("/issues/milestones/new", repo.NewMilestone)
+ r.Post("/issues/milestones/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
+ r.Get("/issues/milestones/:index/edit", repo.UpdateMilestone)
+ r.Post("/issues/milestones/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.UpdateMilestonePost)
+ r.Get("/issues/milestones/:index/:action", repo.UpdateMilestone)
r.Post("/comment/:action", repo.Comment)
r.Get("/releases/new", repo.ReleasesNew)
}, reqSignIn, middleware.RepoAssignment(true))
@@ -208,6 +213,7 @@ func runWeb(*cli.Context) {
r.Get("/raw/:branchname/**", repo.SingleDownload)
r.Get("/commits/:branchname", repo.Commits)
r.Get("/commits/:branchname/search", repo.SearchCommits)
+ r.Get("/commits/:branchname/**", repo.FileHistory)
r.Get("/commit/:branchname", repo.Diff)
r.Get("/commit/:branchname/**", repo.Diff)
r.Get("/releases", repo.Releases)
diff --git a/conf/app.ini b/conf/app.ini
index 33afae97..16a1e2cd 100644
--- a/conf/app.ini
+++ b/conf/app.ini
@@ -9,8 +9,6 @@ RUN_MODE = dev
[repository]
ROOT =
SCRIPT_TYPE = bash
-LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp|Java|Objective-C|Android
-LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License
[server]
PROTOCOL = http
@@ -145,9 +143,9 @@ HOST =
PROVIDER = file
; Provider config options
; memory: not have any config yet
-; file: session file path, e.g. data/sessions
-; redis: config like redis server addr, poolSize, password, e.g. 127.0.0.1:6379,100,astaxie
-; mysql: go-sql-driver/mysql dsn config string, e.g. root:password@/session_table
+; file: session file path, e.g. "data/sessions"
+; redis: config like redis server addr, poolSize, password, e.g. "127.0.0.1:6379,100,astaxie"
+; mysql: go-sql-driver/mysql dsn config string, e.g. "root:password@/session_table"
PROVIDER_CONFIG = data/sessions
; Session cookie name
COOKIE_NAME = i_like_gogits
@@ -171,6 +169,7 @@ DISABLE_GRAVATAR = false
[log]
; Either "console", "file", "conn", "smtp" or "database", default is "console"
+; Use comma to separate multiple modes, e.g. "console, file"
MODE = console
; Buffer length of channel, keep it as it is if you don't know what it is.
BUFFER_LEN = 10000
diff --git a/gogs.go b/gogs.go
index 8d5b021c..341d82b6 100644
--- a/gogs.go
+++ b/gogs.go
@@ -17,7 +17,7 @@ import (
"github.com/gogits/gogs/modules/base"
)
-const APP_VER = "0.3.3.0511 Alpha"
+const APP_VER = "0.3.4.0514 Alpha"
func init() {
base.AppVer = APP_VER
diff --git a/models/git_diff.go b/models/git_diff.go
index cf93af69..8dd5a8c8 100644
--- a/models/git_diff.go
+++ b/models/git_diff.go
@@ -49,6 +49,7 @@ type DiffSection struct {
type DiffFile struct {
Name string
+ Index int
Addition, Deletion int
Type int
IsBin bool
@@ -144,6 +145,7 @@ func ParsePatch(reader io.Reader) (*Diff, error) {
curFile = &DiffFile{
Name: a[strings.Index(a, "/")+1:],
+ Index: len(diff.Files) + 1,
Type: DIFF_FILE_CHANGE,
Sections: make([]*DiffSection, 0, 10),
}
diff --git a/models/issue.go b/models/issue.go
index 40d3bab0..7dd69267 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -10,19 +10,22 @@ import (
"strings"
"time"
+ "github.com/go-xorm/xorm"
+
"github.com/gogits/gogs/modules/base"
)
var (
- ErrIssueNotExist = errors.New("Issue does not exist")
+ ErrIssueNotExist = errors.New("Issue does not exist")
+ ErrMilestoneNotExist = errors.New("Milestone does not exist")
)
// Issue represents an issue or pull request of repository.
type Issue struct {
Id int64
+ RepoId int64 `xorm:"INDEX"`
Index int64 // Index in one repository.
Name string
- RepoId int64 `xorm:"INDEX"`
Repo *Repository `xorm:"-"`
PosterId int64
Poster *User `xorm:"-"`
@@ -164,6 +167,8 @@ type IssueUser struct {
Uid int64 // User ID.
IssueId int64
RepoId int64
+ MilestoneId int64
+ Labels string `xorm:"TEXT"`
IsRead bool
IsAssigned bool
IsMentioned bool
@@ -280,9 +285,10 @@ const (
func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStats {
stats := &IssueStats{}
issue := new(Issue)
+ tmpSess := &xorm.Session{}
sess := orm.Where("repo_id=?", rid)
- tmpSess := sess
+ *tmpSess = *sess
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
*tmpSess = *sess
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
@@ -390,7 +396,7 @@ func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
// Label represents a label of repository for issues.
type Label struct {
Id int64
- Rid int64 `xorm:"INDEX"`
+ RepoId int64 `xorm:"INDEX"`
Name string
Color string
NumIssues int
@@ -401,17 +407,189 @@ type Label struct {
// Milestone represents a milestone of repository.
type Milestone struct {
Id int64
- Rid int64 `xorm:"INDEX"`
+ RepoId int64 `xorm:"INDEX"`
+ Index int64
Name string
Content string
+ RenderedContent string `xorm:"-"`
IsClosed bool
NumIssues int
NumClosedIssues int
+ NumOpenIssues int `xorm:"-"`
Completeness int // Percentage(1-100).
Deadline time.Time
+ DeadlineString string `xorm:"-"`
ClosedDate time.Time
}
+// CalOpenIssues calculates the open issues of milestone.
+func (m *Milestone) CalOpenIssues() {
+ m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
+}
+
+// NewMilestone creates new milestone of repository.
+func NewMilestone(m *Milestone) (err error) {
+ sess := orm.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if _, err = sess.Insert(m); err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ rawSql := "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?"
+ if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
+ sess.Rollback()
+ return err
+ }
+ return sess.Commit()
+}
+
+// GetMilestoneById returns the milestone by given ID.
+func GetMilestoneById(id int64) (*Milestone, error) {
+ m := &Milestone{Id: id}
+ has, err := orm.Get(m)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrMilestoneNotExist
+ }
+ return m, nil
+}
+
+// GetMilestoneByIndex returns the milestone of given repository and index.
+func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) {
+ m := &Milestone{RepoId: repoId, Index: idx}
+ has, err := orm.Get(m)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrMilestoneNotExist
+ }
+ return m, nil
+}
+
+// GetMilestones returns a list of milestones of given repository and status.
+func GetMilestones(repoId int64, isClosed bool) ([]*Milestone, error) {
+ miles := make([]*Milestone, 0, 10)
+ err := orm.Where("repo_id=?", repoId).And("is_closed=?", isClosed).Find(&miles)
+ return miles, err
+}
+
+// UpdateMilestone updates information of given milestone.
+func UpdateMilestone(m *Milestone) error {
+ _, err := orm.Id(m.Id).Update(m)
+ return err
+}
+
+// ChangeMilestoneStatus changes the milestone open/closed status.
+func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
+ repo, err := GetRepositoryById(m.RepoId)
+ if err != nil {
+ return err
+ }
+
+ sess := orm.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ m.IsClosed = isClosed
+ if _, err = sess.Id(m.Id).AllCols().Update(m); err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ if isClosed {
+ repo.NumClosedMilestones++
+ } else {
+ repo.NumClosedMilestones--
+ }
+ if _, err = sess.Id(repo.Id).Update(repo); err != nil {
+ sess.Rollback()
+ return err
+ }
+ return sess.Commit()
+}
+
+// ChangeMilestoneAssign changes assignment of milestone for issue.
+func ChangeMilestoneAssign(oldMid, mid int64, isIssueClosed bool) (err error) {
+ sess := orm.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if oldMid > 0 {
+ m, err := GetMilestoneById(oldMid)
+ if err != nil {
+ return err
+ }
+
+ m.NumIssues--
+ if isIssueClosed {
+ m.NumClosedIssues--
+ }
+ if m.NumIssues > 0 {
+ m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
+ } else {
+ m.Completeness = 0
+ }
+ if _, err = sess.Id(m.Id).Update(m); err != nil {
+ sess.Rollback()
+ return err
+ }
+ }
+
+ if mid > 0 {
+ m, err := GetMilestoneById(mid)
+ if err != nil {
+ return err
+ }
+ m.NumIssues++
+ if isIssueClosed {
+ m.NumClosedIssues++
+ }
+ m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
+ if _, err = sess.Id(m.Id).Update(m); err != nil {
+ sess.Rollback()
+ return err
+ }
+ }
+ return sess.Commit()
+}
+
+// DeleteMilestone deletes a milestone.
+func DeleteMilestone(m *Milestone) (err error) {
+ sess := orm.NewSession()
+ defer sess.Close()
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if _, err = sess.Delete(m); err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ rawSql := "UPDATE `repository` SET num_milestones = num_milestones - 1 WHERE id = ?"
+ if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ rawSql = "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?"
+ if _, err = sess.Exec(rawSql, m.Id); err != nil {
+ sess.Rollback()
+ return err
+ }
+ return sess.Commit()
+}
+
// Issue types.
const (
IT_PLAIN = iota // Pure comment.
diff --git a/models/login.go b/models/login.go
index 313880df..be185488 100644
--- a/models/login.go
+++ b/models/login.go
@@ -37,8 +37,11 @@ var LoginTypes = map[int]string{
LT_SMTP: "SMTP",
}
-var _ core.Conversion = &LDAPConfig{}
-var _ core.Conversion = &SMTPConfig{}
+// Ensure structs implmented interface.
+var (
+ _ core.Conversion = &LDAPConfig{}
+ _ core.Conversion = &SMTPConfig{}
+)
type LDAPConfig struct {
ldap.Ldapsource
diff --git a/models/models.go b/models/models.go
index 02141b7a..3adec1a5 100644
--- a/models/models.go
+++ b/models/models.go
@@ -34,7 +34,8 @@ var (
func init() {
tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch),
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow),
- new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser))
+ new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser),
+ new(Milestone))
}
func LoadModelsConfig() {
@@ -141,7 +142,7 @@ type Statistic struct {
Counter struct {
User, PublicKey, Repo, Watch, Action, Access,
Issue, Comment, Mirror, Oauth, Release,
- LoginSource, Webhook int64
+ LoginSource, Webhook, Milestone int64
}
}
@@ -159,6 +160,7 @@ func GetStatistic() (stats Statistic) {
stats.Counter.Release, _ = orm.Count(new(Release))
stats.Counter.LoginSource, _ = orm.Count(new(LoginSource))
stats.Counter.Webhook, _ = orm.Count(new(Webhook))
+ stats.Counter.Milestone, _ = orm.Count(new(Milestone))
return
}
diff --git a/models/repo.go b/models/repo.go
index e97164d2..0594c6c6 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -18,6 +18,7 @@ import (
"github.com/Unknwon/cae/zip"
"github.com/Unknwon/com"
+ qlog "github.com/qiniu/log"
"github.com/gogits/git"
@@ -39,8 +40,38 @@ var (
)
func LoadRepoConfig() {
- LanguageIgns = strings.Split(base.Cfg.MustValue("repository", "LANG_IGNS"), "|")
- Licenses = strings.Split(base.Cfg.MustValue("repository", "LICENSES"), "|")
+ workDir, err := base.ExecDir()
+ if err != nil {
+ qlog.Fatalf("Fail to get work directory: %s\n", err)
+ }
+
+ // Load .gitignore and license files.
+ types := []string{"gitignore", "license"}
+ typeFiles := make([][]string, 2)
+ for i, t := range types {
+ cfgPath := filepath.Join(workDir, "conf", t)
+ files, err := com.StatDir(cfgPath)
+ if err != nil {
+ qlog.Fatalf("Fail to get default %s files: %v\n", t, err)
+ }
+ cfgPath = filepath.Join(workDir, "custom/conf/gitignore")
+ if com.IsDir(cfgPath) {
+ customFiles, err := com.StatDir(cfgPath)
+ if err != nil {
+ qlog.Fatalf("Fail to get custom %s files: %v\n", t, err)
+ }
+
+ for _, f := range customFiles {
+ if !com.IsSliceContainsStr(files, f) {
+ files = append(files, f)
+ }
+ }
+ }
+ typeFiles[i] = files
+ }
+
+ LanguageIgns = typeFiles[0]
+ Licenses = typeFiles[1]
}
func NewRepoContext() {
@@ -49,43 +80,43 @@ func NewRepoContext() {
// Check if server has basic git setting.
stdout, stderr, err := com.ExecCmd("git", "config", "--get", "user.name")
if strings.Contains(stderr, "fatal:") {
- fmt.Printf("repo.NewRepoContext(fail to get git user.name): %s", stderr)
- os.Exit(2)
+ qlog.Fatalf("repo.NewRepoContext(fail to get git user.name): %s", stderr)
} else if err != nil || len(strings.TrimSpace(stdout)) == 0 {
if _, stderr, err = com.ExecCmd("git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
- fmt.Printf("repo.NewRepoContext(fail to set git user.email): %s", stderr)
- os.Exit(2)
+ qlog.Fatalf("repo.NewRepoContext(fail to set git user.email): %s", stderr)
} else if _, stderr, err = com.ExecCmd("git", "config", "--global", "user.name", "Gogs"); err != nil {
- fmt.Printf("repo.NewRepoContext(fail to set git user.name): %s", stderr)
- os.Exit(2)
+ qlog.Fatalf("repo.NewRepoContext(fail to set git user.name): %s", stderr)
}
}
}
// Repository represents a git repository.
type Repository struct {
- Id int64
- OwnerId int64 `xorm:"unique(s)"`
- Owner *User `xorm:"-"`
- ForkId int64
- LowerName string `xorm:"unique(s) index not null"`
- Name string `xorm:"index not null"`
- Description string
- Website string
- NumWatches int
- NumStars int
- NumForks int
- NumIssues int
- NumClosedIssues int
- NumOpenIssues int `xorm:"-"`
- NumTags int `xorm:"-"`
- IsPrivate bool
- IsMirror bool
- IsBare bool
- IsGoget bool
- DefaultBranch string
- Created time.Time `xorm:"created"`
- Updated time.Time `xorm:"updated"`
+ Id int64
+ OwnerId int64 `xorm:"unique(s)"`
+ Owner *User `xorm:"-"`
+ ForkId int64
+ LowerName string `xorm:"unique(s) index not null"`
+ Name string `xorm:"index not null"`
+ Description string
+ Website string
+ NumWatches int
+ NumStars int
+ NumForks int
+ NumIssues int
+ NumClosedIssues int
+ NumOpenIssues int `xorm:"-"`
+ NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
+ NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
+ NumOpenMilestones int `xorm:"-"`
+ NumTags int `xorm:"-"`
+ IsPrivate bool
+ IsMirror bool
+ IsBare bool
+ IsGoget bool
+ DefaultBranch string
+ Created time.Time `xorm:"created"`
+ Updated time.Time `xorm:"updated"`
}
func (repo *Repository) GetOwner() (err error) {
@@ -645,6 +676,36 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
sess.Rollback()
return err
}
+ if _, err = sess.Delete(&IssueUser{RepoId: repoId}); err != nil {
+ sess.Rollback()
+ return err
+ }
+ if _, err = sess.Delete(&Milestone{RepoId: repoId}); err != nil {
+ sess.Rollback()
+ return err
+ }
+ if _, err = sess.Delete(&Release{RepoId: repoId}); err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ // Delete comments.
+ if err = orm.Iterate(&Issue{RepoId: repoId}, func(idx int, bean interface{}) error {
+ issue := bean.(*Issue)
+ if _, err = sess.Delete(&Comment{IssueId: issue.Id}); err != nil {
+ sess.Rollback()
+ return err
+ }
+ return nil
+ }); err != nil {
+ sess.Rollback()
+ return err
+ }
+
+ if _, err = sess.Delete(&Issue{RepoId: repoId}); err != nil {
+ sess.Rollback()
+ return err
+ }
rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?"
if _, err = sess.Exec(rawSql, userId); err != nil {
@@ -690,7 +751,7 @@ func GetRepositoryById(id int64) (*Repository, error) {
return repo, nil
}
-// GetRepositories returns the list of repositories of given user.
+// GetRepositories returns a list of repositories of given user.
func GetRepositories(user *User, private bool) ([]*Repository, error) {
repos := make([]*Repository, 0, 10)
sess := orm.Desc("updated")
@@ -727,6 +788,36 @@ func GetCollaboratorNames(repoName string) ([]string, error) {
return names, nil
}
+// GetCollaborativeRepos returns a list of repositories that user is collaborator.
+func GetCollaborativeRepos(uname string) ([]*Repository, error) {
+ uname = strings.ToLower(uname)
+ accesses := make([]*Access, 0, 10)
+ if err := orm.Find(&accesses, &Access{UserName: uname}); err != nil {
+ return nil, err
+ }
+
+ repos := make([]*Repository, 0, 10)
+ for _, access := range accesses {
+ if strings.HasPrefix(access.RepoName, uname) {
+ continue
+ }
+
+ infos := strings.Split(access.RepoName, "/")
+ u, err := GetUserByName(infos[0])
+ if err != nil {
+ return nil, err
+ }
+
+ repo, err := GetRepositoryByName(u.Id, infos[1])
+ if err != nil {
+ return nil, err
+ }
+ repo.Owner = u
+ repos = append(repos, repo)
+ }
+ return repos, nil
+}
+
// GetCollaborators returns a list of users of repository's collaborators.
func GetCollaborators(repoName string) (us []*User, err error) {
accesses := make([]*Access, 0, 10)
diff --git a/modules/auth/authentication.go b/modules/auth/authentication.go
index 74d5e11b..e9b21510 100644
--- a/modules/auth/authentication.go
+++ b/modules/auth/authentication.go
@@ -28,6 +28,8 @@ type AuthenticationForm struct {
MsAdSA string `form:"ms_ad_sa"`
IsActived bool `form:"is_actived"`
SmtpAuth string `form:"smtpauth"`
+ SmtpHost string `form:"smtphost"`
+ SmtpPort int `form:"smtpport"`
Tls bool `form:"tls"`
AllowAutoRegister bool `form:"allowautoregister"`
}
diff --git a/modules/auth/issue.go b/modules/auth/issue.go
deleted file mode 100644
index f3cad520..00000000
--- a/modules/auth/issue.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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 auth
-
-import (
- "net/http"
- "reflect"
-
- "github.com/go-martini/martini"
-
- "github.com/gogits/gogs/modules/base"
- "github.com/gogits/gogs/modules/middleware/binding"
-)
-
-type CreateIssueForm struct {
- IssueName string `form:"title" binding:"Required;MaxSize(50)"`
- MilestoneId int64 `form:"milestoneid"`
- AssigneeId int64 `form:"assigneeid"`
- Labels string `form:"labels"`
- Content string `form:"content"`
-}
-
-func (f *CreateIssueForm) Name(field string) string {
- names := map[string]string{
- "IssueName": "Issue name",
- }
- return names[field]
-}
-
-func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
- data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
- validate(errors, data, f)
-}
diff --git a/modules/auth/setting.go b/modules/auth/publickey.go
index b828c92b..b828c92b 100644
--- a/modules/auth/setting.go
+++ b/modules/auth/publickey.go
diff --git a/modules/auth/release.go b/modules/auth/release.go
deleted file mode 100644
index 7fadb3c0..00000000
--- a/modules/auth/release.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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 auth
-
-import (
- "net/http"
- "reflect"
-
- "github.com/go-martini/martini"
-
- "github.com/gogits/gogs/modules/base"
- "github.com/gogits/gogs/modules/middleware/binding"
-)
-
-type NewReleaseForm struct {
- TagName string `form:"tag_name" binding:"Required"`
- Title string `form:"title" binding:"Required"`
- Content string `form:"content" binding:"Required"`
- Prerelease bool `form:"prerelease"`
-}
-
-func (f *NewReleaseForm) Name(field string) string {
- names := map[string]string{
- "TagName": "Tag name",
- "Title": "Release title",
- "Content": "Release content",
- }
- return names[field]
-}
-
-func (f *NewReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
- data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
- validate(errors, data, f)
-}
diff --git a/modules/auth/repo.go b/modules/auth/repo.go
index f880cdf5..92ca839f 100644
--- a/modules/auth/repo.go
+++ b/modules/auth/repo.go
@@ -14,6 +14,13 @@ import (
"github.com/gogits/gogs/modules/middleware/binding"
)
+// __________ .__ __
+// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
+// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
+// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
+// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
+// \/ \/|__| \/ \/
+
type CreateRepoForm struct {
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
Private bool `form:"private"`
@@ -63,7 +70,7 @@ func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, co
type RepoSettingForm struct {
RepoName string `form:"name" binding:"Required;AlphaDash;MaxSize(100)"`
Description string `form:"desc" binding:"MaxSize(100)"`
- Website string `form:"url" binding:"Url;MaxSize(100)"`
+ Website string `form:"site" binding:"Url;MaxSize(100)"`
Branch string `form:"branch"`
Interval int `form:"interval"`
Private bool `form:"private"`
@@ -84,6 +91,13 @@ func (f *RepoSettingForm) Validate(errors *binding.Errors, req *http.Request, co
validate(errors, data, f)
}
+// __ __ ___. .__ .__ __
+// / \ / \ ____\_ |__ | |__ | |__ ____ | | __
+// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ /
+// \ /\ ___/| \_\ \ Y \ Y ( <_> ) <
+// \__/\ / \___ >___ /___| /___| /\____/|__|_ \
+// \/ \/ \/ \/ \/ \/
+
type NewWebhookForm struct {
Url string `form:"url" binding:"Required;Url"`
ContentType string `form:"content_type" binding:"Required"`
@@ -104,3 +118,83 @@ func (f *NewWebhookForm) Validate(errors *binding.Errors, req *http.Request, con
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
validate(errors, data, f)
}
+
+// .___
+// | | ______ ________ __ ____
+// | |/ ___// ___/ | \_/ __ \
+// | |\___ \ \___ \| | /\ ___/
+// |___/____ >____ >____/ \___ >
+// \/ \/ \/
+
+type CreateIssueForm struct {
+ IssueName string `form:"title" binding:"Required;MaxSize(50)"`
+ MilestoneId int64 `form:"milestoneid"`
+ AssigneeId int64 `form:"assigneeid"`
+ Labels string `form:"labels"`
+ Content string `form:"content"`
+}
+
+func (f *CreateIssueForm) Name(field string) string {
+ names := map[string]string{
+ "IssueName": "Issue name",
+ }
+ return names[field]
+}
+
+func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+ data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+ validate(errors, data, f)
+}
+
+// _____ .__.__ __
+// / \ |__| | ____ _______/ |_ ____ ____ ____
+// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
+// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
+// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
+// \/ \/ \/ \/ \/
+
+type CreateMilestoneForm struct {
+ Title string `form:"title" binding:"Required;MaxSize(50)"`
+ Content string `form:"content"`
+ Deadline string `form:"due_date"`
+}
+
+func (f *CreateMilestoneForm) Name(field string) string {
+ names := map[string]string{
+ "Title": "Milestone name",
+ }
+ return names[field]
+}
+
+func (f *CreateMilestoneForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+ data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+ validate(errors, data, f)
+}
+
+// __________ .__
+// \______ \ ____ | | ____ _____ ______ ____
+// | _// __ \| | _/ __ \\__ \ / ___// __ \
+// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
+// |____|_ /\___ >____/\___ >____ /____ >\___ >
+// \/ \/ \/ \/ \/ \/
+
+type NewReleaseForm struct {
+ TagName string `form:"tag_name" binding:"Required"`
+ Title string `form:"title" binding:"Required"`
+ Content string `form:"content" binding:"Required"`
+ Prerelease bool `form:"prerelease"`
+}
+
+func (f *NewReleaseForm) Name(field string) string {
+ names := map[string]string{
+ "TagName": "Tag name",
+ "Title": "Release title",
+ "Content": "Release content",
+ }
+ return names[field]
+}
+
+func (f *NewReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
+ data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
+ validate(errors, data, f)
+}
diff --git a/modules/base/conf.go b/modules/base/conf.go
index 7d26623a..03e453e6 100644
--- a/modules/base/conf.go
+++ b/modules/base/conf.go
@@ -70,8 +70,8 @@ var (
MailService *Mailer
OauthService *Oauther
- LogMode string
- LogConfig string
+ LogModes []string
+ LogConfigs []string
Cache cache.Cache
CacheAdapter string
@@ -130,57 +130,62 @@ func newService() {
}
func newLogService() {
+ log.Info("%s %s", AppName, AppVer)
+
// Get and check log mode.
- LogMode = Cfg.MustValue("log", "MODE", "console")
- modeSec := "log." + LogMode
- if _, err := Cfg.GetSection(modeSec); err != nil {
- qlog.Fatalf("Unknown log mode: %s\n", LogMode)
- }
+ LogModes = strings.Split(Cfg.MustValue("log", "MODE", "console"), ",")
+ LogConfigs = make([]string, len(LogModes))
+ for i, mode := range LogModes {
+ mode = strings.TrimSpace(mode)
+ modeSec := "log." + mode
+ if _, err := Cfg.GetSection(modeSec); err != nil {
+ qlog.Fatalf("Unknown log mode: %s\n", mode)
+ }
- // Log level.
- levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace")
- level, ok := logLevels[levelName]
- if !ok {
- qlog.Fatalf("Unknown log level: %s\n", levelName)
- }
+ // Log level.
+ levelName := Cfg.MustValue("log."+mode, "LEVEL", "Trace")
+ level, ok := logLevels[levelName]
+ if !ok {
+ qlog.Fatalf("Unknown log level: %s\n", levelName)
+ }
- // Generate log configuration.
- switch LogMode {
- case "console":
- LogConfig = fmt.Sprintf(`{"level":%s}`, level)
- case "file":
- logPath := Cfg.MustValue(modeSec, "FILE_NAME", "log/gogs.log")
- os.MkdirAll(path.Dir(logPath), os.ModePerm)
- LogConfig = fmt.Sprintf(
- `{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level,
- logPath,
- Cfg.MustBool(modeSec, "LOG_ROTATE", true),
- Cfg.MustInt(modeSec, "MAX_LINES", 1000000),
- 1<<uint(Cfg.MustInt(modeSec, "MAX_SIZE_SHIFT", 28)),
- Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
- Cfg.MustInt(modeSec, "MAX_DAYS", 7))
- case "conn":
- LogConfig = fmt.Sprintf(`{"level":"%s","reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
- Cfg.MustBool(modeSec, "RECONNECT_ON_MSG", false),
- Cfg.MustBool(modeSec, "RECONNECT", false),
- Cfg.MustValue(modeSec, "PROTOCOL", "tcp"),
- Cfg.MustValue(modeSec, "ADDR", ":7020"))
- case "smtp":
- LogConfig = fmt.Sprintf(`{"level":"%s","username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
- Cfg.MustValue(modeSec, "USER", "example@example.com"),
- Cfg.MustValue(modeSec, "PASSWD", "******"),
- Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
- Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
- Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
- case "database":
- LogConfig = fmt.Sprintf(`{"level":"%s","driver":"%s","conn":"%s"}`, level,
- Cfg.MustValue(modeSec, "Driver"),
- Cfg.MustValue(modeSec, "CONN"))
- }
+ // Generate log configuration.
+ switch mode {
+ case "console":
+ LogConfigs[i] = fmt.Sprintf(`{"level":%s}`, level)
+ case "file":
+ logPath := Cfg.MustValue(modeSec, "FILE_NAME", "log/gogs.log")
+ os.MkdirAll(path.Dir(logPath), os.ModePerm)
+ LogConfigs[i] = fmt.Sprintf(
+ `{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level,
+ logPath,
+ Cfg.MustBool(modeSec, "LOG_ROTATE", true),
+ Cfg.MustInt(modeSec, "MAX_LINES", 1000000),
+ 1<<uint(Cfg.MustInt(modeSec, "MAX_SIZE_SHIFT", 28)),
+ Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
+ Cfg.MustInt(modeSec, "MAX_DAYS", 7))
+ case "conn":
+ LogConfigs[i] = fmt.Sprintf(`{"level":"%s","reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
+ Cfg.MustBool(modeSec, "RECONNECT_ON_MSG", false),
+ Cfg.MustBool(modeSec, "RECONNECT", false),
+ Cfg.MustValue(modeSec, "PROTOCOL", "tcp"),
+ Cfg.MustValue(modeSec, "ADDR", ":7020"))
+ case "smtp":
+ LogConfigs[i] = fmt.Sprintf(`{"level":"%s","username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
+ Cfg.MustValue(modeSec, "USER", "example@example.com"),
+ Cfg.MustValue(modeSec, "PASSWD", "******"),
+ Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
+ Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
+ Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
+ case "database":
+ LogConfigs[i] = fmt.Sprintf(`{"level":"%s","driver":"%s","conn":"%s"}`, level,
+ Cfg.MustValue(modeSec, "Driver"),
+ Cfg.MustValue(modeSec, "CONN"))
+ }
- log.Info("%s %s", AppName, AppVer)
- log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig)
- log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName)
+ log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), mode, LogConfigs[i])
+ log.Info("Log Mode: %s(%s)", strings.Title(mode), levelName)
+ }
}
func newLdapService() {
diff --git a/modules/base/markdown.go b/modules/base/markdown.go
index 057e1b04..0825decb 100644
--- a/modules/base/markdown.go
+++ b/modules/base/markdown.go
@@ -97,12 +97,31 @@ var (
)
func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
- ms := MentionPattern.FindAll(rawBytes, -1)
- for _, m := range ms {
- rawBytes = bytes.Replace(rawBytes, m,
- []byte(fmt.Sprintf(`<a href="/user/%s">%s</a>`, m[1:], m)), -1)
+ buf := bytes.NewBufferString("")
+ inCodeBlock := false
+ codeBlockPrefix := []byte("```")
+ lineBreak := []byte("\n")
+ tab := []byte("\t")
+ lines := bytes.Split(rawBytes, lineBreak)
+ for _, line := range lines {
+ if bytes.HasPrefix(line, codeBlockPrefix) {
+ inCodeBlock = !inCodeBlock
+ }
+
+ if !inCodeBlock && !bytes.HasPrefix(line, tab) {
+ ms := MentionPattern.FindAll(line, -1)
+ for _, m := range ms {
+ line = bytes.Replace(line, m,
+ []byte(fmt.Sprintf(`<a href="/user/%s">%s</a>`, m[1:], m)), -1)
+ }
+ }
+
+ buf.Write(line)
+ buf.Write(lineBreak)
}
- ms = commitPattern.FindAll(rawBytes, -1)
+
+ rawBytes = buf.Bytes()
+ ms := commitPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "commit/")
diff --git a/modules/log/log.go b/modules/log/log.go
index 636ea787..eea3c8ad 100644
--- a/modules/log/log.go
+++ b/modules/log/log.go
@@ -10,8 +10,7 @@ import (
)
var (
- logger *logs.BeeLogger
- Mode, Config string
+ loggers []*logs.BeeLogger
)
func init() {
@@ -19,32 +18,54 @@ func init() {
}
func NewLogger(bufLen int64, mode, config string) {
- Mode, Config = mode, config
- logger = logs.NewLogger(bufLen)
+ logger := logs.NewLogger(bufLen)
+
+ isExist := false
+ for _, l := range loggers {
+ if l.Adapter == mode {
+ isExist = true
+ l = logger
+ }
+ }
+ if !isExist {
+ loggers = append(loggers, logger)
+ }
logger.SetLogFuncCallDepth(3)
logger.SetLogger(mode, config)
}
func Trace(format string, v ...interface{}) {
- logger.Trace(format, v...)
+ for _, logger := range loggers {
+ logger.Trace(format, v...)
+ }
}
func Debug(format string, v ...interface{}) {
- logger.Debug(format, v...)
+ for _, logger := range loggers {
+ logger.Debug(format, v...)
+ }
}
func Info(format string, v ...interface{}) {
- logger.Info(format, v...)
+ for _, logger := range loggers {
+ logger.Info(format, v...)
+ }
}
func Error(format string, v ...interface{}) {
- logger.Error(format, v...)
+ for _, logger := range loggers {
+ logger.Error(format, v...)
+ }
}
func Warn(format string, v ...interface{}) {
- logger.Warn(format, v...)
+ for _, logger := range loggers {
+ logger.Warn(format, v...)
+ }
}
func Critical(format string, v ...interface{}) {
- logger.Critical(format, v...)
+ for _, logger := range loggers {
+ logger.Critical(format, v...)
+ }
}
diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go
index ff99f828..863860e7 100644
--- a/modules/middleware/repo.go
+++ b/modules/middleware/repo.go
@@ -99,6 +99,11 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
return
}
+ // Check if the mirror repository owner(mirror repository doesn't have access).
+ if ctx.IsSigned && !ctx.Repo.IsOwner && repo.OwnerId == ctx.User.Id {
+ ctx.Repo.IsOwner = true
+ }
+
// Check access.
if repo.IsPrivate && !ctx.Repo.IsOwner {
if ctx.User == nil {
@@ -128,6 +133,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
}
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
+ repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
ctx.Repo.Repository = repo
ctx.Data["IsBareRepo"] = ctx.Repo.Repository.IsBare
@@ -225,7 +231,6 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
ctx.Data["IsBranch"] = ctx.Repo.IsBranch
ctx.Data["IsCommit"] = ctx.Repo.IsCommit
- log.Debug("Repo.Commit: %v", ctx.Repo.Commit)
}
log.Debug("displayBare: %v; IsBare: %v", displayBare, ctx.Repo.Repository.IsBare)
diff --git a/public/css/gogs.css b/public/css/gogs.css
index 12fd8138..af787ebd 100755
--- a/public/css/gogs.css
+++ b/public/css/gogs.css
@@ -1441,7 +1441,7 @@ html, body {
margin-left: .8em;
}
-#issue .assignee.dropdown-menu, #issue .assignee ul {
+#issue .assignee.dropdown-menu, #issue .assignee ul, #issue .milestone.dropdown-menu, #issue .milestone ul {
padding: 0;
margin: 0;
min-width: 300px;
@@ -1451,17 +1451,33 @@ html, body {
min-width: 160px;
}
-#issue .issue-bar .assignee .dropdown-menu{
+#issue .issue-bar .assignee .dropdown-menu, #issue .issue-bar .milestone .dropdown-menu {
padding: 0;
margin: 0;
}
-#issue .assignee li {
+#issue .assignee li, #issue .milestone li.clear-milestone {
padding: 4px 12px;
line-height: 30px;
}
-#issue .assignee li:hover {
+#issue .milestone .milestone-item {
+ padding: 8px 12px;
+}
+
+#issue .milestone li.milestone-item {
+ border-bottom: 1px solid #CCC;
+}
+
+#issue .milestone li.milestone-item:last-child {
+ border-bottom: none;
+}
+
+#issue .milestone .milestone-item p {
+ margin-bottom: 0;
+}
+
+#issue .assignee li:hover, #issue .milestone li.clear-milestone:hover, #issue .milestone li.milestone-item:hover {
background-color: #e8f0ff;
cursor: pointer;
}
@@ -1474,7 +1490,7 @@ html, body {
#issue .issue-bar > div {
padding-bottom: 8px;
- margin-bottom: 8px;
+ margin-bottom: 40px;
border-bottom: 1px solid #CCC;
}
@@ -1482,11 +1498,32 @@ html, body {
line-height: 30px;
}
-#issue .issue-bar .assignee .action{
+#issue .issue-bar .assignee .action, #issue .issue-bar .milestone .action {
position: relative;
margin-top: -8px;
}
+#issue .issue-bar .milestone .completion {
+ margin-top: 20px;
+ margin-bottom: 12px;
+}
+
+#issue .issue-bar .milestone .completion span {
+ display: block;
+ height: 12px;
+ background-color: #77c64a;
+}
+
+#issue .milestone .nav-tabs a {
+ padding: 4px 8px;
+ border-top: none;
+}
+
+#milestone {
+ margin-left: 24px;
+ margin-right: 12px;
+}
+
/* wrapper and footer */
#wrapper {
diff --git a/public/js/app.js b/public/js/app.js
index f59442ee..d7514f79 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -55,7 +55,7 @@ var Gogits = {};
toggleShow: function () {
$(this).removeClass("hidden");
},
- toggleAjax: function (successCallback) {
+ toggleAjax: function (successCallback, errorCallback) {
var url = $(this).data("ajax");
var method = $(this).data('ajax-method') || 'get';
var ajaxName = $(this).data('ajax-name');
@@ -91,6 +91,7 @@ var Gogits = {};
url: url,
method: method.toUpperCase(),
data: data,
+ error: errorCallback,
success: function (d) {
if (successCallback) {
successCallback(d);
@@ -527,6 +528,8 @@ function initIssue() {
var $this = $(this);
$this.toggleAjax(function (resp) {
$($this.data("preview")).html(resp);
+ }, function () {
+ $($this.data("preview")).html("no content");
})
});
$('.issue-write a[data-toggle]').on("click", function () {
@@ -537,14 +540,14 @@ function initIssue() {
// assignee
var is_issue_bar = $('.issue-bar').length > 0;
var $a = $('.assignee');
- if($a.data("assigned") > 0){
+ if ($a.data("assigned") > 0) {
$('.clear-assignee').toggleShow();
}
$('.assignee', '#issue').on('click', 'li', function () {
var uid = $(this).data("uid");
- if(is_issue_bar){
+ if (is_issue_bar) {
var assignee = $a.data("assigned");
- if(uid != assignee){
+ if (uid != assignee) {
$.post($a.data("ajax"), {
issue: $('#issue').data("id"),
assigneeid: uid
@@ -566,6 +569,49 @@ function initIssue() {
}
});
+ // milestone
+
+ $('#issue .dropdown-menu a[data-toggle="tab"]').on("click", function (e) {
+ e.stopPropagation();
+ $(this).tab('show');
+ return false;
+ });
+
+ var $m = $('.milestone');
+ if ($m.data("milestone") > 0) {
+ $('.clear-milestone').toggleShow();
+ console.log("show");
+ }
+ $('.milestone', '#issue').on('click', 'li.milestone-item', function () {
+ var id = $(this).data("id");
+ if (is_issue_bar) {
+ var m = $m.data("milestone");
+ if (id != m) {
+ $.post($m.data("ajax"), {
+ issue: $('#issue').data("id"),
+ milestone: id
+ }, function (json) {
+ if (json.ok) {
+ window.location.reload();
+ if (id > 0) {
+ $('.clear-milestone').toggleShow();
+ } else {
+ $('.clear-milestone').toggleHide();
+ }
+ }
+ })
+ }
+ return;
+ }
+ $('#milestone-id').val(id);
+ if (id > 0) {
+ $('.clear-milestone').toggleShow();
+ $('#milestone').text($(this).find("strong").text())
+ } else {
+ $('.clear-milestone').toggleHide();
+ $('#milestone').text($('#milestone').data("no-milestone"));
+ }
+ });
}
function initRelease() {
@@ -574,9 +620,9 @@ function initRelease() {
$('[data-ajax-name=release-preview]').on("click", function () {
var $this = $(this);
$this.toggleAjax(function (json) {
- if (json.ok) {
- $($this.data("preview")).html(json.content);
- }
+ $($this.data("preview")).html(json.ok ? json.content : "no content");
+ }, function () {
+ $($this.data("preview")).html("no content");
})
});
$('.release-write a[data-toggle]').on("click", function () {
@@ -651,6 +697,6 @@ function initRepoSetting() {
});
})(jQuery);
-String.prototype.endsWith = function(suffix) {
+String.prototype.endsWith = function (suffix) {
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
diff --git a/routers/admin/admin.go b/routers/admin/admin.go
index dbd5e945..f9c11f83 100644
--- a/routers/admin/admin.go
+++ b/routers/admin/admin.go
@@ -211,8 +211,14 @@ func Config(ctx *middleware.Context) {
ctx.Data["PictureService"] = base.PictureService
ctx.Data["DisableGravatar"] = base.DisableGravatar
- ctx.Data["LogMode"] = base.LogMode
- ctx.Data["LogConfig"] = base.LogConfig
+ type logger struct {
+ Mode, Config string
+ }
+ loggers := make([]*logger, len(base.LogModes))
+ for i := range base.LogModes {
+ loggers[i] = &logger{base.LogModes[i], base.LogConfigs[i]}
+ }
+ ctx.Data["Loggers"] = loggers
ctx.HTML(200, "admin/config")
}
diff --git a/routers/admin/auths.go b/routers/admin/auths.go
index 70a23baa..b7b382cd 100644
--- a/routers/admin/auths.go
+++ b/routers/admin/auths.go
@@ -56,8 +56,8 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
case models.LT_SMTP:
u = &models.SMTPConfig{
Auth: form.SmtpAuth,
- Host: form.Host,
- Port: form.Port,
+ Host: form.SmtpHost,
+ Port: form.SmtpPort,
TLS: form.Tls,
}
default:
@@ -134,8 +134,8 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) {
case models.LT_SMTP:
config = &models.SMTPConfig{
Auth: form.SmtpAuth,
- Host: form.Host,
- Port: form.Port,
+ Host: form.SmtpHost,
+ Port: form.SmtpPort,
TLS: form.Tls,
}
default:
diff --git a/routers/repo/commit.go b/routers/repo/commit.go
index bc33fe44..88b6593e 100644
--- a/routers/repo/commit.go
+++ b/routers/repo/commit.go
@@ -47,10 +47,10 @@ func Commits(ctx *middleware.Context, params martini.Params) {
nextPage = 0
}
- //both `git log branchName` and `git log commitId` work
+ //both `git log branchName` and `git log commitId` work
commits, err := ctx.Repo.Commit.CommitsByRange(page)
if err != nil {
- ctx.Handle(500, "repo.Commits(get commits)", err)
+ ctx.Handle(500, "repo.Commits(CommitsByRange)", err)
return
}
@@ -149,3 +149,65 @@ func SearchCommits(ctx *middleware.Context, params martini.Params) {
ctx.Data["IsRepoToolbarCommits"] = true
ctx.HTML(200, "repo/commits")
}
+
+func FileHistory(ctx *middleware.Context, params martini.Params) {
+ fileName := params["_1"]
+ if len(fileName) == 0 {
+ Commits(ctx, params)
+ return
+ }
+
+ userName := ctx.Repo.Owner.Name
+ repoName := ctx.Repo.Repository.Name
+ branchName := params["branchname"]
+
+ brs, err := ctx.Repo.GitRepo.GetBranches()
+ if err != nil {
+ ctx.Handle(500, "repo.FileHistory", err)
+ return
+ } else if len(brs) == 0 {
+ ctx.Handle(404, "repo.FileHistory", nil)
+ return
+ }
+
+ commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(branchName, fileName)
+ if err != nil {
+ ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err)
+ return
+ }
+ if commitsCount == 0 {
+ ctx.Handle(404, "repo.FileHistory", nil)
+ return
+ }
+
+ // Calculate and validate page number.
+ page, _ := base.StrTo(ctx.Query("p")).Int()
+ if page < 1 {
+ page = 1
+ }
+ lastPage := page - 1
+ if lastPage < 0 {
+ lastPage = 0
+ }
+ nextPage := page + 1
+ if nextPage*50 > commitsCount {
+ nextPage = 0
+ }
+
+ //both `git log branchName` and `git log commitId` work
+ commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(branchName, fileName, page)
+ if err != nil {
+ ctx.Handle(500, "repo.FileHistory(CommitsByRange)", err)
+ return
+ }
+
+ ctx.Data["Username"] = userName
+ ctx.Data["Reponame"] = repoName
+ ctx.Data["FileName"] = fileName
+ ctx.Data["CommitCount"] = commitsCount
+ ctx.Data["Commits"] = commits
+ ctx.Data["LastPageNum"] = lastPage
+ ctx.Data["NextPageNum"] = nextPage
+ ctx.Data["IsRepoToolbarCommits"] = true
+ ctx.HTML(200, "repo/commits")
+}
diff --git a/routers/repo/issue.go b/routers/repo/issue.go
index 62189595..db0eff9c 100644
--- a/routers/repo/issue.go
+++ b/routers/repo/issue.go
@@ -8,6 +8,7 @@ import (
"fmt"
"net/url"
"strings"
+ "time"
"github.com/Unknwon/com"
"github.com/go-martini/martini"
@@ -52,7 +53,18 @@ func Issues(ctx *middleware.Context) {
filterMode = models.FM_MENTION
}
- mid, _ := base.StrTo(ctx.Query("milestone")).Int64()
+ var mid int64
+ midx, _ := base.StrTo(ctx.Query("milestone")).Int64()
+ if midx > 0 {
+ mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, midx)
+ if err != nil {
+ ctx.Handle(500, "issue.Issues(GetMilestoneByIndex): %v", err)
+ return
+ }
+ mid = mile.Id
+ }
+ fmt.Println(mid)
+
page, _ := base.StrTo(ctx.Query("page")).Int()
// Get issues.
@@ -113,6 +125,19 @@ func CreateIssue(ctx *middleware.Context, params martini.Params) {
ctx.Data["IsRepoToolbarIssues"] = true
ctx.Data["IsRepoToolbarIssuesList"] = false
+ var err error
+ // Get all milestones.
+ ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false)
+ if err != nil {
+ ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err)
+ return
+ }
+ ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true)
+ if err != nil {
+ ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err)
+ return
+ }
+
us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
if err != nil {
ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
@@ -127,6 +152,19 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C
ctx.Data["IsRepoToolbarIssues"] = true
ctx.Data["IsRepoToolbarIssuesList"] = false
+ var err error
+ // Get all milestones.
+ ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false)
+ if err != nil {
+ ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err)
+ return
+ }
+ ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true)
+ if err != nil {
+ ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err)
+ return
+ }
+
us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
if err != nil {
ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
@@ -144,9 +182,9 @@ func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.C
form.AssigneeId = 0
}
issue := &models.Issue{
+ RepoId: ctx.Repo.Repository.Id,
Index: int64(ctx.Repo.Repository.NumIssues) + 1,
Name: form.IssueName,
- RepoId: ctx.Repo.Repository.Id,
PosterId: ctx.User.Id,
MilestoneId: form.MilestoneId,
AssigneeId: form.AssigneeId,
@@ -239,12 +277,37 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
return
}
- us, err := models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
+ // Get assigned milestone.
+ if issue.MilestoneId > 0 {
+ ctx.Data["Milestone"], err = models.GetMilestoneById(issue.MilestoneId)
+ if err != nil {
+ if err == models.ErrMilestoneNotExist {
+ log.Warn("issue.ViewIssue(GetMilestoneById): %v", err)
+ } else {
+ ctx.Handle(500, "issue.ViewIssue(GetMilestoneById)", err)
+ return
+ }
+ }
+ }
+
+ // Get all milestones.
+ ctx.Data["OpenMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, false)
+ if err != nil {
+ ctx.Handle(500, "issue.ViewIssue(GetMilestones.1): %v", err)
+ return
+ }
+ ctx.Data["ClosedMilestones"], err = models.GetMilestones(ctx.Repo.Repository.Id, true)
+ if err != nil {
+ ctx.Handle(500, "issue.ViewIssue(GetMilestones.2): %v", err)
+ return
+ }
+
+ // Get all collaborators.
+ ctx.Data["Collaborators"], err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
if err != nil {
ctx.Handle(500, "issue.CreateIssue(GetCollaborators)", err)
return
}
- ctx.Data["Collaborators"] = us
if ctx.IsSigned {
// Update issue-user.
@@ -330,24 +393,70 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
})
}
+func UpdateIssueMilestone(ctx *middleware.Context) {
+ if !ctx.Repo.IsOwner {
+ ctx.Error(403)
+ return
+ }
+
+ issueId, err := base.StrTo(ctx.Query("issue")).Int64()
+ if err != nil {
+ ctx.Error(404)
+ return
+ }
+
+ issue, err := models.GetIssueById(issueId)
+ if err != nil {
+ if err == models.ErrIssueNotExist {
+ ctx.Handle(404, "issue.UpdateIssueMilestone(GetIssueById)", err)
+ } else {
+ ctx.Handle(500, "issue.UpdateIssueMilestone(GetIssueById)", err)
+ }
+ return
+ }
+
+ oldMid := issue.MilestoneId
+ mid, _ := base.StrTo(ctx.Query("milestone")).Int64()
+ if oldMid == mid {
+ ctx.JSON(200, map[string]interface{}{
+ "ok": true,
+ })
+ return
+ }
+
+ // Not check for invalid milestone id and give responsibility to owners.
+ issue.MilestoneId = mid
+ if err = models.ChangeMilestoneAssign(oldMid, mid, issue.IsClosed); err != nil {
+ ctx.Handle(500, "issue.UpdateIssueMilestone(ChangeMilestoneAssign)", err)
+ return
+ } else if err = models.UpdateIssue(issue); err != nil {
+ ctx.Handle(500, "issue.UpdateIssueMilestone(UpdateIssue)", err)
+ return
+ }
+
+ ctx.JSON(200, map[string]interface{}{
+ "ok": true,
+ })
+}
+
func UpdateAssignee(ctx *middleware.Context) {
if !ctx.Repo.IsOwner {
ctx.Error(403)
return
}
- idx, err := base.StrTo(ctx.Query("issue")).Int64()
+ issueId, err := base.StrTo(ctx.Query("issue")).Int64()
if err != nil {
ctx.Error(404)
return
}
- issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
+ issue, err := models.GetIssueById(issueId)
if err != nil {
if err == models.ErrIssueNotExist {
- ctx.Handle(404, "issue.UpdateAssignee", err)
+ ctx.Handle(404, "issue.UpdateAssignee(GetIssueById)", err)
} else {
- ctx.Handle(500, "issue.UpdateAssignee(GetIssueByIndex)", err)
+ ctx.Handle(500, "issue.UpdateAssignee(GetIssueById)", err)
}
return
}
@@ -385,7 +494,6 @@ func Comment(ctx *middleware.Context, params martini.Params) {
return
}
- // TODO: check collaborators
// Check if issue owner changes the status of issue.
var newStatus string
if ctx.Repo.IsOwner || issue.PosterId == ctx.User.Id {
@@ -488,13 +596,161 @@ func Milestones(ctx *middleware.Context) {
ctx.Data["IsRepoToolbarIssues"] = true
ctx.Data["IsRepoToolbarIssuesList"] = true
+ isShowClosed := ctx.Query("state") == "closed"
+
+ miles, err := models.GetMilestones(ctx.Repo.Repository.Id, isShowClosed)
+ if err != nil {
+ ctx.Handle(500, "issue.Milestones(GetMilestones)", err)
+ return
+ }
+ for _, m := range miles {
+ m.RenderedContent = string(base.RenderSpecialLink([]byte(m.Content), ctx.Repo.RepoLink))
+ m.CalOpenIssues()
+ }
+ ctx.Data["Milestones"] = miles
+
+ if isShowClosed {
+ ctx.Data["State"] = "closed"
+ } else {
+ ctx.Data["State"] = "open"
+ }
ctx.HTML(200, "issue/milestone")
}
-func NewMilestones(ctx *middleware.Context) {
- ctx.Data["Title"] = "New Milestones"
+func NewMilestone(ctx *middleware.Context) {
+ ctx.Data["Title"] = "New Milestone"
ctx.Data["IsRepoToolbarIssues"] = true
ctx.Data["IsRepoToolbarIssuesList"] = true
-
ctx.HTML(200, "issue/milestone_new")
}
+
+func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
+ ctx.Data["Title"] = "New Milestone"
+ ctx.Data["IsRepoToolbarIssues"] = true
+ ctx.Data["IsRepoToolbarIssuesList"] = true
+
+ var deadline time.Time
+ var err error
+ if len(form.Deadline) == 0 {
+ form.Deadline = "12/31/9999"
+ }
+ deadline, err = time.Parse("01/02/2006", form.Deadline)
+ if err != nil {
+ ctx.Handle(500, "issue.NewMilestonePost(time.Parse)", err)
+ return
+ }
+
+ mile := &models.Milestone{
+ RepoId: ctx.Repo.Repository.Id,
+ Index: int64(ctx.Repo.Repository.NumMilestones) + 1,
+ Name: form.Title,
+ Content: form.Content,
+ Deadline: deadline,
+ }
+ if err = models.NewMilestone(mile); err != nil {
+ ctx.Handle(500, "issue.NewMilestonePost(NewMilestone)", err)
+ return
+ }
+
+ ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
+}
+
+func UpdateMilestone(ctx *middleware.Context, params martini.Params) {
+ ctx.Data["Title"] = "Update Milestone"
+ ctx.Data["IsRepoToolbarIssues"] = true
+ ctx.Data["IsRepoToolbarIssuesList"] = true
+
+ idx, _ := base.StrTo(params["index"]).Int64()
+ if idx == 0 {
+ ctx.Handle(404, "issue.UpdateMilestone", nil)
+ return
+ }
+
+ mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx)
+ if err != nil {
+ if err == models.ErrMilestoneNotExist {
+ ctx.Handle(404, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
+ } else {
+ ctx.Handle(500, "issue.UpdateMilestone(GetMilestoneByIndex)", err)
+ }
+ return
+ }
+
+ action := params["action"]
+ if len(action) > 0 {
+ switch action {
+ case "open":
+ if mile.IsClosed {
+ if err = models.ChangeMilestoneStatus(mile, false); err != nil {
+ ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err)
+ return
+ }
+ }
+ case "close":
+ if !mile.IsClosed {
+ mile.ClosedDate = time.Now()
+ if err = models.ChangeMilestoneStatus(mile, true); err != nil {
+ ctx.Handle(500, "issue.UpdateMilestone(ChangeMilestoneStatus)", err)
+ return
+ }
+ }
+ case "delete":
+ if err = models.DeleteMilestone(mile); err != nil {
+ ctx.Handle(500, "issue.UpdateMilestone(DeleteMilestone)", err)
+ return
+ }
+ }
+ ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
+ return
+ }
+
+ mile.DeadlineString = mile.Deadline.UTC().Format("01/02/2006")
+ if mile.DeadlineString == "12/31/9999" {
+ mile.DeadlineString = ""
+ }
+ ctx.Data["Milestone"] = mile
+
+ ctx.HTML(200, "issue/milestone_edit")
+}
+
+func UpdateMilestonePost(ctx *middleware.Context, params martini.Params, form auth.CreateMilestoneForm) {
+ ctx.Data["Title"] = "Update Milestone"
+ ctx.Data["IsRepoToolbarIssues"] = true
+ ctx.Data["IsRepoToolbarIssuesList"] = true
+
+ idx, _ := base.StrTo(params["index"]).Int64()
+ if idx == 0 {
+ ctx.Handle(404, "issue.UpdateMilestonePost", nil)
+ return
+ }
+
+ mile, err := models.GetMilestoneByIndex(ctx.Repo.Repository.Id, idx)
+ if err != nil {
+ if err == models.ErrMilestoneNotExist {
+ ctx.Handle(404, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err)
+ } else {
+ ctx.Handle(500, "issue.UpdateMilestonePost(GetMilestoneByIndex)", err)
+ }
+ return
+ }
+
+ var deadline time.Time
+ if len(form.Deadline) == 0 {
+ form.Deadline = "12/31/9999"
+ }
+ deadline, err = time.Parse("01/02/2006", form.Deadline)
+ if err != nil {
+ ctx.Handle(500, "issue.UpdateMilestonePost(time.Parse)", err)
+ return
+ }
+
+ mile.Name = form.Title
+ mile.Content = form.Content
+ mile.Deadline = deadline
+ if err = models.UpdateMilestone(mile); err != nil {
+ ctx.Handle(500, "issue.UpdateMilestonePost(UpdateMilestone)", err)
+ return
+ }
+
+ ctx.Redirect(ctx.Repo.RepoLink + "/issues/milestones")
+}
diff --git a/routers/repo/repo.go b/routers/repo/repo.go
index 19c9dddc..7769d227 100644
--- a/routers/repo/repo.go
+++ b/routers/repo/repo.go
@@ -254,6 +254,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
ctx.Data["LastCommit"] = ctx.Repo.Commit
ctx.Data["Paths"] = Paths
+ ctx.Data["TreeName"] = treename
ctx.Data["Treenames"] = treenames
ctx.Data["TreePath"] = treePath
ctx.Data["BranchLink"] = branchLink
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
index 89c7cb48..fa4973ec 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -83,6 +83,9 @@ func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
ctx.RenderWithErr("Please make sure you entered repository name is correct.", "repo/setting", nil)
return
+ } else if ctx.Repo.Repository.IsMirror {
+ ctx.Error(404)
+ return
}
newOwner := ctx.Query("owner")
diff --git a/routers/user/home.go b/routers/user/home.go
index f5130f47..2a2401c5 100644
--- a/routers/user/home.go
+++ b/routers/user/home.go
@@ -22,14 +22,21 @@ func Dashboard(ctx *middleware.Context) {
ctx.Data["PageIsUserDashboard"] = true
repos, err := models.GetRepositories(&models.User{Id: ctx.User.Id}, true)
if err != nil {
- ctx.Handle(500, "user.Dashboard", err)
+ ctx.Handle(500, "home.Dashboard(GetRepositories)", err)
return
}
ctx.Data["MyRepos"] = repos
+ collaRepos, err := models.GetCollaborativeRepos(ctx.User.Name)
+ if err != nil {
+ ctx.Handle(500, "home.Dashboard(GetCollaborativeRepos)", err)
+ return
+ }
+ ctx.Data["CollaborativeRepos"] = collaRepos
+
actions, err := models.GetFeeds(ctx.User.Id, 0, false)
if err != nil {
- ctx.Handle(500, "user.Dashboard", err)
+ ctx.Handle(500, "home.Dashboard", err)
return
}
@@ -214,22 +221,31 @@ func Issues(ctx *middleware.Context) {
issues[i], err = models.GetIssueById(ius[i].IssueId)
if err != nil {
if err == models.ErrIssueNotExist {
- log.Error("user.Issues(#%d): issue not exist", ius[i].IssueId)
+ log.Warn("user.Issues(GetIssueById #%d): issue not exist", ius[i].IssueId)
continue
} else {
- ctx.Handle(500, "user.Issues(GetIssue)", err)
+ ctx.Handle(500, fmt.Sprintf("user.Issues(GetIssueById #%d)", ius[i].IssueId), err)
return
}
}
issues[i].Repo, err = models.GetRepositoryById(issues[i].RepoId)
if err != nil {
- ctx.Handle(500, "user.Issues(GetRepositoryById)", err)
+ if err == models.ErrRepoNotExist {
+ log.Warn("user.Issues(GetRepositoryById #%d): repository not exist", issues[i].RepoId)
+ continue
+ } else {
+ ctx.Handle(500, fmt.Sprintf("user.Issues(GetRepositoryById #%d)", issues[i].RepoId), err)
+ return
+ }
+ }
+
+ if err = issues[i].Repo.GetOwner(); err != nil {
+ ctx.Handle(500, "user.Issues(GetOwner)", err)
return
}
- issues[i].Poster, err = models.GetUserById(issues[i].PosterId)
- if err != nil {
+ if err = issues[i].GetPoster(); err != nil {
ctx.Handle(500, "user.Issues(GetUserById)", err)
return
}
diff --git a/templates/admin/auths/edit.tmpl b/templates/admin/auths/edit.tmpl
index e1cef8cf..f2ba68fd 100644
--- a/templates/admin/auths/edit.tmpl
+++ b/templates/admin/auths/edit.tmpl
@@ -18,11 +18,11 @@
<div class="form-group">
<label class="col-md-3 control-label">Auth Type: </label>
<input type="hidden" name="type" value="{{.Source.Type}}"/>
- <div class="col-md-7">
- {{range $key, $val := .LoginTypes}}
- {{if eq $key $type}}{{$val}}{{end}}
- {{end}}
- </div>
+ <label class="control-label">
+ {{range $key, $val := .LoginTypes}}
+ {{if eq $key $type}}{{$val}}{{end}}
+ {{end}}
+ </label>
</div>
<div class="form-group {{if .Err_AuthName}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">Name: </label>
@@ -93,46 +93,47 @@
<label class="col-md-3 control-label">SMTP Auth: </label>
<div class="col-md-7">
<select name="smtpauth" class="form-control">
- {{$auth := .Source.SMTP.Auth}}
+ {{$auth := .Source.SMTP.Auth}}
{{range .SMTPAuths}}
<option value="{{.}}"
{{if eq . $auth}} selected{{end}}>{{.}}</option>
{{end}}
- }
</select>
</div>
</div>
- <div class="form-group {{if .Err_Host}}has-error has-feedback{{end}}">
+ <div class="form-group {{if .Err_SmtpHost}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">Host: </label>
<div class="col-md-7">
<input name="smtphost" class="form-control" placeholder="Type host address" value="{{.Source.SMTP.Host}}">
</div>
</div>
- <div class="form-group {{if .Err_Port}}has-error has-feedback{{end}}">
+ <div class="form-group {{if .Err_SmtpPort}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">Port: </label>
<div class="col-md-7">
<input name="smtpport" class="form-control" placeholder="Type port number" value="{{.Source.SMTP.Port}}">
</div>
</div>
- <div class="form-group {{if .Err_TLS}}has-error has-feedback{{end}}">
+ <!-- <div class="form-group {{if .Err_TLS}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">TLS: </label>
<div class="col-md-7">
- <input name="smtptls" type="checkbox" class="form-control" {{if .Source.SMTP.TLS}}checked{{end}}>
+ <input name="tls" type="checkbox" class="form-control" {{if .Source.SMTP.TLS}}checked{{end}}>
</div>
- </div>
+ </div> -->
{{end}}
-
- <div class="form-group {{if .Err_TLS}}has-error has-feedback{{end}}">
- <label class="col-md-3 control-label">Auto Register: </label>
- <div class="col-md-7">
- <input name="allowautoregister" type="checkbox" class="form-control" {{if .Source.AllowAutoRegister}}checked{{end}}>
- </div>
- </div>
<div class="form-group">
+ <div class="col-md-offset-3 col-md-7">
+ <div class="checkbox">
+ <label>
+ <input name="allowautoregister" type="checkbox" {{if .Source.AllowAutoRegister}}checked{{end}}>
+ <strong>Enable Auto Registeration</strong>
+ </label>
+ </div>
+ </div>
+
<div class="col-md-7 col-md-offset-3">
<div class="checkbox">
<label>
@@ -142,7 +143,9 @@
</div>
</div>
</div>
+
<hr/>
+
<div class="form-group">
<div class="col-md-offset-3 col-md-6">
<button type="submit" class="btn btn-lg btn-primary btn-block">Update authentication config</button>
diff --git a/templates/admin/auths/new.tmpl b/templates/admin/auths/new.tmpl
index d09833fc..f9d9892f 100644
--- a/templates/admin/auths/new.tmpl
+++ b/templates/admin/auths/new.tmpl
@@ -100,17 +100,17 @@
</div>
</div>
- <div class="form-group {{if .Err_Host}}has-error has-feedback{{end}}">
+ <div class="form-group {{if .Err_SmtpHost}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">Host: </label>
<div class="col-md-7">
- <input name="host" class="form-control" placeholder="Type host address" value="{{.host}}">
+ <input name="smtphost" class="form-control" placeholder="Type host address" value="{{.smtphost}}">
</div>
</div>
- <div class="form-group {{if .Err_Port}}has-error has-feedback{{end}}">
+ <div class="form-group {{if .Err_SmtpPort}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">Port: </label>
<div class="col-md-7">
- <input name="port" class="form-control" placeholder="Type port number" value="{{.port}}">
+ <input name="smtpport" class="form-control" placeholder="Type port number" value="{{.smtpport}}">
</div>
</div>
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 96565bac..f49e3827 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -194,12 +194,14 @@
<div class="panel-body">
<dl class="dl-horizontal admin-dl-horizontal">
+ {{range .Loggers}}
<dt>Log Mode</dt>
- <dd>{{.LogMode}}</dd>
+ <dd>{{.Mode}}</dd>
<dt>Log Config</dt>
<dd>
- <div style="padding-top: 5px;"><pre>{{.LogConfig}}</pre></div>
+ <div style="padding-top: 5px;"><pre>{{.Config}}</pre></div>
</dd>
+ {{end}}
</dl>
</div>
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
index 2de10b34..f709cb3f 100644
--- a/templates/admin/dashboard.tmpl
+++ b/templates/admin/dashboard.tmpl
@@ -10,7 +10,7 @@
</div>
<div class="panel-body">
- Gogs database has <b>{{.Stats.Counter.User}}</b> users, <b>{{.Stats.Counter.PublicKey}}</b> SSH keys, <b>{{.Stats.Counter.Repo}}</b> repositories, <b>{{.Stats.Counter.Watch}}</b> watches, <b>{{.Stats.Counter.Action}}</b> actions, <b>{{.Stats.Counter.Access}}</b> accesses, <b>{{.Stats.Counter.Issue}}</b> issues, <b>{{.Stats.Counter.Comment}}</b> comments, <b>{{.Stats.Counter.Mirror}}</b> mirrors, <b>{{.Stats.Counter.Oauth}}</b> oauthes, <b>{{.Stats.Counter.Release}}</b> releases, <b>{{.Stats.Counter.LoginSource}}</b> login sources, <b>{{.Stats.Counter.Webhook}}</b> webhooks.
+ Gogs database has <b>{{.Stats.Counter.User}}</b> users, <b>{{.Stats.Counter.PublicKey}}</b> SSH keys, <b>{{.Stats.Counter.Repo}}</b> repositories, <b>{{.Stats.Counter.Watch}}</b> watches, <b>{{.Stats.Counter.Action}}</b> actions, <b>{{.Stats.Counter.Access}}</b> accesses, <b>{{.Stats.Counter.Issue}}</b> issues, <b>{{.Stats.Counter.Comment}}</b> comments, <b>{{.Stats.Counter.Mirror}}</b> mirrors, <b>{{.Stats.Counter.Oauth}}</b> oauthes, <b>{{.Stats.Counter.Release}}</b> releases, <b>{{.Stats.Counter.LoginSource}}</b> login sources, <b>{{.Stats.Counter.Webhook}}</b> webhooks, <b>{{.Stats.Counter.Milestone}}</b> milestones.
</div>
</div>
diff --git a/templates/issue/create.tmpl b/templates/issue/create.tmpl
index 6d08f699..34cecc78 100644
--- a/templates/issue/create.tmpl
+++ b/templates/issue/create.tmpl
@@ -31,6 +31,56 @@
</ul>
</div>
</div>
+ <span><strong id="milestone" data-no-milestone="No milestone">No milestone</strong></span>
+ <input type="hidden" name="milestoneid" value="0" id="milestone-id"/>
+ <div style="display: inline-block;position: relative">
+ <button type="button" class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown">
+ <i class="fa fa-check-square-o"></i>
+ <span class="caret"></span>
+ </button>
+ <div class="dropdown-menu milestone">
+ <ul class="list-unstyled">
+ <li data-id="0" class="clear-milestone milestone-item hidden"><i class="fa fa-times-circle-o"></i> Clear milestone </li>
+ <li class="milestone-list">
+ <ul class="nav nav-tabs" data-init="tabs">
+ <li class="active"><a href="#milestone-open" data-toggle="tab">Open</a></li>
+ <li><a href="#milestone-close" data-toggle="tab">Closed</a></li>
+ </ul>
+ <div class="tab-content">
+ <div class="tab-pane active" id="milestone-open">
+ {{if not .OpenMilestones}}
+ <p class="milestone-item">Nothing to show</p>
+ {{else}}
+ <ul class="list-unstyled">
+ {{range .OpenMilestones}}
+ <li class="milestone-item" data-id="{{.Id}}">
+ <p><strong>{{.Name}}</strong></p>
+ <!-- <p>due to 3 days later</p> -->
+ </li>
+ {{end}}
+ </ul>
+ {{end}}
+ </div>
+
+ <div class="tab-pane" id="milestone-close">
+ {{if not .ClosedMilestones}}
+ <p class="milestone-item">Nothing to show</p>
+ {{else}}
+ <ul class="list-unstyled">
+ {{range .ClosedMilestones}}
+ <li class="milestone-item" data-id="{{.Id}}">
+ <p><strong>{{.Name}}</strong></p>
+ <p>Closed {{TimeSince .ClosedDate}}</p>
+ </li>
+ {{end}}
+ </ul>
+ {{end}}
+ </div>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
</div>
<div class="form-group panel-body">
<div class="md-help pull-right"><!-- todo help link -->
diff --git a/templates/issue/milestone.tmpl b/templates/issue/milestone.tmpl
index a688fb4c..8a5751c1 100644
--- a/templates/issue/milestone.tmpl
+++ b/templates/issue/milestone.tmpl
@@ -6,10 +6,8 @@
<div id="issue">
<div class="col-md-3 filter-list">
<ul class="list-unstyled">
- <li><a href="{{.RepoLink}}/issues/milestones" class="active">Open Milestones <strong class="pull-right">1</strong></a></li>
- <!-- <li><a href="#">Assigned to you</a></li> -->
- <li><a href="{{.RepoLink}}/issues/milestones">Close Milestones <strong class="pull-right">0</strong></a></li>
- <!-- <li><a href="#">Mentioned</a></li> -->
+ <li><a href="{{.RepoLink}}/issues/milestones"{{if eq .State "open"}} class="active"{{end}}>Open Milestones <strong class="pull-right">{{.Repository.NumOpenMilestones}}</strong></a></li>
+ <li><a href="{{.RepoLink}}/issues/milestones?state=closed"{{if eq .State "closed"}} class="active"{{end}}>Close Milestones <strong class="pull-right">{{.Repository.NumClosedMilestones}}</strong></a></li>
</ul>
<hr/>
<a href="{{.RepoLink}}/issues/milestones/new" class="text-center">
@@ -18,34 +16,25 @@
</div>
<div class="col-md-9">
<div class="milestones list-group">
+ {{range .Milestones}}
<div class="list-group-item milestone-item">
- <h4 class="title pull-left"><a href="#">Milestone Title</a></h4>
- <span class="issue-open label label-success">12</span>
- <span class="issue-close label label-warning">2</span>
+ <h4 class="title pull-left"><a href="{{$.RepoLink}}/issues?milestone={{.Index}}{{if .IsClosed}}&state=closed{{end}}">{{.Name}}</a></h4>
+ <span class="issue-open label label-success">{{.NumOpenIssues}}</span>
+ <span class="issue-close label label-warning">{{.NumClosedIssues}}</span>
<p class="actions pull-right">
- <a href="#">Edit</a>
- <a href="#">Open</a>
- <a href="#">Close</a>
- <a class="text-danger" href="#">Delete</a>
- <a href="#">Issues</a>
+ <a href="{{$.RepoLink}}/issues/milestones/{{.Index}}/edit">Edit</a>
+ {{if .IsClosed}}
+ <a href="{{$.RepoLink}}/issues/milestones/{{.Index}}/open">Open</a>
+ {{else}}
+ <a href="{{$.RepoLink}}/issues/milestones/{{.Index}}/close">Close</a>
+ {{end}}
+ <a class="text-danger" href="{{$.RepoLink}}/issues/milestones/{{.Index}}/delete">Delete</a>
+ <a href="{{$.RepoLink}}/issues?milestone={{.Index}}{{if .IsClosed}}&state=closed{{end}}">Issues</a>
</p>
<hr/>
- <p class="description">In this version of release, users are able to register and log in/out on Gogs, setting up SSH keys and do most of Git operations through SSH with public repositories. And Web UI only for view of Git data, no extra features are supported.</p>
- </div>
- <div class="list-group-item milestone-item">
- <h4 class="title pull-left"><a href="#">Milestone Title</a></h4>
- <span class="issue-open label label-success">12</span>
- <span class="issue-close label label-warning">2</span>
- <p class="actions pull-right">
- <a href="#">Edit</a>
- <a href="#">Open</a>
- <a href="#">Close</a>
- <a class="text-danger" href="#">Delete</a>
- <a href="#">Issues</a>
- </p>
- <hr/>
- <p class="description">In this version of release, users are able to register and log in/out on Gogs, setting up SSH keys and do most of Git operations through SSH with public repositories. And Web UI only for view of Git data, no extra features are supported.</p>
+ <p class="description">{{.RenderedContent | str2html}}</p>
</div>
+ {{end}}
</div>
</div>
</div>
diff --git a/templates/issue/milestone_edit.tmpl b/templates/issue/milestone_edit.tmpl
new file mode 100644
index 00000000..8f1a05e0
--- /dev/null
+++ b/templates/issue/milestone_edit.tmpl
@@ -0,0 +1,61 @@
+{{template "base/head" .}}
+{{template "base/navbar" .}}
+{{template "repo/nav" .}}
+{{template "repo/toolbar" .}}
+<div id="body" class="container">
+ <div id="issue">
+ <form class="form" action="{{.RepoLink}}/issues/milestones/{{.Milestone.Index}}/edit" method="post" id="issue-create-form">
+ {{.CsrfTokenHtml}}
+ {{template "base/alert" .}}
+ <div class="col-md-1">
+ <img class="avatar" src="{{.SignedUser.AvatarLink}}" alt=""/>
+ </div>
+ <div class="col-md-8 panel panel-default">
+ <div class="form-group panel-body">
+ <input class="form-control input-lg" type="text" name="title" required="required" placeholder="Title" value="{{.Milestone.Name}}" />
+ </div>
+ <div class="form-group panel-body">
+ <div class="md-help pull-right"><!-- todo help link -->
+ 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 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" data-ajax-name="issue-preview" data-ajax-context="{{.RepoLink}}" 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" data-ajax-rel="issue-preview" data-ajax-val="val" data-ajax-field="text">{{.Milestone.Content}}</textarea>
+ </div>
+ </div>
+ <div class="tab-pane issue-preview-content" id="issue-preview">loading...</div>
+ </div>
+ </div>
+ <div class="text-right panel-body">
+ <div class="form-group">
+ <input type="hidden" value="id" name="repo-id"/>
+ <button class="btn-success btn">Update milestone</button>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-3">
+ <h4>Milestone Due Date</h4>
+ <div class="form-group">
+ <input name="due_date" type="text" class="form-control" id="milestone-due-date" value="{{.Milestone.DeadlineString}}">
+ </div>
+ </div>
+ </form>
+ </div>
+</div>
+<script src="/js/bootstrap-datepicker.js"></script>
+<script>
+ $(function(){
+ $('#milestone-due-date').datepicker({
+ weekStart: 1,
+ todayBtn: "linked",
+ calendarWeeks: true,
+ todayHighlight: true
+ });
+ });
+</script>
+{{template "base/footer" .}}
diff --git a/templates/issue/milestone_new.tmpl b/templates/issue/milestone_new.tmpl
index 544ae6ea..044b7d10 100644
--- a/templates/issue/milestone_new.tmpl
+++ b/templates/issue/milestone_new.tmpl
@@ -4,7 +4,7 @@
{{template "repo/toolbar" .}}
<div id="body" class="container">
<div id="issue">
- <form class="form" action="{{.RepoLink}}/issues/new" method="post" id="issue-create-form">
+ <form class="form" action="{{.RepoLink}}/issues/milestones/new" method="post" id="issue-create-form">
{{.CsrfTokenHtml}}
{{template "base/alert" .}}
<div class="col-md-1">
@@ -34,7 +34,7 @@
<div class="text-right panel-body">
<div class="form-group">
<input type="hidden" value="id" name="repo-id"/>
- <button class="btn-success btn">Create new issue</button>
+ <button class="btn-success btn">Create new milestone</button>
</div>
</div>
</div>
diff --git a/templates/issue/user.tmpl b/templates/issue/user.tmpl
index f480c63a..d1c2bd99 100644
--- a/templates/issue/user.tmpl
+++ b/templates/issue/user.tmpl
@@ -36,7 +36,7 @@
{{range .Issues}}{{if .}}
<div class="list-group-item issue-item" id="issue-{{.Id}}">
<span class="number pull-right">#{{.Index}}</span>
- <h5 class="title"><a href="/{{$.SignedUser.Name}}/{{.Repo.Name}}/issues/{{.Index}}">{{.Name}}</a></h5>
+ <h5 class="title"><a href="/{{.Repo.Owner.Name}}/{{.Repo.Name}}/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>
diff --git a/templates/issue/view.tmpl b/templates/issue/view.tmpl
index 941f8e2f..18ec5faf 100644
--- a/templates/issue/view.tmpl
+++ b/templates/issue/view.tmpl
@@ -100,6 +100,64 @@
</div>
<div class="issue-bar col-md-2">
+ <div class="milestone" data-milestone="{{.Milestone.Id}}" data-ajax="{{.Issue.Index}}/milestone">
+ <div class="pull-right action">
+ <button class="btn btn-default btn-sm" data-toggle="dropdown">
+ <i class="fa fa-check-square-o"></i>
+ <span class="caret"></span>
+ </button>
+ <div class="dropdown-menu dropdown-menu-right">
+ <ul class="list-unstyled">
+ <li data-id="0" class="clear-milestone milestone-item hidden"><i class="fa fa-times-circle-o"></i> Clear milestone </li>
+ <li class="milestone-list">
+ <ul class="nav nav-tabs" data-init="tabs">
+ <li class="active"><a href="#milestone-open" data-toggle="tab">Open</a></li>
+ <li><a href="#milestone-close" data-toggle="tab">Closed</a></li>
+ </ul>
+ <div class="tab-content">
+ <div class="tab-pane active" id="milestone-open">
+ {{if not .OpenMilestones}}
+ <p class="milestone-item">Nothing to show</p>
+ {{else}}
+ <ul class="list-unstyled">
+ {{range .OpenMilestones}}
+ <li class="milestone-item" data-id="{{.Id}}">
+ <p><strong>{{.Name}}</strong></p>
+ <!-- <p>due to 3 days later</p> -->
+ </li>
+ {{end}}
+ </ul>
+ {{end}}
+ </div>
+
+ <div class="tab-pane" id="milestone-close">
+ {{if not .ClosedMilestones}}
+ <p class="milestone-item">Nothing to show</p>
+ {{else}}
+ <ul class="list-unstyled">
+ {{range .ClosedMilestones}}
+ <li class="milestone-item" data-id="{{.Id}}">
+ <p><strong>{{.Name}}</strong></p>
+ <p>Closed {{TimeSince .ClosedDate}}</p>
+ </li>
+ {{end}}
+ </ul>
+ {{end}}
+ </div>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <h4>Milestone</h4>
+ {{if .Milestone}}
+ <p class="completion{{if eq .Milestone.Completeness 0}} hidden{{end}}"><span style="width:{{.Milestone.Completeness}}%">&nbsp;</span></p>
+ <p class="name"><strong><a href="{{$.RepoLink}}/issues?milestone={{.Milestone.Index}}{{if $.Issue.IsClosed}}&state=closed{{end}}">{{.Milestone.Name}}</a></strong></p>
+ {{else}}
+ <p class="name">No milestone</p>
+ {{end}}
+ </div>
+
<div class="assignee" data-assigned="{{if .Issue.Assignee}}{{.Issue.Assignee.Id}}{{else}}0{{end}}" data-ajax="{{.Issue.Index}}/assignee">{{if .IsRepositoryOwner}}
<div class="pull-right action">
<button type="button" class="dropdown-toggle btn btn-default btn-sm" data-toggle="dropdown">
@@ -110,7 +168,7 @@
<ul class="list-unstyled">
<li data-uid="0" class="clear-assignee hidden"><i class="fa fa-times-circle-o"></i> Clear assignee</li>
{{range .Collaborators}}
- <li data-uid="{{.Id}}"><img src="{{.AvatarLink}}"><strong>{{.Name}}</strong> {{.FullName}}</li>
+ <li data-uid="{{.Id}}"><img src="{{.AvatarLink}}"><strong>{{.Name}}</strong></li>
{{end}}
</ul>
</div>
@@ -120,7 +178,7 @@
</div>
</div><!--
<div class="col-md-3">
- label assignment milestone dashboard
+ label dashboard
</div>-->
</div>
</div>
diff --git a/templates/repo/commits.tmpl b/templates/repo/commits.tmpl
index 74b03074..385f9d5b 100644
--- a/templates/repo/commits.tmpl
+++ b/templates/repo/commits.tmpl
@@ -41,8 +41,8 @@
</table>
</div>
{{if not .IsSearchPage}}<ul class="pagination" id="commits-pager">
- {{if .LastPageNum}}<li><a href="{{.RepoLink}}/commits/{{.BranchName}}?p={{.LastPageNum}}" rel="nofollow">&laquo; Newer</a></li>{{end}}
- {{if .NextPageNum}}<li><a href="{{.RepoLink}}/commits/{{.BranchName}}?p={{.NextPageNum}}" rel="nofollow">&raquo; Older</a></li>{{end}}
+ {{if .LastPageNum}}<li><a href="{{.RepoLink}}/commits/{{.BranchName}}{{if .FileName}}/{{.FileName}}{{end}}?p={{.LastPageNum}}" rel="nofollow">&laquo; Newer</a></li>{{end}}
+ {{if .NextPageNum}}<li><a href="{{.RepoLink}}/commits/{{.BranchName}}{{if .FileName}}/{{.FileName}}{{end}}?p={{.NextPageNum}}" rel="nofollow">&raquo; Older</a></li>{{end}}
</ul>{{end}}
</div>
</div>
diff --git a/templates/repo/diff.tmpl b/templates/repo/diff.tmpl
index 0b6d4f72..c85caa21 100644
--- a/templates/repo/diff.tmpl
+++ b/templates/repo/diff.tmpl
@@ -51,14 +51,14 @@
</div>
<!-- todo finish all file status, now modify, add, delete and rename -->
<span class="status {{DiffTypeToStr .Type}}" data-toggle="tooltip" data-placement="right" title="{{DiffTypeToStr .Type}}">&nbsp;</span>
- <a class="file" href="#diff-1">{{.Name}}</a>
+ <a class="file" href="#diff-{{.Index}}">{{.Name}}</a>
</li>
{{end}}
</ol>
</div>
{{range .Diff.Files}}
- <div class="panel panel-default diff-file-box diff-box file-content" id="diff-2">
+ <div class="panel panel-default diff-file-box diff-box file-content" id="diff-{{.Index}}">
<div class="panel-heading">
<div class="diff-counter count pull-left">
{{if not .IsBin}}
diff --git a/templates/repo/setting.tmpl b/templates/repo/setting.tmpl
index f0f041de..99835640 100644
--- a/templates/repo/setting.tmpl
+++ b/templates/repo/setting.tmpl
@@ -88,6 +88,7 @@
Danger Zone
</div>
+ {{if not .Repository.IsMirror}}
<div class="panel-body">
<button type="button" class="btn btn-default pull-right" href="#transfer-repository-modal" data-toggle="modal">
Transfer ownership
@@ -137,6 +138,8 @@
</div>
</div>
</div>
+ {{end}}
+
<hr>
<div class="panel-body">
<button type="button" class="btn btn-default pull-right" href="#delete-repository-modal" data-toggle="modal">
diff --git a/templates/repo/single_file.tmpl b/templates/repo/single_file.tmpl
index 95c41b70..8c09f3ec 100644
--- a/templates/repo/single_file.tmpl
+++ b/templates/repo/single_file.tmpl
@@ -16,7 +16,7 @@
<a class="btn btn-default hidden" href="#">Edit</a>
<a class="btn btn-default" href="{{.FileLink}}" rel="nofollow">Raw</a>
<a class="btn btn-default hidden" href="#">Blame</a>
- <a class="btn btn-default hidden" href="#">History</a>
+ <a class="btn btn-default" href="{{.RepoLink}}/commits/{{.BranchName}}/{{.TreeName}}">History</a>
<a class="btn btn-danger hidden" href="#">Delete</a>
</div>
{{end}}
diff --git a/templates/user/dashboard.tmpl b/templates/user/dashboard.tmpl
index 9781b8db..5cda6722 100644
--- a/templates/user/dashboard.tmpl
+++ b/templates/user/dashboard.tmpl
@@ -40,6 +40,7 @@
</div>
</div>
</div>
+
<div class="panel-body">
<ul class="list-group">{{range .MyRepos}}
<li class="list-group-item"><a href="/{{$.SignedUserName}}/{{.Name}}">
@@ -49,6 +50,18 @@
</ul>
</div>
</div>
+
+ <div class="panel panel-default repo-panel">
+ <div class="panel-heading">Collaborative Repositories</div>
+ <div class="panel-body">
+ <ul class="list-group">{{range .CollaborativeRepos}}
+ <li class="list-group-item"><a href="/{{.Owner.Name}}/{{.Name}}">
+ <!-- <span class="stars pull-right"><i class="fa fa-star"></i>{{.NumStars}}</span> -->
+ <i class="fa fa-book"></i>{{.Name}}{{if .IsPrivate}} <span class="label label-default">Private</span>{{end}}</a>
+ </li>{{end}}
+ </ul>
+ </div>
+ </div>
</div>
</div>
{{template "base/footer" .}}