Bladeren bron

Huge updates!!!!! Be careful to merge!!!!

Unknwon 10 jaren geleden
bovenliggende
commit
5c4bc3c848
43 gewijzigde bestanden met toevoegingen van 2347 en 2246 verwijderingen
  1. 88 86
      cmd/web.go
  2. 2 0
      conf/locale/locale_en-US.ini
  3. 2 0
      conf/locale/locale_zh-CN.ini
  4. 1 1
      gogs.go
  5. 1 1
      models/release.go
  6. 0 1
      modules/base/template.go
  7. 8 0
      modules/git/commit.go
  8. 57 0
      modules/git/repo_commit.go
  9. 8 0
      modules/git/repo_tag.go
  10. 13 0
      modules/git/tree_blob.go
  11. 21 0
      modules/git/utils.go
  12. 0 0
      public/css/github.min.css
  13. BIN
      public/img/avatar_default.jpg
  14. BIN
      public/img/favicon.bak.png
  15. BIN
      public/img/gogs-lg.png
  16. 0 3
      public/ng/css/font-awesome.min.css
  17. 5 1
      public/ng/css/gogs.css
  18. 2 4
      routers/org/members.go
  19. 17 19
      routers/org/org.go
  20. 12 14
      routers/org/teams.go
  21. 218 221
      routers/repo/commit.go
  22. 32 41
      routers/repo/download.go
  23. 1115 1111
      routers/repo/issue.go
  24. 216 218
      routers/repo/release.go
  25. 93 91
      routers/repo/repo.go
  26. 359 359
      routers/repo/setting.go
  27. 13 10
      routers/user/home.go
  28. 8 0
      routers/user/setting.go
  29. 1 1
      templates/.VERSION
  30. 3 3
      templates/admin/config.tmpl
  31. 2 2
      templates/ng/base/head.tmpl
  32. 1 1
      templates/repo/commits.tmpl
  33. 1 1
      templates/repo/diff.tmpl
  34. 1 1
      templates/repo/issue/list.tmpl
  35. 4 4
      templates/repo/issue/view.tmpl
  36. 3 3
      templates/status/401.tmpl
  37. 1 0
      templates/status/404.tmpl
  38. 4 45
      templates/user/dashboard/dashboard.tmpl
  39. 12 0
      templates/user/dashboard/repo_list.tmpl
  40. 1 1
      templates/user/issues.tmpl
  41. 3 3
      templates/user/profile.tmpl
  42. 1 0
      templates/user/settings/nav.tmpl
  43. 18 0
      templates/user/settings/orgs.tmpl

+ 88 - 86
cmd/web.go

@@ -30,7 +30,7 @@ import (
 	"github.com/gogits/gogs/routers/admin"
 	"github.com/gogits/gogs/routers/api/v1"
 	"github.com/gogits/gogs/routers/dev"
-	// "github.com/gogits/gogs/routers/org"
+	"github.com/gogits/gogs/routers/org"
 	"github.com/gogits/gogs/routers/repo"
 	"github.com/gogits/gogs/routers/user"
 )
@@ -101,8 +101,8 @@ func runWeb(*cli.Context) {
 
 	// Routers.
 	m.Get("/", ignSignIn, routers.Home)
-	// m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install)
-	// m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost)
+	m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install)
+	m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost)
 	m.Group("", func(r *macaron.Router) {
 		r.Get("/issues", user.Issues)
 		r.Get("/pulls", user.Pulls)
@@ -151,6 +151,7 @@ func runWeb(*cli.Context) {
 			r.Get("/ssh", user.SettingsSSHKeys)
 			r.Post("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
 			r.Get("/social", user.SettingsSocial)
+			r.Get("/orgs", user.SettingsOrgs)
 			r.Route("/delete", "GET,POST", user.SettingsDelete)
 		})
 	}, reqSignIn)
@@ -173,8 +174,8 @@ func runWeb(*cli.Context) {
 	m.Group("/repo", func(r *macaron.Router) {
 		r.Get("/create", repo.Create)
 		r.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
-		// r.Get("/migrate", repo.Migrate)
-		// r.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
+		r.Get("/migrate", repo.Migrate)
+		r.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
 	}, reqSignIn)
 
 	adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true})
@@ -210,91 +211,92 @@ func runWeb(*cli.Context) {
 		dev.RegisterDebugRoutes(m)
 	}
 
-	// reqTrueOwner := middleware.RequireTrueOwner()
-
-	// m.Group("/org", func(r *macaron.Router) {
-	// 	r.Get("/create", org.New)
-	// 	r.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.NewPost)
-	// 	r.Get("/:org", org.Home)
-	// 	r.Get("/:org/dashboard", org.Dashboard)
-	// 	r.Get("/:org/members", org.Members)
-
-	// 	r.Get("/:org/teams", org.Teams)
-	// 	r.Get("/:org/teams/new", org.NewTeam)
-	// 	r.Post("/:org/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
-	// 	r.Get("/:org/teams/:team/edit", org.EditTeam)
-
-	// 	r.Get("/:org/team/:team", org.SingleTeam)
-
-	// 	r.Get("/:org/settings", org.Settings)
-	// 	r.Post("/:org/settings", bindIgnErr(auth.OrgSettingForm{}), org.SettingsPost)
-	// 	r.Post("/:org/settings/delete", org.DeletePost)
-	// }, reqSignIn)
-
-	// m.Group("/:username/:reponame", func(r *macaron.Router) {
-	// 	r.Get("/settings", repo.Setting)
-	// 	r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost)
-
-	// 	m.Group("/settings", func(r *macaron.Router) {
-	// 		r.Get("/collaboration", repo.Collaboration)
-	// 		r.Post("/collaboration", repo.CollaborationPost)
-	// 		r.Get("/hooks", repo.WebHooks)
-	// 		r.Get("/hooks/add", repo.WebHooksAdd)
-	// 		r.Post("/hooks/add", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksAddPost)
-	// 		r.Get("/hooks/:id", repo.WebHooksEdit)
-	// 		r.Post("/hooks/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
-	// 	})
-	// }, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner)
-
-	// m.Group("/:username/:reponame", func(r *macaron.Router) {
-	// 	r.Get("/action/:action", repo.Action)
-
-	// 	m.Group("/issues", func(r *macaron.Router) {
-	// 		r.Get("/new", repo.CreateIssue)
-	// 		r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost)
-	// 		r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
-	// 		r.Post("/:index/label", repo.UpdateIssueLabel)
-	// 		r.Post("/:index/milestone", repo.UpdateIssueMilestone)
-	// 		r.Post("/:index/assignee", repo.UpdateAssignee)
-	// 		r.Get("/:index/attachment/:id", repo.IssueGetAttachment)
-	// 		r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
-	// 		r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
-	// 		r.Post("/labels/delete", repo.DeleteLabel)
-	// 		r.Get("/milestones", repo.Milestones)
-	// 		r.Get("/milestones/new", repo.NewMilestone)
-	// 		r.Post("/milestones/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
-	// 		r.Get("/milestones/:index/edit", repo.UpdateMilestone)
-	// 		r.Post("/milestones/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.UpdateMilestonePost)
-	// 		r.Get("/milestones/:index/:action", repo.UpdateMilestone)
-	// 	})
-
-	// 	r.Post("/comment/:action", repo.Comment)
-	// 	r.Get("/releases/new", repo.NewRelease)
-	// 	r.Get("/releases/edit/:tagname", repo.EditRelease)
-	// }, reqSignIn, middleware.RepoAssignment(true))
-
-	// m.Group("/:username/:reponame", func(r *macaron.Router) {
-	// 	r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
-	// 	r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
-	// }, reqSignIn, middleware.RepoAssignment(true, true))
-
-	// m.Group("/:username/:reponame", func(r *macaron.Router) {
-	// 	r.Get("/issues", repo.Issues)
-	// 	r.Get("/issues/:index", repo.ViewIssue)
-	// 	r.Get("/pulls", repo.Pulls)
-	// 	r.Get("/branches", repo.Branches)
-	// }, ignSignIn, middleware.RepoAssignment(true))
+	reqTrueOwner := middleware.RequireTrueOwner()
+
+	// Organization routers.
+	m.Group("/org", func(r *macaron.Router) {
+		r.Get("/create", org.New)
+		r.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.NewPost)
+		r.Get("/:org", org.Home)
+		r.Get("/:org/dashboard", org.Dashboard)
+		r.Get("/:org/members", org.Members)
+
+		r.Get("/:org/teams", org.Teams)
+		r.Get("/:org/teams/new", org.NewTeam)
+		r.Post("/:org/teams/new", bindIgnErr(auth.CreateTeamForm{}), org.NewTeamPost)
+		r.Get("/:org/teams/:team/edit", org.EditTeam)
+
+		r.Get("/:org/team/:team", org.SingleTeam)
+
+		r.Get("/:org/settings", org.Settings)
+		r.Post("/:org/settings", bindIgnErr(auth.OrgSettingForm{}), org.SettingsPost)
+		r.Post("/:org/settings/delete", org.DeletePost)
+	}, reqSignIn)
+
+	m.Group("/:username/:reponame", func(r *macaron.Router) {
+		r.Get("/settings", repo.Setting)
+		r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost)
+
+		m.Group("/settings", func(r *macaron.Router) {
+			r.Get("/collaboration", repo.Collaboration)
+			r.Post("/collaboration", repo.CollaborationPost)
+			r.Get("/hooks", repo.WebHooks)
+			r.Get("/hooks/add", repo.WebHooksAdd)
+			r.Post("/hooks/add", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksAddPost)
+			r.Get("/hooks/:id", repo.WebHooksEdit)
+			r.Post("/hooks/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
+		})
+	}, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner)
+
+	m.Group("/:username/:reponame", func(r *macaron.Router) {
+		// r.Get("/action/:action", repo.Action)
+
+		m.Group("/issues", func(r *macaron.Router) {
+			r.Get("/new", repo.CreateIssue)
+			r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost)
+			r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
+			r.Post("/:index/label", repo.UpdateIssueLabel)
+			r.Post("/:index/milestone", repo.UpdateIssueMilestone)
+			r.Post("/:index/assignee", repo.UpdateAssignee)
+			r.Get("/:index/attachment/:id", repo.IssueGetAttachment)
+			r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
+			r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
+			r.Post("/labels/delete", repo.DeleteLabel)
+			r.Get("/milestones", repo.Milestones)
+			r.Get("/milestones/new", repo.NewMilestone)
+			r.Post("/milestones/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
+			r.Get("/milestones/:index/edit", repo.UpdateMilestone)
+			r.Post("/milestones/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.UpdateMilestonePost)
+			r.Get("/milestones/:index/:action", repo.UpdateMilestone)
+		})
+
+		r.Post("/comment/:action", repo.Comment)
+		r.Get("/releases/new", repo.NewRelease)
+		r.Get("/releases/edit/:tagname", repo.EditRelease)
+	}, reqSignIn, middleware.RepoAssignment(true))
+
+	m.Group("/:username/:reponame", func(r *macaron.Router) {
+		r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
+		r.Post("/releases/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
+	}, reqSignIn, middleware.RepoAssignment(true, true))
+
+	m.Group("/:username/:reponame", func(r *macaron.Router) {
+		r.Get("/issues", repo.Issues)
+		r.Get("/issues/:index", repo.ViewIssue)
+		r.Get("/pulls", repo.Pulls)
+		r.Get("/branches", repo.Branches)
+	}, ignSignIn, middleware.RepoAssignment(true))
 
 	m.Group("/:username/:reponame", func(r *macaron.Router) {
 		r.Get("/src/:branchname", repo.Home)
 		r.Get("/src/:branchname/*", repo.Home)
-		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)
+		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)
 		r.Get("/archive/*.*", repo.Download)
 	}, ignSignIn, middleware.RepoAssignment(true, true))
 

+ 2 - 0
conf/locale/locale_en-US.ini

@@ -89,6 +89,7 @@ profile = Profile
 password = Password
 ssh_keys = SSH Keys
 social = Social Accounts
+orgs = Organizations
 delete = Delete Accoount
 
 public_profile = Public Profile
@@ -118,6 +119,7 @@ add_on = Added on
 last_used = Last used on
 no_activity = No recent activity
 
+manage_orgs = Manage Organizations
 manage_social = Manage Associated Social Accounts
 
 delete_account = Delete Your Account

+ 2 - 0
conf/locale/locale_zh-CN.ini

@@ -89,6 +89,7 @@ profile = 个人信息
 password = 修改密码
 ssh_keys = 管理 SSH 密钥
 social = 社交帐号绑定
+orgs = 管理组织
 delete = 删除帐户
 
 public_profile = 公开信息
@@ -118,6 +119,7 @@ add_on = 增加于
 last_used = 上次使用在
 no_activity = 没有最近活动
 
+manage_orgs = 管理我的组织
 manage_social = 管理关联社交帐户
 
 delete_account = 删除当前帐户

+ 1 - 1
gogs.go

@@ -17,7 +17,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.4.7.0725 Alpha"
+const APP_VER = "0.4.7.0726 Alpha"
 
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())

+ 1 - 1
models/release.go

@@ -10,7 +10,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/gogits/git"
+	"github.com/gogits/gogs/modules/git"
 )
 
 var (

+ 0 - 1
modules/base/template.go

@@ -106,7 +106,6 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
 	"CreateCaptcha":         func() string { return "" },
 }
 
-// TODO: Legacy
 type Actioner interface {
 	GetOpType() int
 	GetActUserName() string

+ 8 - 0
modules/git/commit.go

@@ -73,6 +73,14 @@ func (c *Commit) CommitsCount() (int, error) {
 	return c.repo.commitsCount(c.Id)
 }
 
+func (c *Commit) SearchCommits(keyword string) (*list.List, error) {
+	return c.repo.searchCommits(c.Id, keyword)
+}
+
+func (c *Commit) CommitsByRange(page int) (*list.List, error) {
+	return c.repo.commitsByRange(c.Id, page)
+}
+
 func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
 	return c.repo.getCommitOfRelPath(c.Id, relPath)
 }

+ 57 - 0
modules/git/repo_commit.go

@@ -32,7 +32,18 @@ func (repo *Repository) GetCommitOfBranch(branchName string) (*Commit, error) {
 	if err != nil {
 		return nil, err
 	}
+	return repo.GetCommit(commitId)
+}
+
+func (repo *Repository) GetCommitIdOfTag(tagName string) (string, error) {
+	return repo.getCommitIdOfRef("refs/tags/" + tagName)
+}
 
+func (repo *Repository) GetCommitOfTag(tagName string) (*Commit, error) {
+	commitId, err := repo.GetCommitIdOfTag(tagName)
+	if err != nil {
+		return nil, err
+	}
 	return repo.GetCommit(commitId)
 }
 
@@ -212,6 +223,32 @@ func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *li
 	return nil
 }
 
+func (repo *Repository) CommitsCount(commitId string) (int, error) {
+	id, err := NewIdFromString(commitId)
+	if err != nil {
+		return 0, err
+	}
+	return repo.commitsCount(id)
+}
+
+func (repo *Repository) FileCommitsCount(branch, file string) (int, error) {
+	stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count",
+		branch, "--", file)
+	if err != nil {
+		return 0, errors.New(stderr)
+	}
+	return com.StrTo(strings.TrimSpace(stdout)).Int()
+}
+
+func (repo *Repository) CommitsByFileAndRange(branch, file string, page int) (*list.List, error) {
+	stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", branch,
+		"--skip="+com.ToStr((page-1)*50), "--max-count=50", prettyLogFormat, "--", file)
+	if err != nil {
+		return nil, errors.New(string(stderr))
+	}
+	return parsePrettyFormatLog(repo, stdout)
+}
+
 func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
 	l := list.New()
 	lock := new(sync.Mutex)
@@ -219,6 +256,26 @@ func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
 	return l, err
 }
 
+func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) {
+	stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(), "-100",
+		"-i", "--grep="+keyword, prettyLogFormat)
+	if err != nil {
+		return nil, err
+	} else if len(stderr) > 0 {
+		return nil, errors.New(string(stderr))
+	}
+	return parsePrettyFormatLog(repo, stdout)
+}
+
+func (repo *Repository) commitsByRange(id sha1, page int) (*list.List, error) {
+	stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(),
+		"--skip="+com.ToStr((page-1)*50), "--max-count=50", prettyLogFormat)
+	if err != nil {
+		return nil, errors.New(string(stderr))
+	}
+	return parsePrettyFormatLog(repo, stdout)
+}
+
 func (repo *Repository) getCommitOfRelPath(id sha1, relPath string) (*Commit, error) {
 	stdout, _, err := com.ExecCmdDir(repo.Path, "git", "log", "-1", prettyLogFormat, id.String(), "--", relPath)
 	if err != nil {

+ 8 - 0
modules/git/repo_tag.go

@@ -30,6 +30,14 @@ func (repo *Repository) GetTags() ([]string, error) {
 	return tags[:len(tags)-1], nil
 }
 
+func (repo *Repository) CreateTag(tagName, idStr string) error {
+	_, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", tagName, idStr)
+	if err != nil {
+		return errors.New(stderr)
+	}
+	return nil
+}
+
 func (repo *Repository) getTag(id sha1) (*Tag, error) {
 	if repo.tagCache != nil {
 		if t, ok := repo.tagCache[id]; ok {

+ 13 - 0
modules/git/tree_blob.go

@@ -44,3 +44,16 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
 	}
 	return nil, fmt.Errorf("GetTreeEntryByPath: %v", ErrNotExist)
 }
+
+func (t *Tree) GetBlobByPath(rpath string) (*Blob, error) {
+	entry, err := t.GetTreeEntryByPath(rpath)
+	if err != nil {
+		return nil, err
+	}
+
+	if !entry.IsDir() {
+		return entry.Blob(), nil
+	}
+
+	return nil, ErrNotExist
+}

+ 21 - 0
modules/git/utils.go

@@ -5,12 +5,33 @@
 package git
 
 import (
+	"bytes"
+	"container/list"
 	"path/filepath"
 	"strings"
 )
 
 const prettyLogFormat = `--pretty=format:%H`
 
+func parsePrettyFormatLog(repo *Repository, logByts []byte) (*list.List, error) {
+	l := list.New()
+	if len(logByts) == 0 {
+		return l, nil
+	}
+
+	parts := bytes.Split(logByts, []byte{'\n'})
+
+	for _, commitId := range parts {
+		commit, err := repo.GetCommit(string(commitId))
+		if err != nil {
+			return nil, err
+		}
+		l.PushBack(commit)
+	}
+
+	return l, nil
+}
+
 func RefEndName(refStr string) string {
 	index := strings.LastIndex(refStr, "/")
 	if index != -1 {

+ 0 - 0
public/ng/css/github.min.css → public/css/github.min.css


BIN
public/img/avatar_default.jpg


BIN
public/img/favicon.bak.png


BIN
public/img/gogs-lg.png


File diff suppressed because it is too large
+ 0 - 3
public/ng/css/font-awesome.min.css


+ 5 - 1
public/ng/css/gogs.css

@@ -373,7 +373,7 @@ img.avatar-30 {
   display: inline-block;
   text-decoration: none;
   -webkit-font-smoothing: antialiased;
-  margin-right: 8px;
+  margin-left: 30px;
 }
 .markdown a span.octicon-link {
   opacity: 0;
@@ -1058,6 +1058,9 @@ The register and sign-in page style
 }
 #repo-bare-start pre {
   margin: 0 40px;
+  padding: 6px 10px;
+  border: 1px solid #ddd;
+  background: #f8f8f8;
 }
 .repo-bare #repo-bare-start h2 {
   margin-top: 30px;
@@ -1073,6 +1076,7 @@ The register and sign-in page style
   margin-right: 200px;
 }
 .repo-bare #repo-clone-help {
+  clear: both;
   width: 100%;
 }
 .repo-bare #repo-clone-url {

+ 2 - 4
routers/org/members.go

@@ -5,12 +5,10 @@
 package org
 
 import (
-	"github.com/go-martini/martini"
-
 	"github.com/gogits/gogs/modules/middleware"
 )
 
-func Members(ctx *middleware.Context, params martini.Params) {
-	ctx.Data["Title"] = "Organization " + params["org"] + " Members"
+func Members(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Organization " + ctx.Params(":org") + " Members"
 	ctx.HTML(200, "org/members")
 }

+ 17 - 19
routers/org/org.go

@@ -5,9 +5,7 @@
 package org
 
 import (
-	"github.com/go-martini/martini"
-
-	"github.com/gogits/gogs-ng/models"
+	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
 	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/log"
@@ -21,10 +19,10 @@ const (
 	SETTINGS base.TplName = "org/settings"
 )
 
-func Home(ctx *middleware.Context, params martini.Params) {
-	ctx.Data["Title"] = "Organization " + params["org"]
+func Home(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Organization " + ctx.Params(":org")
 
-	org, err := models.GetUserByName(params["org"])
+	org, err := models.GetUserByName(ctx.Params(":org"))
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "org.Home(GetUserByName)", err)
@@ -99,12 +97,12 @@ func NewPost(ctx *middleware.Context, form auth.CreateOrgForm) {
 	ctx.Redirect("/org/" + form.OrgName + "/dashboard")
 }
 
-func Dashboard(ctx *middleware.Context, params martini.Params) {
+func Dashboard(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Dashboard"
 	ctx.Data["PageIsUserDashboard"] = true
 	ctx.Data["PageIsOrgDashboard"] = true
 
-	org, err := models.GetUserByName(params["org"])
+	org, err := models.GetUserByName(ctx.Params(":org"))
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "org.Dashboard(GetUserByName)", err)
@@ -114,11 +112,11 @@ func Dashboard(ctx *middleware.Context, params martini.Params) {
 		return
 	}
 
-	// if err := ctx.User.GetOrganizations(); err != nil {
-	// 	ctx.Handle(500, "home.Dashboard(GetOrganizations)", err)
-	// 	return
-	// }
-	// ctx.Data["Orgs"] = ctx.User.Orgs
+	if err := ctx.User.GetOrganizations(); err != nil {
+		ctx.Handle(500, "home.Dashboard(GetOrganizations)", err)
+		return
+	}
+	ctx.Data["Orgs"] = ctx.User.Orgs
 	ctx.Data["ContextUser"] = org
 
 	ctx.Data["MyRepos"], err = models.GetRepositories(org.Id, true)
@@ -137,10 +135,10 @@ func Dashboard(ctx *middleware.Context, params martini.Params) {
 	ctx.HTML(200, user.DASHBOARD)
 }
 
-func Settings(ctx *middleware.Context, params martini.Params) {
+func Settings(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Settings"
 
-	org, err := models.GetUserByName(params["org"])
+	org, err := models.GetUserByName(ctx.Params(":org"))
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "org.Settings(GetUserByName)", err)
@@ -154,10 +152,10 @@ func Settings(ctx *middleware.Context, params martini.Params) {
 	ctx.HTML(200, SETTINGS)
 }
 
-func SettingsPost(ctx *middleware.Context, params martini.Params, form auth.OrgSettingForm) {
+func SettingsPost(ctx *middleware.Context, form auth.OrgSettingForm) {
 	ctx.Data["Title"] = "Settings"
 
-	org, err := models.GetUserByName(params["org"])
+	org, err := models.GetUserByName(ctx.Params(":org"))
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "org.SettingsPost(GetUserByName)", err)
@@ -187,10 +185,10 @@ func SettingsPost(ctx *middleware.Context, params martini.Params, form auth.OrgS
 	ctx.Redirect("/org/" + org.Name + "/settings")
 }
 
-func DeletePost(ctx *middleware.Context, params martini.Params) {
+func DeletePost(ctx *middleware.Context) {
 	ctx.Data["Title"] = "Settings"
 
-	org, err := models.GetUserByName(params["org"])
+	org, err := models.GetUserByName(ctx.Params(":org"))
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "org.DeletePost(GetUserByName)", err)

+ 12 - 14
routers/org/teams.go

@@ -5,8 +5,6 @@
 package org
 
 import (
-	"github.com/go-martini/martini"
-
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
 	"github.com/gogits/gogs/modules/base"
@@ -19,10 +17,10 @@ const (
 	TEAM_NEW base.TplName = "org/team_new"
 )
 
-func Teams(ctx *middleware.Context, params martini.Params) {
-	ctx.Data["Title"] = "Organization " + params["org"] + " Teams"
+func Teams(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Organization " + ctx.Params(":org") + " Teams"
 
-	org, err := models.GetUserByName(params["org"])
+	org, err := models.GetUserByName(ctx.Params(":org"))
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "org.Teams(GetUserByName)", err)
@@ -48,8 +46,8 @@ func Teams(ctx *middleware.Context, params martini.Params) {
 	ctx.HTML(200, TEAMS)
 }
 
-func NewTeam(ctx *middleware.Context, params martini.Params) {
-	org, err := models.GetUserByName(params["org"])
+func NewTeam(ctx *middleware.Context) {
+	org, err := models.GetUserByName(ctx.Params(":org"))
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "org.NewTeam(GetUserByName)", err)
@@ -69,8 +67,8 @@ func NewTeam(ctx *middleware.Context, params martini.Params) {
 	ctx.HTML(200, TEAM_NEW)
 }
 
-func NewTeamPost(ctx *middleware.Context, params martini.Params, form auth.CreateTeamForm) {
-	org, err := models.GetUserByName(params["org"])
+func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
+	org, err := models.GetUserByName(ctx.Params(":org"))
 	if err != nil {
 		if err == models.ErrUserNotExist {
 			ctx.Handle(404, "org.NewTeamPost(GetUserByName)", err)
@@ -125,12 +123,12 @@ func NewTeamPost(ctx *middleware.Context, params martini.Params, form auth.Creat
 	ctx.Redirect("/org/" + org.LowerName + "/teams/" + t.LowerName)
 }
 
-func EditTeam(ctx *middleware.Context, params martini.Params) {
-	ctx.Data["Title"] = "Organization " + params["org"] + " Edit Team"
+func EditTeam(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Organization " + ctx.Params(":org") + " Edit Team"
 	ctx.HTML(200, "org/edit_team")
 }
 
-func SingleTeam(ctx *middleware.Context,params martini.Params){
-	ctx.Data["Title"] = "single-team"+params["org"]
-	ctx.HTML(200,"org/team")
+func SingleTeam(ctx *middleware.Context) {
+	ctx.Data["Title"] = "single-team" + ctx.Params(":org")
+	ctx.HTML(200, "org/team")
 }

+ 218 - 221
routers/repo/commit.go

@@ -4,224 +4,221 @@
 
 package repo
 
-// import (
-// 	"path"
-
-// 	"github.com/Unknwon/com"
-// 	"github.com/go-martini/martini"
-
-// 	"github.com/gogits/gogs/models"
-// 	"github.com/gogits/gogs/modules/base"
-// 	"github.com/gogits/gogs/modules/middleware"
-// )
-
-// const (
-// 	COMMITS base.TplName = "repo/commits"
-// 	DIFF    base.TplName = "repo/diff"
-// )
-
-// func Commits(ctx *middleware.Context, params martini.Params) {
-// 	ctx.Data["IsRepoToolbarCommits"] = true
-
-// 	userName := ctx.Repo.Owner.Name
-// 	repoName := ctx.Repo.Repository.Name
-
-// 	brs, err := ctx.Repo.GitRepo.GetBranches()
-// 	if err != nil {
-// 		ctx.Handle(500, "repo.Commits(GetBranches)", err)
-// 		return
-// 	} else if len(brs) == 0 {
-// 		ctx.Handle(404, "repo.Commits(GetBranches)", nil)
-// 		return
-// 	}
-
-// 	commitsCount, err := ctx.Repo.Commit.CommitsCount()
-// 	if err != nil {
-// 		ctx.Handle(500, "repo.Commits(GetCommitsCount)", err)
-// 		return
-// 	}
-
-// 	// Calculate and validate page number.
-// 	page, _ := com.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.
-// 	// ctx.Data["Commits"], err = ctx.Repo.Commit.CommitsByRange(page)
-// 	// if err != nil {
-// 	// 	ctx.Handle(500, "repo.Commits(CommitsByRange)", err)
-// 	// 	return
-// 	// }
-
-// 	ctx.Data["Username"] = userName
-// 	ctx.Data["Reponame"] = repoName
-// 	ctx.Data["CommitCount"] = commitsCount
-// 	ctx.Data["LastPageNum"] = lastPage
-// 	ctx.Data["NextPageNum"] = nextPage
-// 	ctx.HTML(200, COMMITS)
-// }
-
-// func SearchCommits(ctx *middleware.Context, params martini.Params) {
-// 	ctx.Data["IsSearchPage"] = true
-// 	ctx.Data["IsRepoToolbarCommits"] = true
-
-// 	keyword := ctx.Query("q")
-// 	if len(keyword) == 0 {
-// 		ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchName)
-// 		return
-// 	}
-
-// 	userName := params["username"]
-// 	repoName := params["reponame"]
-
-// 	brs, err := ctx.Repo.GitRepo.GetBranches()
-// 	if err != nil {
-// 		ctx.Handle(500, "repo.SearchCommits(GetBranches)", err)
-// 		return
-// 	} else if len(brs) == 0 {
-// 		ctx.Handle(404, "repo.SearchCommits(GetBranches)", nil)
-// 		return
-// 	}
-
-// 	// commits, err := ctx.Repo.Commit.SearchCommits(keyword)
-// 	// if err != nil {
-// 	// 	ctx.Handle(500, "repo.SearchCommits(SearchCommits)", err)
-// 	// 	return
-// 	// }
-
-// 	ctx.Data["Keyword"] = keyword
-// 	ctx.Data["Username"] = userName
-// 	ctx.Data["Reponame"] = repoName
-// 	// ctx.Data["CommitCount"] = commits.Len()
-// 	// ctx.Data["Commits"] = commits
-// 	ctx.HTML(200, COMMITS)
-// }
-
-// func Diff(ctx *middleware.Context, params martini.Params) {
-// 	ctx.Data["IsRepoToolbarCommits"] = true
-
-// 	userName := ctx.Repo.Owner.Name
-// 	repoName := ctx.Repo.Repository.Name
-// 	commitId := ctx.Repo.CommitId
-
-// 	commit := ctx.Repo.Commit
-
-// 	diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId)
-// 	if err != nil {
-// 		ctx.Handle(404, "repo.Diff(GetDiff)", err)
-// 		return
-// 	}
-
-// 	isImageFile := func(name string) bool {
-// 		// blob, err := ctx.Repo.Commit.GetBlobByPath(name)
-// 		// if err != nil {
-// 		// 	return false
-// 		// }
-
-// 		// dataRc, err := blob.Data()
-// 		// if err != nil {
-// 		// 	return false
-// 		// }
-// 		// buf := make([]byte, 1024)
-// 		// n, _ := dataRc.Read(buf)
-// 		// if n > 0 {
-// 		// 	buf = buf[:n]
-// 		// }
-// 		// dataRc.Close()
-// 		// _, isImage := base.IsImageFile(buf)
-// 		// return isImage
-// 		return false
-// 	}
-
-// 	parents := make([]string, commit.ParentCount())
-// 	for i := 0; i < commit.ParentCount(); i++ {
-// 		sha, err := commit.ParentId(i)
-// 		parents[i] = sha.String()
-// 		if err != nil {
-// 			ctx.Handle(404, "repo.Diff", err)
-// 			return
-// 		}
-// 	}
-
-// 	ctx.Data["Username"] = userName
-// 	ctx.Data["Reponame"] = repoName
-// 	ctx.Data["IsImageFile"] = isImageFile
-// 	ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitId)
-// 	ctx.Data["Commit"] = commit
-// 	ctx.Data["Diff"] = diff
-// 	ctx.Data["Parents"] = parents
-// 	ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
-// 	ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", commitId)
-// 	ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", commitId)
-// 	ctx.HTML(200, DIFF)
-// }
-
-// func FileHistory(ctx *middleware.Context, params martini.Params) {
-// 	ctx.Data["IsRepoToolbarCommits"] = true
-
-// 	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
-// 	// } else 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
-// 	// }
-
-// 	// ctx.Data["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["LastPageNum"] = lastPage
-// 	// ctx.Data["NextPageNum"] = nextPage
-// 	ctx.HTML(200, COMMITS)
-// }
+import (
+	"path"
+
+	"github.com/Unknwon/com"
+
+	"github.com/gogits/gogs/models"
+	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/middleware"
+)
+
+const (
+	COMMITS base.TplName = "repo/commits"
+	DIFF    base.TplName = "repo/diff"
+)
+
+func Commits(ctx *middleware.Context) {
+	ctx.Data["IsRepoToolbarCommits"] = true
+
+	userName := ctx.Repo.Owner.Name
+	repoName := ctx.Repo.Repository.Name
+
+	brs, err := ctx.Repo.GitRepo.GetBranches()
+	if err != nil {
+		ctx.Handle(500, "GetBranches", err)
+		return
+	} else if len(brs) == 0 {
+		ctx.Handle(404, "GetBranches", nil)
+		return
+	}
+
+	commitsCount, err := ctx.Repo.Commit.CommitsCount()
+	if err != nil {
+		ctx.Handle(500, "GetCommitsCount", err)
+		return
+	}
+
+	// Calculate and validate page number.
+	page, _ := com.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.
+	ctx.Data["Commits"], err = ctx.Repo.Commit.CommitsByRange(page)
+	if err != nil {
+		ctx.Handle(500, "CommitsByRange", err)
+		return
+	}
+
+	ctx.Data["Username"] = userName
+	ctx.Data["Reponame"] = repoName
+	ctx.Data["CommitCount"] = commitsCount
+	ctx.Data["LastPageNum"] = lastPage
+	ctx.Data["NextPageNum"] = nextPage
+	ctx.HTML(200, COMMITS)
+}
+
+func SearchCommits(ctx *middleware.Context) {
+	ctx.Data["IsSearchPage"] = true
+	ctx.Data["IsRepoToolbarCommits"] = true
+
+	keyword := ctx.Query("q")
+	if len(keyword) == 0 {
+		ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchName)
+		return
+	}
+
+	userName := ctx.Params(":username")
+	repoName := ctx.Params(":reponame")
+
+	brs, err := ctx.Repo.GitRepo.GetBranches()
+	if err != nil {
+		ctx.Handle(500, "GetBranches", err)
+		return
+	} else if len(brs) == 0 {
+		ctx.Handle(404, "GetBranches", nil)
+		return
+	}
+
+	commits, err := ctx.Repo.Commit.SearchCommits(keyword)
+	if err != nil {
+		ctx.Handle(500, "repo.SearchCommits(SearchCommits)", err)
+		return
+	}
+
+	ctx.Data["Keyword"] = keyword
+	ctx.Data["Username"] = userName
+	ctx.Data["Reponame"] = repoName
+	ctx.Data["CommitCount"] = commits.Len()
+	ctx.Data["Commits"] = commits
+	ctx.HTML(200, COMMITS)
+}
+
+func Diff(ctx *middleware.Context) {
+	ctx.Data["IsRepoToolbarCommits"] = true
+
+	userName := ctx.Repo.Owner.Name
+	repoName := ctx.Repo.Repository.Name
+	commitId := ctx.Repo.CommitId
+
+	commit := ctx.Repo.Commit
+
+	diff, err := models.GetDiff(models.RepoPath(userName, repoName), commitId)
+	if err != nil {
+		ctx.Handle(404, "GetDiff", err)
+		return
+	}
+
+	isImageFile := func(name string) bool {
+		blob, err := ctx.Repo.Commit.GetBlobByPath(name)
+		if err != nil {
+			return false
+		}
+
+		dataRc, err := blob.Data()
+		if err != nil {
+			return false
+		}
+		buf := make([]byte, 1024)
+		n, _ := dataRc.Read(buf)
+		if n > 0 {
+			buf = buf[:n]
+		}
+		_, isImage := base.IsImageFile(buf)
+		return isImage
+	}
+
+	parents := make([]string, commit.ParentCount())
+	for i := 0; i < commit.ParentCount(); i++ {
+		sha, err := commit.ParentId(i)
+		parents[i] = sha.String()
+		if err != nil {
+			ctx.Handle(404, "repo.Diff", err)
+			return
+		}
+	}
+
+	ctx.Data["Username"] = userName
+	ctx.Data["Reponame"] = repoName
+	ctx.Data["IsImageFile"] = isImageFile
+	ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitId)
+	ctx.Data["Commit"] = commit
+	ctx.Data["Diff"] = diff
+	ctx.Data["Parents"] = parents
+	ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
+	ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", commitId)
+	ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", commitId)
+	ctx.HTML(200, DIFF)
+}
+
+func FileHistory(ctx *middleware.Context) {
+	ctx.Data["IsRepoToolbarCommits"] = true
+
+	fileName := ctx.Params("*")
+	if len(fileName) == 0 {
+		Commits(ctx)
+		return
+	}
+
+	userName := ctx.Repo.Owner.Name
+	repoName := ctx.Repo.Repository.Name
+	branchName := ctx.Params(":branchname")
+
+	brs, err := ctx.Repo.GitRepo.GetBranches()
+	if err != nil {
+		ctx.Handle(500, "GetBranches", err)
+		return
+	} else if len(brs) == 0 {
+		ctx.Handle(404, "GetBranches", nil)
+		return
+	}
+
+	commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(branchName, fileName)
+	if err != nil {
+		ctx.Handle(500, "repo.FileHistory(GetCommitsCount)", err)
+		return
+	} else if commitsCount == 0 {
+		ctx.Handle(404, "repo.FileHistory", nil)
+		return
+	}
+
+	// Calculate and validate page number.
+	page := com.StrTo(ctx.Query("p")).MustInt()
+	if page < 1 {
+		page = 1
+	}
+	lastPage := page - 1
+	if lastPage < 0 {
+		lastPage = 0
+	}
+	nextPage := page + 1
+	if nextPage*50 > commitsCount {
+		nextPage = 0
+	}
+
+	ctx.Data["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["LastPageNum"] = lastPage
+	ctx.Data["NextPageNum"] = nextPage
+	ctx.HTML(200, COMMITS)
+}

+ 32 - 41
routers/repo/download.go

@@ -5,50 +5,41 @@
 package repo
 
 import (
-	// "io"
-	// "os"
-	// "path/filepath"
+	"io"
+	"path"
 
-	// "github.com/Unknwon/com"
-
-	// "github.com/gogits/git"
-
-	// "github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/base"
 	"github.com/gogits/gogs/modules/middleware"
 )
 
 func SingleDownload(ctx *middleware.Context) {
-	// treename := params["_1"]
-
-	// blob, err := ctx.Repo.Commit.GetBlobByPath(treename)
-	// if err != nil {
-	// 	ctx.Handle(500, "repo.SingleDownload(GetBlobByPath)", err)
-	// 	return
-	// }
-
-	// dataRc, err := blob.Data()
-	// if err != nil {
-	// 	ctx.Handle(500, "repo.SingleDownload(Data)", err)
-	// 	return
-	// }
-
-	// buf := make([]byte, 1024)
-	// n, _ := dataRc.Read(buf)
-	// if n > 0 {
-	// 	buf = buf[:n]
-	// }
-
-	// defer func() {
-	// 	dataRc.Close()
-	// }()
-
-	// contentType, isTextFile := base.IsTextFile(buf)
-	// _, isImageFile := base.IsImageFile(buf)
-	// ctx.Res.Header().Set("Content-Type", contentType)
-	// if !isTextFile && !isImageFile {
-	// 	ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(treename))
-	// 	ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
-	// }
-	// ctx.Res.Write(buf)
-	// io.Copy(ctx.Res, dataRc)
+	treename := ctx.Params("*")
+
+	blob, err := ctx.Repo.Commit.GetBlobByPath(treename)
+	if err != nil {
+		ctx.Handle(500, "GetBlobByPath", err)
+		return
+	}
+
+	dataRc, err := blob.Data()
+	if err != nil {
+		ctx.Handle(500, "repo.SingleDownload(Data)", err)
+		return
+	}
+
+	buf := make([]byte, 1024)
+	n, _ := dataRc.Read(buf)
+	if n > 0 {
+		buf = buf[:n]
+	}
+
+	contentType, isTextFile := base.IsTextFile(buf)
+	_, isImageFile := base.IsImageFile(buf)
+	ctx.Resp.Header().Set("Content-Type", contentType)
+	if !isTextFile && !isImageFile {
+		ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+path.Base(treename))
+		ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
+	}
+	ctx.Resp.Write(buf)
+	io.Copy(ctx.Resp, dataRc)
 }

+ 1115 - 1111
routers/repo/issue.go

@@ -4,1114 +4,1118 @@
 
 package repo
 
-// import (
-// 	"errors"
-// 	// "fmt"
-// 	// "io"
-// 	// "io/ioutil"
-// 	// "mime"
-// 	"net/url"
-// 	// "strings"
-// 	// "time"
-
-// 	"github.com/Unknwon/com"
-// 	"github.com/go-martini/martini"
-
-// 	// "github.com/gogits/gogs-ng/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"
-// 	// "github.com/gogits/gogs/modules/setting"
-// )
-
-// const (
-// 	ISSUES       base.TplName = "repo/issue/list"
-// 	ISSUE_CREATE base.TplName = "repo/issue/create"
-// 	ISSUE_VIEW   base.TplName = "repo/issue/view"
-
-// 	MILESTONE      base.TplName = "repo/issue/milestone"
-// 	MILESTONE_NEW  base.TplName = "repo/issue/milestone_new"
-// 	MILESTONE_EDIT base.TplName = "repo/issue/milestone_edit"
-// )
-
-// var (
-// 	ErrFileTypeForbidden = errors.New("File type is not allowed")
-// 	ErrTooManyFiles      = errors.New("Maximum number of files to upload exceeded")
-// )
-
-// func Issues(ctx *middleware.Context) {
-// 	ctx.Data["Title"] = "Issues"
-// 	ctx.Data["IsRepoToolbarIssues"] = true
-// 	ctx.Data["IsRepoToolbarIssuesList"] = true
-
-// 	viewType := ctx.Query("type")
-// 	types := []string{"assigned", "created_by", "mentioned"}
-// 	if !com.IsSliceContainsStr(types, viewType) {
-// 		viewType = "all"
-// 	}
-
-// 	isShowClosed := ctx.Query("state") == "closed"
-
-// 	if viewType != "all" && !ctx.IsSigned {
-// 		ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
-// 		ctx.Redirect("/user/login")
-// 		return
-// 	}
-
-// 	// var assigneeId, posterId int64
-// 	// var filterMode int
-// 	// switch viewType {
-// 	// case "assigned":
-// 	// 	assigneeId = ctx.User.Id
-// 	// 	filterMode = models.FM_ASSIGN
-// 	// case "created_by":
-// 	// 	posterId = ctx.User.Id
-// 	// 	filterMode = models.FM_CREATE
-// 	// case "mentioned":
-// 	// 	filterMode = models.FM_MENTION
-// 	// }
-
-// 	// var mid int64
-// 	// midx, _ := com.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
-// 	// }
-
-// 	// selectLabels := ctx.Query("labels")
-// 	// labels, err := models.GetLabels(ctx.Repo.Repository.Id)
-// 	// if err != nil {
-// 	// 	ctx.Handle(500, "issue.Issues(GetLabels): %v", err)
-// 	// 	return
-// 	// }
-// 	// for _, l := range labels {
-// 	// 	l.CalOpenIssues()
-// 	// }
-// 	// ctx.Data["Labels"] = labels
-
-// 	page, _ := com.StrTo(ctx.Query("page")).Int()
-
-// 	// Get issues.
-// 	// issues, err := models.GetIssues(assigneeId, ctx.Repo.Repository.Id, posterId, mid, page,
-// 	// 	isShowClosed, selectLabels, ctx.Query("sortType"))
-// 	// if err != nil {
-// 	// 	ctx.Handle(500, "issue.Issues(GetIssues): %v", err)
-// 	// 	return
-// 	// }
-
-// 	// Get issue-user pairs.
-// 	// pairs, err := models.GetIssueUserPairs(ctx.Repo.Repository.Id, posterId, isShowClosed)
-// 	// if err != nil {
-// 	// 	ctx.Handle(500, "issue.Issues(GetIssueUserPairs): %v", err)
-// 	// 	return
-// 	// }
-
-// 	// Get posters.
-// 	// for i := range issues {
-// 	// 	if err = issues[i].GetLabels(); err != nil {
-// 	// 		ctx.Handle(500, "issue.Issues(GetLabels)", fmt.Errorf("[#%d]%v", issues[i].Id, err))
-// 	// 		return
-// 	// 	}
-
-// 	// 	idx := models.PairsContains(pairs, issues[i].Id)
-
-// 	// 	if filterMode == models.FM_MENTION && (idx == -1 || !pairs[idx].IsMentioned) {
-// 	// 		continue
-// 	// 	}
-
-// 	// 	if idx > -1 {
-// 	// 		issues[i].IsRead = pairs[idx].IsRead
-// 	// 	} else {
-// 	// 		issues[i].IsRead = true
-// 	// 	}
-
-// 	// 	if err = issues[i].GetPoster(); err != nil {
-// 	// 		ctx.Handle(500, "issue.Issues(GetPoster)", fmt.Errorf("[#%d]%v", issues[i].Id, err))
-// 	// 		return
-// 	// 	}
-// 	// }
-
-// 	// var uid int64 = -1
-// 	// if ctx.User != nil {
-// 	// 	uid = ctx.User.Id
-// 	// }
-// 	// issueStats := models.GetIssueStats(ctx.Repo.Repository.Id, uid, isShowClosed, filterMode)
-// 	// ctx.Data["IssueStats"] = issueStats
-// 	// ctx.Data["SelectLabels"], _ = com.StrTo(selectLabels).Int64()
-// 	// ctx.Data["ViewType"] = viewType
-// 	// ctx.Data["Issues"] = issues
-// 	// ctx.Data["IsShowClosed"] = isShowClosed
-// 	// if isShowClosed {
-// 	// 	ctx.Data["State"] = "closed"
-// 	// 	ctx.Data["ShowCount"] = issueStats.ClosedCount
-// 	// } else {
-// 	// 	ctx.Data["ShowCount"] = issueStats.OpenCount
-// 	// }
-// 	// ctx.HTML(200, ISSUES)
-// }
-
-// func CreateIssue(ctx *middleware.Context, params martini.Params) {
-// 	// ctx.Data["Title"] = "Create issue"
-// 	// ctx.Data["IsRepoToolbarIssues"] = true
-// 	// ctx.Data["IsRepoToolbarIssuesList"] = false
-// 	// ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled
-
-// 	// 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)
-// 	// 	return
-// 	// }
-
-// 	// ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes
-// 	// ctx.Data["Collaborators"] = us
-
-// 	// ctx.HTML(200, ISSUE_CREATE)
-// }
-
-// func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
-// 	// send := func(status int, data interface{}, err error) {
-// 	// 	if err != nil {
-// 	// 		log.Error(4, "issue.CreateIssuePost(?): %s", err.Error())
-
-// 	// 		ctx.JSON(status, map[string]interface{}{
-// 	// 			"ok":     false,
-// 	// 			"status": status,
-// 	// 			"error":  err.Error(),
-// 	// 		})
-// 	// 	} else {
-// 	// 		ctx.JSON(status, map[string]interface{}{
-// 	// 			"ok":     true,
-// 	// 			"status": status,
-// 	// 			"data":   data,
-// 	// 		})
-// 	// 	}
-// 	// }
-
-// 	// var err error
-// 	// // Get all milestones.
-// 	// _, err = models.GetMilestones(ctx.Repo.Repository.Id, false)
-// 	// if err != nil {
-// 	// 	send(500, nil, err)
-// 	// 	return
-// 	// }
-// 	// _, err = models.GetMilestones(ctx.Repo.Repository.Id, true)
-// 	// if err != nil {
-// 	// 	send(500, nil, err)
-// 	// 	return
-// 	// }
-
-// 	// _, err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
-// 	// if err != nil {
-// 	// 	send(500, nil, err)
-// 	// 	return
-// 	// }
-
-// 	// if ctx.HasError() {
-// 	// 	send(400, nil, errors.New(ctx.Flash.ErrorMsg))
-// 	// 	return
-// 	// }
-
-// 	// // Only collaborators can assign.
-// 	// if !ctx.Repo.IsOwner {
-// 	// 	form.AssigneeId = 0
-// 	// }
-// 	// issue := &models.Issue{
-// 	// 	RepoId:      ctx.Repo.Repository.Id,
-// 	// 	Index:       int64(ctx.Repo.Repository.NumIssues) + 1,
-// 	// 	Name:        form.IssueName,
-// 	// 	PosterId:    ctx.User.Id,
-// 	// 	MilestoneId: form.MilestoneId,
-// 	// 	AssigneeId:  form.AssigneeId,
-// 	// 	LabelIds:    form.Labels,
-// 	// 	Content:     form.Content,
-// 	// }
-// 	// if err := models.NewIssue(issue); err != nil {
-// 	// 	send(500, nil, err)
-// 	// 	return
-// 	// } else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id,
-// 	// 	ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil {
-// 	// 	send(500, nil, err)
-// 	// 	return
-// 	// }
-
-// 	// if setting.AttachmentEnabled {
-// 	// 	uploadFiles(ctx, issue.Id, 0)
-// 	// }
-
-// 	// // Update mentions.
-// 	// ms := base.MentionPattern.FindAllString(issue.Content, -1)
-// 	// if len(ms) > 0 {
-// 	// 	for i := range ms {
-// 	// 		ms[i] = ms[i][1:]
-// 	// 	}
-
-// 	// 	if err := models.UpdateMentions(ms, issue.Id); err != nil {
-// 	// 		send(500, nil, err)
-// 	// 		return
-// 	// 	}
-// 	// }
-
-// 	// act := &models.Action{
-// 	// 	ActUserId:    ctx.User.Id,
-// 	// 	ActUserName:  ctx.User.Name,
-// 	// 	ActEmail:     ctx.User.Email,
-// 	// 	OpType:       models.OP_CREATE_ISSUE,
-// 	// 	Content:      fmt.Sprintf("%d|%s", issue.Index, issue.Name),
-// 	// 	RepoId:       ctx.Repo.Repository.Id,
-// 	// 	RepoUserName: ctx.Repo.Owner.Name,
-// 	// 	RepoName:     ctx.Repo.Repository.Name,
-// 	// 	RefName:      ctx.Repo.BranchName,
-// 	// 	IsPrivate:    ctx.Repo.Repository.IsPrivate,
-// 	// }
-// 	// // Notify watchers.
-// 	// if err := models.NotifyWatchers(act); err != nil {
-// 	// 	send(500, nil, err)
-// 	// 	return
-// 	// }
-
-// 	// // Mail watchers and mentions.
-// 	// if setting.Service.EnableNotifyMail {
-// 	// 	tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
-// 	// 	if err != nil {
-// 	// 		send(500, nil, err)
-// 	// 		return
-// 	// 	}
-
-// 	// 	tos = append(tos, ctx.User.LowerName)
-// 	// 	newTos := make([]string, 0, len(ms))
-// 	// 	for _, m := range ms {
-// 	// 		if com.IsSliceContainsStr(tos, m) {
-// 	// 			continue
-// 	// 		}
-
-// 	// 		newTos = append(newTos, m)
-// 	// 	}
-// 	// 	if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
-// 	// 		ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
-// 	// 		send(500, nil, err)
-// 	// 		return
-// 	// 	}
-// 	// }
-// 	// log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id)
-
-// 	// send(200, fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index), nil)
-// }
-
-// // func checkLabels(labels, allLabels []*models.Label) {
-// // 	for _, l := range labels {
-// // 		for _, l2 := range allLabels {
-// // 			if l.Id == l2.Id {
-// // 				l2.IsChecked = true
-// // 				break
-// // 			}
-// // 		}
-// // 	}
-// // }
-
-// // func ViewIssue(ctx *middleware.Context, params martini.Params) {
-// // 	ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled
-
-// // 	idx, _ := base.StrTo(params["index"]).Int64()
-// // 	if idx == 0 {
-// // 		ctx.Handle(404, "issue.ViewIssue", nil)
-// // 		return
-// // 	}
-
-// // 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
-// // 	if err != nil {
-// // 		if err == models.ErrIssueNotExist {
-// // 			ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err)
-// // 		} else {
-// // 			ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err)
-// // 		}
-// // 		return
-// // 	}
-
-// // 	// Get labels.
-// // 	if err = issue.GetLabels(); err != nil {
-// // 		ctx.Handle(500, "issue.ViewIssue(GetLabels)", err)
-// // 		return
-// // 	}
-// // 	labels, err := models.GetLabels(ctx.Repo.Repository.Id)
-// // 	if err != nil {
-// // 		ctx.Handle(500, "issue.ViewIssue(GetLabels.2)", err)
-// // 		return
-// // 	}
-// // 	checkLabels(issue.Labels, labels)
-// // 	ctx.Data["Labels"] = labels
-
-// // 	// 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
-// // 	}
-
-// // 	if ctx.IsSigned {
-// // 		// Update issue-user.
-// // 		if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.Id); err != nil {
-// // 			ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err)
-// // 			return
-// // 		}
-// // 	}
-
-// // 	// Get poster and Assignee.
-// // 	if err = issue.GetPoster(); err != nil {
-// // 		ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err)
-// // 		return
-// // 	} else if err = issue.GetAssignee(); err != nil {
-// // 		ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err)
-// // 		return
-// // 	}
-// // 	issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))
-
-// // 	// Get comments.
-// // 	comments, err := models.GetIssueComments(issue.Id)
-// // 	if err != nil {
-// // 		ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err)
-// // 		return
-// // 	}
-
-// // 	// Get posters.
-// // 	for i := range comments {
-// // 		u, err := models.GetUserById(comments[i].PosterId)
-// // 		if err != nil {
-// // 			ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err)
-// // 			return
-// // 		}
-// // 		comments[i].Poster = u
-
-// // 		if comments[i].Type == models.COMMENT {
-// // 			comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
-// // 		}
-// // 	}
-
-// // 	ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes
-
-// // 	ctx.Data["Title"] = issue.Name
-// // 	ctx.Data["Issue"] = issue
-// // 	ctx.Data["Comments"] = comments
-// // 	ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && 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) {
-// // 	idx, _ := base.StrTo(params["index"]).Int64()
-// // 	if idx <= 0 {
-// // 		ctx.Error(404)
-// // 		return
-// // 	}
-
-// // 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
-// // 	if err != nil {
-// // 		if err == models.ErrIssueNotExist {
-// // 			ctx.Handle(404, "issue.UpdateIssue", err)
-// // 		} else {
-// // 			ctx.Handle(500, "issue.UpdateIssue(GetIssueByIndex)", err)
-// // 		}
-// // 		return
-// // 	}
-
-// // 	if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
-// // 		ctx.Error(403)
-// // 		return
-// // 	}
-
-// // 	issue.Name = form.IssueName
-// // 	issue.MilestoneId = form.MilestoneId
-// // 	issue.AssigneeId = form.AssigneeId
-// // 	issue.LabelIds = form.Labels
-// // 	issue.Content = form.Content
-// // 	// try get content from text, ignore conflict with preview ajax
-// // 	if form.Content == "" {
-// // 		issue.Content = ctx.Query("text")
-// // 	}
-// // 	if err = models.UpdateIssue(issue); err != nil {
-// // 		ctx.Handle(500, "issue.UpdateIssue(UpdateIssue)", err)
-// // 		return
-// // 	}
-
-// // 	ctx.JSON(200, map[string]interface{}{
-// // 		"ok":      true,
-// // 		"title":   issue.Name,
-// // 		"content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)),
-// // 	})
-// // }
-
-// // func UpdateIssueLabel(ctx *middleware.Context, params martini.Params) {
-// // 	if !ctx.Repo.IsOwner {
-// // 		ctx.Error(403)
-// // 		return
-// // 	}
-
-// // 	idx, _ := base.StrTo(params["index"]).Int64()
-// // 	if idx <= 0 {
-// // 		ctx.Error(404)
-// // 		return
-// // 	}
-
-// // 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
-// // 	if err != nil {
-// // 		if err == models.ErrIssueNotExist {
-// // 			ctx.Handle(404, "issue.UpdateIssueLabel(GetIssueByIndex)", err)
-// // 		} else {
-// // 			ctx.Handle(500, "issue.UpdateIssueLabel(GetIssueByIndex)", err)
-// // 		}
-// // 		return
-// // 	}
-
-// // 	isAttach := ctx.Query("action") == "attach"
-// // 	labelStrId := ctx.Query("id")
-// // 	labelId, _ := base.StrTo(labelStrId).Int64()
-// // 	label, err := models.GetLabelById(labelId)
-// // 	if err != nil {
-// // 		if err == models.ErrLabelNotExist {
-// // 			ctx.Handle(404, "issue.UpdateIssueLabel(GetLabelById)", err)
-// // 		} else {
-// // 			ctx.Handle(500, "issue.UpdateIssueLabel(GetLabelById)", err)
-// // 		}
-// // 		return
-// // 	}
-
-// // 	isHad := strings.Contains(issue.LabelIds, "$"+labelStrId+"|")
-// // 	isNeedUpdate := false
-// // 	if isAttach {
-// // 		if !isHad {
-// // 			issue.LabelIds += "$" + labelStrId + "|"
-// // 			isNeedUpdate = true
-// // 		}
-// // 	} else {
-// // 		if isHad {
-// // 			issue.LabelIds = strings.Replace(issue.LabelIds, "$"+labelStrId+"|", "", -1)
-// // 			isNeedUpdate = true
-// // 		}
-// // 	}
-
-// // 	if isNeedUpdate {
-// // 		if err = models.UpdateIssue(issue); err != nil {
-// // 			ctx.Handle(500, "issue.UpdateIssueLabel(UpdateIssue)", err)
-// // 			return
-// // 		}
-
-// // 		if isAttach {
-// // 			label.NumIssues++
-// // 			if issue.IsClosed {
-// // 				label.NumClosedIssues++
-// // 			}
-// // 		} else {
-// // 			label.NumIssues--
-// // 			if issue.IsClosed {
-// // 				label.NumClosedIssues--
-// // 			}
-// // 		}
-// // 		if err = models.UpdateLabel(label); err != nil {
-// // 			ctx.Handle(500, "issue.UpdateIssueLabel(UpdateLabel)", err)
-// // 			return
-// // 		}
-// // 	}
-// // 	ctx.JSON(200, map[string]interface{}{
-// // 		"ok": true,
-// // 	})
-// // }
-
-// // 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); 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
-// // 	}
-
-// // 	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.UpdateAssignee(GetIssueById)", err)
-// // 		} else {
-// // 			ctx.Handle(500, "issue.UpdateAssignee(GetIssueById)", err)
-// // 		}
-// // 		return
-// // 	}
-
-// // 	aid, _ := base.StrTo(ctx.Query("assigneeid")).Int64()
-// // 	// Not check for invalid assignne id and give responsibility to owners.
-// // 	issue.AssigneeId = aid
-// // 	if err = models.UpdateIssueUserPairByAssignee(aid, issue.Id); err != nil {
-// // 		ctx.Handle(500, "issue.UpdateAssignee(UpdateIssueUserPairByAssignee): %v", err)
-// // 		return
-// // 	} else if err = models.UpdateIssue(issue); err != nil {
-// // 		ctx.Handle(500, "issue.UpdateAssignee(UpdateIssue)", err)
-// // 		return
-// // 	}
-
-// // 	ctx.JSON(200, map[string]interface{}{
-// // 		"ok": true,
-// // 	})
-// // }
-
-// // func uploadFiles(ctx *middleware.Context, issueId, commentId int64) {
-// // 	if !setting.AttachmentEnabled {
-// // 		return
-// // 	}
-
-// // 	allowedTypes := strings.Split(setting.AttachmentAllowedTypes, "|")
-// // 	attachments := ctx.Req.MultipartForm.File["attachments"]
-
-// // 	if len(attachments) > setting.AttachmentMaxFiles {
-// // 		ctx.Handle(400, "issue.Comment", ErrTooManyFiles)
-// // 		return
-// // 	}
-
-// // 	for _, header := range attachments {
-// // 		file, err := header.Open()
-
-// // 		if err != nil {
-// // 			ctx.Handle(500, "issue.Comment(header.Open)", err)
-// // 			return
-// // 		}
-
-// // 		defer file.Close()
-
-// // 		allowed := false
-// // 		fileType := mime.TypeByExtension(header.Filename)
-
-// // 		for _, t := range allowedTypes {
-// // 			t := strings.Trim(t, " ")
-
-// // 			if t == "*/*" || t == fileType {
-// // 				allowed = true
-// // 				break
-// // 			}
-// // 		}
-
-// // 		if !allowed {
-// // 			ctx.Handle(400, "issue.Comment", ErrFileTypeForbidden)
-// // 			return
-// // 		}
-
-// // 		out, err := ioutil.TempFile(setting.AttachmentPath, "attachment_")
-
-// // 		if err != nil {
-// // 			ctx.Handle(500, "issue.Comment(ioutil.TempFile)", err)
-// // 			return
-// // 		}
-
-// // 		defer out.Close()
-
-// // 		_, err = io.Copy(out, file)
-
-// // 		if err != nil {
-// // 			ctx.Handle(500, "issue.Comment(io.Copy)", err)
-// // 			return
-// // 		}
-
-// // 		_, err = models.CreateAttachment(issueId, commentId, header.Filename, out.Name())
-
-// // 		if err != nil {
-// // 			ctx.Handle(500, "issue.Comment(io.Copy)", err)
-// // 			return
-// // 		}
-// // 	}
-// // }
-
-// // func Comment(ctx *middleware.Context, params martini.Params) {
-// // 	send := func(status int, data interface{}, err error) {
-// // 		if err != nil {
-// // 			log.Error("issue.Comment(?): %s", err.Error())
-
-// // 			ctx.JSON(status, map[string]interface{}{
-// // 				"ok":     false,
-// // 				"status": status,
-// // 				"error":  err.Error(),
-// // 			})
-// // 		} else {
-// // 			ctx.JSON(status, map[string]interface{}{
-// // 				"ok":     true,
-// // 				"status": status,
-// // 				"data":   data,
-// // 			})
-// // 		}
-// // 	}
-
-// // 	index, err := base.StrTo(ctx.Query("issueIndex")).Int64()
-// // 	if err != nil {
-// // 		send(404, nil, err)
-// // 		return
-// // 	}
-
-// // 	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index)
-// // 	if err != nil {
-// // 		if err == models.ErrIssueNotExist {
-// // 			send(404, nil, err)
-// // 		} else {
-// // 			send(200, nil, err)
-// // 		}
-
-// // 		return
-// // 	}
-
-// // 	// 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 {
-// // 				send(500, nil, err)
-// // 				return
-// // 			} else if err = models.UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil {
-// // 				send(500, nil, err)
-// // 				return
-// // 			}
-
-// // 			// Change open/closed issue counter for the associated milestone
-// // 			if issue.MilestoneId > 0 {
-// // 				if err = models.ChangeMilestoneIssueStats(issue); err != nil {
-// // 					send(500, nil, err)
-// // 				}
-// // 			}
-
-// // 			cmtType := models.CLOSE
-// // 			if !issue.IsClosed {
-// // 				cmtType = models.REOPEN
-// // 			}
-
-// // 			if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, "", nil); err != nil {
-// // 				send(200, nil, err)
-// // 				return
-// // 			}
-// // 			log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed)
-// // 		}
-// // 	}
-
-// // 	var comment *models.Comment
-
-// // 	var ms []string
-// // 	content := ctx.Query("content")
-// // 	// Fix #321. Allow empty comments, as long as we have attachments.
-// // 	if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 {
-// // 		switch params["action"] {
-// // 		case "new":
-// // 			if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.COMMENT, content, nil); err != nil {
-// // 				send(500, nil, err)
-// // 				return
-// // 			}
-
-// // 			// Update mentions.
-// // 			ms = base.MentionPattern.FindAllString(issue.Content, -1)
-// // 			if len(ms) > 0 {
-// // 				for i := range ms {
-// // 					ms[i] = ms[i][1:]
-// // 				}
-
-// // 				if err := models.UpdateMentions(ms, issue.Id); err != nil {
-// // 					send(500, nil, err)
-// // 					return
-// // 				}
-// // 			}
-
-// // 			log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id)
-// // 		default:
-// // 			ctx.Handle(404, "issue.Comment", err)
-// // 			return
-// // 		}
-// // 	}
-
-// // 	if comment != nil {
-// // 		uploadFiles(ctx, issue.Id, comment.Id)
-// // 	}
-
-// // 	// Notify watchers.
-// // 	act := &models.Action{
-// // 		ActUserId:    ctx.User.Id,
-// // 		ActUserName:  ctx.User.LowerName,
-// // 		ActEmail:     ctx.User.Email,
-// // 		OpType:       models.OP_COMMENT_ISSUE,
-// // 		Content:      fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
-// // 		RepoId:       ctx.Repo.Repository.Id,
-// // 		RepoUserName: ctx.Repo.Owner.LowerName,
-// // 		RepoName:     ctx.Repo.Repository.LowerName,
-// // 	}
-// // 	if err = models.NotifyWatchers(act); err != nil {
-// // 		send(500, nil, err)
-// // 		return
-// // 	}
-
-// // 	// Mail watchers and mentions.
-// // 	if setting.Service.EnableNotifyMail {
-// // 		issue.Content = content
-// // 		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
-// // 		if err != nil {
-// // 			send(500, nil, err)
-// // 			return
-// // 		}
-
-// // 		tos = append(tos, ctx.User.LowerName)
-// // 		newTos := make([]string, 0, len(ms))
-// // 		for _, m := range ms {
-// // 			if com.IsSliceContainsStr(tos, m) {
-// // 				continue
-// // 			}
-
-// // 			newTos = append(newTos, m)
-// // 		}
-// // 		if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
-// // 			ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
-// // 			send(500, nil, err)
-// // 			return
-// // 		}
-// // 	}
-
-// // 	send(200, fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index), nil)
-// // }
-
-// // func NewLabel(ctx *middleware.Context, form auth.CreateLabelForm) {
-// // 	if ctx.HasError() {
-// // 		Issues(ctx)
-// // 		return
-// // 	}
-
-// // 	l := &models.Label{
-// // 		RepoId: ctx.Repo.Repository.Id,
-// // 		Name:   form.Title,
-// // 		Color:  form.Color,
-// // 	}
-// // 	if err := models.NewLabel(l); err != nil {
-// // 		ctx.Handle(500, "issue.NewLabel(NewLabel)", err)
-// // 		return
-// // 	}
-// // 	ctx.Redirect(ctx.Repo.RepoLink + "/issues")
-// // }
-
-// // func UpdateLabel(ctx *middleware.Context, params martini.Params, form auth.CreateLabelForm) {
-// // 	id, _ := base.StrTo(ctx.Query("id")).Int64()
-// // 	if id == 0 {
-// // 		ctx.Error(404)
-// // 		return
-// // 	}
-
-// // 	l := &models.Label{
-// // 		Id:    id,
-// // 		Name:  form.Title,
-// // 		Color: form.Color,
-// // 	}
-// // 	if err := models.UpdateLabel(l); err != nil {
-// // 		ctx.Handle(500, "issue.UpdateLabel(UpdateLabel)", err)
-// // 		return
-// // 	}
-// // 	ctx.Redirect(ctx.Repo.RepoLink + "/issues")
-// // }
-
-// // func DeleteLabel(ctx *middleware.Context) {
-// // 	removes := ctx.Query("remove")
-// // 	if len(strings.TrimSpace(removes)) == 0 {
-// // 		ctx.JSON(200, map[string]interface{}{
-// // 			"ok": true,
-// // 		})
-// // 		return
-// // 	}
-
-// // 	strIds := strings.Split(removes, ",")
-// // 	for _, strId := range strIds {
-// // 		if err := models.DeleteLabel(ctx.Repo.Repository.Id, strId); err != nil {
-// // 			ctx.Handle(500, "issue.DeleteLabel(DeleteLabel)", err)
-// // 			return
-// // 		}
-// // 	}
-
-// // 	ctx.JSON(200, map[string]interface{}{
-// // 		"ok": true,
-// // 	})
-// // }
-
-// // func Milestones(ctx *middleware.Context) {
-// // 	ctx.Data["Title"] = "Milestones"
-// // 	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, MILESTONE)
-// // }
-
-// // func NewMilestone(ctx *middleware.Context) {
-// // 	ctx.Data["Title"] = "New Milestone"
-// // 	ctx.Data["IsRepoToolbarIssues"] = true
-// // 	ctx.Data["IsRepoToolbarIssuesList"] = true
-// // 	ctx.HTML(200, MILESTONE_NEW)
-// // }
-
-// // func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
-// // 	ctx.Data["Title"] = "New Milestone"
-// // 	ctx.Data["IsRepoToolbarIssues"] = true
-// // 	ctx.Data["IsRepoToolbarIssuesList"] = true
-
-// // 	if ctx.HasError() {
-// // 		ctx.HTML(200, MILESTONE_NEW)
-// // 		return
-// // 	}
-
-// // 	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, 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
-// // 	}
-
-// // 	if ctx.HasError() {
-// // 		ctx.HTML(200, MILESTONE_EDIT)
-// // 		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")
-// // }
-
-// // func IssueGetAttachment(ctx *middleware.Context, params martini.Params) {
-// // 	id, err := base.StrTo(params["id"]).Int64()
-
-// // 	if err != nil {
-// // 		ctx.Handle(400, "issue.IssueGetAttachment(base.StrTo.Int64)", err)
-// // 		return
-// // 	}
-
-// // 	attachment, err := models.GetAttachmentById(id)
-
-// // 	if err != nil {
-// // 		ctx.Handle(404, "issue.IssueGetAttachment(models.GetAttachmentById)", err)
-// // 		return
-// // 	}
-
-// // 	// Fix #312. Attachments with , in their name are not handled correctly by Google Chrome.
-// // 	// We must put the name in " manually.
-// // 	ctx.ServeFile(attachment.Path, "\""+attachment.Name+"\"")
-// // }
+import (
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
+	"github.com/Unknwon/com"
+
+	"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"
+	"github.com/gogits/gogs/modules/setting"
+)
+
+const (
+	ISSUES       base.TplName = "repo/issue/list"
+	ISSUE_CREATE base.TplName = "repo/issue/create"
+	ISSUE_VIEW   base.TplName = "repo/issue/view"
+
+	MILESTONE      base.TplName = "repo/issue/milestone"
+	MILESTONE_NEW  base.TplName = "repo/issue/milestone_new"
+	MILESTONE_EDIT base.TplName = "repo/issue/milestone_edit"
+)
+
+var (
+	ErrFileTypeForbidden = errors.New("File type is not allowed")
+	ErrTooManyFiles      = errors.New("Maximum number of files to upload exceeded")
+)
+
+func Issues(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Issues"
+	ctx.Data["IsRepoToolbarIssues"] = true
+	ctx.Data["IsRepoToolbarIssuesList"] = true
+
+	viewType := ctx.Query("type")
+	types := []string{"assigned", "created_by", "mentioned"}
+	if !com.IsSliceContainsStr(types, viewType) {
+		viewType = "all"
+	}
+
+	isShowClosed := ctx.Query("state") == "closed"
+
+	if viewType != "all" && !ctx.IsSigned {
+		ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
+		ctx.Redirect("/user/login")
+		return
+	}
+
+	var assigneeId, posterId int64
+	var filterMode int
+	switch viewType {
+	case "assigned":
+		assigneeId = ctx.User.Id
+		filterMode = models.FM_ASSIGN
+	case "created_by":
+		posterId = ctx.User.Id
+		filterMode = models.FM_CREATE
+	case "mentioned":
+		filterMode = models.FM_MENTION
+	}
+
+	var mid int64
+	midx, _ := com.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
+	}
+
+	selectLabels := ctx.Query("labels")
+	labels, err := models.GetLabels(ctx.Repo.Repository.Id)
+	if err != nil {
+		ctx.Handle(500, "issue.Issues(GetLabels): %v", err)
+		return
+	}
+	for _, l := range labels {
+		l.CalOpenIssues()
+	}
+	ctx.Data["Labels"] = labels
+
+	page, _ := com.StrTo(ctx.Query("page")).Int()
+
+	// Get issues.
+	issues, err := models.GetIssues(assigneeId, ctx.Repo.Repository.Id, posterId, mid, page,
+		isShowClosed, selectLabels, ctx.Query("sortType"))
+	if err != nil {
+		ctx.Handle(500, "issue.Issues(GetIssues): %v", err)
+		return
+	}
+
+	// Get issue-user pairs.
+	pairs, err := models.GetIssueUserPairs(ctx.Repo.Repository.Id, posterId, isShowClosed)
+	if err != nil {
+		ctx.Handle(500, "issue.Issues(GetIssueUserPairs): %v", err)
+		return
+	}
+
+	// Get posters.
+	for i := range issues {
+		if err = issues[i].GetLabels(); err != nil {
+			ctx.Handle(500, "GetLabels", fmt.Errorf("[#%d]%v", issues[i].Id, err))
+			return
+		}
+
+		idx := models.PairsContains(pairs, issues[i].Id)
+
+		if filterMode == models.FM_MENTION && (idx == -1 || !pairs[idx].IsMentioned) {
+			continue
+		}
+
+		if idx > -1 {
+			issues[i].IsRead = pairs[idx].IsRead
+		} else {
+			issues[i].IsRead = true
+		}
+
+		if err = issues[i].GetPoster(); err != nil {
+			ctx.Handle(500, "issue.Issues(GetPoster)", fmt.Errorf("[#%d]%v", issues[i].Id, err))
+			return
+		}
+	}
+
+	var uid int64 = -1
+	if ctx.User != nil {
+		uid = ctx.User.Id
+	}
+	issueStats := models.GetIssueStats(ctx.Repo.Repository.Id, uid, isShowClosed, filterMode)
+	ctx.Data["IssueStats"] = issueStats
+	ctx.Data["SelectLabels"], _ = com.StrTo(selectLabels).Int64()
+	ctx.Data["ViewType"] = viewType
+	ctx.Data["Issues"] = issues
+	ctx.Data["IsShowClosed"] = isShowClosed
+	if isShowClosed {
+		ctx.Data["State"] = "closed"
+		ctx.Data["ShowCount"] = issueStats.ClosedCount
+	} else {
+		ctx.Data["ShowCount"] = issueStats.OpenCount
+	}
+	ctx.HTML(200, ISSUES)
+}
+
+func CreateIssue(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Create issue"
+	ctx.Data["IsRepoToolbarIssues"] = true
+	ctx.Data["IsRepoToolbarIssuesList"] = false
+	ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled
+
+	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)
+		return
+	}
+
+	ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes
+	ctx.Data["Collaborators"] = us
+
+	ctx.HTML(200, ISSUE_CREATE)
+}
+
+func CreateIssuePost(ctx *middleware.Context, form auth.CreateIssueForm) {
+	send := func(status int, data interface{}, err error) {
+		if err != nil {
+			log.Error(4, "issue.CreateIssuePost(?): %s", err)
+
+			ctx.JSON(status, map[string]interface{}{
+				"ok":     false,
+				"status": status,
+				"error":  err.Error(),
+			})
+		} else {
+			ctx.JSON(status, map[string]interface{}{
+				"ok":     true,
+				"status": status,
+				"data":   data,
+			})
+		}
+	}
+
+	var err error
+	// Get all milestones.
+	_, err = models.GetMilestones(ctx.Repo.Repository.Id, false)
+	if err != nil {
+		send(500, nil, err)
+		return
+	}
+	_, err = models.GetMilestones(ctx.Repo.Repository.Id, true)
+	if err != nil {
+		send(500, nil, err)
+		return
+	}
+
+	_, err = models.GetCollaborators(strings.TrimPrefix(ctx.Repo.RepoLink, "/"))
+	if err != nil {
+		send(500, nil, err)
+		return
+	}
+
+	if ctx.HasError() {
+		send(400, nil, errors.New(ctx.Flash.ErrorMsg))
+		return
+	}
+
+	// Only collaborators can assign.
+	if !ctx.Repo.IsOwner {
+		form.AssigneeId = 0
+	}
+	issue := &models.Issue{
+		RepoId:      ctx.Repo.Repository.Id,
+		Index:       int64(ctx.Repo.Repository.NumIssues) + 1,
+		Name:        form.IssueName,
+		PosterId:    ctx.User.Id,
+		MilestoneId: form.MilestoneId,
+		AssigneeId:  form.AssigneeId,
+		LabelIds:    form.Labels,
+		Content:     form.Content,
+	}
+	if err := models.NewIssue(issue); err != nil {
+		send(500, nil, err)
+		return
+	} else if err := models.NewIssueUserPairs(issue.RepoId, issue.Id, ctx.Repo.Owner.Id,
+		ctx.User.Id, form.AssigneeId, ctx.Repo.Repository.Name); err != nil {
+		send(500, nil, err)
+		return
+	}
+
+	if setting.AttachmentEnabled {
+		uploadFiles(ctx, issue.Id, 0)
+	}
+
+	// Update mentions.
+	ms := base.MentionPattern.FindAllString(issue.Content, -1)
+	if len(ms) > 0 {
+		for i := range ms {
+			ms[i] = ms[i][1:]
+		}
+
+		if err := models.UpdateMentions(ms, issue.Id); err != nil {
+			send(500, nil, err)
+			return
+		}
+	}
+
+	act := &models.Action{
+		ActUserId:    ctx.User.Id,
+		ActUserName:  ctx.User.Name,
+		ActEmail:     ctx.User.Email,
+		OpType:       models.CREATE_ISSUE,
+		Content:      fmt.Sprintf("%d|%s", issue.Index, issue.Name),
+		RepoId:       ctx.Repo.Repository.Id,
+		RepoUserName: ctx.Repo.Owner.Name,
+		RepoName:     ctx.Repo.Repository.Name,
+		RefName:      ctx.Repo.BranchName,
+		IsPrivate:    ctx.Repo.Repository.IsPrivate,
+	}
+	// Notify watchers.
+	if err := models.NotifyWatchers(act); err != nil {
+		send(500, nil, err)
+		return
+	}
+
+	// Mail watchers and mentions.
+	if setting.Service.EnableNotifyMail {
+		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
+		if err != nil {
+			send(500, nil, err)
+			return
+		}
+
+		tos = append(tos, ctx.User.LowerName)
+		newTos := make([]string, 0, len(ms))
+		for _, m := range ms {
+			if com.IsSliceContainsStr(tos, m) {
+				continue
+			}
+
+			newTos = append(newTos, m)
+		}
+		if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
+			ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
+			send(500, nil, err)
+			return
+		}
+	}
+	log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id)
+
+	send(200, fmt.Sprintf("/%s/%s/issues/%d", ctx.Params(":username"), ctx.Params(":reponame"), issue.Index), nil)
+}
+
+func checkLabels(labels, allLabels []*models.Label) {
+	for _, l := range labels {
+		for _, l2 := range allLabels {
+			if l.Id == l2.Id {
+				l2.IsChecked = true
+				break
+			}
+		}
+	}
+}
+
+func ViewIssue(ctx *middleware.Context) {
+	ctx.Data["AttachmentsEnabled"] = setting.AttachmentEnabled
+
+	idx := com.StrTo(ctx.Params(":index")).MustInt64()
+	if idx == 0 {
+		ctx.Handle(404, "issue.ViewIssue", nil)
+		return
+	}
+
+	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
+	if err != nil {
+		if err == models.ErrIssueNotExist {
+			ctx.Handle(404, "issue.ViewIssue(GetIssueByIndex)", err)
+		} else {
+			ctx.Handle(500, "issue.ViewIssue(GetIssueByIndex)", err)
+		}
+		return
+	}
+
+	// Get labels.
+	if err = issue.GetLabels(); err != nil {
+		ctx.Handle(500, "issue.ViewIssue(GetLabels)", err)
+		return
+	}
+	labels, err := models.GetLabels(ctx.Repo.Repository.Id)
+	if err != nil {
+		ctx.Handle(500, "issue.ViewIssue(GetLabels.2)", err)
+		return
+	}
+	checkLabels(issue.Labels, labels)
+	ctx.Data["Labels"] = labels
+
+	// 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
+	}
+
+	if ctx.IsSigned {
+		// Update issue-user.
+		if err = models.UpdateIssueUserPairByRead(ctx.User.Id, issue.Id); err != nil {
+			ctx.Handle(500, "issue.ViewIssue(UpdateIssueUserPairByRead): %v", err)
+			return
+		}
+	}
+
+	// Get poster and Assignee.
+	if err = issue.GetPoster(); err != nil {
+		ctx.Handle(500, "issue.ViewIssue(GetPoster): %v", err)
+		return
+	} else if err = issue.GetAssignee(); err != nil {
+		ctx.Handle(500, "issue.ViewIssue(GetAssignee): %v", err)
+		return
+	}
+	issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))
+
+	// Get comments.
+	comments, err := models.GetIssueComments(issue.Id)
+	if err != nil {
+		ctx.Handle(500, "issue.ViewIssue(GetIssueComments): %v", err)
+		return
+	}
+
+	// Get posters.
+	for i := range comments {
+		u, err := models.GetUserById(comments[i].PosterId)
+		if err != nil {
+			ctx.Handle(500, "issue.ViewIssue(GetUserById.2): %v", err)
+			return
+		}
+		comments[i].Poster = u
+
+		if comments[i].Type == models.COMMENT {
+			comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
+		}
+	}
+
+	ctx.Data["AllowedTypes"] = setting.AttachmentAllowedTypes
+
+	ctx.Data["Title"] = issue.Name
+	ctx.Data["Issue"] = issue
+	ctx.Data["Comments"] = comments
+	ctx.Data["IsIssueOwner"] = ctx.Repo.IsOwner || (ctx.IsSigned && issue.PosterId == ctx.User.Id)
+	ctx.Data["IsRepoToolbarIssues"] = true
+	ctx.Data["IsRepoToolbarIssuesList"] = false
+	ctx.HTML(200, ISSUE_VIEW)
+}
+
+func UpdateIssue(ctx *middleware.Context, form auth.CreateIssueForm) {
+	idx := com.StrTo(ctx.Params(":index")).MustInt64()
+	if idx <= 0 {
+		ctx.Error(404)
+		return
+	}
+
+	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
+	if err != nil {
+		if err == models.ErrIssueNotExist {
+			ctx.Handle(404, "issue.UpdateIssue", err)
+		} else {
+			ctx.Handle(500, "issue.UpdateIssue(GetIssueByIndex)", err)
+		}
+		return
+	}
+
+	if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
+		ctx.Error(403)
+		return
+	}
+
+	issue.Name = form.IssueName
+	issue.MilestoneId = form.MilestoneId
+	issue.AssigneeId = form.AssigneeId
+	issue.LabelIds = form.Labels
+	issue.Content = form.Content
+	// try get content from text, ignore conflict with preview ajax
+	if form.Content == "" {
+		issue.Content = ctx.Query("text")
+	}
+	if err = models.UpdateIssue(issue); err != nil {
+		ctx.Handle(500, "issue.UpdateIssue(UpdateIssue)", err)
+		return
+	}
+
+	ctx.JSON(200, map[string]interface{}{
+		"ok":      true,
+		"title":   issue.Name,
+		"content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)),
+	})
+}
+
+func UpdateIssueLabel(ctx *middleware.Context) {
+	if !ctx.Repo.IsOwner {
+		ctx.Error(403)
+		return
+	}
+
+	idx := com.StrTo(ctx.Params(":index")).MustInt64()
+	if idx <= 0 {
+		ctx.Error(404)
+		return
+	}
+
+	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, idx)
+	if err != nil {
+		if err == models.ErrIssueNotExist {
+			ctx.Handle(404, "issue.UpdateIssueLabel(GetIssueByIndex)", err)
+		} else {
+			ctx.Handle(500, "issue.UpdateIssueLabel(GetIssueByIndex)", err)
+		}
+		return
+	}
+
+	isAttach := ctx.Query("action") == "attach"
+	labelStrId := ctx.Query("id")
+	labelId := com.StrTo(labelStrId).MustInt64()
+	label, err := models.GetLabelById(labelId)
+	if err != nil {
+		if err == models.ErrLabelNotExist {
+			ctx.Handle(404, "issue.UpdateIssueLabel(GetLabelById)", err)
+		} else {
+			ctx.Handle(500, "issue.UpdateIssueLabel(GetLabelById)", err)
+		}
+		return
+	}
+
+	isHad := strings.Contains(issue.LabelIds, "$"+labelStrId+"|")
+	isNeedUpdate := false
+	if isAttach {
+		if !isHad {
+			issue.LabelIds += "$" + labelStrId + "|"
+			isNeedUpdate = true
+		}
+	} else {
+		if isHad {
+			issue.LabelIds = strings.Replace(issue.LabelIds, "$"+labelStrId+"|", "", -1)
+			isNeedUpdate = true
+		}
+	}
+
+	if isNeedUpdate {
+		if err = models.UpdateIssue(issue); err != nil {
+			ctx.Handle(500, "issue.UpdateIssueLabel(UpdateIssue)", err)
+			return
+		}
+
+		if isAttach {
+			label.NumIssues++
+			if issue.IsClosed {
+				label.NumClosedIssues++
+			}
+		} else {
+			label.NumIssues--
+			if issue.IsClosed {
+				label.NumClosedIssues--
+			}
+		}
+		if err = models.UpdateLabel(label); err != nil {
+			ctx.Handle(500, "issue.UpdateIssueLabel(UpdateLabel)", err)
+			return
+		}
+	}
+	ctx.JSON(200, map[string]interface{}{
+		"ok": true,
+	})
+}
+
+func UpdateIssueMilestone(ctx *middleware.Context) {
+	if !ctx.Repo.IsOwner {
+		ctx.Error(403)
+		return
+	}
+
+	issueId := com.StrTo(ctx.Params(":issue")).MustInt64()
+	if issueId == 0 {
+		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 := com.StrTo(ctx.Params(":milestone")).MustInt64()
+	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); 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
+	}
+
+	issueId := com.StrTo(ctx.Params(":index")).MustInt64()
+	if issueId == 0 {
+		ctx.Error(404)
+		return
+	}
+
+	issue, err := models.GetIssueById(issueId)
+	if err != nil {
+		if err == models.ErrIssueNotExist {
+			ctx.Handle(404, "GetIssueById", err)
+		} else {
+			ctx.Handle(500, "GetIssueById", err)
+		}
+		return
+	}
+
+	aid := com.StrTo(ctx.Params(":assigneeid")).MustInt64()
+	// Not check for invalid assignne id and give responsibility to owners.
+	issue.AssigneeId = aid
+	if err = models.UpdateIssueUserPairByAssignee(aid, issue.Id); err != nil {
+		ctx.Handle(500, "UpdateIssueUserPairByAssignee: %v", err)
+		return
+	} else if err = models.UpdateIssue(issue); err != nil {
+		ctx.Handle(500, "UpdateIssue", err)
+		return
+	}
+
+	ctx.JSON(200, map[string]interface{}{
+		"ok": true,
+	})
+}
+
+func uploadFiles(ctx *middleware.Context, issueId, commentId int64) {
+	if !setting.AttachmentEnabled {
+		return
+	}
+
+	allowedTypes := strings.Split(setting.AttachmentAllowedTypes, "|")
+	attachments := ctx.Req.MultipartForm.File["attachments"]
+
+	if len(attachments) > setting.AttachmentMaxFiles {
+		ctx.Handle(400, "issue.Comment", ErrTooManyFiles)
+		return
+	}
+
+	for _, header := range attachments {
+		file, err := header.Open()
+
+		if err != nil {
+			ctx.Handle(500, "issue.Comment(header.Open)", err)
+			return
+		}
+
+		defer file.Close()
+
+		buf := make([]byte, 1024)
+		n, _ := file.Read(buf)
+		if n > 0 {
+			buf = buf[:n]
+		}
+		fileType := http.DetectContentType(buf)
+		fmt.Println(fileType)
+
+		allowed := false
+
+		for _, t := range allowedTypes {
+			t := strings.Trim(t, " ")
+
+			if t == "*/*" || t == fileType {
+				allowed = true
+				break
+			}
+		}
+
+		if !allowed {
+			ctx.Handle(400, "issue.Comment", ErrFileTypeForbidden)
+			return
+		}
+
+		out, err := ioutil.TempFile(setting.AttachmentPath, "attachment_")
+
+		if err != nil {
+			ctx.Handle(500, "ioutil.TempFile", err)
+			return
+		}
+
+		defer out.Close()
+
+		out.Write(buf)
+		_, err = io.Copy(out, file)
+		if err != nil {
+			ctx.Handle(500, "io.Copy", err)
+			return
+		}
+
+		_, err = models.CreateAttachment(issueId, commentId, header.Filename, out.Name())
+		if err != nil {
+			ctx.Handle(500, "CreateAttachment", err)
+			return
+		}
+	}
+}
+
+func Comment(ctx *middleware.Context) {
+	send := func(status int, data interface{}, err error) {
+		if err != nil {
+			log.Error(4, "issue.Comment(?): %s", err)
+
+			ctx.JSON(status, map[string]interface{}{
+				"ok":     false,
+				"status": status,
+				"error":  err.Error(),
+			})
+		} else {
+			ctx.JSON(status, map[string]interface{}{
+				"ok":     true,
+				"status": status,
+				"data":   data,
+			})
+		}
+	}
+
+	index := com.StrTo(ctx.Query("issueIndex")).MustInt64()
+	if index == 0 {
+		ctx.Error(404)
+		return
+	}
+
+	issue, err := models.GetIssueByIndex(ctx.Repo.Repository.Id, index)
+	if err != nil {
+		if err == models.ErrIssueNotExist {
+			send(404, nil, err)
+		} else {
+			send(200, nil, err)
+		}
+
+		return
+	}
+
+	// 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 {
+				send(500, nil, err)
+				return
+			} else if err = models.UpdateIssueUserPairsByStatus(issue.Id, issue.IsClosed); err != nil {
+				send(500, nil, err)
+				return
+			}
+
+			// Change open/closed issue counter for the associated milestone
+			if issue.MilestoneId > 0 {
+				if err = models.ChangeMilestoneIssueStats(issue); err != nil {
+					send(500, nil, err)
+				}
+			}
+
+			cmtType := models.CLOSE
+			if !issue.IsClosed {
+				cmtType = models.REOPEN
+			}
+
+			if _, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, cmtType, "", nil); err != nil {
+				send(200, nil, err)
+				return
+			}
+			log.Trace("%s Issue(%d) status changed: %v", ctx.Req.RequestURI, issue.Id, !issue.IsClosed)
+		}
+	}
+
+	var comment *models.Comment
+
+	var ms []string
+	content := ctx.Query("content")
+	// Fix #321. Allow empty comments, as long as we have attachments.
+	if len(content) > 0 || len(ctx.Req.MultipartForm.File["attachments"]) > 0 {
+		switch ctx.Params(":action") {
+		case "new":
+			if comment, err = models.CreateComment(ctx.User.Id, ctx.Repo.Repository.Id, issue.Id, 0, 0, models.COMMENT, content, nil); err != nil {
+				send(500, nil, err)
+				return
+			}
+
+			// Update mentions.
+			ms = base.MentionPattern.FindAllString(issue.Content, -1)
+			if len(ms) > 0 {
+				for i := range ms {
+					ms[i] = ms[i][1:]
+				}
+
+				if err := models.UpdateMentions(ms, issue.Id); err != nil {
+					send(500, nil, err)
+					return
+				}
+			}
+
+			log.Trace("%s Comment created: %d", ctx.Req.RequestURI, issue.Id)
+		default:
+			ctx.Handle(404, "issue.Comment", err)
+			return
+		}
+	}
+
+	if comment != nil {
+		uploadFiles(ctx, issue.Id, comment.Id)
+	}
+
+	// Notify watchers.
+	act := &models.Action{
+		ActUserId:    ctx.User.Id,
+		ActUserName:  ctx.User.LowerName,
+		ActEmail:     ctx.User.Email,
+		OpType:       models.COMMENT_ISSUE,
+		Content:      fmt.Sprintf("%d|%s", issue.Index, strings.Split(content, "\n")[0]),
+		RepoId:       ctx.Repo.Repository.Id,
+		RepoUserName: ctx.Repo.Owner.LowerName,
+		RepoName:     ctx.Repo.Repository.LowerName,
+	}
+	if err = models.NotifyWatchers(act); err != nil {
+		send(500, nil, err)
+		return
+	}
+
+	// Mail watchers and mentions.
+	if setting.Service.EnableNotifyMail {
+		issue.Content = content
+		tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
+		if err != nil {
+			send(500, nil, err)
+			return
+		}
+
+		tos = append(tos, ctx.User.LowerName)
+		newTos := make([]string, 0, len(ms))
+		for _, m := range ms {
+			if com.IsSliceContainsStr(tos, m) {
+				continue
+			}
+
+			newTos = append(newTos, m)
+		}
+		if err = mailer.SendIssueMentionMail(ctx.Render, ctx.User, ctx.Repo.Owner,
+			ctx.Repo.Repository, issue, models.GetUserEmailsByNames(newTos)); err != nil {
+			send(500, nil, err)
+			return
+		}
+	}
+
+	send(200, fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, index), nil)
+}
+
+func NewLabel(ctx *middleware.Context, form auth.CreateLabelForm) {
+	if ctx.HasError() {
+		Issues(ctx)
+		return
+	}
+
+	l := &models.Label{
+		RepoId: ctx.Repo.Repository.Id,
+		Name:   form.Title,
+		Color:  form.Color,
+	}
+	if err := models.NewLabel(l); err != nil {
+		ctx.Handle(500, "issue.NewLabel(NewLabel)", err)
+		return
+	}
+	ctx.Redirect(ctx.Repo.RepoLink + "/issues")
+}
+
+func UpdateLabel(ctx *middleware.Context, form auth.CreateLabelForm) {
+	id := com.StrTo(ctx.Query("id")).MustInt64()
+	if id == 0 {
+		ctx.Error(404)
+		return
+	}
+
+	l := &models.Label{
+		Id:    id,
+		Name:  form.Title,
+		Color: form.Color,
+	}
+	if err := models.UpdateLabel(l); err != nil {
+		ctx.Handle(500, "issue.UpdateLabel(UpdateLabel)", err)
+		return
+	}
+	ctx.Redirect(ctx.Repo.RepoLink + "/issues")
+}
+
+func DeleteLabel(ctx *middleware.Context) {
+	removes := ctx.Query("remove")
+	if len(strings.TrimSpace(removes)) == 0 {
+		ctx.JSON(200, map[string]interface{}{
+			"ok": true,
+		})
+		return
+	}
+
+	strIds := strings.Split(removes, ",")
+	for _, strId := range strIds {
+		if err := models.DeleteLabel(ctx.Repo.Repository.Id, strId); err != nil {
+			ctx.Handle(500, "issue.DeleteLabel(DeleteLabel)", err)
+			return
+		}
+	}
+
+	ctx.JSON(200, map[string]interface{}{
+		"ok": true,
+	})
+}
+
+func Milestones(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Milestones"
+	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, MILESTONE)
+}
+
+func NewMilestone(ctx *middleware.Context) {
+	ctx.Data["Title"] = "New Milestone"
+	ctx.Data["IsRepoToolbarIssues"] = true
+	ctx.Data["IsRepoToolbarIssuesList"] = true
+	ctx.HTML(200, MILESTONE_NEW)
+}
+
+func NewMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
+	ctx.Data["Title"] = "New Milestone"
+	ctx.Data["IsRepoToolbarIssues"] = true
+	ctx.Data["IsRepoToolbarIssuesList"] = true
+
+	if ctx.HasError() {
+		ctx.HTML(200, MILESTONE_NEW)
+		return
+	}
+
+	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) {
+	ctx.Data["Title"] = "Update Milestone"
+	ctx.Data["IsRepoToolbarIssues"] = true
+	ctx.Data["IsRepoToolbarIssuesList"] = true
+
+	idx := com.StrTo(ctx.Params(":index")).MustInt64()
+	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 := ctx.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, MILESTONE_EDIT)
+}
+
+func UpdateMilestonePost(ctx *middleware.Context, form auth.CreateMilestoneForm) {
+	ctx.Data["Title"] = "Update Milestone"
+	ctx.Data["IsRepoToolbarIssues"] = true
+	ctx.Data["IsRepoToolbarIssuesList"] = true
+
+	idx := com.StrTo(ctx.Params(":index")).MustInt64()
+	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
+	}
+
+	if ctx.HasError() {
+		ctx.HTML(200, MILESTONE_EDIT)
+		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")
+}
+
+func IssueGetAttachment(ctx *middleware.Context) {
+	id := com.StrTo(ctx.Params(":id")).MustInt64()
+	if id == 0 {
+		ctx.Error(404)
+		return
+	}
+
+	attachment, err := models.GetAttachmentById(id)
+
+	if err != nil {
+		ctx.Handle(404, "issue.IssueGetAttachment(models.GetAttachmentById)", err)
+		return
+	}
+
+	// Fix #312. Attachments with , in their name are not handled correctly by Google Chrome.
+	// We must put the name in " manually.
+	ctx.ServeFile(attachment.Path, "\""+attachment.Name+"\"")
+}

+ 216 - 218
routers/repo/release.go

@@ -5,13 +5,11 @@
 package repo
 
 import (
-	// "github.com/go-martini/martini"
-
-	// "github.com/gogits/gogs/models"
-	// "github.com/gogits/gogs/modules/auth"
+	"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/middleware"
+	"github.com/gogits/gogs/modules/log"
+	"github.com/gogits/gogs/modules/middleware"
 )
 
 const (
@@ -20,215 +18,215 @@ const (
 	RELEASE_EDIT base.TplName = "repo/release/edit"
 )
 
-// func Releases(ctx *middleware.Context) {
-// 	ctx.Data["Title"] = "Releases"
-// 	ctx.Data["IsRepoToolbarReleases"] = true
-// 	ctx.Data["IsRepoReleaseNew"] = false
-// 	rawTags, err := ctx.Repo.GitRepo.GetTags()
-// 	if err != nil {
-// 		ctx.Handle(500, "release.Releases(GetTags)", err)
-// 		return
-// 	}
-
-// 	rels, err := models.GetReleasesByRepoId(ctx.Repo.Repository.Id)
-// 	if err != nil {
-// 		ctx.Handle(500, "release.Releases(GetReleasesByRepoId)", err)
-// 		return
-// 	}
-
-// 	commitsCount, err := ctx.Repo.Commit.CommitsCount()
-// 	if err != nil {
-// 		ctx.Handle(500, "release.Releases(CommitsCount)", err)
-// 		return
-// 	}
-
-// 	// Temproray cache commits count of used branches to speed up.
-// 	countCache := make(map[string]int)
-
-// 	tags := make([]*models.Release, len(rawTags))
-// 	for i, rawTag := range rawTags {
-// 		for _, rel := range rels {
-// 			if rel.IsDraft && !ctx.Repo.IsOwner {
-// 				continue
-// 			}
-// 			if rel.TagName == rawTag {
-// 				rel.Publisher, err = models.GetUserById(rel.PublisherId)
-// 				if err != nil {
-// 					ctx.Handle(500, "release.Releases(GetUserById)", err)
-// 					return
-// 				}
-// 				// Get corresponding target if it's not the current branch.
-// 				if ctx.Repo.BranchName != rel.Target {
-// 					// Get count if not exists.
-// 					if _, ok := countCache[rel.Target]; !ok {
-// 						commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rel.TagName)
-// 						if err != nil {
-// 							ctx.Handle(500, "release.Releases(GetCommitOfTag)", err)
-// 							return
-// 						}
-// 						countCache[rel.Target], err = commit.CommitsCount()
-// 						if err != nil {
-// 							ctx.Handle(500, "release.Releases(CommitsCount2)", err)
-// 							return
-// 						}
-// 					}
-// 					rel.NumCommitsBehind = countCache[rel.Target] - rel.NumCommits
-// 				} else {
-// 					rel.NumCommitsBehind = commitsCount - rel.NumCommits
-// 				}
-
-// 				rel.Note = base.RenderMarkdownString(rel.Note, ctx.Repo.RepoLink)
-// 				tags[i] = rel
-// 				break
-// 			}
-// 		}
-
-// 		if tags[i] == nil {
-// 			commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rawTag)
-// 			if err != nil {
-// 				ctx.Handle(500, "release.Releases(GetCommitOfTag2)", err)
-// 				return
-// 			}
-
-// 			tags[i] = &models.Release{
-// 				Title:   rawTag,
-// 				TagName: rawTag,
-// 				Sha1:    commit.Id.String(),
-// 			}
-
-// 			tags[i].NumCommits, err = ctx.Repo.GitRepo.CommitsCount(commit.Id.String())
-// 			if err != nil {
-// 				ctx.Handle(500, "release.Releases(CommitsCount)", err)
-// 				return
-// 			}
-// 			tags[i].NumCommitsBehind = commitsCount - tags[i].NumCommits
-// 		}
-// 	}
-// 	models.SortReleases(tags)
-// 	ctx.Data["Releases"] = tags
-// 	ctx.HTML(200, RELEASES)
-// }
-
-// func NewRelease(ctx *middleware.Context) {
-// 	if !ctx.Repo.IsOwner {
-// 		ctx.Handle(403, "release.ReleasesNew", nil)
-// 		return
-// 	}
-
-// 	ctx.Data["Title"] = "New Release"
-// 	ctx.Data["IsRepoToolbarReleases"] = true
-// 	ctx.Data["IsRepoReleaseNew"] = true
-// 	ctx.HTML(200, RELEASE_NEW)
-// }
-
-// func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) {
-// 	if !ctx.Repo.IsOwner {
-// 		ctx.Handle(403, "release.ReleasesNew", nil)
-// 		return
-// 	}
-
-// 	ctx.Data["Title"] = "New Release"
-// 	ctx.Data["IsRepoToolbarReleases"] = true
-// 	ctx.Data["IsRepoReleaseNew"] = true
-
-// 	if ctx.HasError() {
-// 		ctx.HTML(200, RELEASE_NEW)
-// 		return
-// 	}
-
-// 	commitsCount, err := ctx.Repo.Commit.CommitsCount()
-// 	if err != nil {
-// 		ctx.Handle(500, "release.ReleasesNewPost(CommitsCount)", err)
-// 		return
-// 	}
-
-// 	if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
-// 		ctx.RenderWithErr("Target branch does not exist", "release/new", &form)
-// 		return
-// 	}
-
-// 	rel := &models.Release{
-// 		RepoId:       ctx.Repo.Repository.Id,
-// 		PublisherId:  ctx.User.Id,
-// 		Title:        form.Title,
-// 		TagName:      form.TagName,
-// 		Target:       form.Target,
-// 		Sha1:         ctx.Repo.Commit.Id.String(),
-// 		NumCommits:   commitsCount,
-// 		Note:         form.Content,
-// 		IsDraft:      len(form.Draft) > 0,
-// 		IsPrerelease: form.Prerelease,
-// 	}
-
-// 	if err = models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil {
-// 		if err == models.ErrReleaseAlreadyExist {
-// 			ctx.RenderWithErr("Release with this tag name has already existed", "release/new", &form)
-// 		} else {
-// 			ctx.Handle(500, "release.ReleasesNewPost(IsReleaseExist)", err)
-// 		}
-// 		return
-// 	}
-// 	log.Trace("%s Release created: %s/%s:%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.Name, form.TagName)
-
-// 	ctx.Redirect(ctx.Repo.RepoLink + "/releases")
-// }
-
-// func EditRelease(ctx *middleware.Context, params martini.Params) {
-// 	if !ctx.Repo.IsOwner {
-// 		ctx.Handle(403, "release.ReleasesEdit", nil)
-// 		return
-// 	}
-
-// 	tagName := params["tagname"]
-// 	rel, err := models.GetRelease(ctx.Repo.Repository.Id, tagName)
-// 	if err != nil {
-// 		if err == models.ErrReleaseNotExist {
-// 			ctx.Handle(404, "release.ReleasesEdit(GetRelease)", err)
-// 		} else {
-// 			ctx.Handle(500, "release.ReleasesEdit(GetRelease)", err)
-// 		}
-// 		return
-// 	}
-// 	ctx.Data["Release"] = rel
-
-// 	ctx.Data["Title"] = "Edit Release"
-// 	ctx.Data["IsRepoToolbarReleases"] = true
-// 	ctx.HTML(200, RELEASE_EDIT)
-// }
-
-// func EditReleasePost(ctx *middleware.Context, params martini.Params, form auth.EditReleaseForm) {
-// 	if !ctx.Repo.IsOwner {
-// 		ctx.Handle(403, "release.EditReleasePost", nil)
-// 		return
-// 	}
-
-// 	tagName := params["tagname"]
-// 	rel, err := models.GetRelease(ctx.Repo.Repository.Id, tagName)
-// 	if err != nil {
-// 		if err == models.ErrReleaseNotExist {
-// 			ctx.Handle(404, "release.EditReleasePost(GetRelease)", err)
-// 		} else {
-// 			ctx.Handle(500, "release.EditReleasePost(GetRelease)", err)
-// 		}
-// 		return
-// 	}
-// 	ctx.Data["Release"] = rel
-
-// 	if ctx.HasError() {
-// 		ctx.HTML(200, RELEASE_EDIT)
-// 		return
-// 	}
-
-// 	ctx.Data["Title"] = "Edit Release"
-// 	ctx.Data["IsRepoToolbarReleases"] = true
-
-// 	rel.Title = form.Title
-// 	rel.Note = form.Content
-// 	rel.IsDraft = len(form.Draft) > 0
-// 	rel.IsPrerelease = form.Prerelease
-// 	if err = models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil {
-// 		ctx.Handle(500, "release.EditReleasePost(UpdateRelease)", err)
-// 		return
-// 	}
-// 	ctx.Redirect(ctx.Repo.RepoLink + "/releases")
-// }
+func Releases(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Releases"
+	ctx.Data["IsRepoToolbarReleases"] = true
+	ctx.Data["IsRepoReleaseNew"] = false
+	rawTags, err := ctx.Repo.GitRepo.GetTags()
+	if err != nil {
+		ctx.Handle(500, "release.Releases(GetTags)", err)
+		return
+	}
+
+	rels, err := models.GetReleasesByRepoId(ctx.Repo.Repository.Id)
+	if err != nil {
+		ctx.Handle(500, "release.Releases(GetReleasesByRepoId)", err)
+		return
+	}
+
+	commitsCount, err := ctx.Repo.Commit.CommitsCount()
+	if err != nil {
+		ctx.Handle(500, "release.Releases(CommitsCount)", err)
+		return
+	}
+
+	// Temproray cache commits count of used branches to speed up.
+	countCache := make(map[string]int)
+
+	tags := make([]*models.Release, len(rawTags))
+	for i, rawTag := range rawTags {
+		for _, rel := range rels {
+			if rel.IsDraft && !ctx.Repo.IsOwner {
+				continue
+			}
+			if rel.TagName == rawTag {
+				rel.Publisher, err = models.GetUserById(rel.PublisherId)
+				if err != nil {
+					ctx.Handle(500, "GetUserById", err)
+					return
+				}
+				// Get corresponding target if it's not the current branch.
+				if ctx.Repo.BranchName != rel.Target {
+					// Get count if not exists.
+					if _, ok := countCache[rel.Target]; !ok {
+						commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rel.TagName)
+						if err != nil {
+							ctx.Handle(500, "GetCommitOfTag", err)
+							return
+						}
+						countCache[rel.Target], err = commit.CommitsCount()
+						if err != nil {
+							ctx.Handle(500, "CommitsCount2", err)
+							return
+						}
+					}
+					rel.NumCommitsBehind = countCache[rel.Target] - rel.NumCommits
+				} else {
+					rel.NumCommitsBehind = commitsCount - rel.NumCommits
+				}
+
+				rel.Note = base.RenderMarkdownString(rel.Note, ctx.Repo.RepoLink)
+				tags[i] = rel
+				break
+			}
+		}
+
+		if tags[i] == nil {
+			commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rawTag)
+			if err != nil {
+				ctx.Handle(500, "GetCommitOfTag2", err)
+				return
+			}
+
+			tags[i] = &models.Release{
+				Title:   rawTag,
+				TagName: rawTag,
+				Sha1:    commit.Id.String(),
+			}
+
+			tags[i].NumCommits, err = ctx.Repo.GitRepo.CommitsCount(commit.Id.String())
+			if err != nil {
+				ctx.Handle(500, "CommitsCount", err)
+				return
+			}
+			tags[i].NumCommitsBehind = commitsCount - tags[i].NumCommits
+		}
+	}
+	models.SortReleases(tags)
+	ctx.Data["Releases"] = tags
+	ctx.HTML(200, RELEASES)
+}
+
+func NewRelease(ctx *middleware.Context) {
+	if !ctx.Repo.IsOwner {
+		ctx.Handle(403, "release.ReleasesNew", nil)
+		return
+	}
+
+	ctx.Data["Title"] = "New Release"
+	ctx.Data["IsRepoToolbarReleases"] = true
+	ctx.Data["IsRepoReleaseNew"] = true
+	ctx.HTML(200, RELEASE_NEW)
+}
+
+func NewReleasePost(ctx *middleware.Context, form auth.NewReleaseForm) {
+	if !ctx.Repo.IsOwner {
+		ctx.Handle(403, "release.ReleasesNew", nil)
+		return
+	}
+
+	ctx.Data["Title"] = "New Release"
+	ctx.Data["IsRepoToolbarReleases"] = true
+	ctx.Data["IsRepoReleaseNew"] = true
+
+	if ctx.HasError() {
+		ctx.HTML(200, RELEASE_NEW)
+		return
+	}
+
+	commitsCount, err := ctx.Repo.Commit.CommitsCount()
+	if err != nil {
+		ctx.Handle(500, "release.ReleasesNewPost(CommitsCount)", err)
+		return
+	}
+
+	if !ctx.Repo.GitRepo.IsBranchExist(form.Target) {
+		ctx.RenderWithErr("Target branch does not exist", "release/new", &form)
+		return
+	}
+
+	rel := &models.Release{
+		RepoId:       ctx.Repo.Repository.Id,
+		PublisherId:  ctx.User.Id,
+		Title:        form.Title,
+		TagName:      form.TagName,
+		Target:       form.Target,
+		Sha1:         ctx.Repo.Commit.Id.String(),
+		NumCommits:   commitsCount,
+		Note:         form.Content,
+		IsDraft:      len(form.Draft) > 0,
+		IsPrerelease: form.Prerelease,
+	}
+
+	if err = models.CreateRelease(ctx.Repo.GitRepo, rel); err != nil {
+		if err == models.ErrReleaseAlreadyExist {
+			ctx.RenderWithErr("Release with this tag name has already existed", "release/new", &form)
+		} else {
+			ctx.Handle(500, "release.ReleasesNewPost(IsReleaseExist)", err)
+		}
+		return
+	}
+	log.Trace("%s Release created: %s/%s:%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.Name, form.TagName)
+
+	ctx.Redirect(ctx.Repo.RepoLink + "/releases")
+}
+
+func EditRelease(ctx *middleware.Context) {
+	if !ctx.Repo.IsOwner {
+		ctx.Handle(403, "release.ReleasesEdit", nil)
+		return
+	}
+
+	tagName := ctx.Params(":tagname")
+	rel, err := models.GetRelease(ctx.Repo.Repository.Id, tagName)
+	if err != nil {
+		if err == models.ErrReleaseNotExist {
+			ctx.Handle(404, "release.ReleasesEdit(GetRelease)", err)
+		} else {
+			ctx.Handle(500, "release.ReleasesEdit(GetRelease)", err)
+		}
+		return
+	}
+	ctx.Data["Release"] = rel
+
+	ctx.Data["Title"] = "Edit Release"
+	ctx.Data["IsRepoToolbarReleases"] = true
+	ctx.HTML(200, RELEASE_EDIT)
+}
+
+func EditReleasePost(ctx *middleware.Context, form auth.EditReleaseForm) {
+	if !ctx.Repo.IsOwner {
+		ctx.Handle(403, "release.EditReleasePost", nil)
+		return
+	}
+
+	tagName := ctx.Params(":tagname")
+	rel, err := models.GetRelease(ctx.Repo.Repository.Id, tagName)
+	if err != nil {
+		if err == models.ErrReleaseNotExist {
+			ctx.Handle(404, "release.EditReleasePost(GetRelease)", err)
+		} else {
+			ctx.Handle(500, "release.EditReleasePost(GetRelease)", err)
+		}
+		return
+	}
+	ctx.Data["Release"] = rel
+
+	if ctx.HasError() {
+		ctx.HTML(200, RELEASE_EDIT)
+		return
+	}
+
+	ctx.Data["Title"] = "Edit Release"
+	ctx.Data["IsRepoToolbarReleases"] = true
+
+	rel.Title = form.Title
+	rel.Note = form.Content
+	rel.IsDraft = len(form.Draft) > 0
+	rel.IsPrerelease = form.Prerelease
+	if err = models.UpdateRelease(ctx.Repo.GitRepo, rel); err != nil {
+		ctx.Handle(500, "release.EditReleasePost(UpdateRelease)", err)
+		return
+	}
+	ctx.Redirect(ctx.Repo.RepoLink + "/releases")
+}

+ 93 - 91
routers/repo/repo.go

@@ -5,8 +5,10 @@
 package repo
 
 import (
+	"fmt"
 	"os"
 	"path"
+	"strings"
 
 	"github.com/Unknwon/com"
 
@@ -34,22 +36,22 @@ func Create(ctx *middleware.Context) {
 	ctx.Data["Licenses"] = models.Licenses
 
 	ctxUser := ctx.User
-	// orgId := com.StrTo(ctx.Query("org")).MustInt64()
-	// if orgId > 0 {
-	// 	org, err := models.GetUserById(orgId)
-	// 	if err != nil && err != models.ErrUserNotExist {
-	// 		ctx.Handle(500, "home.Dashboard(GetUserById)", err)
-	// 		return
-	// 	}
-	// 	ctxUser = org
-	// }
+	orgId := com.StrTo(ctx.Query("org")).MustInt64()
+	if orgId > 0 {
+		org, err := models.GetUserById(orgId)
+		if err != nil && err != models.ErrUserNotExist {
+			ctx.Handle(500, "home.Dashboard(GetUserById)", err)
+			return
+		}
+		ctxUser = org
+	}
 	ctx.Data["ContextUser"] = ctxUser
 
-	// if err := ctx.User.GetOrganizations(); err != nil {
-	// 	ctx.Handle(500, "home.Dashboard(GetOrganizations)", err)
-	// 	return
-	// }
-	// ctx.Data["AllUsers"] = append([]*models.User{ctx.User}, ctx.User.Orgs...)
+	if err := ctx.User.GetOrganizations(); err != nil {
+		ctx.Handle(500, "home.Dashboard(GetOrganizations)", err)
+		return
+	}
+	ctx.Data["AllUsers"] = append([]*models.User{ctx.User}, ctx.User.Orgs...)
 
 	ctx.HTML(200, CREATE)
 }
@@ -62,22 +64,22 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) {
 	ctx.Data["Licenses"] = models.Licenses
 
 	ctxUser := ctx.User
-	// orgId := com.StrTo(ctx.Query("org")).MustInt64()
-	// if orgId > 0 {
-	// 	org, err := models.GetUserById(orgId)
-	// 	if err != nil && err != models.ErrUserNotExist {
-	// 		ctx.Handle(500, "home.Dashboard(GetUserById)", err)
-	// 		return
-	// 	}
-	// 	ctxUser = org
-	// }
+	orgId := com.StrTo(ctx.Query("org")).MustInt64()
+	if orgId > 0 {
+		org, err := models.GetUserById(orgId)
+		if err != nil && err != models.ErrUserNotExist {
+			ctx.Handle(500, "home.Dashboard(GetUserById)", err)
+			return
+		}
+		ctxUser = org
+	}
 	ctx.Data["ContextUser"] = ctxUser
 
-	// if err := ctx.User.GetOrganizations(); err != nil {
-	// 	ctx.Handle(500, "home.CreatePost(GetOrganizations)", err)
-	// 	return
-	// }
-	// ctx.Data["Orgs"] = ctx.User.Orgs
+	if err := ctx.User.GetOrganizations(); err != nil {
+		ctx.Handle(500, "home.CreatePost(GetOrganizations)", err)
+		return
+	}
+	ctx.Data["Orgs"] = ctx.User.Orgs
 
 	if ctx.HasError() {
 		ctx.HTML(200, CREATE)
@@ -127,78 +129,78 @@ func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) {
 	ctx.Handle(500, "CreateRepository", err)
 }
 
-// func Migrate(ctx *middleware.Context) {
-// 	ctx.Data["Title"] = "Migrate repository"
-// 	ctx.Data["PageIsNewRepo"] = true
+func Migrate(ctx *middleware.Context) {
+	ctx.Data["Title"] = "Migrate repository"
+	ctx.Data["PageIsNewRepo"] = true
 
-// 	if err := ctx.User.GetOrganizations(); err != nil {
-// 		ctx.Handle(500, "home.Migrate(GetOrganizations)", err)
-// 		return
-// 	}
-// 	ctx.Data["Orgs"] = ctx.User.Orgs
+	if err := ctx.User.GetOrganizations(); err != nil {
+		ctx.Handle(500, "home.Migrate(GetOrganizations)", err)
+		return
+	}
+	ctx.Data["Orgs"] = ctx.User.Orgs
 
-// 	ctx.HTML(200, MIGRATE)
-// }
+	ctx.HTML(200, MIGRATE)
+}
 
-// func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) {
-// 	ctx.Data["Title"] = "Migrate repository"
-// 	ctx.Data["PageIsNewRepo"] = true
+func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) {
+	ctx.Data["Title"] = "Migrate repository"
+	ctx.Data["PageIsNewRepo"] = true
 
-// 	if err := ctx.User.GetOrganizations(); err != nil {
-// 		ctx.Handle(500, "home.MigratePost(GetOrganizations)", err)
-// 		return
-// 	}
-// 	ctx.Data["Orgs"] = ctx.User.Orgs
+	if err := ctx.User.GetOrganizations(); err != nil {
+		ctx.Handle(500, "home.MigratePost(GetOrganizations)", err)
+		return
+	}
+	ctx.Data["Orgs"] = ctx.User.Orgs
 
-// 	if ctx.HasError() {
-// 		ctx.HTML(200, MIGRATE)
-// 		return
-// 	}
+	if ctx.HasError() {
+		ctx.HTML(200, MIGRATE)
+		return
+	}
 
-// 	u := ctx.User
-// 	// Not equal means current user is an organization.
-// 	if u.Id != form.Uid {
-// 		var err error
-// 		u, err = models.GetUserById(form.Uid)
-// 		if err != nil {
-// 			if err == models.ErrUserNotExist {
-// 				ctx.Handle(404, "home.MigratePost(GetUserById)", err)
-// 			} else {
-// 				ctx.Handle(500, "home.MigratePost(GetUserById)", err)
-// 			}
-// 			return
-// 		}
-// 	}
+	u := ctx.User
+	// Not equal means current user is an organization.
+	if u.Id != form.Uid {
+		var err error
+		u, err = models.GetUserById(form.Uid)
+		if err != nil {
+			if err == models.ErrUserNotExist {
+				ctx.Handle(404, "home.MigratePost(GetUserById)", err)
+			} else {
+				ctx.Handle(500, "home.MigratePost(GetUserById)", err)
+			}
+			return
+		}
+	}
 
-// 	authStr := strings.Replace(fmt.Sprintf("://%s:%s",
-// 		form.AuthUserName, form.AuthPasswd), "@", "%40", -1)
-// 	url := strings.Replace(form.Url, "://", authStr+"@", 1)
-// 	repo, err := models.MigrateRepository(u, form.RepoName, form.Description, form.Private,
-// 		form.Mirror, url)
-// 	if err == nil {
-// 		log.Trace("%s Repository migrated: %s/%s", ctx.Req.RequestURI, u.LowerName, form.RepoName)
-// 		ctx.Redirect("/" + u.Name + "/" + form.RepoName)
-// 		return
-// 	} else if err == models.ErrRepoAlreadyExist {
-// 		ctx.RenderWithErr("Repository name has already been used", MIGRATE, &form)
-// 		return
-// 	} else if err == models.ErrRepoNameIllegal {
-// 		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), MIGRATE, &form)
-// 		return
-// 	}
+	authStr := strings.Replace(fmt.Sprintf("://%s:%s",
+		form.AuthUserName, form.AuthPasswd), "@", "%40", -1)
+	url := strings.Replace(form.Url, "://", authStr+"@", 1)
+	repo, err := models.MigrateRepository(u, form.RepoName, form.Description, form.Private,
+		form.Mirror, url)
+	if err == nil {
+		log.Trace("%s Repository migrated: %s/%s", ctx.Req.RequestURI, u.LowerName, form.RepoName)
+		ctx.Redirect("/" + u.Name + "/" + form.RepoName)
+		return
+	} else if err == models.ErrRepoAlreadyExist {
+		ctx.RenderWithErr("Repository name has already been used", MIGRATE, &form)
+		return
+	} else if err == models.ErrRepoNameIllegal {
+		ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), MIGRATE, &form)
+		return
+	}
 
-// 	if repo != nil {
-// 		if errDelete := models.DeleteRepository(u.Id, repo.Id, u.Name); errDelete != nil {
-// 			log.Error("repo.MigratePost(DeleteRepository): %v", errDelete)
-// 		}
-// 	}
+	if repo != nil {
+		if errDelete := models.DeleteRepository(u.Id, repo.Id, u.Name); errDelete != nil {
+			log.Error(4, "DeleteRepository: %v", errDelete)
+		}
+	}
 
-// 	if strings.Contains(err.Error(), "Authentication failed") {
-// 		ctx.RenderWithErr(err.Error(), MIGRATE, &form)
-// 		return
-// 	}
-// 	ctx.Handle(500, "repo.Migrate(MigrateRepository)", err)
-// }
+	if strings.Contains(err.Error(), "Authentication failed") {
+		ctx.RenderWithErr(err.Error(), MIGRATE, &form)
+		return
+	}
+	ctx.Handle(500, "MigrateRepository", err)
+}
 
 // func Action(ctx *middleware.Context, params martini.Params) {
 // 	var err error

+ 359 - 359
routers/repo/setting.go

@@ -4,362 +4,362 @@
 
 package repo
 
-// import (
-// 	"fmt"
-// 	"strings"
-// 	"time"
-
-// 	"github.com/go-martini/martini"
-
-// 	"github.com/gogits/gogs-ng/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"
-// 	"github.com/gogits/gogs/modules/setting"
-// )
-
-// const (
-// 	SETTING       base.TplName = "repo/setting"
-// 	COLLABORATION base.TplName = "repo/collaboration"
-
-// 	HOOKS     base.TplName = "repo/hooks"
-// 	HOOK_ADD  base.TplName = "repo/hook_add"
-// 	HOOK_EDIT base.TplName = "repo/hook_edit"
-// )
-
-// func Setting(ctx *middleware.Context) {
-// 	ctx.Data["IsRepoToolbarSetting"] = true
-// 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - settings"
-// 	ctx.HTML(200, SETTING)
-// }
-
-// func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
-// 	ctx.Data["IsRepoToolbarSetting"] = true
-
-// 	switch ctx.Query("action") {
-// 	case "update":
-// 		if ctx.HasError() {
-// 			ctx.HTML(200, SETTING)
-// 			return
-// 		}
-
-// 		newRepoName := form.RepoName
-// 		// Check if repository name has been changed.
-// 		if ctx.Repo.Repository.Name != newRepoName {
-// 			isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName)
-// 			if err != nil {
-// 				ctx.Handle(500, "setting.SettingPost(update: check existence)", err)
-// 				return
-// 			} else if isExist {
-// 				ctx.RenderWithErr("Repository name has been taken in your repositories.", SETTING, nil)
-// 				return
-// 			} else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
-// 				ctx.Handle(500, "setting.SettingPost(change repository name)", err)
-// 				return
-// 			}
-// 			log.Trace("%s Repository name changed: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newRepoName)
-
-// 			ctx.Repo.Repository.Name = newRepoName
-// 		}
-
-// 		br := form.Branch
-
-// 		if ctx.Repo.GitRepo.IsBranchExist(br) {
-// 			ctx.Repo.Repository.DefaultBranch = br
-// 		}
-// 		ctx.Repo.Repository.Description = form.Description
-// 		ctx.Repo.Repository.Website = form.Website
-// 		ctx.Repo.Repository.IsPrivate = form.Private
-// 		ctx.Repo.Repository.IsGoget = form.GoGet
-// 		if err := models.UpdateRepository(ctx.Repo.Repository); err != nil {
-// 			ctx.Handle(404, "setting.SettingPost(update)", err)
-// 			return
-// 		}
-// 		log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
-
-// 		if ctx.Repo.Repository.IsMirror {
-// 			if form.Interval > 0 {
-// 				ctx.Repo.Mirror.Interval = form.Interval
-// 				ctx.Repo.Mirror.NextUpdate = time.Now().Add(time.Duration(form.Interval) * time.Hour)
-// 				if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
-// 					log.Error("setting.SettingPost(UpdateMirror): %v", err)
-// 				}
-// 			}
-// 		}
-
-// 		ctx.Flash.Success("Repository options has been successfully updated.")
-// 		ctx.Redirect(fmt.Sprintf("/%s/%s/settings", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name))
-// 	case "transfer":
-// 		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.", SETTING, nil)
-// 			return
-// 		} else if ctx.Repo.Repository.IsMirror {
-// 			ctx.Error(404)
-// 			return
-// 		}
-
-// 		newOwner := ctx.Query("owner")
-// 		// Check if new owner exists.
-// 		isExist, err := models.IsUserExist(newOwner)
-// 		if err != nil {
-// 			ctx.Handle(500, "setting.SettingPost(transfer: check existence)", err)
-// 			return
-// 		} else if !isExist {
-// 			ctx.RenderWithErr("Please make sure you entered owner name is correct.", SETTING, nil)
-// 			return
-// 		} else if err = models.TransferOwnership(ctx.Repo.Owner, newOwner, ctx.Repo.Repository); err != nil {
-// 			ctx.Handle(500, "setting.SettingPost(transfer repository)", err)
-// 			return
-// 		}
-// 		log.Trace("%s Repository transfered: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newOwner)
-
-// 		ctx.Redirect("/")
-// 	case "delete":
-// 		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.", SETTING, nil)
-// 			return
-// 		}
-
-// 		if ctx.Repo.Owner.IsOrganization() &&
-// 			!ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) {
-// 			ctx.Error(403)
-// 			return
-// 		}
-
-// 		if err := models.DeleteRepository(ctx.Repo.Owner.Id, ctx.Repo.Repository.Id, ctx.Repo.Owner.Name); err != nil {
-// 			ctx.Handle(500, "setting.Delete(DeleteRepository)", err)
-// 			return
-// 		}
-// 		log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.LowerName, ctx.Repo.Repository.LowerName)
-
-// 		if ctx.Repo.Owner.IsOrganization() {
-// 			ctx.Redirect("/org/" + ctx.Repo.Owner.Name + "/dashboard")
-// 		} else {
-// 			ctx.Redirect("/")
-// 		}
-// 	}
-// }
-
-// func Collaboration(ctx *middleware.Context) {
-// 	repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/")
-// 	ctx.Data["IsRepoToolbarCollaboration"] = true
-// 	ctx.Data["Title"] = repoLink + " - collaboration"
-
-// 	// Delete collaborator.
-// 	remove := strings.ToLower(ctx.Query("remove"))
-// 	if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName {
-// 		if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil {
-// 			ctx.Handle(500, "setting.Collaboration(DeleteAccess)", err)
-// 			return
-// 		}
-// 		ctx.Flash.Success("Collaborator has been removed.")
-// 		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
-// 		return
-// 	}
-
-// 	names, err := models.GetCollaboratorNames(repoLink)
-// 	if err != nil {
-// 		ctx.Handle(500, "setting.Collaboration(GetCollaborators)", err)
-// 		return
-// 	}
-
-// 	us := make([]*models.User, len(names))
-// 	for i, name := range names {
-// 		us[i], err = models.GetUserByName(name)
-// 		if err != nil {
-// 			ctx.Handle(500, "setting.Collaboration(GetUserByName)", err)
-// 			return
-// 		}
-// 	}
-
-// 	ctx.Data["Collaborators"] = us
-// 	ctx.HTML(200, COLLABORATION)
-// }
-
-// func CollaborationPost(ctx *middleware.Context) {
-// 	repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/")
-// 	name := strings.ToLower(ctx.Query("collaborator"))
-// 	if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
-// 		ctx.Redirect(ctx.Req.RequestURI)
-// 		return
-// 	}
-// 	has, err := models.HasAccess(name, repoLink, models.WRITABLE)
-// 	if err != nil {
-// 		ctx.Handle(500, "setting.CollaborationPost(HasAccess)", err)
-// 		return
-// 	} else if has {
-// 		ctx.Redirect(ctx.Req.RequestURI)
-// 		return
-// 	}
-
-// 	u, err := models.GetUserByName(name)
-// 	if err != nil {
-// 		if err == models.ErrUserNotExist {
-// 			ctx.Flash.Error("Given user does not exist.")
-// 			ctx.Redirect(ctx.Req.RequestURI)
-// 		} else {
-// 			ctx.Handle(500, "setting.CollaborationPost(GetUserByName)", err)
-// 		}
-// 		return
-// 	}
-
-// 	if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink,
-// 		Mode: models.WRITABLE}); err != nil {
-// 		ctx.Handle(500, "setting.CollaborationPost(AddAccess)", err)
-// 		return
-// 	}
-
-// 	if setting.Service.EnableNotifyMail {
-// 		if err = mailer.SendCollaboratorMail(ctx.Render, u, ctx.User, ctx.Repo.Repository); err != nil {
-// 			ctx.Handle(500, "setting.CollaborationPost(SendCollaboratorMail)", err)
-// 			return
-// 		}
-// 	}
-
-// 	ctx.Flash.Success("New collaborator has been added.")
-// 	ctx.Redirect(ctx.Req.RequestURI)
-// }
-
-// func WebHooks(ctx *middleware.Context) {
-// 	ctx.Data["IsRepoToolbarWebHooks"] = true
-// 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhooks"
-
-// 	// Delete webhook.
-// 	remove, _ := base.StrTo(ctx.Query("remove")).Int64()
-// 	if remove > 0 {
-// 		if err := models.DeleteWebhook(remove); err != nil {
-// 			ctx.Handle(500, "setting.WebHooks(DeleteWebhook)", err)
-// 			return
-// 		}
-// 		ctx.Flash.Success("Webhook has been removed.")
-// 		ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks")
-// 		return
-// 	}
-
-// 	ws, err := models.GetWebhooksByRepoId(ctx.Repo.Repository.Id)
-// 	if err != nil {
-// 		ctx.Handle(500, "setting.WebHooks(GetWebhooksByRepoId)", err)
-// 		return
-// 	}
-
-// 	ctx.Data["Webhooks"] = ws
-// 	ctx.HTML(200, HOOKS)
-// }
-
-// func WebHooksAdd(ctx *middleware.Context) {
-// 	ctx.Data["IsRepoToolbarWebHooks"] = true
-// 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook"
-// 	ctx.HTML(200, HOOK_ADD)
-// }
-
-// func WebHooksAddPost(ctx *middleware.Context, form auth.NewWebhookForm) {
-// 	ctx.Data["IsRepoToolbarWebHooks"] = true
-// 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook"
-
-// 	if ctx.HasError() {
-// 		ctx.HTML(200, HOOK_ADD)
-// 		return
-// 	}
-
-// 	ct := models.JSON
-// 	if form.ContentType == "2" {
-// 		ct = models.FORM
-// 	}
-
-// 	w := &models.Webhook{
-// 		RepoId:      ctx.Repo.Repository.Id,
-// 		Url:         form.Url,
-// 		ContentType: ct,
-// 		Secret:      form.Secret,
-// 		HookEvent: &models.HookEvent{
-// 			PushOnly: form.PushOnly,
-// 		},
-// 		IsActive: form.Active,
-// 	}
-// 	if err := w.UpdateEvent(); err != nil {
-// 		ctx.Handle(500, "setting.WebHooksAddPost(UpdateEvent)", err)
-// 		return
-// 	} else if err := models.CreateWebhook(w); err != nil {
-// 		ctx.Handle(500, "setting.WebHooksAddPost(CreateWebhook)", err)
-// 		return
-// 	}
-
-// 	ctx.Flash.Success("New webhook has been added.")
-// 	ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks")
-// }
-
-// func WebHooksEdit(ctx *middleware.Context, params martini.Params) {
-// 	ctx.Data["IsRepoToolbarWebHooks"] = true
-// 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhook"
-
-// 	hookId, _ := base.StrTo(params["id"]).Int64()
-// 	if hookId == 0 {
-// 		ctx.Handle(404, "setting.WebHooksEdit", nil)
-// 		return
-// 	}
-
-// 	w, err := models.GetWebhookById(hookId)
-// 	if err != nil {
-// 		if err == models.ErrWebhookNotExist {
-// 			ctx.Handle(404, "setting.WebHooksEdit(GetWebhookById)", nil)
-// 		} else {
-// 			ctx.Handle(500, "setting.WebHooksEdit(GetWebhookById)", err)
-// 		}
-// 		return
-// 	}
-
-// 	w.GetEvent()
-// 	ctx.Data["Webhook"] = w
-// 	ctx.HTML(200, HOOK_EDIT)
-// }
-
-// func WebHooksEditPost(ctx *middleware.Context, params martini.Params, form auth.NewWebhookForm) {
-// 	ctx.Data["IsRepoToolbarWebHooks"] = true
-// 	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhook"
-
-// 	hookId, _ := base.StrTo(params["id"]).Int64()
-// 	if hookId == 0 {
-// 		ctx.Handle(404, "setting.WebHooksEditPost", nil)
-// 		return
-// 	}
-
-// 	w, err := models.GetWebhookById(hookId)
-// 	if err != nil {
-// 		if err == models.ErrWebhookNotExist {
-// 			ctx.Handle(404, "setting.WebHooksEditPost(GetWebhookById)", nil)
-// 		} else {
-// 			ctx.Handle(500, "setting.WebHooksEditPost(GetWebhookById)", err)
-// 		}
-// 		return
-// 	}
-
-// 	if ctx.HasError() {
-// 		ctx.HTML(200, HOOK_EDIT)
-// 		return
-// 	}
-
-// 	ct := models.JSON
-// 	if form.ContentType == "2" {
-// 		ct = models.FORM
-// 	}
-
-// 	w.Url = form.Url
-// 	w.ContentType = ct
-// 	w.Secret = form.Secret
-// 	w.HookEvent = &models.HookEvent{
-// 		PushOnly: form.PushOnly,
-// 	}
-// 	w.IsActive = form.Active
-// 	if err := w.UpdateEvent(); err != nil {
-// 		ctx.Handle(500, "setting.WebHooksEditPost(UpdateEvent)", err)
-// 		return
-// 	} else if err := models.UpdateWebhook(w); err != nil {
-// 		ctx.Handle(500, "setting.WebHooksEditPost(WebHooksEditPost)", err)
-// 		return
-// 	}
-
-// 	ctx.Flash.Success("Webhook has been updated.")
-// 	ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId))
-// }
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/Unknwon/com"
+
+	"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"
+	"github.com/gogits/gogs/modules/setting"
+)
+
+const (
+	SETTING       base.TplName = "repo/setting"
+	COLLABORATION base.TplName = "repo/collaboration"
+
+	HOOKS     base.TplName = "repo/hooks"
+	HOOK_ADD  base.TplName = "repo/hook_add"
+	HOOK_EDIT base.TplName = "repo/hook_edit"
+)
+
+func Setting(ctx *middleware.Context) {
+	ctx.Data["IsRepoToolbarSetting"] = true
+	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - settings"
+	ctx.HTML(200, SETTING)
+}
+
+func SettingPost(ctx *middleware.Context, form auth.RepoSettingForm) {
+	ctx.Data["IsRepoToolbarSetting"] = true
+
+	switch ctx.Query("action") {
+	case "update":
+		if ctx.HasError() {
+			ctx.HTML(200, SETTING)
+			return
+		}
+
+		newRepoName := form.RepoName
+		// Check if repository name has been changed.
+		if ctx.Repo.Repository.Name != newRepoName {
+			isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName)
+			if err != nil {
+				ctx.Handle(500, "setting.SettingPost(update: check existence)", err)
+				return
+			} else if isExist {
+				ctx.RenderWithErr("Repository name has been taken in your repositories.", SETTING, nil)
+				return
+			} else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
+				ctx.Handle(500, "setting.SettingPost(change repository name)", err)
+				return
+			}
+			log.Trace("%s Repository name changed: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newRepoName)
+
+			ctx.Repo.Repository.Name = newRepoName
+		}
+
+		br := form.Branch
+
+		if ctx.Repo.GitRepo.IsBranchExist(br) {
+			ctx.Repo.Repository.DefaultBranch = br
+		}
+		ctx.Repo.Repository.Description = form.Description
+		ctx.Repo.Repository.Website = form.Website
+		ctx.Repo.Repository.IsPrivate = form.Private
+		ctx.Repo.Repository.IsGoget = form.GoGet
+		if err := models.UpdateRepository(ctx.Repo.Repository); err != nil {
+			ctx.Handle(404, "UpdateRepository", err)
+			return
+		}
+		log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
+
+		if ctx.Repo.Repository.IsMirror {
+			if form.Interval > 0 {
+				ctx.Repo.Mirror.Interval = form.Interval
+				ctx.Repo.Mirror.NextUpdate = time.Now().Add(time.Duration(form.Interval) * time.Hour)
+				if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
+					log.Error(4, "UpdateMirror: %v", err)
+				}
+			}
+		}
+
+		ctx.Flash.Success("Repository options has been successfully updated.")
+		ctx.Redirect(fmt.Sprintf("/%s/%s/settings", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name))
+	case "transfer":
+		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.", SETTING, nil)
+			return
+		} else if ctx.Repo.Repository.IsMirror {
+			ctx.Error(404)
+			return
+		}
+
+		newOwner := ctx.Query("owner")
+		// Check if new owner exists.
+		isExist, err := models.IsUserExist(newOwner)
+		if err != nil {
+			ctx.Handle(500, "setting.SettingPost(transfer: check existence)", err)
+			return
+		} else if !isExist {
+			ctx.RenderWithErr("Please make sure you entered owner name is correct.", SETTING, nil)
+			return
+		} else if err = models.TransferOwnership(ctx.Repo.Owner, newOwner, ctx.Repo.Repository); err != nil {
+			ctx.Handle(500, "setting.SettingPost(transfer repository)", err)
+			return
+		}
+		log.Trace("%s Repository transfered: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newOwner)
+
+		ctx.Redirect("/")
+	case "delete":
+		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.", SETTING, nil)
+			return
+		}
+
+		if ctx.Repo.Owner.IsOrganization() &&
+			!ctx.Repo.Owner.IsOrgOwner(ctx.User.Id) {
+			ctx.Error(403)
+			return
+		}
+
+		if err := models.DeleteRepository(ctx.Repo.Owner.Id, ctx.Repo.Repository.Id, ctx.Repo.Owner.Name); err != nil {
+			ctx.Handle(500, "setting.Delete(DeleteRepository)", err)
+			return
+		}
+		log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.LowerName, ctx.Repo.Repository.LowerName)
+
+		if ctx.Repo.Owner.IsOrganization() {
+			ctx.Redirect("/org/" + ctx.Repo.Owner.Name + "/dashboard")
+		} else {
+			ctx.Redirect("/")
+		}
+	}
+}
+
+func Collaboration(ctx *middleware.Context) {
+	repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/")
+	ctx.Data["IsRepoToolbarCollaboration"] = true
+	ctx.Data["Title"] = repoLink + " - collaboration"
+
+	// Delete collaborator.
+	remove := strings.ToLower(ctx.Query("remove"))
+	if len(remove) > 0 && remove != ctx.Repo.Owner.LowerName {
+		if err := models.DeleteAccess(&models.Access{UserName: remove, RepoName: repoLink}); err != nil {
+			ctx.Handle(500, "setting.Collaboration(DeleteAccess)", err)
+			return
+		}
+		ctx.Flash.Success("Collaborator has been removed.")
+		ctx.Redirect(ctx.Repo.RepoLink + "/settings/collaboration")
+		return
+	}
+
+	names, err := models.GetCollaboratorNames(repoLink)
+	if err != nil {
+		ctx.Handle(500, "setting.Collaboration(GetCollaborators)", err)
+		return
+	}
+
+	us := make([]*models.User, len(names))
+	for i, name := range names {
+		us[i], err = models.GetUserByName(name)
+		if err != nil {
+			ctx.Handle(500, "setting.Collaboration(GetUserByName)", err)
+			return
+		}
+	}
+
+	ctx.Data["Collaborators"] = us
+	ctx.HTML(200, COLLABORATION)
+}
+
+func CollaborationPost(ctx *middleware.Context) {
+	repoLink := strings.TrimPrefix(ctx.Repo.RepoLink, "/")
+	name := strings.ToLower(ctx.Query("collaborator"))
+	if len(name) == 0 || ctx.Repo.Owner.LowerName == name {
+		ctx.Redirect(ctx.Req.RequestURI)
+		return
+	}
+	has, err := models.HasAccess(name, repoLink, models.WRITABLE)
+	if err != nil {
+		ctx.Handle(500, "setting.CollaborationPost(HasAccess)", err)
+		return
+	} else if has {
+		ctx.Redirect(ctx.Req.RequestURI)
+		return
+	}
+
+	u, err := models.GetUserByName(name)
+	if err != nil {
+		if err == models.ErrUserNotExist {
+			ctx.Flash.Error("Given user does not exist.")
+			ctx.Redirect(ctx.Req.RequestURI)
+		} else {
+			ctx.Handle(500, "setting.CollaborationPost(GetUserByName)", err)
+		}
+		return
+	}
+
+	if err = models.AddAccess(&models.Access{UserName: name, RepoName: repoLink,
+		Mode: models.WRITABLE}); err != nil {
+		ctx.Handle(500, "setting.CollaborationPost(AddAccess)", err)
+		return
+	}
+
+	if setting.Service.EnableNotifyMail {
+		if err = mailer.SendCollaboratorMail(ctx.Render, u, ctx.User, ctx.Repo.Repository); err != nil {
+			ctx.Handle(500, "setting.CollaborationPost(SendCollaboratorMail)", err)
+			return
+		}
+	}
+
+	ctx.Flash.Success("New collaborator has been added.")
+	ctx.Redirect(ctx.Req.RequestURI)
+}
+
+func WebHooks(ctx *middleware.Context) {
+	ctx.Data["IsRepoToolbarWebHooks"] = true
+	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhooks"
+
+	// Delete webhook.
+	remove := com.StrTo(ctx.Query("remove")).MustInt64()
+	if remove > 0 {
+		if err := models.DeleteWebhook(remove); err != nil {
+			ctx.Handle(500, "setting.WebHooks(DeleteWebhook)", err)
+			return
+		}
+		ctx.Flash.Success("Webhook has been removed.")
+		ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks")
+		return
+	}
+
+	ws, err := models.GetWebhooksByRepoId(ctx.Repo.Repository.Id)
+	if err != nil {
+		ctx.Handle(500, "setting.WebHooks(GetWebhooksByRepoId)", err)
+		return
+	}
+
+	ctx.Data["Webhooks"] = ws
+	ctx.HTML(200, HOOKS)
+}
+
+func WebHooksAdd(ctx *middleware.Context) {
+	ctx.Data["IsRepoToolbarWebHooks"] = true
+	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook"
+	ctx.HTML(200, HOOK_ADD)
+}
+
+func WebHooksAddPost(ctx *middleware.Context, form auth.NewWebhookForm) {
+	ctx.Data["IsRepoToolbarWebHooks"] = true
+	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Add Webhook"
+
+	if ctx.HasError() {
+		ctx.HTML(200, HOOK_ADD)
+		return
+	}
+
+	ct := models.JSON
+	if form.ContentType == "2" {
+		ct = models.FORM
+	}
+
+	w := &models.Webhook{
+		RepoId:      ctx.Repo.Repository.Id,
+		Url:         form.Url,
+		ContentType: ct,
+		Secret:      form.Secret,
+		HookEvent: &models.HookEvent{
+			PushOnly: form.PushOnly,
+		},
+		IsActive: form.Active,
+	}
+	if err := w.UpdateEvent(); err != nil {
+		ctx.Handle(500, "setting.WebHooksAddPost(UpdateEvent)", err)
+		return
+	} else if err := models.CreateWebhook(w); err != nil {
+		ctx.Handle(500, "setting.WebHooksAddPost(CreateWebhook)", err)
+		return
+	}
+
+	ctx.Flash.Success("New webhook has been added.")
+	ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks")
+}
+
+func WebHooksEdit(ctx *middleware.Context) {
+	ctx.Data["IsRepoToolbarWebHooks"] = true
+	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhook"
+
+	hookId := com.StrTo(ctx.Params(":id")).MustInt64()
+	if hookId == 0 {
+		ctx.Handle(404, "setting.WebHooksEdit", nil)
+		return
+	}
+
+	w, err := models.GetWebhookById(hookId)
+	if err != nil {
+		if err == models.ErrWebhookNotExist {
+			ctx.Handle(404, "setting.WebHooksEdit(GetWebhookById)", nil)
+		} else {
+			ctx.Handle(500, "setting.WebHooksEdit(GetWebhookById)", err)
+		}
+		return
+	}
+
+	w.GetEvent()
+	ctx.Data["Webhook"] = w
+	ctx.HTML(200, HOOK_EDIT)
+}
+
+func WebHooksEditPost(ctx *middleware.Context, form auth.NewWebhookForm) {
+	ctx.Data["IsRepoToolbarWebHooks"] = true
+	ctx.Data["Title"] = strings.TrimPrefix(ctx.Repo.RepoLink, "/") + " - Webhook"
+
+	hookId := com.StrTo(ctx.Params(":id")).MustInt64()
+	if hookId == 0 {
+		ctx.Handle(404, "setting.WebHooksEditPost", nil)
+		return
+	}
+
+	w, err := models.GetWebhookById(hookId)
+	if err != nil {
+		if err == models.ErrWebhookNotExist {
+			ctx.Handle(404, "GetWebhookById", nil)
+		} else {
+			ctx.Handle(500, "GetWebhookById", err)
+		}
+		return
+	}
+
+	if ctx.HasError() {
+		ctx.HTML(200, HOOK_EDIT)
+		return
+	}
+
+	ct := models.JSON
+	if form.ContentType == "2" {
+		ct = models.FORM
+	}
+
+	w.Url = form.Url
+	w.ContentType = ct
+	w.Secret = form.Secret
+	w.HookEvent = &models.HookEvent{
+		PushOnly: form.PushOnly,
+	}
+	w.IsActive = form.Active
+	if err := w.UpdateEvent(); err != nil {
+		ctx.Handle(500, "UpdateEvent", err)
+		return
+	} else if err := models.UpdateWebhook(w); err != nil {
+		ctx.Handle(500, "WebHooksEditPost", err)
+		return
+	}
+
+	ctx.Flash.Success("Webhook has been updated.")
+	ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId))
+}

+ 13 - 10
routers/user/home.go

@@ -28,11 +28,11 @@ func Dashboard(ctx *middleware.Context) {
 	ctx.Data["PageIsDashboard"] = true
 	ctx.Data["PageIsNews"] = true
 
-	// if err := ctx.User.GetOrganizations(); err != nil {
-	// 	ctx.Handle(500, "home.Dashboard(GetOrganizations)", err)
-	// 	return
-	// }
-	// ctx.Data["Orgs"] = ctx.User.Orgs
+	if err := ctx.User.GetOrganizations(); err != nil {
+		ctx.Handle(500, "home.Dashboard(GetOrganizations)", err)
+		return
+	}
+	ctx.Data["Orgs"] = ctx.User.Orgs
 	ctx.Data["ContextUser"] = ctx.User
 
 	repos, err := models.GetRepositories(ctx.User.Id, true)
@@ -40,13 +40,16 @@ func Dashboard(ctx *middleware.Context) {
 		ctx.Handle(500, "GetRepositories", err)
 		return
 	}
+	for _, repo := range repos {
+		repo.Owner = ctx.User
+	}
 	ctx.Data["Repos"] = repos
 
-	// ctx.Data["CollaborativeRepos"], err = models.GetCollaborativeRepos(ctx.User.Name)
-	// if err != nil {
-	// 	ctx.Handle(500, "home.Dashboard(GetCollaborativeRepos)", err)
-	// 	return
-	// }
+	ctx.Data["CollaborativeRepos"], err = models.GetCollaborativeRepos(ctx.User.Name)
+	if err != nil {
+		ctx.Handle(500, "GetCollaborativeRepos", err)
+		return
+	}
 
 	actions, err := models.GetFeeds(ctx.User.Id, 0, true)
 	if err != nil {

+ 8 - 0
routers/user/setting.go

@@ -19,6 +19,7 @@ const (
 	SETTINGS_PASSWORD base.TplName = "user/settings/password"
 	SETTINGS_SSH_KEYS base.TplName = "user/settings/sshkeys"
 	SETTINGS_SOCIAL   base.TplName = "user/settings/social"
+	SETTINGS_ORGS     base.TplName = "user/settings/orgs"
 	SETTINGS_DELETE   base.TplName = "user/settings/delete"
 	NOTIFICATION      base.TplName = "user/notification"
 	SECURITY          base.TplName = "user/security"
@@ -232,6 +233,13 @@ func SettingsSocial(ctx *middleware.Context) {
 	ctx.HTML(200, SETTINGS_SOCIAL)
 }
 
+func SettingsOrgs(ctx *middleware.Context) {
+	ctx.Data["Title"] = ctx.Tr("settings")
+	ctx.Data["PageIsUserSettings"] = true
+	ctx.Data["PageIsSettingsOrgs"] = true
+	ctx.HTML(200, SETTINGS_ORGS)
+}
+
 func SettingsDelete(ctx *middleware.Context) {
 	ctx.Data["Title"] = ctx.Tr("settings")
 	ctx.Data["PageIsUserSettings"] = true

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.4.7.0725 Alpha
+0.4.7.0726 Alpha

+ 3 - 3
templates/admin/config.tmpl

@@ -176,11 +176,11 @@
                     <dt>Enable Set Cookie</dt>
                     <dd><i class="fa fa{{if .SessionConfig.EnableSetCookie}}-check{{end}}-square-o"></i></dd>
                     <dt>GC Interval Time</dt>
-                    <dd>{{.SessionConfig.GcIntervalTime}} seconds</dd>
+                    <dd>{{.SessionConfig.Gclifetime}} seconds</dd>
                     <dt>Session Life Time</dt>
-                    <dd>{{.SessionConfig.SessionLifeTime}} seconds</dd>
+                    <dd>{{.SessionConfig.Maxlifetime}} seconds</dd>
                     <dt>HTTPS Only</dt>
-                    <dd><i class="fa fa{{if .SessionConfig.CookieSecure}}-check{{end}}-square-o"></i></dd>
+                    <dd><i class="fa fa{{if .SessionConfig.Secure}}-check{{end}}-square-o"></i></dd>
                     <dt>Cookie Life Time</dt>
                     <dd>{{.SessionConfig.CookieLifeTime}} seconds</dd>
                     <dt>Session ID Hash Function</dt>

+ 2 - 2
templates/ng/base/head.tmpl

@@ -12,10 +12,10 @@
 		<!-- Stylesheet -->
 		<link rel="stylesheet" href="/ng/css/ui.css">
 		<link rel="stylesheet" href="/ng/css/gogs.css">
-		<link rel="stylesheet" href="/ng/css/font-awesome.min.css">
+		<link rel="stylesheet" href="/css/font-awesome.min.css">
 		<link rel="stylesheet" href="/ng/fonts/octicons.css">
 		<!-- <link rel="stylesheet" href="http://cdn.bootcss.com/highlight.js/8.1/styles/github.min.css"> -->
-		<link rel="stylesheet" href="/ng/css/github.min.css">
+		<link rel="stylesheet" href="/css/github.min.css">
 
 		<!-- JavaScript -->
 		<script src="/ng/js/lib/jquery-1.11.1.min.js"></script>

+ 1 - 1
templates/repo/commits.tmpl

@@ -34,7 +34,7 @@
                     <td class="author"><img class="avatar" src="{{AvatarLink .Author.Email}}" alt=""/><a href="/user/email2user?email={{.Author.Email}}">{{.Author.Name}}</a></td>
                     <td class="sha"><a rel="nofollow" class="label label-success" href="/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td>
                     <td class="message">{{.Summary}} </td>
-                    <td class="date">{{TimeSince .Author.When}}</td>
+                    <td class="date">{{TimeSince .Author.When $.Lang}}</td>
                 </tr>
                 {{end}}
                 </tbody>

+ 1 - 1
templates/repo/diff.tmpl

@@ -20,7 +20,7 @@
                 <p class="author">
                     <img class="avatar" src="{{AvatarLink .Commit.Author.Email}}" alt=""/>
                     <a class="name" href="/user/email2user?email={{.Commit.Author.Email}}"><strong>{{.Commit.Author.Name}}</strong></a>
-                    <span class="time">{{TimeSince .Commit.Author.When}}</span>
+                    <span class="time">{{TimeSince .Commit.Author.When $.Lang}}</span>
                 </p>
             </div>
         </div>

+ 1 - 1
templates/repo/issue/list.tmpl

@@ -86,7 +86,7 @@
                     <p class="info">
                         <span class="author"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" width="20"/>
                         <a href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a></span>
-                        <span class="time">{{TimeSince .Created}}</span>
+                        <span class="time">{{TimeSince .Created $.Lang}}</span>
                         <span class="comment"><i class="fa fa-comments"></i> {{.NumComments}}</span>
                     </p>
                 </div>

+ 4 - 4
templates/repo/issue/view.tmpl

@@ -18,7 +18,7 @@
                         <a class="btn btn-primary pull-right issue-edit-save hidden" href="#" data-ajax="{{.RepoLink}}/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
+                        <span class="time">{{TimeSince .Issue.Created $.Lang}}</span> · {{.Issue.NumComments}} comments
                     </p>
                 </div>
                 <div class="issue-main">
@@ -66,7 +66,7 @@
                         <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 href="/user/{{.Poster.Name}}" class="user">{{.Poster.Name}}</a> commented <span class="time">{{TimeSince .Created $.Lang}}</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>
@@ -95,14 +95,14 @@
                     <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>
+                            <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 $.Lang}}</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/{{.Poster.Name}}">{{.Poster.Name}}</a> <span class="label label-danger">Closed</span> this issue <span class="time">{{TimeSince .Created}}</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 $.Lang}}</span>
                         </div>
                     </div>
                     {{else if eq .Type 4}}

+ 3 - 3
templates/status/401.tmpl

@@ -1,6 +1,6 @@
-{{template "base/head" .}}
-{{template "base/header" .}}
+{{template "ng/base/head" .}}
+{{template "ng/base/header" .}}
 <div class="container">
 	401 Unauthorized: {{.ErrorMsg}}
 </div>
-{{template "base/footer" .}}
+{{template "ng/base/footer" .}}

+ 1 - 0
templates/status/404.tmpl

@@ -6,5 +6,6 @@
     <br>
     <p>Application Version: {{AppVer}}</p>
     <p>If you think this is an error, please open an issue on <a href="https://github.com/gogits/gogs/issues/new">GitHub</a>.</p>
+    <h3>We're currently working on 0.5 beta version, many pages may be missing at this time. Sorry for confusion!</h3>
 </div>
 {{template "ng/base/footer" .}}

+ 4 - 45
templates/user/dashboard/dashboard.tmpl

@@ -70,15 +70,7 @@
                 <div class="panel-body">
                     <ul class="list-no-style">
                         {{range .Repos}}
-                        <li {{if .IsPrivate}}class="private"{{end}}>
-                            <a href="{{$.ContextUser.Name}}/{{.Name}}">
-                                <i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i>
-                                <span class="repo-name">
-                                    <!-- <span class="repo-name-prefix">gogits / </span> -->
-                                    <strong class="repo">{{.Name}}</strong>
-                                </span>
-                            </a>
-                        </li>
+                            {{template "user/dashboard/repo_list" .}}
                         {{end}}
                     </ul>
                 </div>
@@ -87,42 +79,9 @@
                 </div>
                 <div class="panel-body">
                     <ul class="list-no-style">
-                        <li>
-                            <a href="#">
-                                <i class="octicon octicon-repo"></i>
-                            <span class="repo-name">
-                                <span class="repo-name-prefix">gogits / </span>
-                                <strong class="repo">gogs</strong>
-                            </span>
-                            <span class="right repo-star">
-                                <i class="octicon octicon-star"></i>2048
-                            </span>
-                            </a>
-                        </li>
-                        <li>
-                            <a href="#">
-                                <i class="octicon octicon-repo"></i>
-                            <span class="repo-name">
-                                <span class="repo-name-prefix">astaxie / </span>
-                                <strong class="repo">beego</strong>
-                            </span>
-                            <span class="right repo-star">
-                                <i class="octicon octicon-star"></i>2301
-                            </span>
-                            </a>
-                        </li>
-                        <li>
-                            <a href="#">
-                                <i class="octicon octicon-repo"></i>
-                            <span class="repo-name">
-                                <span class="repo-name-prefix">gogits / </span>
-                                <strong class="repo">scaffold</strong>
-                            </span>
-                            <span class="right repo-star">
-                                <i class="octicon octicon-star"></i>0
-                            </span>
-                            </a>
-                        </li>
+                        {{range .CollaborativeRepos}}
+                            {{template "user/dashboard/repo_list" .}}
+                        {{end}}
                     </ul>
                 </div>
             </div>

+ 12 - 0
templates/user/dashboard/repo_list.tmpl

@@ -0,0 +1,12 @@
+<li {{if .IsPrivate}}class="private"{{end}}>
+    <a href="{{.Owner.Name}}/{{.Name}}">
+        <i class="octicon octicon-{{if .IsPrivate}}lock{{else if .IsFork}}repo-forked{{else if .IsMirror}}repo-clone{{else}}repo{{end}}"></i>
+        <span class="repo-name">
+            <!-- <span class="repo-name-prefix">gogits / </span> -->
+            <strong class="repo">{{.Name}}</strong>
+        </span>
+        <span class="right repo-star">
+            <i class="octicon octicon-star"></i>{{.NumStars}}
+        </span>
+    </a>
+</li>

+ 1 - 1
templates/user/issues.tmpl

@@ -41,7 +41,7 @@
                     <p class="info">
                         <span class="author"><img class="avatar" src="{{.Poster.AvatarLink}}" alt="" width="20"/>
                         <a href="/user/{{.Poster.Name}}">{{.Poster.Name}}</a></span>
-                        <span class="time">{{TimeSince .Created}}</span>
+                        <span class="time">{{TimeSince .Created $.Lang}}</span>
                         <span class="comment"><i class="fa fa-comments"></i> {{.NumComments}}</span>
                     </p>
                 </div>

+ 3 - 3
templates/user/profile.tmpl

@@ -50,8 +50,8 @@
                 <ul class="list-unstyled activity-list">
                 {{range .Feeds}}
                     <li>
-                        <i class="icon fa fa-{{ActionIcon .OpType}}"></i>
-                        <div class="info"><span class="meta">{{TimeSince .Created}}</span><br>{{ActionDesc . | str2html}}</div>
+                        <i class="icon fa fa-{{ActionIcon .GetOpType}}"></i>
+                        <div class="info"><span class="meta">{{TimeSince .Created $.Lang}}</span><br>{{ActionDesc . | str2html}}</div>
                         <span class="clearfix"></span>
                     </li>
                 {{else}}
@@ -69,7 +69,7 @@
                             <a href="/{{$.Owner.Name}}/{{.Name}}">{{.Name}}{{if .IsPrivate}} <span class="label label-default">Private</span>{{end}}</a>
                         </h4>
                         <p class="desc">{{.Description}}</p>
-                        <div class="info">Last updated {{.Updated|TimeSince}}</div>
+                        <div class="info">Last updated {{TimeSince .Updated $.Lang}}</div>
                     </li>
                 {{end}}
                 </ul>

+ 1 - 0
templates/user/settings/nav.tmpl

@@ -6,6 +6,7 @@
             <li {{if .PageIsSettingsPassword}}class="current"{{end}}><a href="/user/settings/password">{{.i18n.Tr "settings.password"}}</a></li>
             <li {{if .PageIsSettingsSSHKeys}}class="current"{{end}}><a href="/user/settings/ssh">{{.i18n.Tr "settings.ssh_keys"}}</a></li>
             <li {{if .PageIsSettingsSocial}}class="current"{{end}}><a href="/user/settings/social">{{.i18n.Tr "settings.social"}}</a></li>
+            <li {{if .PageIsSettingsOrgs}}class="current"{{end}}><a href="/user/settings/orgs">{{.i18n.Tr "settings.orgs"}}</a></li>
             <li {{if .PageIsSettingsDelete}}class="current"{{end}}><a href="/user/settings/delete">{{.i18n.Tr "settings.delete"}}</a></li>
         </ul>
     </div>

+ 18 - 0
templates/user/settings/orgs.tmpl

@@ -0,0 +1,18 @@
+{{template "ng/base/head" .}}
+{{template "ng/base/header" .}}
+<div id="setting-wrapper" class="main-wrapper">
+    <div id="user-profile-setting" class="container clear">
+        {{template "user/settings/nav" .}}
+        <div class="grid-4-5 left">
+            <div class="setting-content">
+                {{template "ng/base/alert" .}}
+                <div id="setting-content">
+                    <div id="user-profile-setting-content" class="panel panel-radius">
+                        <p class="panel-header"><strong>{{.i18n.Tr "settings.manage_orgs"}}</strong></p>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+{{template "ng/base/footer" .}}

Some files were not shown because too many files changed in this diff